diff --git a/lib/ctx/ctx-fontgen.c b/lib/ctx/ctx-fontgen.c new file mode 100644 index 0000000000000000000000000000000000000000..3b2dbbc38ed4052863d8a77407600b46b25aba70 --- /dev/null +++ b/lib/ctx/ctx-fontgen.c @@ -0,0 +1,272 @@ +#include <stdlib.h> +#include <libgen.h> + +#define CTX_MAX_DRAWLIST_SIZE 4096000 +#define CTX_BACKEND_TEXT 0 // we keep then non-backend code paths + // for code handling aroud, this should + // be run-time to permit doing text_to_path +#define CTX_RASTERIZER 0 + +#define CTX_BITPACK_PACKER 1 // pack vectors +#define CTX_BITPACK 1 +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#include <sys/time.h> +#define CTX_EXTRAS 1 +#define CTX_FONTS_FROM_FILE 1 +#define CTX_AVX2 0 +#define CTX_IMPLEMENTATION +#define CTX_PARSER 1 +#include "ctx-nofont.h" + +static int usage(){ + fprintf (stderr, "tool to generate native ctx embedded font format\n"); + fprintf (stderr, "\n"); + fprintf (stderr, "usage: ctx-fontgen <file.ttf> [name [set1-set2-set3]]\n"); + fprintf (stderr, "\nrecognized sets: latin1, ascii, extra, all, emoji\n"); + fprintf (stderr, "\na final argument of \"binary\" might be appended, causing\nthe generated file to be binary ctx.\n"); + return -1; +} + +CtxDrawlist output_font={NULL,}; +uint32_t glyphs[65536]; +int n_glyphs = 0; + +void +add_glyph (Ctx *ctx, uint32_t glyph) +{ + for (int i = 0; i < n_glyphs; i++) + { + if (glyphs[i] == glyph) + return; + } + ctx_reset (ctx); + ctx_font_size (ctx, CTX_BAKE_FONT_SIZE); + ctx_move_to (ctx, 0, 0); + if (ctx_glyph (ctx, glyph, 1)) + return; + glyphs[n_glyphs++] = glyph; + ctx->drawlist.flags = CTX_TRANSFORMATION_BITPACK; + ctx_drawlist_compact (&ctx->drawlist); + + char buf[44]={0,0,0,0,0}; + ctx_unichar_to_utf8 (glyph, (uint8_t*)buf); + uint32_t args[2] = {glyph, ctx_glyph_width (ctx, glyph) * 256}; + ctx_drawlist_add_u32 (&output_font, CTX_DEFINE_GLYPH, args); + + for (int i = 3; i < ctx->drawlist.count - 1; i++) + { + CtxEntry *entry = &ctx->drawlist.entries[i]; + args[0] = entry->data.u32[0]; + args[1] = entry->data.u32[1]; + ctx_drawlist_add_u32 (&output_font, entry->code, &args[0]); + } +} + +static int find_glyph (CtxDrawlist *drawlist, uint32_t unichar) +{ + for (int i = 0; i < drawlist->count; i++) + { + if (drawlist->entries[i].code == CTX_DEFINE_GLYPH && + drawlist->entries[i].data.u32[0] == unichar) + { + return i; + // XXX this could be prone to insertion of valid header + // data in included bitmaps.. is that an issue? + } + } + fprintf (stderr, "Eeeek %i\n", unichar); + return -1; +} + +int main (int argc, char **argv) +{ + int binary = 0; + const char *path; + const char *name = "regular"; + const char *subsets = "latin1"; + + Ctx *ctx; + path = argv[1]; + if (!path) + { + return usage(); + } + if (argv[2]) + { + name = argv[2]; + if (argv[3]) + subsets = argv[3]; + if (argv[4] && !strcmp(argv[4], "binary")) + binary=1; + } + ctx_load_font_ttf_file ("import", argv[1]); + ctx = ctx_new (); + _ctx_set_transformation (ctx, CTX_TRANSFORMATION_RELATIVE); + ctx_font (ctx, "import"); + + if (strstr (subsets, "all")) + for (int glyph = 0; glyph < 65536*8; glyph++) add_glyph (ctx, glyph); + + if (strstr (subsets, "latin1")) + for (int glyph = 0; glyph < 256; glyph++) add_glyph (ctx, glyph); + if (strstr (subsets, "ascii")) + for (int glyph = 0; glyph < 127; glyph++) add_glyph (ctx, glyph); + + + if (strstr (subsets, "terminal")) + { +char* string = "☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼! #$%&'()*+,-.🔒" +"⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐" +"└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■" +"!#$%&'()*+,-./◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"; + + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1)) + add_glyph (ctx, ctx_utf8_to_unichar (utf8)); + } + + if (strstr (subsets, "cp437")) + { +char* string = " ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼!\"#$%&'()*+,-./0123456789:;<=>?" +"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^,`abcdefghijklmnopqrstuvwxyz{|}~⌂" +"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" +"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀" +"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■"; + + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1)) + add_glyph (ctx, ctx_utf8_to_unichar (utf8)); + } + + if (strstr (subsets, "vt100")) + { +char* string = +" !\"#$%&'()*+,-./0123456789:;<=>? @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· "; + + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1)) + add_glyph (ctx, ctx_utf8_to_unichar (utf8)); + } + + if (strstr (subsets, "extras")) + { + char *string = + +"éñÑßæøåö£ÆÖØÅ€§π°üÜ…”““”«»©®™⭾⏎⌫·←↑↓→☀☁☂☢☭☮☯☽✉⚙⚠␣²◆♥♦♣♠÷≈±╴−╶"; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1)) + add_glyph (ctx, ctx_utf8_to_unichar (utf8)); + } + + if (strstr (subsets, "emoji")) + { + char *string = "๛ ☬☣☠☀☁☂☃☢☭☮☯☼☽✉✓❤№℃∞◌◎☎☐☑♉♩♪♫♬☆◦☄☉☏☤☥☹☺☻♲♨♼♽♾⚀⚁🥕⚂⚃⚄⚅⚙⚠␣⚡⌨⌚⏏⋯•‥․‣•↺↻⌘☕✈✉✓✔✕✖✗✘☑☒☐☓✶❄❍❢❣❤❥❤❥❦❧➲㎏㎆㎅㎇㎤㎥㎦㎝㎧㎨㎞㎐㎏㎑㎒㎓㎠㎡㎢★⁈ ⁉ ⁂✵✺✽✾✿❀❁❂❉❆✡✠🤐🤑🤒🤓🤔🤕🤖🤗🤘🤙🤚🤛🤜🤝🤞🤟🤠🤡🤢🤣🤤🤥🤦🤧🤨🤩🤪🤫🤬🤭🤮🤯🤷🤸🤹🤺🥂🥇🥈🥉🥤🥨🥬🥰🥱🥳🥴🥵🥶🥺🦁🦄🦉🦊🦋🦎🦓🦔🦕🦖🦘🦙🦚🦜🦝🦟🦴🦽🦾🦿🧁🧉🧐🧙🧚🧛🧜🧟🧠🧡🧥🧤🧦🧪🧬🧭🧮🧯🧰🧱🧲🧵🧷🧸🧺🧻🛹🛸🛵🛴🛰🛬🛫🛩🛠🛣🛤🛥🛒🛏🛌🛂🛁🚶🚴🚲🚬🚩🚗🚜🚴🚘🚖🚔🚚🚑🚍🚆🚀🙈🙉🙊🙄🙃🙂🙁🙀😿😾😽😼😻😺😹😸😷😶😵😴😳😲😱😰😯😮😭😬😫😪😩😨😧😦😥😤😣😢😡😠😟😞😝😜😛😚😙😘😗😖😕😔😓😒😑😐😏😎😍😌😋😊😉😈😇😆😅😄😃😂😁😀🗝🗜🗞🗓🗒🗑🗄🖼🖤🖥🖨🖖🖕🖐🖍🖌🖋🖊🖇🕺🕹🕸🕷🕶🕵🕴🕰🕯🔭🔬🔫🔪🔩🔨🔧🔦🔥🔤🔢🔣🔡🔠🔗🔕🔔🔓🔒🔑🔐🔏🔌🔋🔊🔉🔈🔇🔅🔆📽📻📺📹📸📷📳📱📰📯📭📬📫📪📨📦📡📞📚📖📐📏📎📍📌📉📈📄📃📂📁💿💾💻💸💶💳💰💯💫💩💧💦💤💣💢💡💞💖💕💔💓💊💃💀👾👽👻👺👹👸👷👶👵👴👯👭👬👫👣👓👋👍👎🐾🐼🐻🐺🐹🐸🐷🐶🐵🐳🐲🐱🐰🐯🐮🐭🐬🐧🐦🐥🐤🐣🐢🐡🐠🐟🐞🐝🐜🐛🐙🐘🐔🐓🐒🐑🐐🐏🐎🐍🐌🐋🐊🐉🐈🐇🐆🐅🐄🐃🐂🐁🐀🏺🏹🏸🏷🏴🏳🏵🏰🏭🏬🏫🏪🏧🏢🏠🏡🏐🏍🏆🏅🏁🎼🎶🎵🎲🎰🎮🎬🎨🎧🎥🎞🎃🎄🍸🍷🍄🌿🍀🍁🍒🌵🌳🌲🌩🌪🌨🌧🌦🌥🌤🌡🌠🌟🌞🌝🌜🌛🌚🌙🌘🌗🌖🌕🌔🌓🌒🌑🌐🌏🌎🌍🌌🌋🌊🌅🌄🌂🌀"; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1)) + add_glyph (ctx, ctx_utf8_to_unichar (utf8)); + } + + for (int i = 0; i < n_glyphs; i++) + for (int j = 0; j < n_glyphs; j++) + { + float kerning = ctx_glyph_kern (ctx, glyphs[i], glyphs[j]); + if (kerning > 0.2) + { + CtxCommand command; + int pos = find_glyph (&output_font, glyphs[i]); + pos ++; + while (pos < output_font.count && + output_font.entries[pos].code != CTX_DEFINE_GLYPH) + pos++; + + command.code = CTX_KERNING_PAIR; + command.kern.glyph_before = glyphs[i]; + command.kern.glyph_after = glyphs[j]; + command.kern.amount = kerning * 256; + ctx_drawlist_insert_entry (&output_font, pos, (CtxEntry*)&command); + } + } + + ctx_free (ctx); + + if (!binary) + { + printf ("#ifndef CTX_FONT_%s\n", name); + printf ("/* this is a ctx encoded font based on %s */\n", basename (argv[1])); + printf ("/* CTX_SUBDIV:%i CTX_BAKE_FONT_SIZE:%i */\n", CTX_SUBDIV, CTX_BAKE_FONT_SIZE); + + printf ("/* glyphs covered: \n\n"); + int col = 0; + for (int i = 0; i < output_font.count; i++) + { + CtxEntry *entry = &output_font.entries[i]; + if (entry->code == '@') + { + char buf[44]={0,0,0,0,0}; + ctx_unichar_to_utf8 (entry->data.u32[0], (uint8_t*)buf); + switch (buf[0]) + { + case '\\': + printf ("\\"); + break; + default: + printf ("%s", buf); + } + col++; + if (col > 73) + { + col = 0; + printf ("\n "); + } + } + } + + printf (" */\n"); + + printf ("static const struct __attribute__ ((packed)) {uint8_t code; uint32_t a; uint32_t b;}\nctx_font_%s[]={\n", name); + + for (int i = 0; i < output_font.count; i++) + { + CtxEntry *entry = &output_font.entries[i]; + if (entry->code > 32 && entry->code < 127) + { + printf ("{'%c', 0x%08x, 0x%08x},", entry->code, + entry->data.u32[0], + entry->data.u32[1]); + } + else + { + printf ("{%i, 0x%08x, 0x%08x},", entry->code, + entry->data.u32[0], + entry->data.u32[1]); + } + if (entry->code == '@') + { + char buf[44]={0,0,0,0,0}; + ctx_unichar_to_utf8 (entry->data.u32[0], (uint8_t*)buf); + switch (buf[0]) + { + case '\\': + printf ("/* \\ x-advance: %f */", entry->data.u32[1]/256.0); + break; + default: + printf ("/* %s x-advance: %f */", buf, entry->data.u32[1]/256.0); + } + } + else + { + } + printf ("\n"); + } + printf ("};\n"); + printf ("#define CTX_FONT_%s 1\n", name); + printf ("#endif\n"); + } + else + { + for (int i = 0; i < output_font.count; i++) + { + CtxEntry *entry = &output_font.entries[i]; + for (int c = 0; c < (int)sizeof (CtxEntry); c++) + printf ("%c",((uint8_t*)(entry))[c]); + } + } + + return 0; +} diff --git a/lib/ctx/ctx-nofont.h b/lib/ctx/ctx-nofont.h new file mode 100644 index 0000000000000000000000000000000000000000..41b4f40ada5d492615818f8b1d7772b3b2fece58 --- /dev/null +++ b/lib/ctx/ctx-nofont.h @@ -0,0 +1,44960 @@ +/* + * ctx.h is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * ctx.h is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with ctx; if not, see <https://www.gnu.org/licenses/>. + * + * 2012, 2015, 2019, 2020 Øyvind Kolås <pippin@gimp.org> + * + * ctx is a single header 2d vector graphics processing framework. + * + * To use ctx in a project, do the following: + * + * #define CTX_IMPLEMENTATION + * #include "ctx.h" + * + * Ctx contains a minimal default fallback font with only ascii, so + * you probably want to also include a font, and perhaps enable + * the cairo or SDL2 optional renderers, a more complete example + * could be: + * + * #include <cairo.h> + * #include <SDL.h> + * #include "ctx-font-regular.h" + * #define CTX_IMPLEMENTATION + * #include "ctx.h" + * + * The behavior of ctx can be tweaked, and features can be configured, enabled + * or disabled with other #defines, see further down in the start of this file + * for details. + */ + +#ifndef CTX_H +#define CTX_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if !__COSMOPOLITAN__ +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#endif + +typedef struct _Ctx Ctx; + +/* The pixel formats supported as render targets + */ +enum _CtxPixelFormat +{ + CTX_FORMAT_NONE=0, + CTX_FORMAT_GRAY8, // 1 - these enum values are not coincidence + CTX_FORMAT_GRAYA8, // 2 - + CTX_FORMAT_RGB8, // 3 - + CTX_FORMAT_RGBA8, // 4 - + CTX_FORMAT_BGRA8, // 5 + CTX_FORMAT_RGB565, // 6 + CTX_FORMAT_RGB565_BYTESWAPPED, // 7 + CTX_FORMAT_RGB332, // 8 + CTX_FORMAT_RGBAF, // 9 + CTX_FORMAT_GRAYF, // 10 + CTX_FORMAT_GRAYAF, // 11 + CTX_FORMAT_GRAY1, //12 MONO + CTX_FORMAT_GRAY2, //13 DUO + CTX_FORMAT_GRAY4, //14 + CTX_FORMAT_CMYK8, //15 + CTX_FORMAT_CMYKA8, //16 + CTX_FORMAT_CMYKAF, //17 + CTX_FORMAT_YUV420, //18 +}; +typedef enum _CtxPixelFormat CtxPixelFormat; + +typedef struct _CtxGlyph CtxGlyph; + +/** + * ctx_new: + * + * Create a new drawing context, this context has no pixels but + * accumulates commands and can be played back on other ctx + * render contexts. + */ +Ctx *ctx_new (void); + + + + + +/** + * ctx_new_for_framebuffer: + * + * Create a new drawing context for a framebuffer, rendering happens + * immediately. + */ +Ctx *ctx_new_for_framebuffer (void *data, + int width, + int height, + int stride, + CtxPixelFormat pixel_format); +/** + * ctx_new_ui: + * + * Create a new interactive ctx context, might depend on additional + * integration. + */ +Ctx *ctx_new_ui (int width, int height); + +/** + * ctx_new_for_drawlist: + * + * Create a new drawing context for a pre-existing drawlist. + */ +Ctx *ctx_new_for_drawlist (void *data, size_t length); + + +/** + * ctx_dirty_rect: + * + * Query the dirtied bounding box of drawing commands thus far. + */ +void ctx_dirty_rect (Ctx *ctx, int *x, int *y, int *width, int *height); + +/** + * ctx_free: + * @ctx: a ctx context + */ +void ctx_free (Ctx *ctx); + +/* clears and resets a context */ +void ctx_reset (Ctx *ctx); +void ctx_begin_path (Ctx *ctx); +void ctx_save (Ctx *ctx); +void ctx_restore (Ctx *ctx); +void ctx_start_group (Ctx *ctx); +void ctx_end_group (Ctx *ctx); +void ctx_clip (Ctx *ctx); +void ctx_identity (Ctx *ctx); +void ctx_rotate (Ctx *ctx, float x); + +void ctx_image_smoothing (Ctx *ctx, int enabled); +int ctx_get_image_smoothing (Ctx *ctx); + +#define CTX_LINE_WIDTH_HAIRLINE -1000.0 +#define CTX_LINE_WIDTH_ALIASED -1.0 +#define CTX_LINE_WIDTH_FAST -1.0 /* aliased 1px wide line */ +void ctx_miter_limit (Ctx *ctx, float limit); +float ctx_get_miter_limit (Ctx *ctx); +void ctx_line_width (Ctx *ctx, float x); +void ctx_line_dash_offset (Ctx *ctx, float line_dash); +float ctx_get_line_dash_offset (Ctx *ctx); +void ctx_apply_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f); // htran, vtran +void ctx_set_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f); +void ctx_line_dash (Ctx *ctx, float *dashes, int count); +void ctx_font_size (Ctx *ctx, float x); +void ctx_font (Ctx *ctx, const char *font); +void ctx_font_family (Ctx *ctx, const char *font_family); +void ctx_scale (Ctx *ctx, float x, float y); +void ctx_translate (Ctx *ctx, float x, float y); +void ctx_line_to (Ctx *ctx, float x, float y); +void ctx_move_to (Ctx *ctx, float x, float y); +void ctx_curve_to (Ctx *ctx, float cx0, float cy0, + float cx1, float cy1, + float x, float y); +void ctx_quad_to (Ctx *ctx, float cx, float cy, + float x, float y); +void ctx_arc (Ctx *ctx, + float x, float y, + float radius, + float angle1, float angle2, + int direction); +void ctx_arc_to (Ctx *ctx, float x1, float y1, + float x2, float y2, float radius); +void ctx_rel_arc_to (Ctx *ctx, float x1, float y1, + float x2, float y2, float radius); +void ctx_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h); +void ctx_round_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h, + float radius); +void ctx_rel_line_to (Ctx *ctx, + float x, float y); +void ctx_rel_move_to (Ctx *ctx, + float x, float y); +void ctx_rel_curve_to (Ctx *ctx, + float x0, float y0, + float x1, float y1, + float x2, float y2); +void ctx_rel_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y); +void ctx_close_path (Ctx *ctx); +float ctx_get_font_size (Ctx *ctx); +const char *ctx_get_font (Ctx *ctx); +float ctx_get_line_width (Ctx *ctx); +int ctx_width (Ctx *ctx); +int ctx_height (Ctx *ctx); +int ctx_rev (Ctx *ctx); +float ctx_x (Ctx *ctx); +float ctx_y (Ctx *ctx); +void ctx_current_point (Ctx *ctx, float *x, float *y); +void ctx_get_transform (Ctx *ctx, float *a, float *b, + float *c, float *d, + float *e, float *f); + +CtxGlyph *ctx_glyph_allocate (int n_glyphs); + +void gtx_glyph_free (CtxGlyph *glyphs); + +int ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke); + +void ctx_preserve (Ctx *ctx); +void ctx_fill (Ctx *ctx); +void ctx_stroke (Ctx *ctx); + +void ctx_parse (Ctx *ctx, const char *string); + +void ctx_shadow_rgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_shadow_blur (Ctx *ctx, float x); +void ctx_shadow_offset_x (Ctx *ctx, float x); +void ctx_shadow_offset_y (Ctx *ctx, float y); +void ctx_view_box (Ctx *ctx, + float x0, float y0, + float w, float h); +void +ctx_set_pixel_u8 (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_global_alpha (Ctx *ctx, float global_alpha); +float ctx_get_global_alpha (Ctx *ctx); + +void ctx_named_source (Ctx *ctx, const char *name); +// followed by a color, gradient or pattern definition + +void ctx_stroke_source (Ctx *ctx); // next source definition is for stroking + +void ctx_rgba_stroke (Ctx *ctx, float r, float g, float b, float a); +void ctx_rgb_stroke (Ctx *ctx, float r, float g, float b); +void ctx_rgba8_stroke (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_gray_stroke (Ctx *ctx, float gray); +void ctx_drgba_stroke (Ctx *ctx, float r, float g, float b, float a); +void ctx_cmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_cmyk_stroke (Ctx *ctx, float c, float m, float y, float k); +void ctx_dcmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_dcmyk_stroke (Ctx *ctx, float c, float m, float y, float k); + + + +void ctx_rgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_rgb (Ctx *ctx, float r, float g, float b); +void ctx_rgba8 (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_gray (Ctx *ctx, float gray); +void ctx_drgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_cmyka (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_cmyk (Ctx *ctx, float c, float m, float y, float k); +void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_dcmyk (Ctx *ctx, float c, float m, float y, float k); + +/* there is also getters for colors, by first setting a color in one format and getting + * it with another color conversions can be done + */ + +void ctx_get_rgba (Ctx *ctx, float *rgba); +void ctx_get_graya (Ctx *ctx, float *ya); +void ctx_get_drgba (Ctx *ctx, float *drgba); +void ctx_get_cmyka (Ctx *ctx, float *cmyka); +void ctx_get_dcmyka (Ctx *ctx, float *dcmyka); +int ctx_in_fill (Ctx *ctx, float x, float y); +int ctx_in_stroke (Ctx *ctx, float x, float y); + +void ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1); +void ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0, + float x1, float y1, float r1); +/* XXX should be ctx_gradient_add_stop_rgba */ +void ctx_gradient_add_stop (Ctx *ctx, float pos, float r, float g, float b, float a); + +void ctx_gradient_add_stop_u8 (Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + +/* + * + */ +void ctx_define_texture (Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + int format, + void *data, + char *ret_eid); + +void +ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh, + CtxPixelFormat format, int dst_stride, + uint8_t *dst_data); + +void +ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format, + uint8_t *data, + int ox, int oy, + int dirtyX, int dirtyY, + int dirtyWidth, int dirtyHeight); + + +/* loads an image file from disk into texture, returning pixel width, height + * and eid, the eid is based on the path; not the contents - avoiding doing + * sha1 checksum of contents. The width and height of the image is returned + * along with the used eid, width height or eid can be NULL if we + * do not care about their values. + */ +void ctx_texture_load (Ctx *ctx, + const char *path, + int *width, + int *height, + char *eid); + +/* sets the paint source to be a texture by eid + */ +void ctx_texture (Ctx *ctx, const char *eid, float x, float y); + +void ctx_draw_texture (Ctx *ctx, const char *eid, float x, float y, float w, float h); + +void ctx_draw_texture_clipped (Ctx *ctx, const char *eid, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight); + +void ctx_draw_image (Ctx *ctx, const char *path, float x, float y, float w, float h); + +void ctx_draw_image_clipped (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight); + +/* used by the render threads of fb and sdl backends. + */ +void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source); +/* used when sharing cache state of eids between clients + */ +void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache); + +typedef struct _CtxDrawlist CtxDrawlist; +typedef void (*CtxFullCb) (CtxDrawlist *drawlist, void *data); + +int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format); // bits per pixel +int ctx_pixel_format_get_stride (CtxPixelFormat format, int width); +int ctx_pixel_format_components (CtxPixelFormat format); + +void _ctx_set_store_clear (Ctx *ctx); +void _ctx_set_transformation (Ctx *ctx, int transformation); + +Ctx *ctx_hasher_new (int width, int height, int cols, int rows); +uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row); + +int ctx_utf8_strlen (const char *s); + +#ifdef _BABL_H +#define CTX_BABL 1 +#else +#define CTX_BABL 0 +#endif + +/* If cairo.h is included before ctx.h add cairo integration code + */ +#ifdef CAIRO_H +#define CTX_CAIRO 1 +#else +#define CTX_CAIRO 0 +#endif + +#ifdef SDL_h_ +#define CTX_SDL 1 +#else +#define CTX_SDL 0 +#endif + +#ifndef CTX_FB +#if CTX_SDL +#define CTX_FB 1 +#else +#define CTX_FB 0 +#endif +#endif + +#if CTX_SDL +#define ctx_mutex_t SDL_mutex +#define ctx_create_mutex() SDL_CreateMutex() +#define ctx_lock_mutex(a) SDL_LockMutex(a) +#define ctx_unlock_mutex(a) SDL_UnlockMutex(a) +#else +#define ctx_mutex_t int +#define ctx_create_mutex() NULL +#define ctx_lock_mutex(a) +#define ctx_unlock_mutex(a) +#endif + +#if CTX_CAIRO + +/* render the deferred commands of a ctx context to a cairo + * context + */ +void ctx_render_cairo (Ctx *ctx, cairo_t *cr); + +/* create a ctx context that directly renders to the specified + * cairo context + */ +Ctx * ctx_new_for_cairo (cairo_t *cr); +#endif + +/* free with free() */ +char *ctx_render_string (Ctx *ctx, int longform, int *retlen); + +void ctx_render_stream (Ctx *ctx, FILE *stream, int formatter); + +void ctx_render_ctx (Ctx *ctx, Ctx *d_ctx); +void ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx); /* cycles through all + used texture eids + */ + +void ctx_start_move (Ctx *ctx); + + +int ctx_add_single (Ctx *ctx, void *entry); + +uint32_t ctx_utf8_to_unichar (const char *input); +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); + + +typedef enum +{ + CTX_FILL_RULE_WINDING, + CTX_FILL_RULE_EVEN_ODD +} CtxFillRule; + +typedef enum +{ +#if 0 + CTX_COMPOSITE_SOURCE_OVER = 0, + CTX_COMPOSITE_COPY = 32, + CTX_COMPOSITE_SOURCE_IN = 64, + CTX_COMPOSITE_SOURCE_OUT = 96, + CTX_COMPOSITE_SOURCE_ATOP = 128, + CTX_COMPOSITE_CLEAR = 160, + + CTX_COMPOSITE_DESTINATION_OVER = 192, + CTX_COMPOSITE_DESTINATION = 224, + CTX_COMPOSITE_DESTINATION_IN = 256, + CTX_COMPOSITE_DESTINATION_OUT = 288, + CTX_COMPOSITE_DESTINATION_ATOP = 320, + CTX_COMPOSITE_XOR = 352, + + CTX_COMPOSITE_ALL = (32+64+128+256) +#else + CTX_COMPOSITE_SOURCE_OVER =0, + CTX_COMPOSITE_COPY , + CTX_COMPOSITE_SOURCE_IN , + CTX_COMPOSITE_SOURCE_OUT , + CTX_COMPOSITE_SOURCE_ATOP , + CTX_COMPOSITE_CLEAR , + + CTX_COMPOSITE_DESTINATION_OVER , + CTX_COMPOSITE_DESTINATION , + CTX_COMPOSITE_DESTINATION_IN , + CTX_COMPOSITE_DESTINATION_OUT , + CTX_COMPOSITE_DESTINATION_ATOP , + CTX_COMPOSITE_XOR , +#endif +} CtxCompositingMode; + +typedef enum +{ + CTX_BLEND_NORMAL, + CTX_BLEND_MULTIPLY, + CTX_BLEND_SCREEN, + CTX_BLEND_OVERLAY, + CTX_BLEND_DARKEN, + CTX_BLEND_LIGHTEN, + CTX_BLEND_COLOR_DODGE, + CTX_BLEND_COLOR_BURN, + CTX_BLEND_HARD_LIGHT, + CTX_BLEND_SOFT_LIGHT, + CTX_BLEND_DIFFERENCE, + CTX_BLEND_EXCLUSION, + CTX_BLEND_HUE, + CTX_BLEND_SATURATION, + CTX_BLEND_COLOR, + CTX_BLEND_LUMINOSITY, // 15 + CTX_BLEND_DIVIDE, + CTX_BLEND_ADDITION, + CTX_BLEND_SUBTRACT, // 18 +} CtxBlend; + +void ctx_blend_mode (Ctx *ctx, CtxBlend mode); + +typedef enum +{ + CTX_JOIN_BEVEL = 0, + CTX_JOIN_ROUND = 1, + CTX_JOIN_MITER = 2 +} CtxLineJoin; + +typedef enum +{ + CTX_CAP_NONE = 0, + CTX_CAP_ROUND = 1, + CTX_CAP_SQUARE = 2 +} CtxLineCap; + +typedef enum +{ + CTX_TEXT_BASELINE_ALPHABETIC = 0, + CTX_TEXT_BASELINE_TOP, + CTX_TEXT_BASELINE_HANGING, + CTX_TEXT_BASELINE_MIDDLE, + CTX_TEXT_BASELINE_IDEOGRAPHIC, + CTX_TEXT_BASELINE_BOTTOM +} CtxTextBaseline; + +typedef enum +{ + CTX_TEXT_ALIGN_START = 0, + CTX_TEXT_ALIGN_END, + CTX_TEXT_ALIGN_CENTER, + CTX_TEXT_ALIGN_LEFT, + CTX_TEXT_ALIGN_RIGHT +} CtxTextAlign; + +typedef enum +{ + CTX_TEXT_DIRECTION_INHERIT = 0, + CTX_TEXT_DIRECTION_LTR, + CTX_TEXT_DIRECTION_RTL +} CtxTextDirection; + +struct +_CtxGlyph +{ + uint32_t index; + float x; + float y; +}; + +CtxTextAlign ctx_get_text_align (Ctx *ctx); +CtxTextBaseline ctx_get_text_baseline (Ctx *ctx); +CtxTextDirection ctx_get_text_direction (Ctx *ctx); +CtxFillRule ctx_get_fill_rule (Ctx *ctx); +CtxLineCap ctx_get_line_cap (Ctx *ctx); +CtxLineJoin ctx_get_line_join (Ctx *ctx); +CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx); +CtxBlend ctx_get_blend_mode (Ctx *ctx); + +void ctx_gradient_add_stop_string (Ctx *ctx, float pos, const char *color); + +void ctx_text_align (Ctx *ctx, CtxTextAlign align); +void ctx_text_baseline (Ctx *ctx, CtxTextBaseline baseline); +void ctx_text_direction (Ctx *ctx, CtxTextDirection direction); +void ctx_fill_rule (Ctx *ctx, CtxFillRule fill_rule); +void ctx_line_cap (Ctx *ctx, CtxLineCap cap); +void ctx_line_join (Ctx *ctx, CtxLineJoin join); +void ctx_compositing_mode (Ctx *ctx, CtxCompositingMode mode); +int ctx_set_drawlist (Ctx *ctx, void *data, int length); +typedef struct _CtxEntry CtxEntry; +/* we only care about the tight packing for this specific + * struct as we do indexing across members in arrays of it, + * to make sure its size becomes 9bytes - + * the pack pragma is also sufficient on recent gcc versions + */ +#pragma pack(push,1) +struct + _CtxEntry +{ + uint8_t code; + union + { + float f[2]; + uint8_t u8[8]; + int8_t s8[8]; + uint16_t u16[4]; + int16_t s16[4]; + uint32_t u32[2]; + int32_t s32[2]; + uint64_t u64[1]; // unused + } data; // 9bytes long, we're favoring compactness and correctness + // over performance. By sacrificing float precision, zeroing + // first 8bit of f[0] would permit 8bytes long and better + // aglinment and cacheline behavior. +}; +#pragma pack(pop) +const CtxEntry *ctx_get_drawlist (Ctx *ctx); +int ctx_append_drawlist (Ctx *ctx, void *data, int length); + +/* these are only needed for clients rendering text, as all text gets + * converted to paths. + */ +void ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs); + +void ctx_glyphs_stroke (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs); + +void ctx_text (Ctx *ctx, + const char *string); +void ctx_text_stroke (Ctx *ctx, + const char *string); + +void ctx_fill_text (Ctx *ctx, + const char *string, + float x, + float y); + +void ctx_stroke_text (Ctx *ctx, + const char *string, + float x, + float y); + +/* returns the total horizontal advance if string had been rendered */ +float ctx_text_width (Ctx *ctx, + const char *string); + +float ctx_glyph_width (Ctx *ctx, int unichar); + +int ctx_load_font_ttf (const char *name, const void *ttf_contents, int length); + + + +enum _CtxModifierState +{ + CTX_MODIFIER_STATE_SHIFT = (1<<0), + CTX_MODIFIER_STATE_CONTROL = (1<<1), + CTX_MODIFIER_STATE_ALT = (1<<2), + CTX_MODIFIER_STATE_BUTTON1 = (1<<3), + CTX_MODIFIER_STATE_BUTTON2 = (1<<4), + CTX_MODIFIER_STATE_BUTTON3 = (1<<5), + CTX_MODIFIER_STATE_DRAG = (1<<6), // pointer button is down (0 or any) +}; +typedef enum _CtxModifierState CtxModifierState; + +enum _CtxScrollDirection +{ + CTX_SCROLL_DIRECTION_UP, + CTX_SCROLL_DIRECTION_DOWN, + CTX_SCROLL_DIRECTION_LEFT, + CTX_SCROLL_DIRECTION_RIGHT +}; +typedef enum _CtxScrollDirection CtxScrollDirection; + +typedef struct _CtxEvent CtxEvent; + +void ctx_set_renderer (Ctx *ctx, + void *renderer); +void *ctx_get_renderer (Ctx *ctx); + +int ctx_renderer_is_sdl (Ctx *ctx); +int ctx_renderer_is_fb (Ctx *ctx); +int ctx_renderer_is_ctx (Ctx *ctx); +int ctx_renderer_is_term (Ctx *ctx); + +/* the following API is only available when CTX_EVENTS is defined to 1 + * + * it provides the ability to register callbacks with the current path + * that get delivered with transformed coordinates. + */ +int ctx_is_dirty (Ctx *ctx); +void ctx_set_dirty (Ctx *ctx, int dirty); +float ctx_get_float (Ctx *ctx, uint64_t hash); +void ctx_set_float (Ctx *ctx, uint64_t hash, float value); + +unsigned long ctx_ticks (void); +void ctx_flush (Ctx *ctx); + +void ctx_set_clipboard (Ctx *ctx, const char *text); +char *ctx_get_clipboard (Ctx *ctx); + +void _ctx_events_init (Ctx *ctx); +typedef struct _CtxIntRectangle CtxIntRectangle; +struct _CtxIntRectangle { + int x; + int y; + int width; + int height; +}; + +void ctx_quit (Ctx *ctx); +int ctx_has_quit (Ctx *ctx); + +typedef void (*CtxCb) (CtxEvent *event, + void *data, + void *data2); +typedef void (*CtxDestroyNotify) (void *data); + +enum _CtxEventType { + CTX_PRESS = 1 << 0, + CTX_MOTION = 1 << 1, + CTX_RELEASE = 1 << 2, + CTX_ENTER = 1 << 3, + CTX_LEAVE = 1 << 4, + CTX_TAP = 1 << 5, + CTX_TAP_AND_HOLD = 1 << 6, + + /* NYI: SWIPE, ZOOM ROT_ZOOM, */ + + CTX_DRAG_PRESS = 1 << 7, + CTX_DRAG_MOTION = 1 << 8, + CTX_DRAG_RELEASE = 1 << 9, + CTX_KEY_PRESS = 1 << 10, + CTX_KEY_DOWN = 1 << 11, + CTX_KEY_UP = 1 << 12, + CTX_SCROLL = 1 << 13, + CTX_MESSAGE = 1 << 14, + CTX_DROP = 1 << 15, + + CTX_SET_CURSOR = 1 << 16, // used internally + + /* client should store state - preparing + * for restart + */ + CTX_POINTER = (CTX_PRESS | CTX_MOTION | CTX_RELEASE | CTX_DROP), + CTX_TAPS = (CTX_TAP | CTX_TAP_AND_HOLD), + CTX_CROSSING = (CTX_ENTER | CTX_LEAVE), + CTX_DRAG = (CTX_DRAG_PRESS | CTX_DRAG_MOTION | CTX_DRAG_RELEASE), + CTX_KEY = (CTX_KEY_DOWN | CTX_KEY_UP | CTX_KEY_PRESS), + CTX_MISC = (CTX_MESSAGE), + CTX_ANY = (CTX_POINTER | CTX_DRAG | CTX_CROSSING | CTX_KEY | CTX_MISC | CTX_TAPS), +}; +typedef enum _CtxEventType CtxEventType; + +#define CTX_CLICK CTX_PRESS // SHOULD HAVE MORE LOGIC + +struct _CtxEvent { + CtxEventType type; + uint32_t time; + Ctx *ctx; + int stop_propagate; /* when set - propagation is stopped */ + + CtxModifierState state; + + int device_no; /* 0 = left mouse button / virtual focus */ + /* 1 = middle mouse button */ + /* 2 = right mouse button */ + /* 3 = first multi-touch .. (NYI) */ + + float device_x; /* untransformed (device) coordinates */ + float device_y; + + /* coordinates; and deltas for motion/drag events in user-coordinates: */ + float x; + float y; + float start_x; /* start-coordinates (press) event for drag, */ + float start_y; /* untransformed coordinates */ + float prev_x; /* previous events coordinates */ + float prev_y; + float delta_x; /* x - prev_x, redundant - but often useful */ + float delta_y; /* y - prev_y, redundant - .. */ + + + unsigned int unicode; /* only valid for key-events, re-use as keycode? */ + const char *string; /* as key can be "up" "down" "space" "backspace" "a" "b" "ø" etc .. */ + /* this is also where the message is delivered for + * MESSAGE events + * + * and the data for drop events are delivered + */ + CtxScrollDirection scroll_direction; + + + // would be nice to add the bounding box of the hit-area causing + // the event, making for instance scissored enter/leave repaint easier. +}; + +// layer-event "layer" motion x y device_no + +void ctx_add_key_binding_full (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data, + CtxDestroyNotify destroy_notify, + void *destroy_data); +void ctx_add_key_binding (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data); +typedef struct CtxBinding { + char *nick; + char *command; + char *label; + CtxCb cb; + void *cb_data; + CtxDestroyNotify destroy_notify; + void *destroy_data; +} CtxBinding; +CtxBinding *ctx_get_bindings (Ctx *ctx); +void ctx_clear_bindings (Ctx *ctx); +void ctx_remove_idle (Ctx *ctx, int handle); +int ctx_add_timeout_full (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data); +int ctx_add_timeout (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data); +int ctx_add_idle_full (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data); +int ctx_add_idle (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data); + + +void ctx_add_hit_region (Ctx *ctx, const char *id); + +void ctx_set_title (Ctx *ctx, const char *title); + +void ctx_listen_full (Ctx *ctx, + float x, + float y, + float width, + float height, + CtxEventType types, + CtxCb cb, + void *data1, + void *data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data); +void ctx_event_stop_propagate (CtxEvent *event); +void ctx_listen (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2); +void ctx_listen_with_finalize (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data); + +void ctx_init (int *argc, char ***argv); // is a no-op but could launch + // terminal +CtxEvent *ctx_get_event (Ctx *ctx); +int ctx_has_event (Ctx *ctx, int timeout); +void ctx_get_event_fds (Ctx *ctx, int *fd, int *count); + +int ctx_pointer_is_down (Ctx *ctx, int no); +float ctx_pointer_x (Ctx *ctx); +float ctx_pointer_y (Ctx *ctx); +void ctx_freeze (Ctx *ctx); +void ctx_thaw (Ctx *ctx); +int ctx_events_frozen (Ctx *ctx); +void ctx_events_clear_items (Ctx *ctx); +int ctx_events_width (Ctx *ctx); +int ctx_events_height (Ctx *ctx); + +/* The following functions drive the event delivery, registered callbacks + * are called in response to these being called. + */ + +int ctx_key_down (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); +int ctx_key_up (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); +int ctx_key_press (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); + + +int ctx_scrolled (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time); +void ctx_incoming_message (Ctx *ctx, const char *message, long time); +int ctx_pointer_motion (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_release (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_press (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_drop (Ctx *ctx, float x, float y, int device_no, uint32_t time, + char *string); + +typedef enum +{ + CTX_CONT = '\0', // - contains args from preceding entry + CTX_NOP = ' ', // + CTX_DATA = '(', // size size-in-entries - u32 + CTX_DATA_REV = ')', // reverse traversal data marker + CTX_SET_RGBA_U8 = '*', // r g b a - u8 + CTX_NEW_EDGE = '+', // x0 y0 x1 y1 - s16 + // set pixel might want a shorter ascii form? or keep it an embedded + // only option? + CTX_SET_PIXEL = '-', // 8bit "fast-path" r g b a x y - u8 for rgba, and u16 for x,y + /* optimizations that reduce the number of entries used, + * not visible outside the drawlist compression, thus + * using entries that cannot be used directly as commands + * since they would be interpreted as numbers - if values>127 + * then the embedded font data is harder to escape. + */ + CTX_REL_LINE_TO_X4 = '0', // x1 y1 x2 y2 x3 y3 x4 y4 -- s8 + CTX_REL_LINE_TO_REL_CURVE_TO = '1', // x1 y1 cx1 cy1 cx2 cy2 x y -- s8 + CTX_REL_CURVE_TO_REL_LINE_TO = '2', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8 + CTX_REL_CURVE_TO_REL_MOVE_TO = '3', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8 + CTX_REL_LINE_TO_X2 = '4', // x1 y1 x2 y2 -- s16 + CTX_MOVE_TO_REL_LINE_TO = '5', // x1 y1 x2 y2 -- s16 + CTX_REL_LINE_TO_REL_MOVE_TO = '6', // x1 y1 x2 y2 -- s16 + CTX_FILL_MOVE_TO = '7', // x y + CTX_REL_QUAD_TO_REL_QUAD_TO = '8', // cx1 x1 cy1 y1 cx1 x2 cy1 y1 -- s8 + CTX_REL_QUAD_TO_S16 = '9', // cx1 cy1 x y - s16 + // expand with: . : + CTX_FLUSH = ';', + + CTX_DEFINE_GLYPH = '@', // unichar width - u32 + CTX_ARC_TO = 'A', // x1 y1 x2 y2 radius + CTX_ARC = 'B', // x y radius angle1 angle2 direction + CTX_CURVE_TO = 'C', // cx1 cy1 cx2 cy2 x y + CTX_STROKE = 'E', // + CTX_FILL = 'F', // + CTX_RESTORE = 'G', // + CTX_HOR_LINE_TO = 'H', // x + CTX_DEFINE_TEXTURE = 'I', // "eid" width height format "data" + CTX_ROTATE = 'J', // radians + CTX_COLOR = 'K', // model, c1 c2 c3 ca - has a variable set of + // arguments. + CTX_LINE_TO = 'L', // x y + CTX_MOVE_TO = 'M', // x y + CTX_BEGIN_PATH = 'N', // + CTX_SCALE = 'O', // xscale yscale + CTX_NEW_PAGE = 'P', // - NYI - optional page-size + CTX_QUAD_TO = 'Q', // cx cy x y + CTX_VIEW_BOX = 'R', // x y width height + CTX_SMOOTH_TO = 'S', // cx cy x y + CTX_SMOOTHQ_TO = 'T', // x y + CTX_RESET = 'U', // + CTX_VER_LINE_TO = 'V', // y + CTX_APPLY_TRANSFORM = 'W', // a b c d e f - for set_transform combine with identity + CTX_EXIT = 'X', // + CTX_ROUND_RECTANGLE = 'Y', // x y width height radius + + CTX_CLOSE_PATH2 = 'Z', // + CTX_STROKE_SOURCE = '_', // next source definition applies to strokes + CTX_KERNING_PAIR = '[', // glA glB kerning, glA and glB in u16 kerning in s32 + CTX_COLOR_SPACE = ']', // IccSlot data data_len, + // data can be a string with a name, + // icc data or perhaps our own serialization + // of profile data + CTX_REL_ARC_TO = 'a', // x1 y1 x2 y2 radius + CTX_CLIP = 'b', + CTX_REL_CURVE_TO = 'c', // cx1 cy1 cx2 cy2 x y + CTX_LINE_DASH = 'd', // dashlen0 [dashlen1 ...] + CTX_TRANSLATE = 'e', // x y + CTX_LINEAR_GRADIENT = 'f', // x1 y1 x2 y2 + CTX_SAVE = 'g', + CTX_REL_HOR_LINE_TO = 'h', // x + CTX_TEXTURE = 'i', + CTX_PRESERVE = 'j', // + CTX_SET_KEY = 'k', // - used together with another char to identify + // a key to set + CTX_REL_LINE_TO = 'l', // x y + CTX_REL_MOVE_TO = 'm', // x y + CTX_FONT = 'n', // as used by text parser + CTX_RADIAL_GRADIENT = 'o', // x1 y1 radius1 x2 y2 radius2 + CTX_GRADIENT_STOP = 'p', // argument count depends on current color model + CTX_REL_QUAD_TO = 'q', // cx cy x y + CTX_RECTANGLE = 'r', // x y width height + CTX_REL_SMOOTH_TO = 's', // cx cy x y + CTX_REL_SMOOTHQ_TO = 't', // x y + CTX_STROKE_TEXT = 'u', // string - utf8 string + CTX_REL_VER_LINE_TO = 'v', // y + CTX_GLYPH = 'w', // unichar fontsize + CTX_TEXT = 'x', // string | kern - utf8 data to shape or horizontal kerning amount + CTX_IDENTITY = 'y', // + CTX_CLOSE_PATH = 'z', // + CTX_START_GROUP = '{', + CTX_END_GROUP = '}', + CTX_SOURCE_TRANSFORM = '`', + + CTX_EDGE = '&', // will not occur in commandstream + CTX_EDGE_FLIPPED = '^', // x0 y0 x1 y1 - s16 // thus these use reserved entries as code + + /* though expressed as two chars in serialization we have + * dedicated byte commands for the setters to keep the dispatch + * simpler. There is no need for these to be human readable thus we go >128 + * + * unused: !&<=>?:.=/\`, + * reserved: '"& # %^@ + */ + + + CTX_FILL_RULE = 128, // kr rule - u8, default = CTX_FILLE_RULE_EVEN_ODD + CTX_BLEND_MODE = 129, // kB mode - u8 , default=0 + + CTX_MITER_LIMIT = 130, // km limit - float, default = 0.0 + + CTX_LINE_JOIN = 131, // kj join - u8 , default=0 + CTX_LINE_CAP = 132, // kc cap - u8, default = 0 + CTX_LINE_WIDTH = 133, // kw width, default = 2.0 + CTX_GLOBAL_ALPHA = 134, // ka alpha - default=1.0 + CTX_COMPOSITING_MODE = 135, // kc mode - u8 , default=0 + + CTX_FONT_SIZE = 136, // kf size - float, default=? + CTX_TEXT_ALIGN = 137, // kt align - u8, default = CTX_TEXT_ALIGN_START + CTX_TEXT_BASELINE = 138, // kb baseline - u8, default = CTX_TEXT_ALIGN_ALPHABETIC + CTX_TEXT_DIRECTION = 139, // kd + + CTX_SHADOW_BLUR = 140, // ks + CTX_SHADOW_COLOR = 141, // kC + CTX_SHADOW_OFFSET_X = 142, // kx + CTX_SHADOW_OFFSET_Y = 143, // ky + CTX_IMAGE_SMOOTHING = 144, // kS + CTX_LINE_DASH_OFFSET = 145, // kD lineDashOffset + + // items marked with % are currently only for the parser + // for instance for svg compatibility or simulated/converted color spaces + // not the serialization/internal render stream + // + CTX_STROKE_RECT = 200, // strokeRect - only exist in long form + CTX_FILL_RECT = 201, // fillRect - only exist in long form +} CtxCode; + + +#pragma pack(push,1) + +typedef struct _CtxCommand CtxCommand; +typedef struct _CtxIterator CtxIterator; + +CtxIterator * +ctx_current_path (Ctx *ctx); +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2); + +#define CTX_ASSERT 0 + +#if CTX_ASSERT==1 +#define ctx_assert(a) if(!(a)){fprintf(stderr,"%s:%i assertion failed\n", __FUNCTION__, __LINE__); } +#else +#define ctx_assert(a) +#endif + +int ctx_get_drawlist_count (Ctx *ctx); + +struct + _CtxCommand +{ + union + { + uint8_t code; + CtxEntry entry; + struct + { + uint8_t code; + float scalex; + float scaley; + } scale; + struct + { + uint8_t code; + uint32_t stringlen; + uint32_t blocklen; + uint8_t cont; + uint8_t data[8]; /* ... and continues */ + } data; + struct + { + uint8_t code; + uint32_t stringlen; + uint32_t blocklen; + } data_rev; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } text; + struct + { + uint8_t code; + uint32_t key_hash; + float pad; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } set; + struct + { + uint8_t code; + uint32_t pad0; + float pad1; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } get; + struct { + uint8_t code; + uint32_t count; /* better than byte_len in code, but needs to then be set */ + float pad1; + uint8_t code_data; + uint32_t byte_len; + uint32_t blocklen; + uint8_t code_cont; + float data[2]; /* .. and - possibly continues */ + } line_dash; + struct { + uint8_t code; + uint32_t space_slot; + float pad1; + uint8_t code_data; + uint32_t data_len; + uint32_t blocklen; + uint8_t code_cont; + uint8_t data[8]; /* .. and continues */ + } colorspace; + struct + { + uint8_t code; + float x; + float y; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + char eid[8]; /* .. and continues */ + } texture; + struct + { + uint8_t code; + uint32_t width; + uint32_t height; + uint8_t code_cont0; + uint16_t format; + uint16_t pad0; + uint32_t pad1; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont1; + char eid[8]; /* .. and continues */ + // followed by - in variable offset code_Data, data_len, datablock_len, cont, pixeldata + } define_texture; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } text_stroke; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } set_font; + struct + { + uint8_t code; + float model; + float r; + uint8_t pad1; + float g; + float b; + uint8_t pad2; + float a; + } rgba; + struct + { + uint8_t code; + float model; + float c; + uint8_t pad1; + float m; + float y; + uint8_t pad2; + float k; + float a; + } cmyka; + struct + { + uint8_t code; + float model; + float g; + uint8_t pad1; + float a; + } graya; + + struct + { + uint8_t code; + float model; + float c0; + uint8_t pad1; + float c1; + float c2; + uint8_t pad2; + float c3; + float c4; + uint8_t pad3; + float c5; + float c6; + uint8_t pad4; + float c7; + float c8; + uint8_t pad5; + float c9; + float c10; + } set_color; + struct + { + uint8_t code; + float x; + float y; + } rel_move_to; + struct + { + uint8_t code; + float x; + float y; + } rel_line_to; + struct + { + uint8_t code; + float x; + float y; + } line_to; + struct + { + uint8_t code; + float cx1; + float cy1; + uint8_t pad0; + float cx2; + float cy2; + uint8_t pad1; + float x; + float y; + } rel_curve_to; + struct + { + uint8_t code; + float x; + float y; + } move_to; + struct + { + uint8_t code; + float cx1; + float cy1; + uint8_t pad0; + float cx2; + float cy2; + uint8_t pad1; + float x; + float y; + } curve_to; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float r1; + float x2; + uint8_t pad1; + float y2; + float r2; + } radial_gradient; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float x2; + float y2; + } linear_gradient; + struct + { + uint8_t code; + float x; + float y; + uint8_t pad0; + float width; + float height; + uint8_t pad1; + float radius; + } rectangle; + struct { + uint8_t code; + float x; + float y; + uint8_t pad0; + float width; + float height; + } view_box; + + struct + { + uint8_t code; + uint16_t glyph_before; + uint16_t glyph_after; + int32_t amount; + } kern; + + struct + { + uint8_t code; + uint32_t glyph; + uint32_t advance; // * 256 + } define_glyph; + + struct + { + uint8_t code; + uint8_t rgba[4]; + uint16_t x; + uint16_t y; + } set_pixel; + struct + { + uint8_t code; + float cx; + float cy; + uint8_t pad0; + float x; + float y; + } quad_to; + struct + { + uint8_t code; + float cx; + float cy; + uint8_t pad0; + float x; + float y; + } rel_quad_to; + struct + { + uint8_t code; + float x; + float y; + uint8_t pad0; + float radius; + float angle1; + uint8_t pad1; + float angle2; + float direction; + } + arc; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float x2; + float y2; + uint8_t pad1; + float radius; + } + arc_to; + /* some format specific generic accesors: */ + struct + { + uint8_t code; + float x0; + float y0; + uint8_t pad0; + float x1; + float y1; + uint8_t pad1; + float x2; + float y2; + uint8_t pad2; + float x3; + float y3; + uint8_t pad3; + float x4; + float y4; + } c; + struct + { + uint8_t code; + float a0; + float a1; + uint8_t pad0; + float a2; + float a3; + uint8_t pad1; + float a4; + float a5; + uint8_t pad2; + float a6; + float a7; + uint8_t pad3; + float a8; + float a9; + } f; + struct + { + uint8_t code; + uint32_t a0; + uint32_t a1; + uint8_t pad0; + uint32_t a2; + uint32_t a3; + uint8_t pad1; + uint32_t a4; + uint32_t a5; + uint8_t pad2; + uint32_t a6; + uint32_t a7; + uint8_t pad3; + uint32_t a8; + uint32_t a9; + } u32; + struct + { + uint8_t code; + uint64_t a0; + uint8_t pad0; + uint64_t a1; + uint8_t pad1; + uint64_t a2; + uint8_t pad2; + uint64_t a3; + uint8_t pad3; + uint64_t a4; + } u64; + struct + { + uint8_t code; + int32_t a0; + int32_t a1; + uint8_t pad0; + int32_t a2; + int32_t a3; + uint8_t pad1; + int32_t a4; + int32_t a5; + uint8_t pad2; + int32_t a6; + int32_t a7; + uint8_t pad3; + int32_t a8; + int32_t a9; + } s32; + struct + { + uint8_t code; + int16_t a0; + int16_t a1; + int16_t a2; + int16_t a3; + uint8_t pad0; + int16_t a4; + int16_t a5; + int16_t a6; + int16_t a7; + uint8_t pad1; + int16_t a8; + int16_t a9; + int16_t a10; + int16_t a11; + uint8_t pad2; + int16_t a12; + int16_t a13; + int16_t a14; + int16_t a15; + uint8_t pad3; + int16_t a16; + int16_t a17; + int16_t a18; + int16_t a19; + } s16; + struct + { + uint8_t code; + uint16_t a0; + uint16_t a1; + uint16_t a2; + uint16_t a3; + uint8_t pad0; + uint16_t a4; + uint16_t a5; + uint16_t a6; + uint16_t a7; + uint8_t pad1; + uint16_t a8; + uint16_t a9; + uint16_t a10; + uint16_t a11; + uint8_t pad2; + uint16_t a12; + uint16_t a13; + uint16_t a14; + uint16_t a15; + uint8_t pad3; + uint16_t a16; + uint16_t a17; + uint16_t a18; + uint16_t a19; + } u16; + struct + { + uint8_t code; + uint8_t a0; + uint8_t a1; + uint8_t a2; + uint8_t a3; + uint8_t a4; + uint8_t a5; + uint8_t a6; + uint8_t a7; + uint8_t pad0; + uint8_t a8; + uint8_t a9; + uint8_t a10; + uint8_t a11; + uint8_t a12; + uint8_t a13; + uint8_t a14; + uint8_t a15; + uint8_t pad1; + uint8_t a16; + uint8_t a17; + uint8_t a18; + uint8_t a19; + uint8_t a20; + uint8_t a21; + uint8_t a22; + uint8_t a23; + } u8; + struct + { + uint8_t code; + int8_t a0; + int8_t a1; + int8_t a2; + int8_t a3; + int8_t a4; + int8_t a5; + int8_t a6; + int8_t a7; + uint8_t pad0; + int8_t a8; + int8_t a9; + int8_t a10; + int8_t a11; + int8_t a12; + int8_t a13; + int8_t a14; + int8_t a15; + uint8_t pad1; + int8_t a16; + int8_t a17; + int8_t a18; + int8_t a19; + int8_t a20; + int8_t a21; + int8_t a22; + int8_t a23; + } s8; + }; + CtxEntry next_entry; // also pads size of CtxCommand slightly. +}; + +typedef struct _CtxImplementation CtxImplementation; +struct _CtxImplementation +{ + void (*process) (void *renderer, CtxCommand *entry); + void (*reset) (void *renderer); + void (*flush) (void *renderer); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *renderer); +}; + +CtxCommand *ctx_iterator_next (CtxIterator *iterator); + +#define ctx_arg_string() ((char*)&entry[2].data.u8[0]) + + +/* The above should be public API + */ + +#pragma pack(pop) + +/* access macros for nth argument of a given type when packed into + * an CtxEntry pointer in current code context + */ +#define ctx_arg_float(no) entry[(no)>>1].data.f[(no)&1] +#define ctx_arg_u64(no) entry[(no)].data.u64[0] +#define ctx_arg_u32(no) entry[(no)>>1].data.u32[(no)&1] +#define ctx_arg_s32(no) entry[(no)>>1].data.s32[(no)&1] +#define ctx_arg_u16(no) entry[(no)>>2].data.u16[(no)&3] +#define ctx_arg_s16(no) entry[(no)>>2].data.s16[(no)&3] +#define ctx_arg_u8(no) entry[(no)>>3].data.u8[(no)&7] +#define ctx_arg_s8(no) entry[(no)>>3].data.s8[(no)&7] +#define ctx_arg_string() ((char*)&entry[2].data.u8[0]) + +typedef enum +{ + CTX_GRAY = 1, + CTX_RGB = 3, + CTX_DRGB = 4, + CTX_CMYK = 5, + CTX_DCMYK = 6, + CTX_LAB = 7, + CTX_LCH = 8, + CTX_GRAYA = 101, + CTX_RGBA = 103, + CTX_DRGBA = 104, + CTX_CMYKA = 105, + CTX_DCMYKA = 106, + CTX_LABA = 107, + CTX_LCHA = 108, + CTX_GRAYA_A = 201, + CTX_RGBA_A = 203, + CTX_RGBA_A_DEVICE = 204, + CTX_CMYKA_A = 205, + CTX_DCMYKA_A = 206, + // RGB device and RGB ? +} CtxColorModel; + +enum _CtxAntialias +{ + CTX_ANTIALIAS_DEFAULT, // fast - suitable for realtime UI + CTX_ANTIALIAS_NONE, // non-antialiased + CTX_ANTIALIAS_FAST, // aa 3 // deprected or is default equal to this now? + CTX_ANTIALIAS_GOOD, // aa 5 // this should perhaps still be 5? + CTX_ANTIALIAS_BEST // aa 17 // accurate-suitable for saved assets +}; +typedef enum _CtxAntialias CtxAntialias; + +enum _CtxCursor +{ + CTX_CURSOR_UNSET, + CTX_CURSOR_NONE, + CTX_CURSOR_ARROW, + CTX_CURSOR_IBEAM, + CTX_CURSOR_WAIT, + CTX_CURSOR_HAND, + CTX_CURSOR_CROSSHAIR, + CTX_CURSOR_RESIZE_ALL, + CTX_CURSOR_RESIZE_N, + CTX_CURSOR_RESIZE_S, + CTX_CURSOR_RESIZE_E, + CTX_CURSOR_RESIZE_NE, + CTX_CURSOR_RESIZE_SE, + CTX_CURSOR_RESIZE_W, + CTX_CURSOR_RESIZE_NW, + CTX_CURSOR_RESIZE_SW, + CTX_CURSOR_MOVE +}; +typedef enum _CtxCursor CtxCursor; + +/* to be used immediately after a ctx_listen or ctx_listen_full causing the + * cursor to change when hovering the listen area. + */ +void ctx_listen_set_cursor (Ctx *ctx, + CtxCursor cursor); + +/* lower level cursor setting that is independent of ctx event handling + */ +void ctx_set_cursor (Ctx *ctx, CtxCursor cursor); +CtxCursor ctx_get_cursor (Ctx *ctx); +void ctx_set_antialias (Ctx *ctx, CtxAntialias antialias); +CtxAntialias ctx_get_antialias (Ctx *ctx); +void ctx_set_render_threads (Ctx *ctx, int n_threads); +int ctx_get_render_threads (Ctx *ctx); + +void ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache); +int ctx_get_hash_cache (Ctx *ctx); + + +typedef struct _CtxParser CtxParser; + CtxParser *ctx_parser_new ( + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data); + + +enum _CtxColorSpace +{ + CTX_COLOR_SPACE_DEVICE_RGB, + CTX_COLOR_SPACE_DEVICE_CMYK, + CTX_COLOR_SPACE_USER_RGB, + CTX_COLOR_SPACE_USER_CMYK, + CTX_COLOR_SPACE_TEXTURE +}; +typedef enum _CtxColorSpace CtxColorSpace; + +/* sets the color space for a slot, the space is either a string of + * "sRGB" "rec2020" .. etc or an icc profile. + * + * The slots device_rgb and device_cmyk is mostly to be handled outside drawing + * code, and user_rgb and user_cmyk is to be used. With no user_cmyk set + * user_cmyk == device_cmyk. + * + * The set profiles follows the graphics state. + */ +void ctx_colorspace (Ctx *ctx, + CtxColorSpace space_slot, + unsigned char *data, + int data_length); + +void +ctx_parser_set_size (CtxParser *parser, + int width, + int height, + float cell_width, + float cell_height); + +void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count); + +int +ctx_get_contents (const char *path, + unsigned char **contents, + long *length); + +void ctx_parser_free (CtxParser *parser); +typedef struct _CtxSHA1 CtxSHA1; + +void +ctx_bin2base64 (const void *bin, + int bin_length, + char *ascii); +int +ctx_base642bin (const char *ascii, + int *length, + unsigned char *bin); +float ctx_term_get_cell_width (Ctx *ctx); +float ctx_term_get_cell_height (Ctx *ctx); + + +#if 1 // CTX_VT + +typedef struct _VT VT; +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str); +void vt_paste (VT *vt, const char *str); +char *vt_get_selection (VT *vt); +long vt_rev (VT *vt); +int vt_has_blink (VT *vt); + +int ctx_clients_need_redraw (Ctx *ctx); +void ctx_clients_handle_events (Ctx *ctx); + + +typedef struct _CtxBuffer CtxBuffer; +CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data); + + +#endif + +#ifndef CTX_CODEC_CHAR +//#define CTX_CODEC_CHAR '\035' +//#define CTX_CODEC_CHAR 'a' +#define CTX_CODEC_CHAR '\020' // datalink escape +//#define CTX_CODEC_CHAR '^' +#endif + +#ifndef assert +#define assert(a) +#endif + +#ifdef __cplusplus +} +#endif +#endif +#ifndef __CTX_H__ +#define __CTX_H__ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#ifndef CTX_STRING_H +#define CTX_STRING_H + +typedef struct _CtxString CtxString; +struct _CtxString +{ + char *str; + int length; + int utf8_length; + int allocated_length; + int is_line; +}; + +CtxString *ctx_string_new_with_size (const char *initial, int initial_size); +CtxString *ctx_string_new (const char *initial); +void ctx_string_free (CtxString *string, int freealloc); +const char *ctx_string_get (CtxString *string); +uint32_t ctx_string_get_unichar (CtxString *string, int pos); +int ctx_string_get_length (CtxString *string); +int ctx_string_get_utf8length (CtxString *string); +void ctx_string_set (CtxString *string, const char *new_string); +void ctx_string_clear (CtxString *string); +void ctx_string_append_str (CtxString *string, const char *str); +void ctx_string_append_byte (CtxString *string, char val); +void ctx_string_append_string (CtxString *string, CtxString *string2); +void ctx_string_append_unichar (CtxString *string, unsigned int unichar); +void ctx_string_append_data (CtxString *string, const char *data, int len); + +void ctx_string_append_utf8char (CtxString *string, const char *str); +void ctx_string_append_printf (CtxString *string, const char *format, ...); +void ctx_string_replace_utf8 (CtxString *string, int pos, const char *new_glyph); +void ctx_string_insert_utf8 (CtxString *string, int pos, const char *new_glyph); + +void ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar); +void ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar); +void ctx_string_remove (CtxString *string, int pos); +char *ctx_strdup_printf (const char *format, ...); + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#endif +#ifndef __CTX_LIST__ +#define __CTX_LIST__ + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#endif + +/* The whole ctx_list implementation is in the header and will be inlined + * wherever it is used. + */ + +static inline void *ctx_calloc (size_t size, size_t count) +{ + size_t byte_size = size * count; + char *ret = (char*)malloc (byte_size); + for (size_t i = 0; i < byte_size; i++) + ret[i] = 0; + return ret; +} + +typedef struct _CtxList CtxList; +struct _CtxList { + void *data; + CtxList *next; + void (*freefunc)(void *data, void *freefunc_data); + void *freefunc_data; +}; + +static inline void ctx_list_prepend_full (CtxList **list, void *data, + void (*freefunc)(void *data, void *freefunc_data), + void *freefunc_data) +{ + CtxList *new_= (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = *list; + new_->data=data; + new_->freefunc=freefunc; + new_->freefunc_data = freefunc_data; + *list = new_; +} + +static inline int ctx_list_length (CtxList *list) +{ + int length = 0; + CtxList *l; + for (l = list; l; l = l->next, length++); + return length; +} + +static inline void ctx_list_prepend (CtxList **list, void *data) +{ + CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1); + new_->next= *list; + new_->data=data; + *list = new_; +} + +static inline CtxList *ctx_list_nth (CtxList *list, int no) +{ + while (no-- && list) + { list = list->next; } + return list; +} + +static inline void *ctx_list_nth_data (CtxList *list, int no) +{ + CtxList *l = ctx_list_nth (list, no); + if (l) + return l->data; + return NULL; +} + + +static inline void +ctx_list_insert_before (CtxList **list, CtxList *sibling, + void *data) +{ + if (*list == NULL || *list == sibling) + { + ctx_list_prepend (list, data); + } + else + { + CtxList *prev = NULL; + for (CtxList *l = *list; l; l=l->next) + { + if (l == sibling) + { break; } + prev = l; + } + if (prev) + { + CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + } + } +} + +static inline void ctx_list_remove_link (CtxList **list, CtxList *link) +{ + CtxList *iter, *prev = NULL; + if ((*list) == link) + { + prev = (*list)->next; + *list = prev; + link->next = NULL; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter == link) + { + if (prev) + prev->next = iter->next; + link->next = NULL; + return; + } + else + prev = iter; +} + +static inline void ctx_list_remove (CtxList **list, void *data) +{ + CtxList *iter, *prev = NULL; + if ((*list)->data == data) + { + if ((*list)->freefunc) + (*list)->freefunc ((*list)->data, (*list)->freefunc_data); + prev = (*list)->next; + free (*list); + *list = prev; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter->data == data) + { + if (iter->freefunc) + iter->freefunc (iter->data, iter->freefunc_data); + prev->next = iter->next; + free (iter); + break; + } + else + prev = iter; +} + +static inline void ctx_list_free (CtxList **list) +{ + while (*list) + ctx_list_remove (list, (*list)->data); +} + +static inline void +ctx_list_reverse (CtxList **list) +{ + CtxList *new_ = NULL; + CtxList *l; + for (l = *list; l; l=l->next) + ctx_list_prepend (&new_, l->data); + ctx_list_free (list); + *list = new_; +} + +static inline void *ctx_list_last (CtxList *list) +{ + if (list) + { + CtxList *last; + for (last = list; last->next; last=last->next); + return last->data; + } + return NULL; +} + +static inline void ctx_list_concat (CtxList **list, CtxList *list_b) +{ + if (*list) + { + CtxList *last; + for (last = *list; last->next; last=last->next); + last->next = list_b; + return; + } + *list = list_b; +} + +static inline void ctx_list_append_full (CtxList **list, void *data, + void (*freefunc)(void *data, void *freefunc_data), + void *freefunc_data) +{ + CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1); + new_->data=data; + new_->freefunc = freefunc; + new_->freefunc_data = freefunc_data; + ctx_list_concat (list, new_); +} + +static inline void ctx_list_append (CtxList **list, void *data) +{ + ctx_list_append_full (list, data, NULL, NULL); +} + +static inline void +ctx_list_insert_at (CtxList **list, + int no, + void *data) +{ + if (*list == NULL || no == 0) + { + ctx_list_prepend (list, data); + } + else + { + int pos = 0; + CtxList *prev = NULL; + CtxList *sibling = NULL; + for (CtxList *l = *list; l && pos < no; l=l->next) + { + prev = sibling; + sibling = l; + pos ++; + } + if (prev) + { + CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + return; + } + ctx_list_append (list, data); + } +} + +static CtxList* +ctx_list_merge_sorted (CtxList* list1, + CtxList* list2, + int(*compare)(const void *a, const void *b, void *userdata), void *userdata +) +{ + if (list1 == NULL) + return(list2); + else if (list2==NULL) + return(list1); + + if (compare (list1->data, list2->data, userdata) >= 0) + { + list1->next = ctx_list_merge_sorted (list1->next,list2, compare, userdata); + /*list1->next->prev = list1; + list1->prev = NULL;*/ + return list1; + } + else + { + list2->next = ctx_list_merge_sorted (list1,list2->next, compare, userdata); + /*list2->next->prev = list2; + list2->prev = NULL;*/ + return list2; + } +} + +static void +ctx_list_split_half (CtxList* head, + CtxList** list1, + CtxList** list2) +{ + CtxList* fast; + CtxList* slow; + if (head==NULL || head->next==NULL) + { + *list1 = head; + *list2 = NULL; + } + else + { + slow = head; + fast = head->next; + + while (fast != NULL) + { + fast = fast->next; + if (fast != NULL) + { + slow = slow->next; + fast = fast->next; + } + } + + *list1 = head; + *list2 = slow->next; + slow->next = NULL; + } +} + +static inline void ctx_list_sort (CtxList **head, + int(*compare)(const void *a, const void *b, void *userdata), + void *userdata) +{ + CtxList* list1; + CtxList* list2; + + /* Base case -- length 0 or 1 */ + if ((*head == NULL) || ((*head)->next == NULL)) + { + return; + } + + ctx_list_split_half (*head, &list1, &list2); + ctx_list_sort (&list1, compare, userdata); + ctx_list_sort (&list2, compare, userdata); + *head = ctx_list_merge_sorted (list1, list2, compare, userdata); +} + +static inline void ctx_list_insert_sorted (CtxList **list, + void *item, + int(*compare)(const void *a, const void *b, void *userdata), + void *userdata) +{ + ctx_list_prepend (list, item); + ctx_list_sort (list, compare, userdata); +} + + +static inline CtxList *ctx_list_find_custom (CtxList *list, + void *needle, + int(*compare)(const void *a, const void *b), + void *userdata) +{ + CtxList *l; + for (l = list; l; l = l->next) + { + if (compare (l->data, needle) == 0) + return l; + } + return NULL; +} + +#endif + +/* definitions that determine which features are included and their settings, + * for particular platforms - in particular microcontrollers ctx might need + * tuning for different quality/performance/resource constraints. + * + * the way to configure ctx is to set these defines, before both including it + * as a header and in the file where CTX_IMPLEMENTATION is set to include the + * implementation for different featureset and runtime settings. + * + */ + +/* whether the font rendering happens in backend or front-end of API, the + * option is used set to 0 by the tool that converts ttf fonts to ctx internal + * representation - both should be possible so that this tool can be made + * into a TTF/OTF font import at runtime (perhaps even with live subsetting). + */ +#ifndef CTX_BACKEND_TEXT +#define CTX_BACKEND_TEXT 1 +#endif + + +#define CTX_RASTERIZER_AA_SLOPE_LIMIT3 (65536/CTX_SUBDIV/15) +//#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (120536/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (100000/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT5 (140425/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT15 (260425/CTX_SUBDIV/15) + + +#ifndef CTX_ONLY_FAST_AA +#define CTX_ONLY_FAST_AA 0 +#endif + +/* subpixel-aa coordinates used in BITPACKing of drawlist + * + * powers of 2 is faster + */ +#ifndef CTX_SUBDIV +#define CTX_SUBDIV 8 // max framebufer width 4095 +//#define CTX_SUBDIV 10 // max framebufer width 3250 +//#define CTX_SUBDIV 16 // max framebufer width 2047 +//#define CTX_SUBDIV 24 // max framebufer width 1350 +//#define CTX_SUBDIV 32 // max framebufer width 1023 +#endif + + +// 8 12 68 40 24 +// 16 12 68 40 24 +/* scale-factor for font outlines prior to bit quantization by CTX_SUBDIV + * + * changing this also changes font file format - the value should be baked + * into the ctxf files making them less dependent on the ctx used to + * generate them + */ +#define CTX_BAKE_FONT_SIZE 160 + +/* pack some linetos/curvetos/movetos into denser drawlist instructions, + * permitting more vectors to be stored in the same space, experimental + * feature with added overhead. + */ +#ifndef CTX_BITPACK +#define CTX_BITPACK 1 +#endif + +/* whether we have a shape-cache where we keep pre-rasterized bitmaps of + * commonly occuring small shapes, disabled by default since it has some + * glitches (and potential hangs with multi threading). + */ +#ifndef CTX_SHAPE_CACHE +#define CTX_SHAPE_CACHE 0 +#endif + +/* size (in pixels, w*h) that we cache rasterization for + */ +#ifndef CTX_SHAPE_CACHE_DIM +#define CTX_SHAPE_CACHE_DIM (16*16) +#endif + +#ifndef CTX_SHAPE_CACHE_MAX_DIM +#define CTX_SHAPE_CACHE_MAX_DIM 32 +#endif + +/* maximum number of entries in shape cache + */ +#ifndef CTX_SHAPE_CACHE_ENTRIES +#define CTX_SHAPE_CACHE_ENTRIES 160 +#endif + + +#ifndef CTX_PARSER_FIXED_TEMP +#define CTX_PARSER_FIXED_TEMP 0 + // when 1 CTX_PARSER_MAXLEN is the fixed max stringlen +#endif // and no allocations happens beyond creating the parser, + // when 0 the scratchbuf for parsing is a separate dynamically + // growing buffer, that maxes out at CTX_PARSER_MAXLEN + // +#ifndef CTX_PARSER_MAXLEN +#if CTX_PARSER_FIXED_TEMP +#define CTX_PARSER_MAXLEN 1024*128 // This is the maximum texture/string size supported +#else +#define CTX_PARSER_MAXLEN 1024*1024*16 // 16mb +#endif +#endif + +#ifndef CTX_COMPOSITING_GROUPS +#define CTX_COMPOSITING_GROUPS 1 +#endif + +/* maximum nesting level of compositing groups + */ +#ifndef CTX_GROUP_MAX +#define CTX_GROUP_MAX 8 +#endif + +#ifndef CTX_ENABLE_CLIP +#define CTX_ENABLE_CLIP 1 +#endif + +/* use a 1bit clip buffer, saving RAM on microcontrollers, other rendering + * will still be antialiased. + */ +#ifndef CTX_1BIT_CLIP +#define CTX_1BIT_CLIP 0 +#endif + + +#ifndef CTX_ENABLE_SHADOW_BLUR +#define CTX_ENABLE_SHADOW_BLUR 1 +#endif + +#ifndef CTX_GRADIENTS +#define CTX_GRADIENTS 1 +#endif + +#ifndef CTX_ALIGNED_STRUCTS +#define CTX_ALIGNED_STRUCTS 1 +#endif + +#ifndef CTX_GRADIENT_CACHE +#define CTX_GRADIENT_CACHE 1 +#endif + +#ifndef CTX_FONTS_FROM_FILE +#define CTX_FONTS_FROM_FILE 0 +#endif + +#ifndef CTX_FORMATTER +#define CTX_FORMATTER 1 +#endif + +#ifndef CTX_PARSER +#define CTX_PARSER 1 +#endif + +#ifndef CTX_CURRENT_PATH +#define CTX_CURRENT_PATH 1 +#endif + +#ifndef CTX_XML +#define CTX_XML 1 +#endif + +#ifndef CTX_VT +#define CTX_VT 0 +#endif + +/* when ctx_math is defined, which it is by default, we use ctx' own + * implementations of math functions, instead of relying on math.h + * the possible inlining gives us a slight speed-gain, and on + * embedded platforms guarantees that we do not do double precision + * math. + */ +#ifndef CTX_MATH +#define CTX_MATH 1 // use internal fast math for sqrt,sin,cos,atan2f etc. +#endif + +#define ctx_log(fmt, ...) +//#define ctx_log(str, a...) fprintf(stderr, str, ##a) + +/* the initial journal size - for both rasterizer + * edgelist and drawlist. + */ +#ifndef CTX_MIN_JOURNAL_SIZE +#define CTX_MIN_JOURNAL_SIZE 512 +#endif + +/* The maximum size we permit the drawlist to grow to, + * the memory used is this number * 9, where 9 is sizeof(CtxEntry) + */ +#ifndef CTX_MAX_JOURNAL_SIZE +//#define CTX_MAX_JOURNAL_SIZE CTX_MIN_JOURNAL_SIZE +#define CTX_MAX_JOURNAL_SIZE 1024*1024*16 +#endif + +#ifndef CTX_DRAWLIST_STATIC +#define CTX_DRAWLIST_STATIC 0 +#endif + +#ifndef CTX_MIN_EDGE_LIST_SIZE +#define CTX_MIN_EDGE_LIST_SIZE 1024 +#endif + +#ifndef CTX_RASTERIZER_AA +#define CTX_RASTERIZER_AA 15 // vertical-AA of CTX_ANTIALIAS_DEFAULT +#endif + +/* The maximum complexity of a single path + */ +#ifndef CTX_MAX_EDGE_LIST_SIZE +#define CTX_MAX_EDGE_LIST_SIZE CTX_MIN_EDGE_LIST_SIZE +#endif + +#ifndef CTX_STRINGPOOL_SIZE + // XXX should be possible to make zero and disappear when codepaths not in use + // to save size, for card10 this is defined as a low number (some text + // properties still make use of it) + // + // for desktop-use this should be fully dynamic, possibly + // with chained pools, gradients are stored here. +#define CTX_STRINGPOOL_SIZE 1000 // +#endif + +/* whether we dither or not for gradients + */ +#ifndef CTX_DITHER +#define CTX_DITHER 0 +#endif + +/* only source-over clear and copy will work, the API still + * through - but the renderer is limited, for use to measure + * size and possibly in severely constrained ROMs. + */ +#ifndef CTX_BLENDING_AND_COMPOSITING +#define CTX_BLENDING_AND_COMPOSITING 1 +#endif + +/* this forces the inlining of some performance + * critical paths. + */ +#ifndef CTX_FORCE_INLINES +#define CTX_FORCE_INLINES 1 +#endif + +/* create one-off inlined inner loop for normal blend mode (for floating point, + * for RGBA8 manual loops overrrides + */ +#ifndef CTX_INLINED_NORMAL +#define CTX_INLINED_NORMAL 1 +#endif + +#ifndef CTX_INLINED_GRADIENTS +#define CTX_INLINED_GRADIENTS 1 +#endif + +#ifndef CTX_BRAILLE_TEXT +#define CTX_BRAILLE_TEXT 0 +#endif + +/* Build code paths for grayscale rasterization, this makes clipping + * faster. + */ +#ifndef CTX_NATIVE_GRAYA8 +#define CTX_NATIVE_GRAYA8 1 +#endif + +/* enable CMYK rasterization targets + */ +#ifndef CTX_ENABLE_CMYK +#define CTX_ENABLE_CMYK 1 +#endif + +/* enable color management, slightly increases CtxColor struct size, can + * be disabled for microcontrollers. + */ +#ifndef CTX_ENABLE_CM +#define CTX_ENABLE_CM 1 +#endif + +#ifndef CTX_EVENTS +#define CTX_EVENTS 1 +#endif + +#ifndef CTX_LIMIT_FORMATS +#define CTX_LIMIT_FORMATS 0 +#endif + +#ifndef CTX_ENABLE_FLOAT +#define CTX_ENABLE_FLOAT 0 +#endif + +/* by default ctx includes all pixel formats, on microcontrollers + * it can be useful to slim down code and runtime size by only + * defining the used formats, set CTX_LIMIT_FORMATS to 1, and + * manually add CTX_ENABLE_ flags for each of them. + */ +#if CTX_LIMIT_FORMATS +#if CTX_NATIVE_GRAYA8 +#define CTX_ENABLE_GRAYA8 1 +#define CTX_ENABLE_GRAY8 1 +#endif +#else + +#define CTX_ENABLE_GRAY1 1 +#define CTX_ENABLE_GRAY2 1 +#define CTX_ENABLE_GRAY4 1 +#define CTX_ENABLE_GRAY8 1 +#define CTX_ENABLE_GRAYA8 1 +#define CTX_ENABLE_GRAYF 1 +#define CTX_ENABLE_GRAYAF 1 + +#define CTX_ENABLE_RGB8 1 +#define CTX_ENABLE_RGBA8 1 +#define CTX_ENABLE_BGRA8 1 +#define CTX_ENABLE_RGB332 1 +#define CTX_ENABLE_RGB565 1 +#define CTX_ENABLE_RGB565_BYTESWAPPED 1 +#define CTX_ENABLE_RGBAF 1 +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#define CTX_ENABLE_YUV420 1 + +#if CTX_ENABLE_CMYK +#define CTX_ENABLE_CMYK8 1 +#define CTX_ENABLE_CMYKA8 1 +#define CTX_ENABLE_CMYKAF 1 +#endif +#endif + +#ifndef CTX_RGB565_ALPHA +#define CTX_RGB565_ALPHA 0 // when enabled pure purple is transparent, + // for a ~15% overall performance hit +#endif + +#ifndef CTX_RGB332_ALPHA +#define CTX_RGB332_ALPHA 0 // when enabled pure purple is transparent, + // for a ~15% overall performance hit +#endif + +/* by including ctx-font-regular.h, or ctx-font-mono.h the + * built-in fonts using ctx drawlist encoding is enabled + */ +#if CTX_FONT_regular || CTX_FONT_mono || CTX_FONT_bold \ + || CTX_FONT_italic || CTX_FONT_sans || CTX_FONT_serif \ + || CTX_FONT_ascii +#ifndef CTX_FONT_ENGINE_CTX +#define CTX_FONT_ENGINE_CTX 1 +#endif +#endif + +#ifndef CTX_FONT_ENGINE_CTX_FS +#define CTX_FONT_ENGINE_CTX_FS 0 +#endif + +/* If stb_strutype.h is included before ctx.h add integration code for runtime loading + * of opentype fonts. + */ +#ifdef __STB_INCLUDE_STB_TRUETYPE_H__ +#ifndef CTX_FONT_ENGINE_STB +#define CTX_FONT_ENGINE_STB 1 +#endif +#else +#define CTX_FONT_ENGINE_STB 0 +#endif + +#ifdef _BABL_H +#define CTX_BABL 1 +#else +#define CTX_BABL 0 +#endif + +#ifndef CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 +#define CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 1 +#endif + +/* force add format if we have shape cache */ +#if CTX_SHAPE_CACHE +#ifdef CTX_ENABLE_GRAY8 +#undef CTX_ENABLE_GRAY8 +#endif +#define CTX_ENABLE_GRAY8 1 +#endif + +/* include the bitpack packer, can be opted out of to decrease code size + */ +#ifndef CTX_BITPACK_PACKER +#define CTX_BITPACK_PACKER 0 +#endif + +/* enable RGBA8 intermediate format for + *the indirectly implemented pixel-formats. + */ +#if CTX_ENABLE_GRAY1 | CTX_ENABLE_GRAY2 | CTX_ENABLE_GRAY4 | CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED | CTX_ENABLE_RGB8 | CTX_ENABLE_RGB332 + + #ifdef CTX_ENABLE_RGBA8 + #undef CTX_ENABLE_RGBA8 + #endif + #define CTX_ENABLE_RGBA8 1 +#endif + +#ifdef CTX_ENABLE_CMYKF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_GRAYF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_GRAYAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_RGBAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_CMYKAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_CMYKF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + + +/* enable cmykf which is cmyk intermediate format + */ +#ifdef CTX_ENABLE_CMYK8 +#ifdef CTX_ENABLE_CMYKF +#undef CTX_ENABLE_CMYKF +#endif +#define CTX_ENABLE_CMYKF 1 +#endif +#ifdef CTX_ENABLE_CMYKA8 +#ifdef CTX_ENABLE_CMYKF +#undef CTX_ENABLE_CMYKF +#endif +#define CTX_ENABLE_CMYKF 1 +#endif + +#ifdef CTX_ENABLE_CMYKF8 +#ifdef CTX_ENABLE_CMYK +#undef CTX_ENABLE_CMYK +#endif +#define CTX_ENABLE_CMYK 1 +#endif + +#define CTX_PI 3.141592653589793f +#ifndef CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS +#define CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS 400 +#endif + +#ifndef CTX_MAX_FRAMEBUFFER_WIDTH +#define CTX_MAX_FRAMEBUFFER_WIDTH 2560 +#endif + +#ifndef CTX_RASTERIZER_INLINED_FAST_COPY_OVER +#define CTX_RASTERIZER_INLINED_FAST_COPY_OVER 1 +#endif + +#ifndef CTX_MAX_FONTS +#define CTX_MAX_FONTS 3 +#endif + +#ifndef CTX_MAX_STATES +#define CTX_MAX_STATES 10 +#endif + +#ifndef CTX_MAX_EDGES +#define CTX_MAX_EDGES 257 +#endif + +#ifndef CTX_MAX_LINGERING_EDGES +#define CTX_MAX_LINGERING_EDGES 64 +#endif + + +#ifndef CTX_MAX_PENDING +#define CTX_MAX_PENDING 128 +#endif + +#ifndef CTX_MAX_TEXTURES +#define CTX_MAX_TEXTURES 16 +#endif + +#ifndef CTX_HASH_ROWS +#define CTX_HASH_ROWS 8 +#endif +#ifndef CTX_HASH_COLS +#define CTX_HASH_COLS 8 +#endif + +#ifndef CTX_MAX_THREADS +#define CTX_MAX_THREADS 8 // runtime is max of cores/2 and this +#endif + + + +#define CTX_RASTERIZER_EDGE_MULTIPLIER 1024 + +#ifndef CTX_IMPLEMENTATION +#define CTX_IMPLEMENTATION 0 +#else +#undef CTX_IMPLEMENTATION +#define CTX_IMPLEMENTATION 1 +#endif + + +#ifdef CTX_RASTERIZER +#if CTX_RASTERIZER==0 +#if CTX_SDL || CTX_FB +#undef CTX_RASTERIZER +#define CTX_RASTERIZER 1 +#endif +#else +#undef CTX_RASTERIZER +#define CTX_RASTERIZER 1 +#endif +#endif + +#if CTX_RASTERIZER +#ifndef CTX_COMPOSITE +#define CTX_COMPOSITE 1 +#endif +#else +#ifndef CTX_COMPOSITE +#define CTX_COMPOSITE 0 +#endif +#endif + +#ifndef CTX_GRADIENT_CACHE_ELEMENTS +#define CTX_GRADIENT_CACHE_ELEMENTS 256 +#endif + +#ifndef CTX_PARSER_MAX_ARGS +#define CTX_PARSER_MAX_ARGS 20 +#endif + + +#ifndef CTX_SCREENSHOT +#define CTX_SCREENSHOT 0 +#endif + +#ifndef CTX_ALSA_AUDIO +#define CTX_ALSA_AUDIO 0 +#endif + +#if NO_ALSA +#undef CTX_ALSA_AUDIO +#define CTX_ALSA_AUDIO 0 +#endif + +#ifndef CTX_AUDIO +#define CTX_AUDIO 0 +#endif + +#ifndef CTX_TILED +#if CTX_SDL || CTX_FB +#define CTX_TILED 1 +#else +#define CTX_TILED 0 +#endif +#endif + +#ifndef CTX_THREADS +#if CTX_FB +#define CTX_THREADS 1 +#else +#define CTX_THREADS 0 +#endif +#endif + +#if CTX_THREADS +#include <threads.h> +#endif + /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + */ + +#if CTX_FORMATTER + +/* returns the maximum string length including terminating \0 */ +int ctx_a85enc_len (int input_length); +int ctx_a85enc (const void *srcp, char *dst, int count); + +#if CTX_PARSER + +int ctx_a85dec (const char *src, char *dst, int count); +int ctx_a85len (const char *src, int count); +#endif + +#endif +#ifndef __CTX_EXTRA_H +#define __CTX_EXTRA_H + + +#define CTX_CLAMP(val,min,max) ((val)<(min)?(min):(val)>(max)?(max):(val)) +static inline int ctx_mini (int a, int b) { return (a < b) * a + (a >= b) * b; } +static inline float ctx_minf (float a, float b) { return (a < b) * a + (a >= b) * b; } +static inline int ctx_maxi (int a, int b) { return (a > b) * a + (a <= b) * b; } +static inline float ctx_maxf (float a, float b) { return (a > b) * a + (a <= b) * b; } + + +typedef enum CtxOutputmode +{ + CTX_OUTPUT_MODE_QUARTER, + CTX_OUTPUT_MODE_BRAILLE, + CTX_OUTPUT_MODE_SIXELS, + CTX_OUTPUT_MODE_GRAYS, + CTX_OUTPUT_MODE_CTX, + CTX_OUTPUT_MODE_CTX_COMPACT, + CTX_OUTPUT_MODE_UI +} CtxOutputmode; + + + + + +#if CTX_FORCE_INLINES +#define CTX_INLINE inline __attribute__((always_inline)) +#else +#define CTX_INLINE inline +#endif + +static inline float ctx_pow2 (float a) { return a * a; } +#if CTX_MATH + +static inline float +ctx_fabsf (float x) +{ + union + { + float f; + uint32_t i; + } u = { x }; + u.i &= 0x7fffffff; + return u.f; +} + +static inline float +ctx_invsqrtf (float x) +{ + void *foo = &x; + float xhalf = 0.5f * x; + int i=* (int *) foo; + void *bar = &i; + i = 0x5f3759df - (i >> 1); + x = * (float *) bar; + x *= (1.5f - xhalf * x * x); + x *= (1.5f - xhalf * x * x); //repeating Newton-Raphson step for higher precision + return x; +} + +static inline float +ctx_invsqrtf_fast (float x) +{ + void *foo = &x; +//float xhalf = 0.5f * x; + int i=* (int *) foo; + void *bar = &i; + i = 0x5f3759df - (i >> 1); + x = * (float *) bar; +//x *= (1.5f - xhalf * x * x); + return x; +} + +CTX_INLINE static float ctx_sqrtf (float a) +{ + return 1.0f/ctx_invsqrtf (a); +} + +CTX_INLINE static float ctx_sqrtf_fast (float a) +{ + return 1.0f/ctx_invsqrtf_fast (a); +} + +CTX_INLINE static float ctx_hypotf (float a, float b) +{ + return ctx_sqrtf (ctx_pow2 (a)+ctx_pow2 (b) ); +} + +CTX_INLINE static float ctx_hypotf_fast (float a, float b) +{ + return ctx_sqrtf_fast (ctx_pow2 (a)+ctx_pow2 (b) ); +} + +CTX_INLINE static float +ctx_sinf (float x) +{ + if (x < -CTX_PI * 2) + { + x = -x; + long ix = x / (CTX_PI * 2); + x = x - ix * CTX_PI * 2; + x = -x; + } + if (x < -CTX_PI * 1000) + { + x = -0.5; + } + if (x > CTX_PI * 1000) + { + // really large numbers tend to cause practically inifinite + // loops since the > CTX_PI * 2 seemingly fails + x = 0.5; + } + if (x > CTX_PI * 2) + { + long ix = x / (CTX_PI * 2); + x = x - (ix * CTX_PI * 2); + } + while (x < -CTX_PI) + { x += CTX_PI * 2; } + while (x > CTX_PI) + { x -= CTX_PI * 2; } + + /* source : http://mooooo.ooo/chebyshev-sine-approximation/ */ + float coeffs[]= + { + -0.10132118f, // x + 0.0066208798f, // x^3 + -0.00017350505f, // x^5 + 0.0000025222919f, // x^7 + -0.000000023317787f, // x^9 + 0.00000000013291342f + }; // x^11 + float x2 = x*x; + float p11 = coeffs[5]; + float p9 = p11*x2 + coeffs[4]; + float p7 = p9*x2 + coeffs[3]; + float p5 = p7*x2 + coeffs[2]; + float p3 = p5*x2 + coeffs[1]; + float p1 = p3*x2 + coeffs[0]; + return (x - CTX_PI + 0.00000008742278f) * + (x + CTX_PI - 0.00000008742278f) * p1 * x; +} + +static inline float ctx_atan2f (float y, float x) +{ + float atan, z; + if ( x == 0.0f ) + { + if ( y > 0.0f ) + { return CTX_PI/2; } + if ( y == 0.0f ) + { return 0.0f; } + return -CTX_PI/2; + } + z = y/x; + if ( ctx_fabsf ( z ) < 1.0f ) + { + atan = z/ (1.0f + 0.28f*z*z); + if (x < 0.0f) + { + if ( y < 0.0f ) + { return atan - CTX_PI; } + return atan + CTX_PI; + } + } + else + { + atan = CTX_PI/2 - z/ (z*z + 0.28f); + if ( y < 0.0f ) { return atan - CTX_PI; } + } + return atan; +} + + +static inline float ctx_atanf (float a) +{ + return ctx_atan2f ( (a), 1.0f); +} + +static inline float ctx_asinf (float x) +{ + return ctx_atanf ( (x) * (ctx_invsqrtf (1.0f-ctx_pow2 (x) ) ) ); +} + +static inline float ctx_acosf (float x) +{ + return ctx_atanf ( (ctx_sqrtf (1.0f-ctx_pow2 (x) ) / (x) ) ); +} + +CTX_INLINE static float ctx_cosf (float a) +{ + return ctx_sinf ( (a) + CTX_PI/2.0f); +} + +static inline float ctx_tanf (float a) +{ + return (ctx_cosf (a) /ctx_sinf (a) ); +} +static inline float +ctx_floorf (float x) +{ + return (int)x; // XXX +} +static inline float +ctx_expf (float x) +{ + union { uint32_t i; float f; } v = + { (uint32_t)( (1 << 23) * (x + 183.1395965f)) }; + return v.f; +} + +/* define more trig based on having sqrt, sin and atan2 */ + +#else +#if !__COSMOPOLITAN__ +#include <math.h> +#endif +static inline float ctx_fabsf (float x) { return fabsf (x); } +static inline float ctx_floorf (float x) { return floorf (x); } +static inline float ctx_sinf (float x) { return sinf (x); } +static inline float ctx_atan2f (float y, float x) { return atan2f (y, x); } +static inline float ctx_hypotf (float a, float b) { return hypotf (a, b); } +static inline float ctx_acosf (float a) { return acosf (a); } +static inline float ctx_cosf (float a) { return cosf (a); } +static inline float ctx_tanf (float a) { return tanf (a); } +static inline float ctx_expf (float p) { return expf (p); } +static inline float ctx_sqrtf (float a) { return sqrtf (a); } +#endif + +static inline float _ctx_parse_float (const char *str, char **endptr) +{ + return strtod (str, endptr); /* XXX: , vs . problem in some locales */ +} + +const char *ctx_get_string (Ctx *ctx, uint64_t hash); +void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value); +typedef struct _CtxColor CtxColor; + +typedef struct _CtxMatrix CtxMatrix; +struct + _CtxMatrix +{ + float m[3][2]; +}; +void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix); + +int ctx_color (Ctx *ctx, const char *string); +typedef struct _CtxState CtxState; +CtxColor *ctx_color_new (); +CtxState *ctx_get_state (Ctx *ctx); +void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out); +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +void ctx_color_free (CtxColor *color); +void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color); +int ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color); +int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string); + +int ctx_color_is_transparent (CtxColor *color); +int ctx_utf8_len (const unsigned char first_byte); + +void ctx_user_to_device (Ctx *ctx, float *x, float *y); +void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y); +const char *ctx_utf8_skip (const char *s, int utf8_length); +void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix); +void ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y); +void ctx_matrix_invert (CtxMatrix *m); +void ctx_matrix_identity (CtxMatrix *matrix); +void ctx_matrix_scale (CtxMatrix *matrix, float x, float y); +void ctx_matrix_rotate (CtxMatrix *matrix, float angle); +void ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s); +void +ctx_matrix_translate (CtxMatrix *matrix, float x, float y); +int ctx_is_set_now (Ctx *ctx, uint64_t hash); +void ctx_set_size (Ctx *ctx, int width, int height); + +static inline float ctx_matrix_get_scale (CtxMatrix *matrix) +{ + return ctx_maxf (ctx_maxf (ctx_fabsf (matrix->m[0][0]), + ctx_fabsf (matrix->m[0][1]) ), + ctx_maxf (ctx_fabsf (matrix->m[1][0]), + ctx_fabsf (matrix->m[1][1]) ) ); +} + +#if CTX_FONTS_FROM_FILE +int ctx_load_font_ttf_file (const char *name, const char *path); +int +_ctx_file_get_contents (const char *path, + unsigned char **contents, + long *length); +#endif + +#endif +#ifndef __CTX_CONSTANTS +#define __CTX_CONSTANTS + +#define TOKENHASH(a) ((uint64_t)a) + +#define CTX_strokeSource TOKENHASH(3061861651908008) +#define CTX_add_stop TOKENHASH(1274978316678) +#define CTX_addStop TOKENHASH(40799943078278) +#define CTX_alphabetic TOKENHASH(2629359926678406) +#define CTX_arc TOKENHASH(11526) +#define CTX_arc_to TOKENHASH(1187065094) +#define CTX_arcTo TOKENHASH(38558051590) +#define CTX_begin_path TOKENHASH(3004110681622984) +#define CTX_beginPath TOKENHASH(8437143659599196) +#define CTX_bevel TOKENHASH(29868488) +#define CTX_bottom TOKENHASH(1043772488) +#define CTX_cap TOKENHASH(37066) +#define CTX_center TOKENHASH(1358332362) +#define CTX_clear TOKENHASH(42154890) +#define CTX_color TOKENHASH(43086922) +#define CTX_copy TOKENHASH(1807434) +#define CTX_clip TOKENHASH(1203082) +#define CTX_close_path TOKENHASH(3004110663420810) +#define CTX_closePath TOKENHASH(8437144279135038) +#define CTX_cmyka TOKENHASH(7199690) +#define CTX_cmyk TOKENHASH(908234) +#define CTX_cmykaS TOKENHASH(36313095114) +#define CTX_cmykS TOKENHASH(1135467466) +#define CTX_color TOKENHASH(43086922) +#define CTX_blending TOKENHASH(653586873224) +#define CTX_blend TOKENHASH(13646728) +#define CTX_blending_mode TOKENHASH(8147360531130856) +#define CTX_blendingMode TOKENHASH(7483585768187540) +#define CTX_blend_mode TOKENHASH(2758775686577032) +#define CTX_blendMode TOKENHASH(7773213171090182) +#define CTX_composite TOKENHASH(16930059746378) +#define CTX_compositing_mode TOKENHASH(2417816728103524) +#define CTX_compositingMode TOKENHASH(2807194446992106) +#define CTX_curve_to TOKENHASH(1215559149002) +#define CTX_curveTo TOKENHASH(39483449320906) +#define CTX_darken TOKENHASH(1089315020) +#define CTX_defineGlyph TOKENHASH(2497926167421194) +#define CTX_defineTexture TOKENHASH(2623744577477404) +#define CTX_kerningPair TOKENHASH(6964644556489058) +#define CTX_destinationIn TOKENHASH(8153299143600102) +#define CTX_destination_in TOKENHASH(3824201982576824) +#define CTX_destinationAtop TOKENHASH(8185118415574560) +#define CTX_destination_atop TOKENHASH(7742210324901698) +#define CTX_destinationOver TOKENHASH(3261713333438500) +#define CTX_destination_over TOKENHASH(7742210324728474) +#define CTX_destinationOut TOKENHASH(7742210322269456) +#define CTX_destination_out TOKENHASH(8153299143489102) +#define CTX_difference TOKENHASH(2756492040618700) +#define CTX_done TOKENHASH(492620) +#define CTX_drgba TOKENHASH(6573324) +#define CTX_drgb TOKENHASH(281868) +#define CTX_drgbaS TOKENHASH(36312468748) +#define CTX_drgbS TOKENHASH(1134841100) +#define CTX_end TOKENHASH(13326) +#define CTX_endfun TOKENHASH(1122513934) +#define CTX_end_group TOKENHASH(41200834917390) +#define CTX_endGroup TOKENHASH(3570227948106766) +#define CTX_even_odd TOKENHASH(426345774606) +#define CTX_evenOdd TOKENHASH(13671748091406) +#define CTX_exit TOKENHASH(1465998) +#define CTX_fill TOKENHASH(946896) +#define CTX_fill_rule TOKENHASH(16405972808400) +#define CTX_fillRule TOKENHASH(2776813389378256) +#define CTX_flush TOKENHASH(22395792) +#define CTX_font TOKENHASH(1475664) +#define CTX_font_size TOKENHASH(17342343316560) +#define CTX_setFontSize TOKENHASH(8657699789799734) +#define CTX_fontSize TOKENHASH(2806775148872784) +#define CTX_function TOKENHASH(1136803546576) +#define CTX_getkey TOKENHASH(1827516882) +#define CTX_global_alpha TOKENHASH(6945103263242432) +#define CTX_globalAlpha TOKENHASH(2684560928159160) +#define CTX_glyph TOKENHASH(22207378) +#define CTX_gradient_add_stop TOKENHASH(7829524561074416) +#define CTX_gradientAddStop TOKENHASH(8126442749593072) +#define CTX_graya TOKENHASH(8068370) +#define CTX_gray TOKENHASH(1776914) +#define CTX_grayaS TOKENHASH(36313963794) +#define CTX_grayS TOKENHASH(1136336146) +#define CTX_hanging TOKENHASH(20424786132) +#define CTX_height TOKENHASH(1497979348) +#define CTX_hor_line_to TOKENHASH(8345271542735158) +#define CTX_horLineTo TOKENHASH(3629696407754856) +#define CTX_hue TOKENHASH(15828) +#define CTX_identity TOKENHASH(1903455910294) +#define CTX_ideographic TOKENHASH(4370819675496700) +#define CTX_imageSmoothing TOKENHASH(4268778175825416) +#define CTX_join TOKENHASH(1072216) +#define CTX_laba TOKENHASH(205020) +#define CTX_lab TOKENHASH(8412) +#define CTX_lcha TOKENHASH(217436) +#define CTX_lch TOKENHASH(20828) +#define CTX_labaS TOKENHASH(1134764252) +#define CTX_labS TOKENHASH(35463388) +#define CTX_lchaS TOKENHASH(1134776668) +#define CTX_lchS TOKENHASH(35475804) +#define CTX_left TOKENHASH(1458652) +#define CTX_lighter TOKENHASH(43466246876) +#define CTX_lighten TOKENHASH(34876312284) +#define CTX_linear_gradient TOKENHASH(7801595375834212) +#define CTX_linearGradient TOKENHASH(4439260636789186) +#define CTX_line_cap TOKENHASH(1243731165916) +#define CTX_lineCap TOKENHASH(3436510399409980) +#define CTX_setLineCap TOKENHASH(7897176123029482) +#define CTX_line_height TOKENHASH(3300223516389168) +#define CTX_line_join TOKENHASH(35977601450716) +#define CTX_lineJoin TOKENHASH(3403122163024604) +#define CTX_setLineJoin TOKENHASH(2768281536656332) +#define CTX_line_spacing TOKENHASH(2519451230887150) +#define CTX_line_to TOKENHASH(37986206428) +#define CTX_lineTo TOKENHASH(1233857774300) +#define CTX_lineDash TOKENHASH(3001937455186652) +#define CTX_lineDashOffset TOKENHASH(3704120356324362) +#define CTX_line_width TOKENHASH(3004303386575580) +#define CTX_lineWidth TOKENHASH(8241159254028040) +#define CTX_setLineWidth TOKENHASH(8037913618228476) +#define CTX_view_box TOKENHASH(1823485803248) +#define CTX_viewBox TOKENHASH(3915860941641152) +#define CTX_middle TOKENHASH(499528414) +#define CTX_miter TOKENHASH(42447582) +#define CTX_miter_limit TOKENHASH(4281255327472850) +#define CTX_miterLimit TOKENHASH(7937453649653124) +#define CTX_move_to TOKENHASH(37986223198) +#define CTX_moveTo TOKENHASH(1233857791070) +#define CTX_multiply TOKENHASH(1886723143134) +#define CTX_new_page TOKENHASH(500602882528) +#define CTX_newPage TOKENHASH(16020123011552) +#define CTX_new_path TOKENHASH(734678600160) +#define CTX_newPath TOKENHASH(23510545975776) +#define CTX_new_state TOKENHASH(16912954280416) +#define CTX_none TOKENHASH(492640) +#define CTX_nonzero TOKENHASH(37865948256) +#define CTX_non_zero TOKENHASH(1211709359200) +#define CTX_normal TOKENHASH(946840672) +#define CTX_quad_to TOKENHASH(37986115046) +#define CTX_quadTo TOKENHASH(1233857682918) +#define CTX_radial_gradient TOKENHASH(8267515704460560) +#define CTX_radialGradient TOKENHASH(4399889250822134) +#define CTX_rectangle TOKENHASH(16375644301800) +#define CTX_rect TOKENHASH(1452520) +#define CTX_rel_arc_to TOKENHASH(3496527781786088) +#define CTX_relArcTo TOKENHASH(3209152175601038) +#define CTX_rel_curve_to TOKENHASH(4439651822639910) +#define CTX_relCurveTo TOKENHASH(7294415873689320) +#define CTX_rel_hor_line_to TOKENHASH(7051067105640810) +#define CTX_relHorLineTo TOKENHASH(8737419863647946) +#define CTX_relVerLineTo TOKENHASH(8737441317512906) +#define CTX_rel_line_to TOKENHASH(8345271542378314) +#define CTX_relLineTo TOKENHASH(3629696197927444) +#define CTX_rel_move_to TOKENHASH(8344984486309706) +#define CTX_relMoveTo TOKENHASH(3571677202293268) +#define CTX_rel_quad_to TOKENHASH(8343627754794826) +#define CTX_relQuadTo TOKENHASH(7894357900599828) +#define CTX_rel_smoothq_to TOKENHASH(7340038162167138) +#define CTX_relSmoothqTo TOKENHASH(3188040406230844) +#define CTX_rel_smooth_to TOKENHASH(8144941131301668) +#define CTX_relSmoothTo TOKENHASH(8947422784198618) +#define CTX_rel_ver_line_to TOKENHASH(8148126344839530) +#define CTX_restore TOKENHASH(16411699688) +#define CTX_reset TOKENHASH(46639592) +#define CTX_rgba TOKENHASH(205416) +#define CTX_rgb TOKENHASH(8808) +#define CTX_rgbaS TOKENHASH(1134764648) +#define CTX_rgbS TOKENHASH(35463784) +#define CTX_right TOKENHASH(46811880) +#define CTX_rotate TOKENHASH(516142184) +#define CTX_round TOKENHASH(13679720) +#define CTX_round_rectangle TOKENHASH(4332080966833870) +#define CTX_roundRectangle TOKENHASH(8317255488676642) +#define CTX_save TOKENHASH(508138) +#define CTX_scale TOKENHASH(15604074) +#define CTX_screen TOKENHASH(1088921962) +#define CTX_setkey TOKENHASH(1827516906) +#define CTX_shadowBlur TOKENHASH(2924056626980284) +#define CTX_shadowColor TOKENHASH(3509599043947446) +#define CTX_shadowOffsetX TOKENHASH(8499312693589794) +#define CTX_shadowOffsetY TOKENHASH(8499312693589796) +#define CTX_smooth_quad_to TOKENHASH(6832232668547050) +#define CTX_smoothQuadTo TOKENHASH(8278352345012646) +#define CTX_smooth_to TOKENHASH(38898089692138) +#define CTX_smoothTo TOKENHASH(3515270388878314) +#define CTX_sourceIn TOKENHASH(3444145493687402) +#define CTX_source_in TOKENHASH(35942915423338) +#define CTX_sourceAtop TOKENHASH(2920281959978332) +#define CTX_source_atop TOKENHASH(3007410591464110) +#define CTX_sourceOut TOKENHASH(7371294932695718) +#define CTX_source_out TOKENHASH(3851660580666474) +#define CTX_sourceOver TOKENHASH(7584784067385004) +#define CTX_sourceTransform TOKENHASH(7515321363744130) +#define CTX_source_over TOKENHASH(8690648756484770) +#define CTX_square TOKENHASH(511950058) +#define CTX_start TOKENHASH(47455658) +#define CTX_start_move TOKENHASH(2798358138985898) +#define CTX_start_group TOKENHASH(7836274863228782) +#define CTX_startGroup TOKENHASH(3812645199786240) +#define CTX_stroke TOKENHASH(498181546) +#define CTX_text_align TOKENHASH(3398277113762284) +#define CTX_textAlign TOKENHASH(3063795447820748) +#define CTX_texture TOKENHASH(16424292844) +#define CTX_text_baseline TOKENHASH(2589194334827348) +#define CTX_text_baseline TOKENHASH(2589194334827348) +#define CTX_textBaseline TOKENHASH(8381669925369340) +#define CTX_fillRect TOKENHASH(3811453831115472) +#define CTX_text TOKENHASH(1495532) +#define CTX_text_direction TOKENHASH(3614589880641524) +#define CTX_textDirection TOKENHASH(6790122975782654) +#define CTX_text_indent TOKENHASH(3633795456290560) +#define CTX_text_stroke TOKENHASH(8259523149811490) +#define CTX_strokeText TOKENHASH(8131451867629426) +#define CTX_strokeRect TOKENHASH(8165399289138988) +#define CTX_top TOKENHASH(37996) +#define CTX_transform TOKENHASH(34396827557164) +#define CTX_translate TOKENHASH(16912418348332) +#define CTX_verLineTo TOKENHASH(3629696413166220) +#define CTX_ver_line_to TOKENHASH(8345271542726354) +#define CTX_width TOKENHASH(22426354) +#define CTX_winding TOKENHASH(20424590066) +#define CTX_x TOKENHASH(52) +#define CTX_xor TOKENHASH(42100) +#define CTX_y TOKENHASH(54) +#define CTX_colorSpace TOKENHASH(3674150843793134) +#define CTX_userRGB TOKENHASH(59177128181102) +#define CTX_userCMYK TOKENHASH(3354734206905240) +#define CTX_deviceRGB TOKENHASH(7818727413767480) +#define CTX_deviceCMYK TOKENHASH(8943291245184210) +#define CTX_silver TOKENHASH(1358459626) +#define CTX_fuchsia TOKENHASH(7225355728) +#define CTX_gray TOKENHASH(1776914) +#define CTX_yellow TOKENHASH(1714319862) +#define CTX_white TOKENHASH(16145074) +#define CTX_maroon TOKENHASH(1110548702) +#define CTX_magenta TOKENHASH(7952877790) +#define CTX_blue TOKENHASH(506760) +#define CTX_green TOKENHASH(34028818) +#define CTX_red TOKENHASH(12776) +#define CTX_purple TOKENHASH(500344292) +#define CTX_olive TOKENHASH(16276386) +#define CTX_teal TOKENHASH(924140) +#define CTX_black TOKENHASH(27597704) +#define CTX_cyan TOKENHASH(1056458) +#define CTX_navy TOKENHASH(1818848) +#define CTX_lime TOKENHASH(490204) +#define CTX_aqua TOKENHASH(244934) +#define CTX_transparent TOKENHASH(3654078210101184) +#define CTX_currentColor TOKENHASH(7501877057638746) + +#endif +#ifndef __CTX_LIBC_H +#define __CTX_LIBC_H + +#if !__COSMOPOLITAN__ +#include <stddef.h> +#endif + +#if 0 +static inline void +ctx_memset (void *ptr, uint8_t val, int length) +{ + uint8_t *p = (uint8_t *) ptr; + for (int i = 0; i < length; i ++) + { p[i] = val; } +} +#else +#define ctx_memset memset +#endif + + +static inline void ctx_strcpy (char *dst, const char *src) +{ + int i = 0; + for (i = 0; src[i]; i++) + { dst[i] = src[i]; } + dst[i] = 0; +} + +static inline char *_ctx_strchr (const char *haystack, char needle) +{ + const char *p = haystack; + while (*p && *p != needle) + { + p++; + } + if (*p == needle) + { return (char *) p; } + return NULL; +} +static inline char *ctx_strchr (const char *haystack, char needle) +{ + return _ctx_strchr (haystack, needle); +} + +static inline int ctx_strcmp (const char *a, const char *b) +{ + int i; + for (i = 0; a[i] && b[i]; a++, b++) + if (a[0] != b[0]) + { return 1; } + if (a[0] == 0 && b[0] == 0) { return 0; } + return 1; +} + +static inline int ctx_strncmp (const char *a, const char *b, size_t n) +{ + size_t i; + for (i = 0; a[i] && b[i] && i < n; a++, b++) + if (a[0] != b[0]) + { return 1; } + return 0; +} + +static inline int ctx_strlen (const char *s) +{ + int len = 0; + for (; *s; s++) { len++; } + return len; +} + +static inline char *ctx_strstr (const char *h, const char *n) +{ + int needle_len = ctx_strlen (n); + if (n[0]==0) + { return (char *) h; } + while (h) + { + h = ctx_strchr (h, n[0]); + if (!h) + { return NULL; } + if (!ctx_strncmp (h, n, needle_len) ) + { return (char *) h; } + h++; + } + return NULL; +} + +#endif + +#if CTX_IMPLEMENTATION|CTX_COMPOSITE + +#ifndef __CTX_INTERNAL_H +#define __CTX_INTERNAL_H + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <math.h> +#endif + +#define CTX_BRANCH_HINTS 1 + +#if CTX_BRANCH_HINTS +#define CTX_LIKELY(x) __builtin_expect(!!(x), 1) +#define CTX_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define CTX_LIKELY(x) (x) +#define CTX_UNLIKELY(x) (x) +#endif + +typedef struct _CtxRasterizer CtxRasterizer; +typedef struct _CtxGState CtxGState; +typedef struct _CtxState CtxState; + +typedef struct _CtxSource CtxSource; + + +#define CTX_VALID_RGBA_U8 (1<<0) +#define CTX_VALID_RGBA_DEVICE (1<<1) +#if CTX_ENABLE_CM +#define CTX_VALID_RGBA (1<<2) +#endif +#if CTX_ENABLE_CMYK +#define CTX_VALID_CMYKA (1<<3) +#define CTX_VALID_DCMYKA (1<<4) +#endif +#define CTX_VALID_GRAYA (1<<5) +#define CTX_VALID_GRAYA_U8 (1<<6) +#define CTX_VALID_LABA ((1<<7) | CTX_VALID_GRAYA) + +struct _CtxColor +{ + uint8_t magic; // for colors used in keydb, set to a non valid start of + // string value. + uint8_t rgba[4]; + uint8_t l_u8; + uint8_t original; // the bitmask of the originally set color + uint8_t valid; // bitmask of which members contain valid + // values, gets denser populated as more + // formats are requested from a set color. + float device_red; + float device_green; + float device_blue; + float alpha; + float l; // luminance and gray +#if CTX_ENABLE_LAB // NYI + float a; + float b; +#endif +#if CTX_ENABLE_CMYK + float device_cyan; + float device_magenta; + float device_yellow; + float device_key; + float cyan; + float magenta; + float yellow; + float key; +#endif + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *space; // gets copied from state when color is declared +#else + void *space; // gets copied from state when color is declared, +#endif + float red; + float green; + float blue; +#endif +}; + +typedef struct _CtxGradientStop CtxGradientStop; + +struct _CtxGradientStop +{ + float pos; + CtxColor color; +}; + + +enum _CtxSourceType +{ + CTX_SOURCE_COLOR = 0, + CTX_SOURCE_TEXTURE, + CTX_SOURCE_LINEAR_GRADIENT, + CTX_SOURCE_RADIAL_GRADIENT, + CTX_SOURCE_INHERIT_FILL +}; + +typedef enum _CtxSourceType CtxSourceType; + +typedef struct _CtxPixelFormatInfo CtxPixelFormatInfo; + + +struct _CtxBuffer +{ + void *data; + int width; + int height; + int stride; + char *eid; // might be NULL, when not - should be unique for pixel contents + int frame; // last frame used in, everything > 3 can be removed, + // as clients wont rely on it. + CtxPixelFormatInfo *format; + void (*free_func) (void *pixels, void *user_data); + void *user_data; + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *space; +#else + void *space; +#endif +#endif +#if 1 + CtxBuffer *color_managed; /* only valid for one render target, cache + for a specific space + */ +#endif +}; + +//void _ctx_user_to_device (CtxState *state, float *x, float *y); +//void _ctx_user_to_device_distance (CtxState *state, float *x, float *y); + +typedef struct _CtxGradient CtxGradient; +struct _CtxGradient +{ + CtxGradientStop stops[16]; + int n_stops; +}; + +struct _CtxSource +{ + int type; + CtxMatrix transform; + union + { + CtxColor color; + struct + { + uint8_t rgba[4]; // shares data with set color + uint8_t pad; + float x0; + float y0; + CtxBuffer *buffer; + } texture; + struct + { + float x0; + float y0; + float x1; + float y1; + float dx; + float dy; + float start; + float end; + float length; + float rdelta; + } linear_gradient; + struct + { + float x0; + float y0; + float r0; + float x1; + float y1; + float r1; + float rdelta; + } radial_gradient; + }; +}; + +struct _CtxGState +{ + int keydb_pos; + int stringpool_pos; + + CtxMatrix transform; + CtxSource source_stroke; + CtxSource source_fill; + float global_alpha_f; + uint8_t global_alpha_u8; + + float line_width; + float line_dash_offset; + float miter_limit; + float font_size; +#if CTX_ENABLE_SHADOW_BLUR + float shadow_blur; + float shadow_offset_x; + float shadow_offset_y; +#endif + int clipped:1; + + int16_t clip_min_x; + int16_t clip_min_y; + int16_t clip_max_x; + int16_t clip_max_y; + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *device_space; + const Babl *texture_space; + const Babl *rgb_space; + const Babl *cmyk_space; + + const Babl *fish_rgbaf_user_to_device; + const Babl *fish_rgbaf_texture_to_device; + const Babl *fish_rgbaf_device_to_user; + +#else + void *device_space; + void *texture_space; + void *rgb_space; + void *cmyk_space; + void *fish_rgbaf_user_to_device; // dummy padding + void *fish_rgbaf_texture_to_device; // dummy padding + void *fish_rgbaf_device_to_user; // dummy padding +#endif +#endif + CtxCompositingMode compositing_mode; // bitfield refs lead to + CtxBlend blend_mode; // non-vectorization + + float dashes[CTX_PARSER_MAX_ARGS]; + int n_dashes; + + CtxColorModel color_model; + /* bitfield-pack small state-parts */ + CtxLineCap line_cap:2; + CtxLineJoin line_join:2; + CtxFillRule fill_rule:1; + unsigned int image_smoothing:1; + unsigned int font:6; + unsigned int bold:1; + unsigned int italic:1; +}; + +typedef enum +{ + CTX_TRANSFORMATION_NONE = 0, + CTX_TRANSFORMATION_SCREEN_SPACE = 1, + CTX_TRANSFORMATION_RELATIVE = 2, +#if CTX_BITPACK + CTX_TRANSFORMATION_BITPACK = 4, +#endif + CTX_TRANSFORMATION_STORE_CLEAR = 16, +} CtxTransformation; + +#define CTX_DRAWLIST_DOESNT_OWN_ENTRIES 64 +#define CTX_DRAWLIST_EDGE_LIST 128 +#define CTX_DRAWLIST_CURRENT_PATH 512 +// BITPACK + +struct _CtxDrawlist +{ + CtxEntry *entries; + int count; + int size; + uint32_t flags; + int bitpack_pos; // stream is bitpacked up to this offset +}; + +#define CTX_MAX_KEYDB 64 // number of entries in keydb + // entries are "copy-on-change" between states + +// the keydb consists of keys set to floating point values, +// that might also be interpreted as integers for enums. +// +// the hash +typedef struct _CtxKeyDbEntry CtxKeyDbEntry; +struct _CtxKeyDbEntry +{ + uint64_t key; + float value; + //union { float f[1]; uint8_t u8[4]; }value; +}; + +struct _CtxState +{ + int has_moved:1; + int has_clipped:1; + float x; + float y; + int min_x; + int min_y; + int max_x; + int max_y; + int16_t gstate_no; + CtxGState gstate; + CtxGState gstate_stack[CTX_MAX_STATES];//at end, so can be made dynamic +#if CTX_GRADIENTS + CtxGradient gradient; /* we keep only one gradient, + this goes icky with multiple + restores - it should really be part of + graphics state.. + XXX, with the stringpool gradients + can be stored there. + */ +#endif + CtxKeyDbEntry keydb[CTX_MAX_KEYDB]; + char stringpool[CTX_STRINGPOOL_SIZE]; + int8_t source; // used for the single-shifting to stroking + // 0 = fill + // 1 = start_stroke + // 2 = in_stroke + // + // if we're at in_stroke at start of a source definition + // we do filling +}; + + +typedef struct _CtxFont CtxFont; +typedef struct _CtxFontEngine CtxFontEngine; + +struct _CtxFontEngine +{ +#if CTX_FONTS_FROM_FILE + int (*load_file) (const char *name, const char *path); +#endif + int (*load_memory) (const char *name, const void *data, int length); + int (*glyph) (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke); + float (*glyph_width) (CtxFont *font, Ctx *ctx, uint32_t unichar); + float (*glyph_kern) (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB); +}; + +struct _CtxFont +{ + CtxFontEngine *engine; + const char *name; + int type; // 0 ctx 1 stb 2 monobitmap + union + { + struct + { + CtxEntry *data; + int length; + /* we've got ~110 bytes to fill to cover as + much data as stbtt_fontinfo */ + //int16_t glyph_pos[26]; // for a..z + int glyphs; // number of glyphs + uint32_t *index; + } ctx; + struct + { + char *path; + } ctx_fs; +#if CTX_FONT_ENGINE_STB + struct + { + stbtt_fontinfo ttf_info; + int cache_index; + uint32_t cache_unichar; + } stb; +#endif + struct { int start; int end; int gw; int gh; const uint8_t *data;} monobitmap; + }; +}; + + +enum _CtxIteratorFlag +{ + CTX_ITERATOR_FLAT = 0, + CTX_ITERATOR_EXPAND_BITPACK = 2, + CTX_ITERATOR_DEFAULTS = CTX_ITERATOR_EXPAND_BITPACK +}; +typedef enum _CtxIteratorFlag CtxIteratorFlag; + + +struct + _CtxIterator +{ + int pos; + int first_run; + CtxDrawlist *drawlist; + int end_pos; + int flags; + + int bitpack_pos; + int bitpack_length; // if non 0 bitpack is active + CtxEntry bitpack_command[6]; // the command returned to the + // user if unpacking is needed. +}; +#define CTX_MAX_DEVICES 16 +#define CTX_MAX_KEYBINDINGS 256 + +#if CTX_EVENTS + +// include list implementation - since it already is a header+inline online +// implementation? + +typedef struct CtxItemCb { + CtxEventType types; + CtxCb cb; + void* data1; + void* data2; + + void (*finalize) (void *data1, void *data2, void *finalize_data); + void *finalize_data; + +} CtxItemCb; + + +#define CTX_MAX_CBS 128 + +typedef struct CtxItem { + CtxMatrix inv_matrix; /* for event coordinate transforms */ + + /* bounding box */ + float x0; + float y0; + float x1; + float y1; + + void *path; + double path_hash; + + CtxCursor cursor; /* if 0 then UNSET and no cursor change is requested + */ + + CtxEventType types; /* all cb's ored together */ + CtxItemCb cb[CTX_MAX_CBS]; + int cb_count; + int ref_count; +} CtxItem; + + +typedef struct _CtxEvents CtxEvents; +struct _CtxEvents +{ + int frozen; + int fullscreen; + CtxList *grabs; /* could split the grabs per device in the same way, + to make dispatch overhead smaller,. probably + not much to win though. */ + CtxItem *prev[CTX_MAX_DEVICES]; + float pointer_x[CTX_MAX_DEVICES]; + float pointer_y[CTX_MAX_DEVICES]; + unsigned char pointer_down[CTX_MAX_DEVICES]; + CtxEvent drag_event[CTX_MAX_DEVICES]; + CtxList *idles; + CtxList *events; // for ctx_get_event + int ctx_get_event_enabled; + int idle_id; + CtxBinding bindings[CTX_MAX_KEYBINDINGS]; /*< better as list, uses no mem if unused */ + int n_bindings; + int width; + int height; + CtxList *items; + CtxItem *last_item; + CtxModifierState modifier_state; + int tap_delay_min; + int tap_delay_max; + int tap_delay_hold; + double tap_hysteresis; +}; + + +#endif + +typedef struct _CtxEidInfo +{ + char *eid; + int frame; + int width; + int height; +} CtxEidInfo; + +struct _Ctx +{ + CtxImplementation *renderer; + CtxDrawlist drawlist; + int transformation; + CtxBuffer texture[CTX_MAX_TEXTURES]; + Ctx *texture_cache; + CtxList *eid_db; + int rev; + void *backend; + CtxState state; /**/ + int frame; /* used for texture lifetime */ +#if CTX_EVENTS + CtxCursor cursor; + int quit; + int dirty; + CtxEvents events; + int mouse_fd; + int mouse_x; + int mouse_y; +#endif +#if CTX_CURRENT_PATH + CtxDrawlist current_path; // possibly transformed coordinates ! + CtxIterator current_path_iterator; +#endif +}; + + +static void ctx_process (Ctx *ctx, CtxEntry *entry); +CtxBuffer *ctx_buffer_new (int width, int height, + CtxPixelFormat pixel_format); +void ctx_buffer_free (CtxBuffer *buffer); + +void +ctx_state_gradient_clear_stops (CtxState *state); + +static inline void ctx_interpret_style (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_transforms (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_pos (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data); + +struct _CtxInternalFsEntry +{ + char *path; + int length; + char *data; +}; + +struct _CtxPixelFormatInfo +{ + CtxPixelFormat pixel_format; + uint8_t components:4; /* number of components */ + uint8_t bpp; /* bits per pixel - for doing offset computations + along with rowstride found elsewhere, if 0 it indicates + 1/8 */ + uint8_t ebpp; /*effective bytes per pixel - for doing offset + computations, for formats that get converted, the + ebpp of the working space applied */ + uint8_t dither_red_blue; + uint8_t dither_green; + CtxPixelFormat composite_format; + + void (*to_comp) (CtxRasterizer *r, + int x, const void * __restrict__ src, uint8_t * __restrict__ comp, int count); + void (*from_comp) (CtxRasterizer *r, + int x, const uint8_t * __restrict__ comp, void *__restrict__ dst, int count); + void (*apply_coverage) (CtxRasterizer *r, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x, uint8_t *coverage, + int count); + void (*setup) (CtxRasterizer *r); +}; + + +static inline void +_ctx_user_to_device (CtxState *state, float *x, float *y); +static void +_ctx_user_to_device_distance (CtxState *state, float *x, float *y); +static void ctx_state_init (CtxState *state); +static inline void +ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data); +static inline void +ctx_drawlist_deinit (CtxDrawlist *drawlist); + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format); + + +int ctx_utf8_len (const unsigned char first_byte); +const char *ctx_utf8_skip (const char *s, int utf8_length); +int ctx_utf8_strlen (const char *s); +int +ctx_unichar_to_utf8 (uint32_t ch, + uint8_t *dest); + +uint32_t +ctx_utf8_to_unichar (const char *input); + + +typedef struct _CtxHasher CtxHasher; + +typedef struct CtxEdge +{ +#if CTX_ALIGNED_STRUCTS==1 + uint32_t index; +#else + uint16_t index; +#endif + int32_t delta; + int32_t val; /* the center-line intersection */ +} CtxEdge; + +typedef void (*CtxFragment) (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy); + +#define CTX_MAX_GAUSSIAN_KERNEL_DIM 512 + +struct _CtxShapeEntry +{ + uint32_t hash; + uint16_t width; + uint16_t height; + int last_frame; // xxx + uint32_t uses; // instrumented for longer keep-alive + uint8_t data[]; +}; + +typedef struct _CtxShapeEntry CtxShapeEntry; + + +struct _CtxShapeCache +{ + CtxShapeEntry *entries[CTX_SHAPE_CACHE_ENTRIES]; + long size; +}; + +typedef struct _CtxShapeCache CtxShapeCache; + + +struct _CtxRasterizer +{ + CtxImplementation vfuncs; + /* these should be initialized and used as the bounds for rendering into the + buffer as well XXX: not yet in use, and when in use will only be + correct for axis aligned clips - proper rasterization of a clipping path + would be yet another refinement on top. + */ + +#if CTX_ENABLE_SHADOW_BLUR + float kernel[CTX_MAX_GAUSSIAN_KERNEL_DIM]; +#endif + + unsigned int aa; // level of vertical aa + int fast_aa; + int prev_active_edges; + int active_edges; + int pending_edges; + int ending_edges; + int edge_pos; // where we're at in iterating all edges + CtxEdge edges[CTX_MAX_EDGES]; + + int scanline; + int scan_min; + int scan_max; + int col_min; + int col_max; + + CtxDrawlist edge_list; + + CtxState *state; + Ctx *ctx; + Ctx *texture_source; /* normally same as ctx */ + + void *buf; + + +#if CTX_COMPOSITING_GROUPS + void *saved_buf; // when group redirected + CtxBuffer *group[CTX_GROUP_MAX]; +#endif + + + float x; // < redundant? use state instead? + float y; + + float first_x; + float first_y; + unsigned int needs_aa3; // count of how many edges implies antialiasing + unsigned int needs_aa5; // count of how many edges implies antialiasing + unsigned int needs_aa15; // count of how many edges implies antialiasing + int horizontal_edges; + uint8_t *opaque; // fully opaque-scanline + int uses_transforms; + int has_shape:2; + int has_prev:2; + int preserve:1; + + int16_t blit_x; + int16_t blit_y; + int16_t blit_width; + int16_t blit_height; + int16_t blit_stride; + + CtxPixelFormatInfo *format; + +#if CTX_ENABLE_SHADOW_BLUR + int in_shadow; +#endif + int in_text; + int shadow_x; + int shadow_y; + + CtxFragment fragment; + int swap_red_green; + uint8_t color[4*5]; + +#define CTX_COMPOSITE_ARGUMENTS CtxRasterizer *rasterizer, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x0, uint8_t * __restrict__ coverage, int count + + void (*comp_op)(CTX_COMPOSITE_ARGUMENTS); +#if CTX_ENABLE_CLIP + CtxBuffer *clip_buffer; +#endif + + int clip_rectangle; + +#if CTX_SHAPE_CACHE + CtxShapeCache shape_cache; +#endif +#if CTX_BRAILLE_TEXT + int term_glyphs:1; // store appropriate glyphs for redisplay + CtxList *glyphs; +#endif +}; + +struct _CtxSHA1 { + uint64_t length; + uint32_t state[5], curlen; + unsigned char buf[64]; +}; + + +struct _CtxHasher +{ + CtxRasterizer rasterizer; + int cols; + int rows; + uint8_t *hashes; + CtxSHA1 sha1_fill; + CtxSHA1 sha1_stroke; +}; + +#if CTX_RASTERIZER +void ctx_rasterizer_deinit (CtxRasterizer *rasterizer); +#endif + +#if CTX_EVENTS +extern int ctx_native_events; + +#if CTX_SDL +extern int ctx_sdl_events; +int ctx_sdl_consume_events (Ctx *ctx); +#endif + +#if CTX_FB +extern int ctx_fb_events; +int ctx_fb_consume_events (Ctx *ctx); +#endif + + +int ctx_nct_consume_events (Ctx *ctx); +int ctx_ctx_consume_events (Ctx *ctx); + +#endif + +enum { + NC_MOUSE_NONE = 0, + NC_MOUSE_PRESS = 1, /* "mouse-pressed", "mouse-released" */ + NC_MOUSE_DRAG = 2, /* + "mouse-drag" (motion with pressed button) */ + NC_MOUSE_ALL = 3 /* + "mouse-motion" (also delivered for release) */ +}; +void _ctx_mouse (Ctx *term, int mode); +void nc_at_exit (void); + +int ctx_terminal_width (void); +int ctx_terminal_height (void); +int ctx_terminal_cols (void); +int ctx_terminal_rows (void); +extern int ctx_frame_ack; + +int ctx_nct_consume_events (Ctx *ctx); + +typedef struct _CtxCtx CtxCtx; +struct _CtxCtx +{ + void (*render) (void *ctxctx, CtxCommand *command); + void (*reset) (void *ctxvtx); + void (*flush) (void *ctxctx); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *ctxctx); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; +}; + + +extern int _ctx_max_threads; +extern int _ctx_enable_hash_cache; +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len); +const char * +ctx_get (Ctx *ctx, const char *key); + +int ctx_renderer_is_term (Ctx *ctx); +Ctx *ctx_new_ctx (int width, int height); +Ctx *ctx_new_fb (int width, int height, int drm); +Ctx *ctx_new_sdl (int width, int height); +Ctx *ctx_new_term (int width, int height); +Ctx *ctx_new_termimg (int width, int height); + +int ctx_resolve_font (const char *name); +extern float ctx_u8_float[256]; +#define ctx_u8_to_float(val_u8) ctx_u8_float[((uint8_t)(val_u8))] +//#define ctx_u8_to_float(val_u8) (val_u8/255.0f) +// +// + + +static uint8_t ctx_float_to_u8 (float val_f) +{ + return val_f < 0.0f ? 0 : val_f > 1.0f ? 0xff : 0xff * val_f + 0.5f; +#if 0 + int val_i = val_f * 255.999f; + if (val_i < 0) { return 0; } + else if (val_i > 255) { return 255; } + return val_i; +#endif +} + + +#define CTX_CSS_LUMINANCE_RED 0.3f +#define CTX_CSS_LUMINANCE_GREEN 0.59f +#define CTX_CSS_LUMINANCE_BLUE 0.11f + +/* works on both float and uint8_t */ +#define CTX_CSS_RGB_TO_LUMINANCE(rgb) (\ + (rgb[0]) * CTX_CSS_LUMINANCE_RED + \ + (rgb[1]) * CTX_CSS_LUMINANCE_GREEN +\ + (rgb[2]) * CTX_CSS_LUMINANCE_BLUE) + +const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y); +const char *ctx_native_get_event (Ctx *n, int timeoutms); +void +ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out); +void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out); +float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb); +void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out); +void ctx_rgb_to_cmyk (float r, float g, float b, + float *c_out, float *m_out, float *y_out, float *k_out); +uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb); +#if CTX_ENABLE_CMYK +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out); +#endif +static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out); +static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a); +static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a); +static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha); + +int ctx_color_model_get_components (CtxColorModel model); + +static void ctx_state_set (CtxState *state, uint64_t key, float value); +static void +ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f); +static void ctx_font_setup (); +static float ctx_state_get (CtxState *state, uint64_t hash); + +#if CTX_RASTERIZER + +static void +ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y); + +static void +ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2); +static void +ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2); + +static void +ctx_rasterizer_reset (CtxRasterizer *rasterizer); +static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer); +static void +ctx_rasterizer_arc (CtxRasterizer *rasterizer, + float x, + float y, + float radius, + float start_angle, + float end_angle, + int anticlockwise); + +static void +ctx_rasterizer_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y); + +static void +ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y); + +static void +ctx_rasterizer_rectangle (CtxRasterizer *rasterizer, + float x, + float y, + float width, + float height); + +static void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer); +static void ctx_rasterizer_clip (CtxRasterizer *rasterizer); +static void +ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name); + +static void +ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba); +static void +ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer, + uint16_t x, + uint16_t y, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a); +static void +ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius); + +#endif + +#if CTX_ENABLE_CM // XXX to be moved to ctx.h +void +ctx_set_drgb_space (Ctx *ctx, int device_space); +void +ctx_set_dcmyk_space (Ctx *ctx, int device_space); +void +ctx_rgb_space (Ctx *ctx, int device_space); +void +ctx_set_cmyk_space (Ctx *ctx, int device_space); +#endif + +#endif + +CtxRasterizer * +ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias); + + +CTX_INLINE static uint8_t ctx_lerp_u8 (uint8_t v0, uint8_t v1, uint8_t dx) +{ +#if 0 + return v0 + ((v1-v0) * dx)/255; +#else + return ( ( ( ( (v0) <<8) + (dx) * ( (v1) - (v0) ) ) ) >>8); +#endif +} + +CTX_INLINE static uint32_t ctx_lerp_RGBA8 (const uint32_t v0, const uint32_t v1, const uint8_t dx) +{ +#if 0 + char bv0[4]; + char bv1[4]; + char res[4]; + memcpy (&bv0[0], &v0, 4); + memcpy (&bv1[0], &v1, 4); + for (int c = 0; c < 4; c++) + res [c] = ctx_lerp_u8 (bv0[c], bv1[c], dx); + return ((uint32_t*)(&res[0]))[0]; +#else + const uint32_t cov = dx; + const uint32_t si_ga = (v1 & 0xff00ff00) >> 8; + 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 - (di_ga>>8); + return + (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((0xff00ff + d_ga * cov) & 0xff00ff00))); + +#endif +} + +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; + uint32_t d_rb = si_rb - di_rb; + const uint32_t d_ga = si_ga - (di_ga>>8); + return + (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((0xff00ff + d_ga * cov) & 0xff00ff00))); +} + + + +CTX_INLINE static float +ctx_lerpf (float v0, float v1, float dx) +{ + return v0 + (v1-v0) * dx; +} + + +#ifndef CTX_MIN +#define CTX_MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef CTX_MAX +#define CTX_MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +static inline void *ctx_calloc (size_t size, size_t count); + +void ctx_screenshot (Ctx *ctx, const char *output_path); + + +CtxSHA1 *ctx_sha1_new (void); +void ctx_sha1_free (CtxSHA1 *sha1); +int ctx_sha1_process(CtxSHA1 *sha1, const unsigned char * msg, unsigned long len); +int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out); + +void _ctx_texture_lock (void); +void _ctx_texture_unlock (void); +uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry); +void ctx_buffer_pixels_free (void *pixels, void *userdata); + +/*ctx_texture_init: + * return value: eid, as passed in or if NULL generated by hashing pixels and width/height + * XXX this is low-level and not to be used directly use define_texture instead. XXX + */ +const char *ctx_texture_init ( + Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + CtxPixelFormat format, + void *space, + uint8_t *pixels, + void (*freefunc) (void *pixels, void *user_data), + void *user_data); + +#if CTX_TILED +#if !__COSMOPOLITAN__ +#include <threads.h> +#endif +#endif +typedef struct _CtxTiled CtxTiled; + +struct _CtxTiled +{ + void (*render) (void *term, CtxCommand *command); + void (*reset) (void *term); + void (*flush) (void *term); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *term); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + uint8_t *pixels; + Ctx *ctx_copy; + Ctx *host[CTX_MAX_THREADS]; + CtxAntialias antialias; + int quit; +#if CTX_TILED + _Atomic int thread_quit; +#endif + int shown_frame; + int render_frame; + int rendered_frame[CTX_MAX_THREADS]; + int frame; + int min_col; // hasher cols and rows + int min_row; + int max_col; + int max_row; + uint8_t hashes[CTX_HASH_ROWS * CTX_HASH_COLS * 20]; + int8_t tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is + // responsible for a tile + // + + int pointer_down[3]; + + CtxCursor shown_cursor; +#if CTX_TILED + cnd_t cond; + mtx_t mtx; +#endif +}; + +static void +_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer, + CtxBuffer *buffer); + +#endif + + +#if CTX_IMPLEMENTATION + +#define SHA1_IMPLEMENTATION +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + * + * The plain ANSIC sha1 functionality has been extracted from libtomcrypt, + * and is included directly in the sources. /Øyvind K. - since libtomcrypt + * is public domain the adaptations done here to make the sha1 self contained + * also is public domain. + */ +#ifndef __SHA1_H +#define __SHA1_H +#if !__COSMOPOLITAN__ +#include <inttypes.h> +#endif + + +int ctx_sha1_init(CtxSHA1 * sha1); +CtxSHA1 *ctx_sha1_new (void) +{ + CtxSHA1 *state = (CtxSHA1*)calloc (sizeof (CtxSHA1), 1); + ctx_sha1_init (state); + return state; +} +void ctx_sha1_free (CtxSHA1 *sha1) +{ + free (sha1); +} + +#if 0 + CtxSHA1 sha1; + ctx_sha1_init (&sha1); + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); +#endif + +#ifdef SHA1_FF0 +#undef SHA1_FF0 +#endif +#ifdef SHA1_FF1 +#undef SHA1_FF1 +#endif + +#ifdef SHA1_IMPLEMENTATION +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#include <string.h> +#endif + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define STORE32H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ + (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } + +#define LOAD32H(x, y) \ + { x = ((unsigned long)((y)[0] & 255)<<24) | \ + ((unsigned long)((y)[1] & 255)<<16) | \ + ((unsigned long)((y)[2] & 255)<<8) | \ + ((unsigned long)((y)[3] & 255)); } + +/* rotates the hard way */ +#define ROL(x, y) ((((unsigned long)(x)<<(unsigned long)((y)&31)) | (((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) +#define ROLc(x, y) ROL(x,y) + +#define CRYPT_OK 0 +#define CRYPT_ERROR 1 +#define CRYPT_NOP 2 + +#ifndef MAX + #define MAX(x, y) ( ((x)>(y))?(x):(y) ) +#endif +#ifndef MIN + #define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* a simple macro for making hash "process" functions */ +#define HASH_PROCESS(func_name, compress_name, state_var, block_size) \ +int func_name (CtxSHA1 *sha1, const unsigned char *in, unsigned long inlen) \ +{ \ + unsigned long n; \ + int err; \ + assert (sha1 != NULL); \ + assert (in != NULL); \ + if (sha1->curlen > sizeof(sha1->buf)) { \ + return -1; \ + } \ + while (inlen > 0) { \ + if (sha1->curlen == 0 && inlen >= block_size) { \ + if ((err = compress_name (sha1, (unsigned char *)in)) != CRYPT_OK) { \ + return err; \ + } \ + sha1->length += block_size * 8; \ + in += block_size; \ + inlen -= block_size; \ + } else { \ + n = MIN(inlen, (block_size - sha1->curlen)); \ + memcpy(sha1->buf + sha1->curlen, in, (size_t)n); \ + sha1->curlen += n; \ + in += n; \ + inlen -= n; \ + if (sha1->curlen == block_size) { \ + if ((err = compress_name (sha1, sha1->buf)) != CRYPT_OK) { \ + return err; \ + } \ + sha1->length += 8*block_size; \ + sha1->curlen = 0; \ + } \ + } \ + } \ + return CRYPT_OK; \ +} + +/**********************/ + +#define F0(x,y,z) (z ^ (x & (y ^ z))) +#define F1(x,y,z) (x ^ y ^ z) +#define F2(x,y,z) ((x & y) | (z & (x | y))) +#define F3(x,y,z) (x ^ y ^ z) + +static int ctx_sha1_compress(CtxSHA1 *sha1, unsigned char *buf) +{ + uint32_t a,b,c,d,e,W[80],i; + + /* copy the state into 512-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD32H(W[i], buf + (4*i)); + } + + /* copy state */ + a = sha1->state[0]; + b = sha1->state[1]; + c = sha1->state[2]; + d = sha1->state[3]; + e = sha1->state[4]; + + /* expand it */ + for (i = 16; i < 80; i++) { + W[i] = ROL(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); + } + + /* compress */ + /* round one */ + #define SHA1_FF0(a,b,c,d,e,i) e = (ROLc(a, 5) + F0(b,c,d) + e + W[i] + 0x5a827999UL); b = ROLc(b, 30); + #define SHA1_FF1(a,b,c,d,e,i) e = (ROLc(a, 5) + F1(b,c,d) + e + W[i] + 0x6ed9eba1UL); b = ROLc(b, 30); + #define SHA1_FF2(a,b,c,d,e,i) e = (ROLc(a, 5) + F2(b,c,d) + e + W[i] + 0x8f1bbcdcUL); b = ROLc(b, 30); + #define SHA1_FF3(a,b,c,d,e,i) e = (ROLc(a, 5) + F3(b,c,d) + e + W[i] + 0xca62c1d6UL); b = ROLc(b, 30); + + for (i = 0; i < 20; ) { + SHA1_FF0(a,b,c,d,e,i++); + SHA1_FF0(e,a,b,c,d,i++); + SHA1_FF0(d,e,a,b,c,i++); + SHA1_FF0(c,d,e,a,b,i++); + SHA1_FF0(b,c,d,e,a,i++); + } + + /* round two */ + for (; i < 40; ) { + SHA1_FF1(a,b,c,d,e,i++); + SHA1_FF1(e,a,b,c,d,i++); + SHA1_FF1(d,e,a,b,c,i++); + SHA1_FF1(c,d,e,a,b,i++); + SHA1_FF1(b,c,d,e,a,i++); + } + + /* round three */ + for (; i < 60; ) { + SHA1_FF2(a,b,c,d,e,i++); + SHA1_FF2(e,a,b,c,d,i++); + SHA1_FF2(d,e,a,b,c,i++); + SHA1_FF2(c,d,e,a,b,i++); + SHA1_FF2(b,c,d,e,a,i++); + } + + /* round four */ + for (; i < 80; ) { + SHA1_FF3(a,b,c,d,e,i++); + SHA1_FF3(e,a,b,c,d,i++); + SHA1_FF3(d,e,a,b,c,i++); + SHA1_FF3(c,d,e,a,b,i++); + SHA1_FF3(b,c,d,e,a,i++); + } + + #undef SHA1_FF0 + #undef SHA1_FF1 + #undef SHA1_FF2 + #undef SHA1_FF3 + + /* store */ + sha1->state[0] = sha1->state[0] + a; + sha1->state[1] = sha1->state[1] + b; + sha1->state[2] = sha1->state[2] + c; + sha1->state[3] = sha1->state[3] + d; + sha1->state[4] = sha1->state[4] + e; + + return CRYPT_OK; +} + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return CRYPT_OK if successful +*/ +int ctx_sha1_init(CtxSHA1 * sha1) +{ + assert(sha1 != NULL); + sha1->state[0] = 0x67452301UL; + sha1->state[1] = 0xefcdab89UL; + sha1->state[2] = 0x98badcfeUL; + sha1->state[3] = 0x10325476UL; + sha1->state[4] = 0xc3d2e1f0UL; + sha1->curlen = 0; + sha1->length = 0; + return CRYPT_OK; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return CRYPT_OK if successful +*/ +HASH_PROCESS(ctx_sha1_process, ctx_sha1_compress, sha1, 64) + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (20 bytes) + @return CRYPT_OK if successful +*/ +int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out) +{ + int i; + + assert(sha1 != NULL); + assert(out != NULL); + + if (sha1->curlen >= sizeof(sha1->buf)) { + return -1; + } + + /* increase the length of the message */ + sha1->length += sha1->curlen * 8; + + /* append the '1' bit */ + sha1->buf[sha1->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 56 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (sha1->curlen > 56) { + while (sha1->curlen < 64) { + sha1->buf[sha1->curlen++] = (unsigned char)0; + } + ctx_sha1_compress(sha1, sha1->buf); + sha1->curlen = 0; + } + + /* pad upto 56 bytes of zeroes */ + while (sha1->curlen < 56) { + sha1->buf[sha1->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(sha1->length, sha1->buf+56); + ctx_sha1_compress(sha1, sha1->buf); + + /* copy output */ + for (i = 0; i < 5; i++) { + STORE32H(sha1->state[i], out+(4*i)); + } + return CRYPT_OK; +} +#endif + +#endif +#endif +#ifndef CTX_AUDIO_H +#define CTX_AUDIO_H + +#if !__COSMOPOLITAN__ +#include <stdint.h> +#endif + +/* This enum should be kept in sync with the corresponding mmm enum. + */ +typedef enum { + CTX_f32, + CTX_f32S, + CTX_s16, + CTX_s16S +} CtxPCM; + +void ctx_pcm_set_format (Ctx *ctx, CtxPCM format); +CtxPCM ctx_pcm_get_format (Ctx *ctx); +int ctx_pcm_get_sample_rate (Ctx *ctx); +void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate); +int ctx_pcm_get_frame_chunk (Ctx *ctx); +int ctx_pcm_get_queued (Ctx *ctx); +float ctx_pcm_get_queued_length (Ctx *ctx); +int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames); + +#endif + +#if CTX_IMPLEMENTATION +#if CTX_AUDIO + +//#include <string.h> +//#include "ctx-internal.h" +//#include "mmm.h" + +#if !__COSMOPOLITAN__ + +#include <pthread.h> +#if CTX_ALSA_AUDIO +#include <alsa/asoundlib.h> +#endif +#include <alloca.h> + +#endif + +#define DESIRED_PERIOD_SIZE 1000 + +int ctx_pcm_bytes_per_frame (CtxPCM format) +{ + switch (format) + { + case CTX_f32: return 4; + case CTX_f32S: return 8; + case CTX_s16: return 2; + case CTX_s16S: return 4; + default: return 1; + } +} + +static float ctx_host_freq = 48000; +static CtxPCM ctx_host_format = CTX_s16S; +static float client_freq = 48000; +static CtxPCM ctx_client_format = CTX_s16S; +static int ctx_pcm_queued = 0; +static int ctx_pcm_cur_left = 0; +static CtxList *ctx_pcm_list; /* data is a blob a 32bit uint first, followed by pcm-data */ + + +//static long int ctx_pcm_queued_ticks = 0; /* the number of ticks into the future + // * we've queued audio for + + + +int +ctx_pcm_channels (CtxPCM format) +{ + switch (format) + { + case CTX_s16: + case CTX_f32: + return 1; + case CTX_s16S: + case CTX_f32S: + return 2; + } + return 0; +} + +/* todo: only start audio thread on first write - enabling dynamic choice + * of sample-rate? or is it better to keep to opening 48000 as a standard + * and do better internal resampling for others? + */ + +#if CTX_ALSA_AUDIO +static snd_pcm_t *alsa_open (char *dev, int rate, int channels) +{ + snd_pcm_hw_params_t *hwp; + snd_pcm_sw_params_t *swp; + snd_pcm_t *h; + int r; + int dir; + snd_pcm_uframes_t period_size_min; + snd_pcm_uframes_t period_size_max; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + + if ((r = snd_pcm_open(&h, dev, SND_PCM_STREAM_PLAYBACK, 0) < 0)) + return NULL; + + hwp = alloca(snd_pcm_hw_params_sizeof()); + memset(hwp, 0, snd_pcm_hw_params_sizeof()); + snd_pcm_hw_params_any(h, hwp); + + snd_pcm_hw_params_set_access(h, hwp, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(h, hwp, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(h, hwp, rate, 0); + snd_pcm_hw_params_set_channels(h, hwp, channels); + dir = 0; + snd_pcm_hw_params_get_period_size_min(hwp, &period_size_min, &dir); + dir = 0; + snd_pcm_hw_params_get_period_size_max(hwp, &period_size_max, &dir); + + period_size = DESIRED_PERIOD_SIZE; + + dir = 0; + r = snd_pcm_hw_params_set_period_size_near(h, hwp, &period_size, &dir); + r = snd_pcm_hw_params_get_period_size(hwp, &period_size, &dir); + buffer_size = period_size * 4; + r = snd_pcm_hw_params_set_buffer_size_near(h, hwp, &buffer_size); + r = snd_pcm_hw_params(h, hwp); + swp = alloca(snd_pcm_sw_params_sizeof()); + memset(hwp, 0, snd_pcm_sw_params_sizeof()); + snd_pcm_sw_params_current(h, swp); + r = snd_pcm_sw_params_set_avail_min(h, swp, period_size); + snd_pcm_sw_params_set_start_threshold(h, swp, 0); + r = snd_pcm_sw_params(h, swp); + r = snd_pcm_prepare(h); + + return h; +} + +static snd_pcm_t *h = NULL; +static void *ctx_alsa_audio_start(Ctx *ctx) +{ +// Lyd *lyd = aux; + int c; + + /* The audio handler is implemented as a mixer that adds data on top + * of 0s, XXX: it should be ensured that minimal work is there is + * no data available. + */ + for (;;) + { + int client_channels = ctx_pcm_channels (ctx_client_format); + int is_float = 0; + int16_t data[81920*8]={0,}; + + if (ctx_client_format == CTX_f32 || + ctx_client_format == CTX_f32S) + is_float = 1; + + c = snd_pcm_wait(h, 1000); + + if (c >= 0) + c = snd_pcm_avail_update(h); + + if (c > 1000) c = 1000; // should use max mmm buffer sizes + + if (c == -EPIPE) + snd_pcm_prepare(h); + + if (c > 0) + { + int i; + for (i = 0; i < c && ctx_pcm_cur_left; i ++) + { + if (ctx_pcm_cur_left) // XXX this line can be removed + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + uint16_t left = 0, right = 0; + + if (is_float) + { + float *packet = (ctx_pcm_list->data); + packet += 4; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + left = right = packet[0] * (1<<15); + if (client_channels > 1) + right = packet[0] * (1<<15); + } + else // s16 + { + uint16_t *packet = (ctx_pcm_list->data); + packet += 8; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + + left = right = packet[0]; + if (client_channels > 1) + right = packet[1]; + } + data[i * 2 + 0] = left; + data[i * 2 + 1] = right; + + ctx_pcm_cur_left--; + ctx_pcm_queued --; + if (ctx_pcm_cur_left == 0) + { + void *old = ctx_pcm_list->data; + ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data); + free (old); + ctx_pcm_cur_left = 0; + if (ctx_pcm_list) + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + ctx_pcm_cur_left = packet_size; + } + } + } + } + + c = snd_pcm_writei(h, data, c); + if (c < 0) + c = snd_pcm_recover (h, c, 0); + }else{ + if (getenv("LYD_FATAL_UNDERRUNS")) + { + printf ("dying XXxx need to add API for this debug\n"); + //printf ("%i", lyd->active); + exit(0); + } + fprintf (stderr, "ctx alsa underun\n"); + //exit(0); + } + } +} +#endif + +static char MuLawCompressTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +static unsigned char LinearToMuLawSample(int16_t sample) +{ + const int cBias = 0x84; + const int cClip = 32635; + int sign = (sample >> 8) & 0x80; + + if (sign) + sample = (int16_t)-sample; + + if (sample > cClip) + sample = cClip; + + sample = (int16_t)(sample + cBias); + + int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF]; + int mantissa = (sample >> (exponent+3)) & 0x0F; + + int compressedByte = ~ (sign | (exponent << 4) | mantissa); + + return (unsigned char)compressedByte; +} + +void ctx_ctx_pcm (Ctx *ctx) +{ + int client_channels = ctx_pcm_channels (ctx_client_format); + int is_float = 0; + uint8_t data[81920*8]={0,}; + int c; + + if (ctx_client_format == CTX_f32 || + ctx_client_format == CTX_f32S) + is_float = 1; + + c = 2000; + + if (c > 0) + { + int i; + for (i = 0; i < c && ctx_pcm_cur_left; i ++) + { + if (ctx_pcm_cur_left) // XXX this line can be removed + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + int left = 0, right = 0; + + if (is_float) + { + float *packet = (ctx_pcm_list->data); + packet += 4; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + left = right = packet[0] * (1<<15); + if (client_channels > 1) + right = packet[1] * (1<<15); + } + else // s16 + { + uint16_t *packet = (ctx_pcm_list->data); + packet += 8; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + + left = right = packet[0]; + if (client_channels > 1) + right = packet[1]; + } + data[i] = LinearToMuLawSample((left+right)/2); + + ctx_pcm_cur_left--; + ctx_pcm_queued --; + if (ctx_pcm_cur_left == 0) + { + void *old = ctx_pcm_list->data; + ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data); + free (old); + ctx_pcm_cur_left = 0; + if (ctx_pcm_list) + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + ctx_pcm_cur_left = packet_size; + } + } + } + } + + char encoded[81920*8]=""; + + int encoded_len = ctx_a85enc (data, encoded, i); + fprintf (stdout, "\033_Af=%i;", i); + fwrite (encoded, 1, encoded_len, stdout); + fwrite ("\e\\", 1, 2, stdout); + fflush (stdout); + } +} + +int ctx_pcm_init (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return 0; + } + else +#endif + if (ctx_renderer_is_ctx (ctx)) + { + ctx_host_freq = 8000; + ctx_host_format = CTX_s16; +#if 0 + pthread_t tid; + pthread_create(&tid, NULL, (void*)ctx_audio_start, ctx); +#endif + } + else + { +#if CTX_ALSA_AUDIO + pthread_t tid; + h = alsa_open("default", ctx_host_freq, ctx_pcm_channels (ctx_host_format)); + if (!h) { + fprintf(stderr, "ctx unable to open ALSA device (%d channels, %f Hz), dying\n", + ctx_pcm_channels (ctx_host_format), ctx_host_freq); + return -1; + } + pthread_create(&tid, NULL, (void*)ctx_alsa_audio_start, ctx); +#endif + } + return 0; +} + +int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames) +{ + static int inited = 0; +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_queue (ctx->backend_data, data, frames); + } + else +#endif + { + if (!inited) + { + ctx_pcm_init (ctx); + inited = 1; + } + float factor = client_freq * 1.0 / ctx_host_freq; + int scaled_frames = frames / factor; + int bpf = ctx_pcm_bytes_per_frame (ctx_client_format); + + uint8_t *packet = malloc (scaled_frames * ctx_pcm_bytes_per_frame (ctx_client_format) + 16); + *((uint32_t *)packet) = scaled_frames; + + if (factor > 0.999 && factor < 1.0001) + { + memcpy (packet + 16, data, frames * bpf); + } + else + { + /* a crude nearest / sample-and hold resampler */ + int i; + for (i = 0; i < scaled_frames; i++) + { + int source_frame = i * factor; + memcpy (packet + 16 + bpf * i, data + source_frame * bpf, bpf); + } + } + if (ctx_pcm_list == NULL) // otherwise it is another frame at front + ctx_pcm_cur_left = scaled_frames; // and current cur_left is valid + + ctx_list_append (&ctx_pcm_list, packet); + ctx_pcm_queued += scaled_frames; + + return frames; + } + return 0; +} + +static int ctx_pcm_get_queued_frames (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_queued_frames (ctx->backend_data); + } +#endif + return ctx_pcm_queued; +} + +int ctx_pcm_get_queued (Ctx *ctx) +{ + return ctx_pcm_get_queued_frames (ctx); +} + +float ctx_pcm_get_queued_length (Ctx *ctx) +{ + return 1.0 * ctx_pcm_get_queued_frames (ctx) / ctx_host_freq; +} + +int ctx_pcm_get_frame_chunk (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_frame_chunk (ctx->backend_data); + } +#endif + if (ctx_renderer_is_ctx (ctx)) + { + // 300 stuttering + // 350 nothing + // 380 slight buzz + // 390 buzzing + // 400 ok - but sometimes falling out + // 410 buzzing + // 420 ok - but odd latency + // 450 buzzing + + if (ctx_pcm_get_queued_frames (ctx) > 400) + return 0; + else + return 400 - ctx_pcm_get_queued_frames (ctx); + + } + + if (ctx_pcm_get_queued_frames (ctx) > 1000) + return 0; + else + return 1000 - ctx_pcm_get_queued_frames (ctx); +} + +void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + mmm_pcm_set_sample_rate (ctx->backend_data, sample_rate); + } + else +#endif + client_freq = sample_rate; +} + +void ctx_pcm_set_format (Ctx *ctx, CtxPCM format) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + mmm_pcm_set_format (ctx->backend_data, format); + } + else +#endif + ctx_client_format = format; +} + +CtxPCM ctx_pcm_get_format (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_format (ctx->backend_data); + } +#endif + return ctx_client_format; +} + +int ctx_pcm_get_sample_rate (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_sample_rate (ctx->backend_data); + } +#endif + return client_freq; +} + +#endif + /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + */ + +#if CTX_FORMATTER + +/* returns the maximum string length including terminating \0 */ +int ctx_a85enc_len (int input_length) +{ + return (input_length / 4 + 1) * 5; +} + +int ctx_a85enc (const void *srcp, char *dst, int count) +{ + const uint8_t *src = (uint8_t*)srcp; + int out_len = 0; + + int padding = 4-(count % 4); + if (padding == 4) padding = 0; + + for (int i = 0; i < (count+3)/4; i ++) + { + uint32_t input = 0; + for (int j = 0; j < 4; j++) + { + input = (input << 8); + if (i*4+j<=count) + input += src[i*4+j]; + } + + int divisor = 85 * 85 * 85 * 85; +#if 0 + if (input == 0) + { + dst[out_len++] = 'z'; + } + /* todo: encode 4 spaces as 'y' */ + else +#endif + { + for (int j = 0; j < 5; j++) + { + dst[out_len++] = ((input / divisor) % 85) + '!'; + divisor /= 85; + } + } + } + out_len -= padding; + dst[out_len]=0; + return out_len; +} +#endif + +#if CTX_PARSER + +int ctx_a85dec (const char *src, char *dst, int count) +{ + int out_len = 0; + uint32_t val = 0; + int k = 0; + int i = 0; + int p = 0; + for (i = 0; i < count; i ++) + { + p = src[i]; + val *= 85; + if (CTX_UNLIKELY(p == '~')) + { + break; + } +#if 0 + else if (p == 'z') + { + for (int j = 0; j < 4; j++) + dst[out_len++] = 0; + k = 0; + } + else if (p == 'y') /* lets support this extension */ + { + for (int j = 0; j < 4; j++) + dst[out_len++] = 32; + k = 0; + } +#endif + else if (CTX_LIKELY(p >= '!' && p <= 'u')) + { + val += p-'!'; + if (CTX_UNLIKELY (k % 5 == 4)) + { + for (int j = 0; j < 4; j++) + { + dst[out_len++] = (val & (0xff << 24)) >> 24; + val <<= 8; + } + val = 0; + } + k++; + } + // we treat all other chars as whitespace + } + if (CTX_LIKELY (p != '~')) + { + val *= 85; + } + k = k % 5; + if (k) + { + val += 84; + for (int j = k; j < 4; j++) + { + val *= 85; + val += 84; + } + + for (int j = 0; j < k-1; j++) + { + dst[out_len++] = (val & (0xff << 24)) >> 24; + val <<= 8; + } + val = 0; + } + dst[out_len] = 0; + return out_len; +} + +#if 1 +int ctx_a85len (const char *src, int count) +{ + int out_len = 0; + int k = 0; + for (int i = 0; i < count; i ++) + { + if (src[i] == '~') + break; + else if (src[i] == 'z') + { + for (int j = 0; j < 4; j++) + out_len++; + k = 0; + } + else if (src[i] >= '!' && src[i] <= 'u') + { + if (k % 5 == 4) + out_len += 4; + k++; + } + // we treat all other chars as whitespace + } + k = k % 5; + if (k) + out_len += k-1; + return out_len; +} +#endif + +#endif +#ifndef THASH_H +#define THASH_H + +#if !__COSMOPOLITAN__ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#endif + +#define THASH_NO_INTERNING // ctx doesn't make use of thash_decode + +#define THASH_ENTER_DIRECT 16 + +#define THASH_SPACE 0 +#define THASH_DEC_OFFSET 29 +#define THASH_INC_OFFSET 30 +#define THASH_ENTER_UTF5 31 +#define THASH_START_OFFSET 'l' +#define THASH_JUMP_OFFSET 27 +#define THASH_MAXLEN 10 + +// todo: better whitespace handling for double version + + +static inline int thash_new_offset (uint32_t unichar) +{ + int offset = unichar % 32; + return unichar - offset + 14; // this gives ~85% compression on test corpus + return unichar; // this gives 88% compression on test corpus +} + +static int thash_is_in_range (uint32_t offset, uint32_t unichar) +{ + if (unichar == 32) + return 1; + if (offset - unichar <= 13 || + unichar - offset <= 14) + return 1; + return 0; +} + +static int thash_is_in_jump_range_dec (uint32_t offset, uint32_t unichar) +{ + return thash_is_in_range (offset - THASH_JUMP_OFFSET, unichar); +} + +static int thash_is_in_jump_range_inc (uint32_t offset, uint32_t unichar) +{ + return thash_is_in_range (offset + THASH_JUMP_OFFSET, unichar); +} + +//uint32_t ctx_utf8_to_unichar (const char *input); +//int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +//int ctx_utf8_len (const unsigned char first_byte); + +static int thash_utf5_length (uint32_t unichar) +{ + int octets = 0; + if (unichar == 0) return 1; + while (unichar) + { octets ++; + unichar /= 16; + } + return octets; +} + +typedef struct EncodeUtf5 { + int is_utf5; + int offset; + int length; + void *write_data; + uint32_t current; +} EncodeUtf5; + +void thash_encode_utf5 (const char *input, int inlen, + char *output, int *r_outlen) +{ + uint32_t offset = THASH_START_OFFSET; + + int is_utf5 = 1; + int len = 0; + + for (int i = 0; i < inlen; i+= ctx_utf8_len (input[i])) + { + int val = ctx_utf8_to_unichar(&input[i]); + int next_val = ' '; // always in range + int next_next_val = ' '; + int first_len = ctx_utf8_len (input[i]); + if (i + first_len < inlen) + { + int next_len = ctx_utf8_to_unichar (&input[i + first_len]); + if (i + first_len + next_len < inlen) + { + next_next_val = ctx_utf8_to_unichar (&input[i + first_len + next_len]); + } + } + + if (is_utf5) + { + int in_range = + thash_is_in_range (offset, val) + + thash_is_in_range (offset, next_val) + + thash_is_in_range (offset, next_next_val); + int change_cost = 4; + int no_change_cost = thash_utf5_length (val) + thash_utf5_length (next_val) + + thash_utf5_length (next_next_val); + + if (in_range > 2 && change_cost < no_change_cost) + { + output[len++] = THASH_ENTER_DIRECT; + is_utf5 = 0; + } + } else + { + if (!thash_is_in_range(offset, val)) + { + if (thash_is_in_jump_range_dec (offset, val)) + { + output[len++] = THASH_DEC_OFFSET; + offset -= THASH_JUMP_OFFSET; + } + else if (thash_is_in_jump_range_inc (offset, val)) + { + output[len++] = THASH_INC_OFFSET; + offset += THASH_JUMP_OFFSET; + } + else + { + output[len++] = THASH_ENTER_UTF5; + is_utf5 = 1; + } + } + } + + if (is_utf5) + { + int octets = 0; + offset = thash_new_offset (val); + while (val) + { + int oval = val % 16; + int last = 0; + if (val / 32 == 0) last = 16; + output[len+ (octets++)] = oval + last; + val /= 16; + } + for (int j = 0; j < octets/2; j++) // mirror in-place + { + int tmp = output[len+j]; + output[len+j] = output[len+octets-1-j]; + output[len+octets-1-j] = tmp; + } + len += octets; + } + else + { + if (val == 32) + { + output[len++] = THASH_SPACE; + } + else + { + output[len++]= val-offset+14; + } + } + } + if (len && output[len-1]==0) + output[len++] = 16; + output[len]=0; + *r_outlen = len; +} + +uint64_t _thash (const char *utf8) +{ + char encoded[4096]=""; + int encoded_len=0; + int wordlen = 0; + thash_encode_utf5 (utf8, strlen (utf8), encoded, &encoded_len); +#if 0 + Word word = {0}; + word.utf5 = (encoded[0] != THASH_ENTER_DIRECT); + for (int i = !word.utf5; i < encoded_len; i++) + word_set_val (&word, wordlen++, encoded[i]); + return word.hash; +#else + uint64_t hash = 0; + int utf5 = (encoded[0] != THASH_ENTER_DIRECT); + for (int i = !utf5; i < encoded_len; i++) + { + uint64_t val = encoded[i]; + + if (wordlen < THASH_MAXLEN) + { + hash = hash | (val << (5*wordlen)); + hash &= (((uint64_t)1<<52)-1); + } + else + { + hash = hash ^ ((hash << 4) + val); + hash &= (((uint64_t)1<<52)-1); + } + wordlen++; + } + hash <<= 1; + if (wordlen >= THASH_MAXLEN) + hash |= ((uint64_t)1<<51); // overflowed + return hash | utf5; +#endif +} + +typedef struct _Interned Interned; + +struct _Interned { + uint64_t hash; + char *string; +}; + +static Interned *interned = NULL; +static int n_interned = 0; +static int s_interned = 0; +static int interned_sorted = 1; + +static int interned_compare (const void *a, const void *b) +{ + const Interned *ia = (Interned*)a; + const Interned *ib = (Interned*)b; + if (ia->hash < ib->hash ) return -1; + else if (ia->hash > ib->hash ) return 1; + return 0; +} + + +uint64_t thash (const char *utf8) +{ + uint64_t hash = _thash (utf8); +#ifdef THASH_NO_INTERNING + return hash; +#endif + if (hash & ((uint64_t)1<<51)) /* overflowed */ + { + int i; + for (i = 0; i < n_interned; i++) + { + Interned *entry = &interned[i]; + if (entry->hash == hash) + return hash; + } + + if (n_interned + 1 >= s_interned) + { + s_interned = (s_interned + 128)*1.5; + //fprintf (stderr, "\r%p %i ", interned, s_interned); + interned = (Interned*)realloc (interned, s_interned * sizeof (Interned)); + } + + { + Interned *entry = &interned[n_interned]; + entry->hash = hash; + entry->string = strdup (utf8); + } + n_interned++; + interned_sorted = 0; + } + return hash; +} +uint64_t ctx_strhash(const char *str, int ignored) { return thash (str);} + +typedef struct ThashUtf5Dec { + int is_utf5; + int offset; + void *write_data; + uint32_t current; + void (*append_unichar) (uint32_t unichar, void *write_data); +} ThashUtf5Dec; + +typedef struct ThashUtf5DecDefaultData { + uint8_t *buf; + int length; +} ThashUtf5DecDefaultData; + +static void thash_decode_utf5_append_unichar_as_utf8 (uint32_t unichar, void *write_data) +{ + ThashUtf5DecDefaultData *data = (ThashUtf5DecDefaultData*)write_data; + unsigned char utf8[8]=""; + utf8[ctx_unichar_to_utf8 (unichar, utf8)]=0; + for (int j = 0; utf8[j]; j++) + data->buf[data->length++]=utf8[j]; + data->buf[data->length]=0; +} + +void thash_decode_utf5 (ThashUtf5Dec *dec, uint8_t in) +{ + if (dec->is_utf5) + { + if (in > 16) + { + if (dec->current) + { + dec->offset = thash_new_offset (dec->current); + dec->append_unichar (dec->current, dec->write_data); + dec->current = 0; + } + } + if (in == THASH_ENTER_DIRECT) + { + if (dec->current) + { + dec->offset = thash_new_offset (dec->current); + dec->append_unichar (dec->current, dec->write_data); + dec->current = 0; + } + dec->is_utf5 = 0; + } + else + { + dec->current = dec->current * 16 + (in % 16); + } + } + else + { + switch (in) + { + case THASH_ENTER_UTF5: dec->is_utf5 = 1; break; + case THASH_SPACE: dec->append_unichar (' ', dec->write_data); break; + case THASH_DEC_OFFSET: dec->offset -= THASH_JUMP_OFFSET; break; + case THASH_INC_OFFSET: dec->offset += THASH_JUMP_OFFSET; break; + default: + dec->append_unichar (dec->offset + in - 14, dec->write_data); + } + } +} + +void thash_decode_utf5_bytes (int is_utf5, + const unsigned char *input, int inlen, + char *output, int *r_outlen) +{ + ThashUtf5DecDefaultData append_data= {(unsigned char*)output, }; + ThashUtf5Dec dec = {is_utf5, + THASH_START_OFFSET, + &append_data, + 0, thash_decode_utf5_append_unichar_as_utf8 + }; + for (int i = 0; i < inlen; i++) + { + thash_decode_utf5 (&dec, input[i]); + } + if (dec.current) + dec.append_unichar (dec.current, dec.write_data); + if (r_outlen)*r_outlen = append_data.length; +} + +const char *thash_decode (uint64_t hash) +{ + if (!interned_sorted && interned) + { + qsort (interned, n_interned, sizeof (Interned), interned_compare); + interned_sorted = 1; + } + if (hash & ((uint64_t)1<<51)) + { + + for (int i = 0; i < n_interned; i++) + { + Interned *entry = &interned[i]; + if (entry->hash == hash) + return entry->string; + } + return "[missing string]"; + } + + static char ret[4096]=""; + uint8_t utf5[40]=""; + uint64_t tmp = hash & (((uint64_t)1<<51)-1); + int len = 0; + int is_utf5 = tmp & 1; + tmp /= 2; + int in_utf5 = is_utf5; + while (tmp > 0) + { + uint64_t remnant = tmp % 32; + uint64_t val = remnant; + + if ( in_utf5 && val == THASH_ENTER_DIRECT) in_utf5 = 0; + else if (!in_utf5 && val == THASH_ENTER_UTF5) in_utf5 = 1; + + utf5[len++] = val; + tmp -= remnant; + tmp /= 32; + } + if (in_utf5 && len && utf5[len-1] > 'G') + { + utf5[len++] = 0;//utf5_alphabet[0]; + } + utf5[len]=0; + int retlen = sizeof (ret); + thash_decode_utf5_bytes (is_utf5, utf5, len, ret, &retlen); + ret[len]=0; + return ret; +} + +#if 0 + +#include <assert.h> +#pragma pack(push,1) +typedef union Word +{ + uint64_t hash; + struct { + unsigned int utf5:1; + unsigned int c0:5; + unsigned int c1:5; + unsigned int c2:5; + unsigned int c3:5; + unsigned int c4:5; + unsigned int c5:5; + unsigned int c6:5; + unsigned int c7:5; + unsigned int c8:5; + unsigned int c9:5; + unsigned int overflowed:1; + }; +} Word; + +static inline void word_set_val (Word *word, int no, int val) +{ +#if 0 + switch(no) + { + case 0: word->c0 = val; break; + case 1: word->c1 = val; break; + case 2: word->c2 = val; break; + case 3: word->c3 = val; break; + case 4: word->c4 = val; break; + case 5: word->c5 = val; break; + case 6: word->c6 = val; break; + case 7: word->c7 = val; break; + case 8: word->c8 = val; break; + case 9: word->c9 = val; break; + default: + // for overflow only works when setting all in sequence + word->hash = word->hash + ((uint64_t)(val) << (5*no+1)); + word->overflowed = 1; + break; + } +#else + word->hash = word->hash | ((uint64_t)(val) << (5*no+1)); + if (no >= 9) + word->hash |= ((uint64_t)1<<51); + word->hash &= (((uint64_t)1<<52)-1); +#endif +} + +static inline int word_get_val (Word *word, int no) +{ + switch(no) + { + case 0: return word->c0;break; + case 1: return word->c1;break; + case 2: return word->c2;break; + case 3: return word->c3;break; + case 4: return word->c4;break; + case 5: return word->c5;break; + case 6: return word->c6;break; + case 7: return word->c7;break; + case 8: return word->c8;break; + case 9: return word->c9;break; + } +} + +static inline int word_get_length (Word *word) +{ + int len = 0; + if (word->c0) len ++; else return len; + if (word->c1) len ++; else return len; + if (word->c2) len ++; else return len; + if (word->c3) len ++; else return len; + if (word->c4) len ++; else return len; + if (word->c5) len ++; else return len; + if (word->c6) len ++; else return len; + if (word->c7) len ++; else return len; + if (word->c8) len ++; else return len; + if (word->c9) len ++; else return len; + return len; +} + + +static Word *word_append_unichar (Word *word, uint32_t unichar) +{ + //word_set_char (word, word_get_length (word), unichar); + // append unichar - possibly advancing. + return word; +} +#endif + +#endif +/* atty - audio interface and driver for terminals + * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +static const char *base64_map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +static void bin2base64_group (const unsigned char *in, int remaining, char *out) +{ + unsigned char digit[4] = {0,0,64,64}; + int i; + digit[0] = in[0] >> 2; + digit[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4); + if (remaining > 1) + { + digit[2] = ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6); + if (remaining > 2) + digit[3] = ((in[2] & 0x3f)); + } + for (i = 0; i < 4; i++) + out[i] = base64_map[digit[i]]; +} + +void +ctx_bin2base64 (const void *bin, + int bin_length, + char *ascii) +{ + /* this allocation is a hack to ensure we always produce the same result, + * regardless of padding data accidentally taken into account. + */ + unsigned char *bin2 = (unsigned char*)calloc (bin_length + 4, 1); + unsigned const char *p = bin2; + int i; + memcpy (bin2, bin, bin_length); + for (i=0; i*3 < bin_length; i++) + { + int remaining = bin_length - i*3; + bin2base64_group (&p[i*3], remaining, &ascii[i*4]); + } + free (bin2); + ascii[i*4]=0; +} + +static unsigned char base64_revmap[255]; +static void base64_revmap_init (void) +{ + static int done = 0; + if (done) + return; + + for (int i = 0; i < 255; i ++) + base64_revmap[i]=255; + for (int i = 0; i < 64; i ++) + base64_revmap[((const unsigned char*)base64_map)[i]]=i; + /* include variants used in URI encodings for decoder, + * even if that is not how we encode + */ + base64_revmap['-']=62; + base64_revmap['_']=63; + base64_revmap['+']=62; + base64_revmap['/']=63; + + done = 1; +} + + +int +ctx_base642bin (const char *ascii, + int *length, + unsigned char *bin) +{ + int i; + int charno = 0; + int outputno = 0; + int carry = 0; + base64_revmap_init (); + for (i = 0; ascii[i]; i++) + { + int bits = base64_revmap[((const unsigned char*)ascii)[i]]; + if (length && outputno > *length) + { + *length = -1; + return -1; + } + if (bits != 255) + { + switch (charno % 4) + { + case 0: + carry = bits; + break; + case 1: + bin[outputno] = (carry << 2) | (bits >> 4); + outputno++; + carry = bits & 15; + break; + case 2: + bin[outputno] = (carry << 4) | (bits >> 2); + outputno++; + carry = bits & 3; + break; + case 3: + bin[outputno] = (carry << 6) | bits; + outputno++; + carry = 0; + break; + } + charno++; + } + } + bin[outputno]=0; + if (length) + *length= outputno; + return outputno; +} +#include <stdio.h> +#include <string.h> + +static int ctx_yenc (const char *src, char *dst, int count) +{ + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = (src[i] + 42) % 256; + switch (o) + { + case 0x00: //null + case 0x20: //space// but better safe + case 0x0A: //lf // than sorry + case 0x0D: //cr + case 0x09: //tab // not really needed + case 0x10: //datalink escape (used by ctx) + case 0x11: //xoff + case 0x13: //xon + case 0x1b: // + case 0xff: // + case 0x3D: //= + dst[out_len++] = '='; + o = (o + 64) % 256; + /* FALLTHROUGH */ + default: + dst[out_len++] = o; + break; + } + } + dst[out_len]=0; + return out_len; +} + +static int ctx_ydec (const char *tmp_src, char *dst, int count) +{ + const char *src = tmp_src; +#if 0 + if (tmp_src == dst) + { + src = malloc (count); + memcpy (src, tmp_src, count); + } +#endif + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = src[i]; + switch (o) + { + case '=': + i++; + o = src[i]; + if (o == 'y') + { + dst[out_len]=0; +#if 0 + if (tmp_src == dst) free (src); +#endif + return out_len; + } + o = (o-42-64) % 256; + dst[out_len++] = o; + break; + case '\n': + case '\e': + case '\r': + case '\0': + break; + default: + o = (o-42) % 256; + dst[out_len++] = o; + break; + } + } + dst[out_len]=0; +#if 0 + if (tmp_src == dst) free (src); +#endif + return out_len; +} + +#if 0 +int main (){ + char *input="this is a testæøåÅØ'''\"!:_asdac\n\r"; + char encoded[256]=""; + char decoded[256]=""; + int in_len = strlen (input); + int out_len; + int dec_len; + + printf ("input: %s\n", input); + + out_len = ctx_yenc (input, encoded, in_len); + printf ("encoded: %s\n", encoded); + + dec_len = ydec (encoded, encoded, out_len); + + printf ("decoded: %s\n", encoded); + + return 0; +} +#endif +#ifndef CTX_DRAWLIST_H +#define CTX_DRAWLIST_H + +static int +ctx_conts_for_entry (CtxEntry *entry); +static void +ctx_iterator_init (CtxIterator *iterator, + CtxDrawlist *drawlist, + int start_pos, + int flags); + +int ctx_iterator_pos (CtxIterator *iterator); + +static void +ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size); +static int +ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry); +static int ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry); +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry); +int +ctx_add_data (Ctx *ctx, void *data, int length); + +int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2]); +int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length); + +static CtxEntry +ctx_void (CtxCode code); +static inline CtxEntry +ctx_f (CtxCode code, float x, float y); +static CtxEntry +ctx_u32 (CtxCode code, uint32_t x, uint32_t y); +#if 0 +static CtxEntry +ctx_s32 (CtxCode code, int32_t x, int32_t y); +#endif + +static inline CtxEntry +ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1); +static CtxEntry +ctx_u8 (CtxCode code, + uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h); + +#define CTX_PROCESS_VOID(cmd) do {\ + CtxEntry command = {cmd};\ + ctx_process (ctx, &command);}while(0) \ + +#define CTX_PROCESS_F(cmd, x, y) do {\ + CtxEntry command = ctx_f(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_F1(cmd, x) do {\ + CtxEntry command = ctx_f(cmd, x, 0);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U32(cmd, x, y) do {\ + CtxEntry command = ctx_u32(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U8(cmd, x) do {\ + CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\ + ctx_process (ctx, &command);}while(0) + + +#if CTX_BITPACK_PACKER +static int +ctx_last_history (CtxDrawlist *drawlist); +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos); + +static void +ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos); +#endif + +static void +ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1); +static void +ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1); +static void +ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len); + +typedef struct /* it has the same structure as CtxEntry, but should be given better names, + now that it is refactored to be a multiple of 4 bytes + */ +CtxSegment { + uint32_t code; + union { + int16_t s16[4]; + uint32_t u32[2]; + } data; +} CtxSegment; + +#endif + +#ifndef __CTX_UTIL_H +#define __CTX_UTIL_H + +inline static float ctx_fast_hypotf (float x, float y) +{ + if (x < 0) { x = -x; } + if (y < 0) { y = -y; } + if (x < y) + { return 0.96f * y + 0.4f * x; } + else + { return 0.96f * x + 0.4f * y; } +} + +static int ctx_str_is_number (const char *str) +{ + int got_digit = 0; + for (int i = 0; str[i]; i++) + { + if (str[i] >= '0' && str[i] <= '9') + { + got_digit ++; + } + else if (str[i] == '.') + { + } + else + return 0; + } + if (got_digit) + return 1; + return 0; +} + +#if CTX_FONTS_FROM_FILE + +typedef struct CtxFileContent +{ + char *path; + unsigned char *contents; + long length; + int free_data; +} CtxFileContent; + +CtxList *registered_contents = NULL; + +void +ctx_register_contents (const char *path, + const unsigned char *contents, + long length, + int free_data) +{ + // if (path[0] != '/') && strchr(path, ':')) + // with this check regular use is faster, but we lose + // generic filesystem overrides.. + for (CtxList *l = registered_contents; l; l = l->next) + { + CtxFileContent *c = (CtxFileContent*)l->data; + if (!strcmp (c->path, path)) + { + if (c->free_data) + { + free (c->contents); + } + c->free_data = free_data; + c->contents = (unsigned char*)contents; + c->length = length; + return; + } + } + CtxFileContent *c = (CtxFileContent*)calloc (sizeof (CtxFileContent), 1); + c->free_data = free_data; + c->contents = (unsigned char*)contents; + c->length = length; + ctx_list_append (®istered_contents, c); +} + +void +_ctx_file_set_contents (const char *path, + const unsigned char *contents, + long length) +{ + FILE *file; + file = fopen (path, "wb"); + if (!file) + { return; } + if (length < 0) length = strlen ((const char*)contents); + fwrite (contents, 1, length, file); + fclose (file); +} + +static int +__ctx_file_get_contents (const char *path, + unsigned char **contents, + long *length) +{ + FILE *file; + long size; + long remaining; + char *buffer; + file = fopen (path, "rb"); + if (!file) + { return -1; } + fseek (file, 0, SEEK_END); + size = remaining = ftell (file); + if (length) + { *length =size; } + rewind (file); + buffer = (char*)malloc (size + 8); + if (!buffer) + { + fclose (file); + return -1; + } + remaining -= fread (buffer, 1, remaining, file); + if (remaining) + { + fclose (file); + free (buffer); + return -1; + } + fclose (file); + *contents = (unsigned char*) buffer; + buffer[size] = 0; + return 0; +} + +#if !__COSMOPOLITAN__ +#include <limits.h> +#endif + + + + +#endif + + +#endif + + + +static inline int +ctx_conts_for_entry (CtxEntry *entry) +{ + switch (entry->code) + { + case CTX_DATA: + return entry->data.u32[1]; + case CTX_LINEAR_GRADIENT: + //case CTX_DEFINE_TEXTURE: + return 1; + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_SOURCE_TRANSFORM: + case CTX_COLOR: + case CTX_ROUND_RECTANGLE: + case CTX_SHADOW_COLOR: + return 2; + case CTX_FILL_RECT: + case CTX_STROKE_RECT: + case CTX_RECTANGLE: + case CTX_VIEW_BOX: + case CTX_REL_QUAD_TO: + case CTX_QUAD_TO: + return 1; + + case CTX_TEXT: + case CTX_LINE_DASH: + case CTX_COLOR_SPACE: + case CTX_STROKE_TEXT: + case CTX_FONT: + case CTX_TEXTURE: + { + int eid_len = entry[1].data.u32[1]; + return eid_len + 1; + } + case CTX_DEFINE_TEXTURE: + { + int eid_len = entry[2].data.u32[1]; + int pix_len = entry[2 + eid_len + 1].data.u32[1]; + return eid_len + pix_len + 2 + 1; + } + default: + return 0; + } +} + +// expanding arc_to to arc can be the job +// of a layer in front of renderer? +// doing: +// rectangle +// arc +// ... etc reduction to beziers +// or even do the reduction to +// polylines directly here... +// making the rasterizer able to +// only do poly-lines? will that be faster? + +/* the iterator - should decode bitpacked data as well - + * making the rasterizers simpler, possibly do unpacking + * all the way to absolute coordinates.. unless mixed + * relative/not are wanted. + */ + + +static void +ctx_iterator_init (CtxIterator *iterator, + CtxDrawlist *drawlist, + int start_pos, + int flags) +{ + iterator->drawlist = drawlist; + iterator->flags = flags; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 0; + iterator->pos = start_pos; + iterator->end_pos = drawlist->count; + iterator->first_run = 1; // -1 is a marker used for first run + ctx_memset (iterator->bitpack_command, 0, sizeof (iterator->bitpack_command) ); +} + +int ctx_iterator_pos (CtxIterator *iterator) +{ + return iterator->pos; +} + +static CtxEntry *_ctx_iterator_next (CtxIterator *iterator) +{ + int ret = iterator->pos; + CtxEntry *entry = &iterator->drawlist->entries[ret]; + if (CTX_UNLIKELY(ret >= iterator->end_pos)) + { return NULL; } + + if (CTX_UNLIKELY(iterator->first_run)) + iterator->first_run = 0; + else + iterator->pos += (ctx_conts_for_entry (entry) + 1); + + if (CTX_UNLIKELY(iterator->pos >= iterator->end_pos)) + { return NULL; } + return &iterator->drawlist->entries[iterator->pos]; +} + +// 6024x4008 +#if CTX_BITPACK +static void +ctx_iterator_expand_s8_args (CtxIterator *iterator, CtxEntry *entry) +{ + int no = 0; + for (int cno = 0; cno < 4; cno++) + for (int d = 0; d < 2; d++, no++) + iterator->bitpack_command[cno].data.f[d] = + entry->data.s8[no] * 1.0f / CTX_SUBDIV; + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = + iterator->bitpack_command[3].code = CTX_CONT; + iterator->bitpack_length = 4; + iterator->bitpack_pos = 0; +} + +static void +ctx_iterator_expand_s16_args (CtxIterator *iterator, CtxEntry *entry) +{ + int no = 0; + for (int cno = 0; cno < 2; cno++) + for (int d = 0; d < 2; d++, no++) + iterator->bitpack_command[cno].data.f[d] = entry->data.s16[no] * 1.0f / + CTX_SUBDIV; + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = CTX_CONT; + iterator->bitpack_length = 2; + iterator->bitpack_pos = 0; +} +#endif + +CtxCommand * +ctx_iterator_next (CtxIterator *iterator) +{ + CtxEntry *ret; +#if CTX_BITPACK + int expand_bitpack = iterator->flags & CTX_ITERATOR_EXPAND_BITPACK; +again: + if (CTX_UNLIKELY(iterator->bitpack_length)) + { + ret = &iterator->bitpack_command[iterator->bitpack_pos]; + iterator->bitpack_pos += (ctx_conts_for_entry (ret) + 1); + if (iterator->bitpack_pos >= iterator->bitpack_length) + { + iterator->bitpack_length = 0; + } + return (CtxCommand *) ret; + } +#endif + ret = _ctx_iterator_next (iterator); +#if CTX_BITPACK + if (CTX_UNLIKELY(ret && expand_bitpack)) + switch ((CtxCode)(ret->code)) + { + case CTX_REL_CURVE_TO_REL_LINE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_CURVE_TO; + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = CTX_CONT; + iterator->bitpack_command[3].code = CTX_REL_LINE_TO; + // 0.0 here is a common optimization - so check for it + if (ret->data.s8[6]== 0 && ret->data.s8[7] == 0) + { iterator->bitpack_length = 3; } + else + iterator->bitpack_length = 4; + goto again; + case CTX_REL_LINE_TO_REL_CURVE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_LINE_TO; + iterator->bitpack_command[1].code = CTX_REL_CURVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_REL_CURVE_TO_REL_MOVE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_CURVE_TO; + iterator->bitpack_command[3].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 4; + goto again; + case CTX_REL_LINE_TO_X4: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = + iterator->bitpack_command[3].code = CTX_REL_LINE_TO; + iterator->bitpack_length = 4; + goto again; + case CTX_REL_QUAD_TO_S16: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_QUAD_TO; + iterator->bitpack_length = 1; + goto again; + case CTX_REL_QUAD_TO_REL_QUAD_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[2].code = CTX_REL_QUAD_TO; + iterator->bitpack_length = 3; + goto again; + case CTX_REL_LINE_TO_X2: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = CTX_REL_LINE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_REL_LINE_TO_REL_MOVE_TO: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_LINE_TO; + iterator->bitpack_command[1].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_MOVE_TO_REL_LINE_TO: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_MOVE_TO; + iterator->bitpack_command[1].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_FILL_MOVE_TO: + iterator->bitpack_command[1] = *ret; + iterator->bitpack_command[0].code = CTX_FILL; + iterator->bitpack_command[1].code = CTX_MOVE_TO; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 2; + goto again; + case CTX_LINEAR_GRADIENT: + case CTX_QUAD_TO: + case CTX_REL_QUAD_TO: + case CTX_TEXTURE: + case CTX_RECTANGLE: + case CTX_VIEW_BOX: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_COLOR: + case CTX_SHADOW_COLOR: + case CTX_RADIAL_GRADIENT: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_SOURCE_TRANSFORM: + case CTX_ROUND_RECTANGLE: + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_FONT: + case CTX_LINE_DASH: + case CTX_FILL: + case CTX_NOP: + case CTX_MOVE_TO: + case CTX_LINE_TO: + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + case CTX_VER_LINE_TO: + case CTX_REL_VER_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_REL_HOR_LINE_TO: + case CTX_ROTATE: + case CTX_FLUSH: + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_MITER_LIMIT: + case CTX_GLOBAL_ALPHA: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_RESET: + case CTX_EXIT: + case CTX_BEGIN_PATH: + case CTX_CLOSE_PATH: + case CTX_SAVE: + case CTX_CLIP: + case CTX_PRESERVE: + case CTX_DEFINE_GLYPH: + case CTX_IDENTITY: + case CTX_FONT_SIZE: + case CTX_START_GROUP: + case CTX_END_GROUP: + case CTX_RESTORE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_STROKE: + case CTX_KERNING_PAIR: + case CTX_SCALE: + case CTX_GLYPH: + case CTX_SET_PIXEL: + case CTX_FILL_RULE: + case CTX_LINE_CAP: + case CTX_LINE_JOIN: + case CTX_NEW_PAGE: + case CTX_SET_KEY: + case CTX_TRANSLATE: + case CTX_DEFINE_TEXTURE: + case CTX_GRADIENT_STOP: + case CTX_DATA: // XXX : would be better if we hide the DATAs + case CTX_CONT: // shouldnt happen + default: + iterator->bitpack_length = 0; + return (CtxCommand *) ret; +#if 0 + default: // XXX remove - and get better warnings + iterator->bitpack_command[0] = ret[0]; + iterator->bitpack_command[1] = ret[1]; + iterator->bitpack_command[2] = ret[2]; + iterator->bitpack_command[3] = ret[3]; + iterator->bitpack_command[4] = ret[4]; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 1; + goto again; +#endif + } +#endif + return (CtxCommand *) ret; +} + +static void ctx_drawlist_compact (CtxDrawlist *drawlist); +static void +ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size) +{ + int flags=drawlist->flags; +#if CTX_DRAWLIST_STATIC + if (flags & CTX_DRAWLIST_EDGE_LIST) + { + static CtxSegment sbuf[CTX_MAX_EDGE_LIST_SIZE]; + drawlist->entries = (CtxEntry*)&sbuf[0]; + drawlist->size = CTX_MAX_EDGE_LIST_SIZE; + } + else if (flags & CTX_DRAWLIST_CURRENT_PATH) + { + static CtxEntry sbuf[CTX_MAX_EDGE_LIST_SIZE]; + drawlist->entries = &sbuf[0]; + drawlist->size = CTX_MAX_EDGE_LIST_SIZE; + } + else + { + static CtxEntry sbuf[CTX_MAX_JOURNAL_SIZE]; + drawlist->entries = &sbuf[0]; + drawlist->size = CTX_MAX_JOURNAL_SIZE; + ctx_drawlist_compact (drawlist); + } +#else + int new_size = desired_size; + int min_size = CTX_MIN_JOURNAL_SIZE; + int max_size = CTX_MAX_JOURNAL_SIZE; + if ((flags & CTX_DRAWLIST_EDGE_LIST)) + { + min_size = CTX_MIN_EDGE_LIST_SIZE; + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + else if (flags & CTX_DRAWLIST_CURRENT_PATH) + { + min_size = CTX_MIN_EDGE_LIST_SIZE; + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + else + { +#if 0 + ctx_drawlist_compact (drawlist); +#endif + } + + if (CTX_UNLIKELY(new_size < drawlist->size)) + { return; } + if (CTX_UNLIKELY(drawlist->size == max_size)) + { return; } + new_size = ctx_maxi (new_size, min_size); + //if (new_size < drawlist->count) + // { new_size = drawlist->count + 4; } + new_size = ctx_mini (new_size, max_size); + if (new_size != drawlist->size) + { + int item_size = sizeof (CtxEntry); + if (flags & CTX_DRAWLIST_EDGE_LIST) item_size = sizeof (CtxSegment); + //fprintf (stderr, "growing drawlist %p %i to %d from %d\n", drawlist, flags, new_size, drawlist->size); + if (drawlist->entries) + { + //printf ("grow %p to %d from %d\n", drawlist, new_size, drawlist->size); + CtxEntry *ne = (CtxEntry *) malloc (item_size * new_size); + memcpy (ne, drawlist->entries, drawlist->size * item_size ); + free (drawlist->entries); + drawlist->entries = ne; + //drawlist->entries = (CtxEntry*)malloc (drawlist->entries, item_size * new_size); + } + else + { + //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size); + drawlist->entries = (CtxEntry *) malloc (item_size * new_size); + } + drawlist->size = new_size; + } + //fprintf (stderr, "drawlist %p is %d\n", drawlist, drawlist->size); +#endif +} + +static inline int +ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry) +{ + int max_size = CTX_MAX_JOURNAL_SIZE; + int ret = drawlist->count; + int flags = drawlist->flags; + if (CTX_LIKELY((flags & CTX_DRAWLIST_EDGE_LIST || + flags & CTX_DRAWLIST_CURRENT_PATH))) + { + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + if (CTX_UNLIKELY(flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES)) + { + return ret; + } + if (CTX_UNLIKELY(ret + 64 >= drawlist->size - 40)) + { + int new_ = CTX_MAX (drawlist->size * 2, ret + 1024); + ctx_drawlist_resize (drawlist, new_); + } + + if (CTX_UNLIKELY(drawlist->count >= max_size - 20)) + { + return 0; + } + if ((flags & CTX_DRAWLIST_EDGE_LIST)) + ((CtxSegment*)(drawlist->entries))[drawlist->count] = *(CtxSegment*)entry; + else + drawlist->entries[drawlist->count] = *entry; + ret = drawlist->count; + drawlist->count++; + return ret; +} + +int +ctx_add_single (Ctx *ctx, void *entry) +{ + return ctx_drawlist_add_single (&ctx->drawlist, (CtxEntry *) entry); +} + +static inline int +ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int ret = 0; + for (int i = 0; i < length; i ++) + { + ret = ctx_drawlist_add_single (drawlist, &entry[i]); + } + return ret; +} + +#if 0 +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int tmp_pos = ctx_drawlist_add_entry (drawlist, entry); + for (int i = 0; i < length; i++) + { + for (int j = pos + i + 1; j < tmp_pos; j++) + drawlist->entries[j] = entry[j-1]; + drawlist->entries[pos + i] = entry[i]; + } + return pos; +} +#endif +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int tmp_pos = ctx_drawlist_add_entry (drawlist, entry); +#if 1 + for (int i = 0; i < length; i++) + { + for (int j = tmp_pos; j > pos + i; j--) + drawlist->entries[j] = drawlist->entries[j-1]; + drawlist->entries[pos + i] = entry[i]; + } + return pos; +#endif + return tmp_pos; +} + +int ctx_append_drawlist (Ctx *ctx, void *data, int length) +{ + CtxEntry *entries = (CtxEntry *) data; + if (length % sizeof (CtxEntry) ) + { + ctx_log("drawlist not multiple of 9\n"); + return -1; + } + for (unsigned int i = 0; i < length / sizeof (CtxEntry); i++) + { + ctx_drawlist_add_single (&ctx->drawlist, &entries[i]); + } + return 0; +} + +int ctx_set_drawlist (Ctx *ctx, void *data, int length) +{ + CtxDrawlist *drawlist = &ctx->drawlist; + ctx->drawlist.count = 0; + if (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) + { + return -1; + } + if (CTX_UNLIKELY(length % 9)) return -1; + ctx_drawlist_resize (drawlist, length/9); + memcpy (drawlist->entries, data, length); + drawlist->count = length / 9; + return length; +} + +int ctx_get_drawlist_count (Ctx *ctx) +{ + return ctx->drawlist.count; +} + +const CtxEntry *ctx_get_drawlist (Ctx *ctx) +{ + return ctx->drawlist.entries; +} + +int +ctx_add_data (Ctx *ctx, void *data, int length) +{ + if (CTX_UNLIKELY(length % sizeof (CtxEntry) )) + { + //ctx_log("err\n"); + return -1; + } + /* some more input verification might be in order.. like + * verify that it is well-formed up to length? + * + * also - it would be very useful to stop processing + * upon flush - and do drawlist resizing. + */ + return ctx_drawlist_add_entry (&ctx->drawlist, (CtxEntry *) data); +} + +int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2]) +{ + CtxEntry entry = {code, {{0},}}; + entry.data.u32[0] = u32[0]; + entry.data.u32[1] = u32[1]; + return ctx_drawlist_add_single (drawlist, &entry); +} + +int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length) +{ + CtxEntry entry = {CTX_DATA, {{0},}}; + entry.data.u32[0] = 0; + entry.data.u32[1] = 0; + int ret = ctx_drawlist_add_single (drawlist, &entry); + if (CTX_UNLIKELY(!data)) { return -1; } + int length_in_blocks; + if (length <= 0) { length = strlen ( (char *) data) + 1; } + length_in_blocks = length / sizeof (CtxEntry); + length_in_blocks += (length % sizeof (CtxEntry) ) ?1:0; + if (drawlist->count + length_in_blocks + 4 > drawlist->size) + { ctx_drawlist_resize (drawlist, drawlist->count * 1.2 + length_in_blocks + 32); } + if (CTX_UNLIKELY(drawlist->count >= drawlist->size)) + { return -1; } + drawlist->count += length_in_blocks; + drawlist->entries[ret].data.u32[0] = length; + drawlist->entries[ret].data.u32[1] = length_in_blocks; + memcpy (&drawlist->entries[ret+1], data, length); + { + //int reverse = ctx_drawlist_add (drawlist, CTX_DATA_REV); + CtxEntry entry = {CTX_DATA_REV, {{0},}}; + entry.data.u32[0] = length; + entry.data.u32[1] = length_in_blocks; + ctx_drawlist_add_single (drawlist, &entry); + /* this reverse marker exist to enable more efficient + front to back traversal, can be ignored in other + direction, is this needed after string setters as well? + */ + } + return ret; +} + +static inline CtxEntry +ctx_void (CtxCode code) +{ + CtxEntry command; + command.code = code; + return command; +} + +static inline CtxEntry +ctx_f (CtxCode code, float x, float y) +{ + CtxEntry command; + command.code = code; + command.data.f[0] = x; + command.data.f[1] = y; + return command; +} + +static CtxEntry +ctx_u32 (CtxCode code, uint32_t x, uint32_t y) +{ + CtxEntry command = ctx_void (code); + command.data.u32[0] = x; + command.data.u32[1] = y; + return command; +} + +#if 0 +static CtxEntry +ctx_s32 (CtxCode code, int32_t x, int32_t y) +{ + CtxEntry command = ctx_void (code); + command.data.s32[0] = x; + command.data.s32[1] = y; + return command; +} +#endif + +static inline CtxEntry +ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1) +{ + CtxEntry command; + command.code = code; + command.data.s16[0] = x0; + command.data.s16[1] = y0; + command.data.s16[2] = x1; + command.data.s16[3] = y1; + return command; +} + +static inline CtxSegment +ctx_segment_s16 (CtxCode code, int x0, int y0, int x1, int y1) +{ + CtxSegment command; + command.code = code; + command.data.s16[0] = x0; + command.data.s16[1] = y0; + command.data.s16[2] = x1; + command.data.s16[3] = y1; + return command; +} + +static CtxEntry +ctx_u8 (CtxCode code, + uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h) +{ + CtxEntry command; + command.code = code; + command.data.u8[0] = a; + command.data.u8[1] = b; + command.data.u8[2] = c; + command.data.u8[3] = d; + command.data.u8[4] = e; + command.data.u8[5] = f; + command.data.u8[6] = g; + command.data.u8[7] = h; + return command; +} + +#define CTX_PROCESS_VOID(cmd) do {\ + CtxEntry command = {cmd};\ + ctx_process (ctx, &command);}while(0) \ + +#define CTX_PROCESS_F(cmd, x, y) do {\ + CtxEntry command = ctx_f(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_F1(cmd, x) do {\ + CtxEntry command = ctx_f(cmd, x, 0);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U32(cmd, x, y) do {\ + CtxEntry command = ctx_u32(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U8(cmd, x) do {\ + CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\ + ctx_process (ctx, &command);}while(0) + + +static void +ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len) +{ + CtxEntry commands[1 + 2 + (len+1+1)/9]; + ctx_memset (commands, 0, sizeof (commands) ); + commands[0] = ctx_u32 (code, arg0, arg1); + commands[1].code = CTX_DATA; + commands[1].data.u32[0] = len; + commands[1].data.u32[1] = (len+1+1)/9 + 1; + memcpy( (char *) &commands[2].data.u8[0], string, len); + ( (char *) (&commands[2].data.u8[0]) ) [len]=0; + ctx_process (ctx, commands); +} + +static void +ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1) +{ + ctx_process_cmd_str_with_len (ctx, code, string, arg0, arg1, strlen (string)); +} + +static void +ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1) +{ + uint32_t iarg0; + uint32_t iarg1; + memcpy (&iarg0, &arg0, sizeof (iarg0)); + memcpy (&iarg1, &arg1, sizeof (iarg1)); + ctx_process_cmd_str_with_len (ctx, code, string, iarg0, iarg1, strlen (string)); +} + +#if CTX_BITPACK_PACKER +static int +ctx_last_history (CtxDrawlist *drawlist) +{ + int last_history = 0; + int i = 0; + while (i < drawlist->count) + { + CtxEntry *entry = &drawlist->entries[i]; + i += (ctx_conts_for_entry (entry) + 1); + } + return last_history; +} +#endif + +#if CTX_BITPACK_PACKER + +static float +find_max_dev (CtxEntry *entry, int nentrys) +{ + float max_dev = 0.0; + for (int c = 0; c < nentrys; c++) + { + for (int d = 0; d < 2; d++) + { + if (entry[c].data.f[d] > max_dev) + { max_dev = entry[c].data.f[d]; } + if (entry[c].data.f[d] < -max_dev) + { max_dev = -entry[c].data.f[d]; } + } + } + return max_dev; +} + +static void +pack_s8_args (CtxEntry *entry, int npairs) +{ + for (int c = 0; c < npairs; c++) + for (int d = 0; d < 2; d++) + { entry[0].data.s8[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; } +} + +static void +pack_s16_args (CtxEntry *entry, int npairs) +{ + for (int c = 0; c < npairs; c++) + for (int d = 0; d < 2; d++) + { entry[0].data.s16[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; } +} +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos) +{ + CtxIterator iterator; + if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0) + { return; } + ctx_iterator_init (&iterator, drawlist, start_pos, CTX_ITERATOR_FLAT); + iterator.end_pos = drawlist->count - 5; + CtxCommand *command = NULL; + while ( (command = ctx_iterator_next (&iterator) ) ) + { + CtxEntry *entry = &command->entry; + /* things smaller than this have probably been scaled down + beyond recognition, bailing for both better packing and less rasterization work + */ + if (command[0].code == CTX_REL_CURVE_TO) + { + float max_dev = find_max_dev (entry, 3); + if (max_dev < 1.0) + { + entry[0].code = CTX_REL_LINE_TO; + entry[0].data.f[0] = entry[2].data.f[0]; + entry[0].data.f[1] = entry[2].data.f[1]; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } + } + } +} +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos) +{ +#if CTX_BITPACK + int i = 0; + if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0) + { return; } + ctx_drawlist_remove_tiny_curves (drawlist, drawlist->bitpack_pos); + i = drawlist->bitpack_pos; + if (start_pos > i) + { i = start_pos; } + while (i < drawlist->count - 4) /* the -4 is to avoid looking past + initialized data we're not ready + to bitpack yet*/ + { + CtxEntry *entry = &drawlist->entries[i]; + if (entry[0].code == CTX_SET_RGBA_U8 && + entry[1].code == CTX_MOVE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO && + entry[4].code == CTX_REL_LINE_TO && + entry[5].code == CTX_REL_LINE_TO && + entry[6].code == CTX_FILL && + ctx_fabsf (entry[2].data.f[0] - 1.0f) < 0.02f && + ctx_fabsf (entry[3].data.f[1] - 1.0f) < 0.02f) + { + entry[0].code = CTX_SET_PIXEL; + entry[0].data.u16[2] = entry[1].data.f[0]; + entry[0].data.u16[3] = entry[1].data.f[1]; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + entry[4].code = CTX_NOP; + entry[5].code = CTX_NOP; + entry[6].code = CTX_NOP; + } +#if 1 + else if (entry[0].code == CTX_REL_LINE_TO) + { + if (entry[1].code == CTX_REL_LINE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_X4; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_CURVE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_REL_CURVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_LINE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_X4; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_MOVE_TO) + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 31000 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_LINE_TO_REL_MOVE_TO; + entry[1].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 31000 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_LINE_TO_X2; + entry[1].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_REL_CURVE_TO) + { + if (entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[3].code == CTX_REL_MOVE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_CURVE_TO_REL_MOVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else + { + float max_dev = find_max_dev (entry, 3); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 3); + ctx_arg_s8 (6) = + ctx_arg_s8 (7) = 0; + entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_REL_QUAD_TO) + { + if (entry[2].code == CTX_REL_QUAD_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_QUAD_TO_REL_QUAD_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 3100 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_QUAD_TO_S16; + entry[1].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_FILL && + entry[1].code == CTX_MOVE_TO) + { + entry[0] = entry[1]; + entry[0].code = CTX_FILL_MOVE_TO; + entry[1].code = CTX_NOP; + } +#endif +#if 1 + else if (entry[0].code == CTX_MOVE_TO && + entry[1].code == CTX_MOVE_TO && + entry[2].code == CTX_MOVE_TO) + { + entry[0] = entry[2]; + entry[0].code = CTX_MOVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } +#endif +#if 1 + else if ( (entry[0].code == CTX_MOVE_TO && + entry[1].code == CTX_MOVE_TO) || + (entry[0].code == CTX_REL_MOVE_TO && + entry[1].code == CTX_MOVE_TO) ) + { + entry[0] = entry[1]; + entry[0].code = CTX_MOVE_TO; + entry[1].code = CTX_NOP; + } +#endif + i += (ctx_conts_for_entry (entry) + 1); + } + int source = drawlist->bitpack_pos; + int target = drawlist->bitpack_pos; + int removed = 0; + /* remove nops that have been inserted as part of shortenings + */ + while (source < drawlist->count) + { + CtxEntry *sentry = &drawlist->entries[source]; + CtxEntry *tentry = &drawlist->entries[target]; + while (sentry->code == CTX_NOP && source < drawlist->count) + { + source++; + sentry = &drawlist->entries[source]; + removed++; + } + if (sentry != tentry) + { *tentry = *sentry; } + source ++; + target ++; + } + drawlist->count -= removed; + drawlist->bitpack_pos = drawlist->count; +#endif +} + +#endif + +static void +ctx_drawlist_compact (CtxDrawlist *drawlist) +{ +#if CTX_BITPACK_PACKER + int last_history; + last_history = ctx_last_history (drawlist); +#else + if (drawlist) {}; +#endif +#if CTX_BITPACK_PACKER + ctx_drawlist_bitpack (drawlist, last_history); +#endif +} + +uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry) +{ + return &entry[2 + 1 + 1 + ctx_conts_for_entry (&entry[2])].data.u8[0]; +} + +#ifndef __CTX_TRANSFORM +#define __CTX_TRANSFORM + +static inline void +_ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y) +{ + float x_in = *x; + float y_in = *y; + *x = ( (x_in * m->m[0][0]) + (y_in * m->m[1][0]) + m->m[2][0]); + *y = ( (y_in * m->m[1][1]) + (x_in * m->m[0][1]) + m->m[2][1]); +} + +void +ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y) +{ + _ctx_matrix_apply_transform (m, x, y); +} + +static inline void +_ctx_user_to_device (CtxState *state, float *x, float *y) +{ + _ctx_matrix_apply_transform (&state->gstate.transform, x, y); +} + +static void +_ctx_user_to_device_distance (CtxState *state, float *x, float *y) +{ + const CtxMatrix *m = &state->gstate.transform; + _ctx_matrix_apply_transform (m, x, y); + *x -= m->m[2][0]; + *y -= m->m[2][1]; +} + +void ctx_user_to_device (Ctx *ctx, float *x, float *y) +{ + _ctx_user_to_device (&ctx->state, x, y); +} +void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y) +{ + _ctx_user_to_device_distance (&ctx->state, x, y); +} + +static void +ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f) +{ + matrix->m[0][0] = a; + matrix->m[0][1] = b; + matrix->m[1][0] = c; + matrix->m[1][1] = d; + matrix->m[2][0] = e; + matrix->m[2][1] = f; +} + +static inline void +_ctx_matrix_identity (CtxMatrix *matrix) +{ + matrix->m[0][0] = 1.0f; + matrix->m[0][1] = 0.0f; + matrix->m[1][0] = 0.0f; + matrix->m[1][1] = 1.0f; + matrix->m[2][0] = 0.0f; + matrix->m[2][1] = 0.0f; +} + +void +ctx_matrix_identity (CtxMatrix *matrix) +{ + _ctx_matrix_identity (matrix); +} + +static void +_ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s) +{ + CtxMatrix r; + r.m[0][0] = t->m[0][0] * s->m[0][0] + t->m[0][1] * s->m[1][0]; + r.m[0][1] = t->m[0][0] * s->m[0][1] + t->m[0][1] * s->m[1][1]; + r.m[1][0] = t->m[1][0] * s->m[0][0] + t->m[1][1] * s->m[1][0]; + r.m[1][1] = t->m[1][0] * s->m[0][1] + t->m[1][1] * s->m[1][1]; + r.m[2][0] = t->m[2][0] * s->m[0][0] + t->m[2][1] * s->m[1][0] + s->m[2][0]; + r.m[2][1] = t->m[2][0] * s->m[0][1] + t->m[2][1] * s->m[1][1] + s->m[2][1]; + *result = r; +} + +void +ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s) +{ + _ctx_matrix_multiply (result, t, s); +} + +void +ctx_matrix_translate (CtxMatrix *matrix, float x, float y) +{ + CtxMatrix transform; + transform.m[0][0] = 1.0f; + transform.m[0][1] = 0.0f; + transform.m[1][0] = 0.0f; + transform.m[1][1] = 1.0f; + transform.m[2][0] = x; + transform.m[2][1] = y; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +void +ctx_matrix_scale (CtxMatrix *matrix, float x, float y) +{ + CtxMatrix transform; + transform.m[0][0] = x; + transform.m[0][1] = 0.0f; + transform.m[1][0] = 0.0f; + transform.m[1][1] = y; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +void +ctx_matrix_rotate (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_sin = ctx_sinf (angle); + float val_cos = ctx_cosf (angle); + transform.m[0][0] = val_cos; + transform.m[0][1] = val_sin; + transform.m[1][0] = -val_sin; + transform.m[1][1] = val_cos; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +#if 0 +static void +ctx_matrix_skew_x (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_tan = ctx_tanf (angle); + transform.m[0][0] = 1.0f; + transform.m[0][1] = 0.0f; + transform.m[1][0] = val_tan; + transform.m[1][1] = 1.0f; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +static void +ctx_matrix_skew_y (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_tan = ctx_tanf (angle); + transform.m[0][0] = 1.0f; + transform.m[0][1] = val_tan; + transform.m[1][0] = 0.0f; + transform.m[1][1] = 1.0f; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} +#endif + + +void +ctx_identity (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_IDENTITY); +} + + + +void +ctx_apply_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f) // htran, vtran +{ + CtxEntry command[3]= + { + ctx_f (CTX_APPLY_TRANSFORM, a, b), + ctx_f (CTX_CONT, c, d), + ctx_f (CTX_CONT, e, f) + }; + ctx_process (ctx, command); +} + +void +ctx_get_transform (Ctx *ctx, float *a, float *b, + float *c, float *d, + float *e, float *f) +{ + if (a) { *a = ctx->state.gstate.transform.m[0][0]; } + if (b) { *b = ctx->state.gstate.transform.m[0][1]; } + if (c) { *c = ctx->state.gstate.transform.m[1][0]; } + if (d) { *d = ctx->state.gstate.transform.m[1][1]; } + if (e) { *e = ctx->state.gstate.transform.m[2][0]; } + if (f) { *f = ctx->state.gstate.transform.m[2][1]; } +} + +void +ctx_source_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f) // htran, vtran +{ + CtxEntry command[3]= + { + ctx_f (CTX_SOURCE_TRANSFORM, a, b), + ctx_f (CTX_CONT, c, d), + ctx_f (CTX_CONT, e, f) + }; + ctx_process (ctx, command); +} + +void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + ctx_apply_transform (ctx, + matrix->m[0][0], matrix->m[0][1], + matrix->m[1][0], matrix->m[1][1], + matrix->m[2][0], matrix->m[2][1]); +} + +void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + *matrix = ctx->state.gstate.transform; +} + +void ctx_set_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + ctx_identity (ctx); + ctx_apply_matrix (ctx, matrix); +} + +void ctx_rotate (Ctx *ctx, float x) +{ + if (x == 0.0f) + return; + CTX_PROCESS_F1 (CTX_ROTATE, x); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void ctx_scale (Ctx *ctx, float x, float y) +{ + if (x == 1.0f && y == 1.0f) + return; + CTX_PROCESS_F (CTX_SCALE, x, y); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void ctx_translate (Ctx *ctx, float x, float y) +{ + if (x == 0.0f && y == 0.0f) + return; + CTX_PROCESS_F (CTX_TRANSLATE, x, y); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void +ctx_matrix_invert (CtxMatrix *m) +{ + CtxMatrix t = *m; + float invdet, det = m->m[0][0] * m->m[1][1] - + m->m[1][0] * m->m[0][1]; + if (det > -0.0000001f && det < 0.0000001f) + { + m->m[0][0] = m->m[0][1] = + m->m[1][0] = m->m[1][1] = + m->m[2][0] = m->m[2][1] = 0.0; + return; + } + invdet = 1.0f / det; + m->m[0][0] = t.m[1][1] * invdet; + m->m[1][0] = -t.m[1][0] * invdet; + m->m[2][0] = (t.m[1][0] * t.m[2][1] - t.m[1][1] * t.m[2][0]) * invdet; + m->m[0][1] = -t.m[0][1] * invdet; + m->m[1][1] = t.m[0][0] * invdet; + m->m[2][1] = (t.m[0][1] * t.m[2][0] - t.m[0][0] * t.m[2][1]) * invdet ; +} + + + +#endif +#ifndef __CTX_COLOR +#define __CTX_COLOR + +int ctx_color_model_get_components (CtxColorModel model) +{ + switch (model) + { + case CTX_GRAY: + return 1; + case CTX_GRAYA: + case CTX_GRAYA_A: + return 1; + case CTX_RGB: + case CTX_LAB: + case CTX_LCH: + case CTX_DRGB: + return 3; + case CTX_CMYK: + case CTX_DCMYK: + case CTX_LABA: + case CTX_LCHA: + case CTX_RGBA: + case CTX_DRGBA: + case CTX_RGBA_A: + case CTX_RGBA_A_DEVICE: + return 4; + case CTX_DCMYKA: + case CTX_CMYKA: + case CTX_CMYKA_A: + case CTX_DCMYKA_A: + return 5; + } + return 0; +} + +#if 0 +inline static float ctx_u8_to_float (uint8_t val_u8) +{ + float val_f = val_u8 / 255.0; + return val_f; +} +#else +float ctx_u8_float[256]; +#endif + +CtxColor *ctx_color_new () +{ + CtxColor *color = (CtxColor*)ctx_calloc (sizeof (CtxColor), 1); + return color; +} + +int ctx_color_is_transparent (CtxColor *color) +{ + return color->alpha <= 0.001f; +} + + +void ctx_color_free (CtxColor *color) +{ + free (color); +} + +static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + color->original = color->valid = CTX_VALID_RGBA_U8; + color->rgba[0] = r; + color->rgba[1] = g; + color->rgba[2] = b; + color->rgba[3] = a; +#if CTX_ENABLE_CM + color->space = state->gstate.device_space; +#endif +} + +#if 0 +static void ctx_color_set_RGBA8_ (CtxColor *color, const uint8_t *in) +{ + ctx_color_set_RGBA8 (color, in[0], in[1], in[2], in[3]); +} +#endif + +static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha) +{ + color->original = color->valid = CTX_VALID_GRAYA; + color->l = gray; + color->alpha = alpha; +} +#if 0 +static void ctx_color_set_graya_ (CtxColor *color, const float *in) +{ + return ctx_color_set_graya (color, in[0], in[1]); +} +#endif + +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a) +{ +#if CTX_ENABLE_CM + color->original = color->valid = CTX_VALID_RGBA; + color->red = r; + color->green = g; + color->blue = b; + color->space = state->gstate.rgb_space; +#else + color->original = color->valid = CTX_VALID_RGBA_DEVICE; + color->device_red = r; + color->device_green = g; + color->device_blue = b; +#endif + color->alpha = a; +} + +static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a) +{ +#if CTX_ENABLE_CM + color->original = color->valid = CTX_VALID_RGBA_DEVICE; + color->device_red = r; + color->device_green = g; + color->device_blue = b; + color->alpha = a; + color->space = state->gstate.device_space; +#else + ctx_color_set_rgba (state, color, r, g, b, a); +#endif +} + +#if 0 +static void ctx_color_set_rgba_ (CtxState *state, CtxColor *color, const float *in) +{ + ctx_color_set_rgba (color, in[0], in[1], in[2], in[3]); +} +#endif + +/* the baseline conversions we have whether CMYK support is enabled or not, + * providing an effort at right rendering + */ +static void ctx_cmyk_to_rgb (float c, float m, float y, float k, float *r, float *g, float *b) +{ + *r = (1.0f-c) * (1.0f-k); + *g = (1.0f-m) * (1.0f-k); + *b = (1.0f-y) * (1.0f-k); +} + +void ctx_rgb_to_cmyk (float r, float g, float b, + float *c_out, float *m_out, float *y_out, float *k_out) +{ + float c = 1.0f - r; + float m = 1.0f - g; + float y = 1.0f - b; + float k = ctx_minf (c, ctx_minf (y, m) ); + if (k < 1.0f) + { + c = (c - k) / (1.0f - k); + m = (m - k) / (1.0f - k); + y = (y - k) / (1.0f - k); + } + else + { + c = m = y = 0.0f; + } + *c_out = c; + *m_out = m; + *y_out = y; + *k_out = k; +} + +#if CTX_ENABLE_CMYK +static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a) +{ + color->original = color->valid = CTX_VALID_CMYKA; + color->cyan = c; + color->magenta = m; + color->yellow = y; + color->key = k; + color->alpha = a; +#if CTX_ENABLE_CM + color->space = state->gstate.cmyk_space; +#endif +} + +static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a) +{ + color->original = color->valid = CTX_VALID_DCMYKA; + color->device_cyan = c; + color->device_magenta = m; + color->device_yellow = y; + color->device_key = k; + color->alpha = a; +#if CTX_ENABLE_CM + color->space = state->gstate.device_space; +#endif +} + +#endif + +#if CTX_ENABLE_CM + +static void ctx_rgb_user_to_device (CtxState *state, float rin, float gin, float bin, + float *rout, float *gout, float *bout) +{ +#if CTX_BABL +#if 0 + fprintf (stderr, "-[%p %p\n", + state->gstate.fish_rgbaf_user_to_device, + state->gstate.fish_rgbaf_device_to_user); +#endif + if (state->gstate.fish_rgbaf_user_to_device) + { + float rgbaf[4]={rin,gin,bin,1.0}; + float rgbafo[4]; + babl_process (state->gstate.fish_rgbaf_user_to_device, + rgbaf, rgbafo, 1); + + *rout = rgbafo[0]; + *gout = rgbafo[1]; + *bout = rgbafo[2]; + return; + } +#endif + *rout = rin; + *gout = gin; + *bout = bin; +} + +static void ctx_rgb_device_to_user (CtxState *state, float rin, float gin, float bin, + float *rout, float *gout, float *bout) +{ +#if CTX_BABL +#if 0 + fprintf (stderr, "=[%p %p\n", + state->gstate.fish_rgbaf_user_to_device, + state->gstate.fish_rgbaf_device_to_user); +#endif + if (state->gstate.fish_rgbaf_device_to_user) + { + float rgbaf[4]={rin,gin,bin,1.0}; + float rgbafo[4]; + babl_process (state->gstate.fish_rgbaf_device_to_user, + rgbaf, rgbafo, 1); + + *rout = rgbafo[0]; + *gout = rgbafo[1]; + *bout = rgbafo[2]; + return; + } +#endif + *rout = rin; + *gout = gin; + *bout = bin; +} +#endif + +static void ctx_color_get_drgba (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_RGBA_DEVICE) ) + { +#if CTX_ENABLE_CM + if (color->valid & CTX_VALID_RGBA) + { + ctx_rgb_user_to_device (state, color->red, color->green, color->blue, + & (color->device_red), & (color->device_green), & (color->device_blue) ); + } + else +#endif + if (color->valid & CTX_VALID_RGBA_U8) + { + float red = ctx_u8_to_float (color->rgba[0]); + float green = ctx_u8_to_float (color->rgba[1]); + float blue = ctx_u8_to_float (color->rgba[2]); +#if CTX_ENABLE_CM + ctx_rgb_user_to_device (state, red, green, blue, + & (color->device_red), & (color->device_green), & (color->device_blue) ); +#else + color->device_red = red; + color->device_green = green; + color->device_blue = blue; +#endif + color->alpha = ctx_u8_to_float (color->rgba[3]); + } +#if CTX_ENABLE_CMYK + else if (color->valid & CTX_VALID_CMYKA) + { + ctx_cmyk_to_rgb (color->cyan, color->magenta, color->yellow, color->key, + &color->device_red, + &color->device_green, + &color->device_blue); + } +#endif + else if (color->valid & CTX_VALID_GRAYA) + { + color->device_red = + color->device_green = + color->device_blue = color->l; + } + color->valid |= CTX_VALID_RGBA_DEVICE; + } + out[0] = color->device_red; + out[1] = color->device_green; + out[2] = color->device_blue; + out[3] = color->alpha; +} + + +static inline void +_ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out) +{ +#if CTX_ENABLE_CM + if (! (color->valid & CTX_VALID_RGBA) ) + { + ctx_color_get_drgba (state, color, out); + if (color->valid & CTX_VALID_RGBA_DEVICE) + { + ctx_rgb_device_to_user (state, color->device_red, color->device_green, color->device_blue, + & (color->red), & (color->green), & (color->blue) ); + } + color->valid |= CTX_VALID_RGBA; + } + out[0] = color->red; + out[1] = color->green; + out[2] = color->blue; + out[3] = color->alpha; +#else + ctx_color_get_drgba (state, color, out); +#endif +} + +void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out) +{ + _ctx_color_get_rgba (state, color, out); +} + + + +float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb) +{ + // XXX todo replace with correct according to primaries + return CTX_CSS_RGB_TO_LUMINANCE(rgb); +} +uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb) +{ + // XXX todo replace with correct according to primaries + return CTX_CSS_RGB_TO_LUMINANCE(rgb); +} + +void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_GRAYA) ) + { + float rgba[4]; + ctx_color_get_drgba (state, color, rgba); + color->l = ctx_float_color_rgb_to_gray (state, rgba); + color->valid |= CTX_VALID_GRAYA; + } + out[0] = color->l; + out[1] = color->alpha; +} + +#if CTX_ENABLE_CMYK +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_CMYKA) ) + { + if (color->valid & CTX_VALID_GRAYA) + { + color->cyan = color->magenta = color->yellow = 0.0; + color->key = color->l; + } + else + { + float rgba[4]; + ctx_color_get_rgba (state, color, rgba); + ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2], + &color->cyan, &color->magenta, &color->yellow, &color->key); + color->alpha = rgba[3]; + } + color->valid |= CTX_VALID_CMYKA; + } + out[0] = color->cyan; + out[1] = color->magenta; + out[2] = color->yellow; + out[3] = color->key; + out[4] = color->alpha; +} + +#if 0 +static void ctx_color_get_cmyka_u8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_CMYKA_U8) ) + { + float cmyka[5]; + ctx_color_get_cmyka (color, cmyka); + for (int i = 0; i < 5; i ++) + { color->cmyka[i] = ctx_float_to_u8 (cmyka[i]); } + color->valid |= CTX_VALID_CMYKA_U8; + } + out[0] = color->cmyka[0]; + out[1] = color->cmyka[1]; + out[2] = color->cmyka[2]; + out[3] = color->cmyka[3]; +} +#endif +#endif + +static inline void +_ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_RGBA_U8) ) + { + float rgba[4]; + ctx_color_get_drgba (state, color, rgba); + for (int i = 0; i < 4; i ++) + { color->rgba[i] = ctx_float_to_u8 (rgba[i]); } + color->valid |= CTX_VALID_RGBA_U8; + } + out[0] = color->rgba[0]; + out[1] = color->rgba[1]; + out[2] = color->rgba[2]; + out[3] = color->rgba[3]; +} + +void +ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + _ctx_color_get_rgba8 (state, color, out); +} + +void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_GRAYA_U8) ) + { + float graya[2]; + ctx_color_get_graya (state, color, graya); + color->l_u8 = ctx_float_to_u8 (graya[0]); + color->rgba[3] = ctx_float_to_u8 (graya[1]); + color->valid |= CTX_VALID_GRAYA_U8; + } + out[0] = color->l_u8; + out[1] = color->rgba[3]; +} + +#if 0 +void +ctx_get_rgba (Ctx *ctx, float *rgba) +{ + ctx_color_get_rgba (& (ctx->state), &ctx->state.gstate.source.color, rgba); +} + +void +ctx_get_drgba (Ctx *ctx, float *rgba) +{ + ctx_color_get_drgba (& (ctx->state), &ctx->state.gstate.source.color, rgba); +} +#endif + +int ctx_in_fill (Ctx *ctx, float x, float y) +{ + float x1, y1, x2, y2; + ctx_path_extents (ctx, &x1, &y1, &x2, &y2); + + if (x1 <= x && x <= x2 && // XXX - just bounding box for now + y1 <= y && y <= y2) // + return 1; + return 0; +} + + +#if CTX_ENABLE_CMYK +#if 0 +void +ctx_get_cmyka (Ctx *ctx, float *cmyka) +{ + ctx_color_get_cmyka (& (ctx->state), &ctx->state.gstate.source.color, cmyka); +} +#endif +#endif +#if 0 +void +ctx_get_graya (Ctx *ctx, float *ya) +{ + ctx_color_get_graya (& (ctx->state), &ctx->state.gstate.source.color, ya); +} +#endif + +void ctx_stroke_source (Ctx *ctx) +{ + CtxEntry set_stroke = ctx_void (CTX_STROKE_SOURCE); + ctx_process (ctx, &set_stroke); +} + +void ctx_color_raw (Ctx *ctx, CtxColorModel model, float *components, int stroke) +{ +#if 0 + CtxSource *source = stroke? + &ctx->state.gstate.source_stroke: + &ctx->state.gstate.source_fill; + + if (model == CTX_RGB || model == CTX_RGBA) + { + float rgba[4]; + // XXX it should be possible to disable this, to get a more accurate record + // when it is intentional + float a = 1.0f; + if (model == CTX_RGBA) a = components[3]; + ctx_color_get_rgba (&ctx->state, &source->color, rgba); + if (rgba[0] == components[0] && rgba[1] == components[1] && rgba[2] == components[2] && rgba[3] == a) + return; + } +#endif + + if (stroke) + { + ctx_stroke_source (ctx); + } + + CtxEntry command[3]= { + ctx_f (CTX_COLOR, model, 0) + }; + switch (model) + { + case CTX_RGBA: + case CTX_RGBA_A: + case CTX_RGBA_A_DEVICE: + case CTX_DRGBA: + case CTX_LABA: + case CTX_LCHA: + command[2].data.f[0]=components[3]; + /*FALLTHROUGH*/ + case CTX_RGB: + case CTX_LAB: + case CTX_LCH: + case CTX_DRGB: + command[0].data.f[1]=components[0]; + command[1].data.f[0]=components[1]; + command[1].data.f[1]=components[2]; + break; + case CTX_DCMYKA: + case CTX_CMYKA: + case CTX_DCMYKA_A: + case CTX_CMYKA_A: + command[2].data.f[1]=components[4]; + /*FALLTHROUGH*/ + case CTX_CMYK: + case CTX_DCMYK: + command[0].data.f[1]=components[0]; + command[1].data.f[0]=components[1]; + command[1].data.f[1]=components[2]; + command[2].data.f[0]=components[3]; + break; + case CTX_GRAYA: + case CTX_GRAYA_A: + command[1].data.f[0]=components[1]; + /*FALLTHROUGH*/ + case CTX_GRAY: + command[0].data.f[1]=components[0]; + break; + } + ctx_process (ctx, command); +} + +void ctx_rgba (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_RGBA, components, 0); +} + +void ctx_rgba_stroke (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_RGBA, components, 1); +} + +void ctx_rgb (Ctx *ctx, float r, float g, float b) +{ + ctx_rgba (ctx, r, g, b, 1.0f); +} + +void ctx_rgb_stroke (Ctx *ctx, float r, float g, float b) +{ + ctx_rgba_stroke (ctx, r, g, b, 1.0f); +} + +void ctx_gray_stroke (Ctx *ctx, float gray) +{ + ctx_color_raw (ctx, CTX_GRAY, &gray, 1); +} +void ctx_gray (Ctx *ctx, float gray) +{ + ctx_color_raw (ctx, CTX_GRAY, &gray, 0); +} + +void ctx_drgba_stroke (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_DRGBA, components, 1); +} +void ctx_drgba (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_DRGBA, components, 0); +} + +#if CTX_ENABLE_CMYK + +void ctx_cmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_CMYKA, components, 1); +} +void ctx_cmyka (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_CMYKA, components, 0); +} +void ctx_cmyk_stroke (Ctx *ctx, float c, float m, float y, float k) +{ + float components[4]={c,m,y,k}; + ctx_color_raw (ctx, CTX_CMYK, components, 1); +} +void ctx_cmyk (Ctx *ctx, float c, float m, float y, float k) +{ + float components[4]={c,m,y,k}; + ctx_color_raw (ctx, CTX_CMYK, components, 0); +} + +static void ctx_dcmyk_raw (Ctx *ctx, float c, float m, float y, float k, int stroke) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYKA, components, stroke); +} + +static void ctx_dcmyka_raw (Ctx *ctx, float c, float m, float y, float k, float a, int stroke) +{ + CtxEntry command[3]= + { + ctx_f (CTX_COLOR, CTX_DCMYKA + 512 * stroke, c), + ctx_f (CTX_CONT, m, y), + ctx_f (CTX_CONT, k, a) + }; + ctx_process (ctx, command); +} + +void ctx_dcmyk_stroke (Ctx *ctx, float c, float m, float y, float k) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYK, components, 1); +} +void ctx_dcmyk (Ctx *ctx, float c, float m, float y, float k) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYK, components, 0); +} + +void ctx_dcmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_DCMYKA, components, 1); +} +void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_DCMYKA, components, 0); +} + +#endif + +/* XXX: missing CSS1: + * + * EM { color: rgb(110%, 0%, 0%) } // clipped to 100% + * + * + * :first-letter + * :first-list + * :link :visited :active + * + */ + +typedef struct ColorDef { + uint64_t name; + float r; + float g; + float b; + float a; +} ColorDef; + +#if 0 +#define CTX_silver CTX_STRH('s','i','l','v','e','r',0,0,0,0,0,0,0,0) +#define CTX_fuchsia CTX_STRH('f','u','c','h','s','i','a',0,0,0,0,0,0,0) +#define CTX_gray CTX_STRH('g','r','a','y',0,0,0,0,0,0,0,0,0,0) +#define CTX_yellow CTX_STRH('y','e','l','l','o','w',0,0,0,0,0,0,0,0) +#define CTX_white CTX_STRH('w','h','i','t','e',0,0,0,0,0,0,0,0,0) +#define CTX_maroon CTX_STRH('m','a','r','o','o','n',0,0,0,0,0,0,0,0) +#define CTX_magenta CTX_STRH('m','a','g','e','n','t','a',0,0,0,0,0,0,0) +#define CTX_blue CTX_STRH('b','l','u','e',0,0,0,0,0,0,0,0,0,0) +#define CTX_green CTX_STRH('g','r','e','e','n',0,0,0,0,0,0,0,0,0) +#define CTX_red CTX_STRH('r','e','d',0,0,0,0,0,0,0,0,0,0,0) +#define CTX_purple CTX_STRH('p','u','r','p','l','e',0,0,0,0,0,0,0,0) +#define CTX_olive CTX_STRH('o','l','i','v','e',0,0,0,0,0,0,0,0,0) +#define CTX_teal CTX_STRH('t','e','a','l',0,0,0,0,0,0,0,0,0,0) +#define CTX_black CTX_STRH('b','l','a','c','k',0,0,0,0,0,0,0,0,0) +#define CTX_cyan CTX_STRH('c','y','a','n',0,0,0,0,0,0,0,0,0,0) +#define CTX_navy CTX_STRH('n','a','v','y',0,0,0,0,0,0,0,0,0,0) +#define CTX_lime CTX_STRH('l','i','m','e',0,0,0,0,0,0,0,0,0,0) +#define CTX_aqua CTX_STRH('a','q','u','a',0,0,0,0,0,0,0,0,0,0) +#define CTX_transparent CTX_STRH('t','r','a','n','s','p','a','r','e','n','t',0,0,0) +#endif + +static ColorDef _ctx_colors[]={ + {CTX_black, 0, 0, 0, 1}, + {CTX_red, 1, 0, 0, 1}, + {CTX_green, 0, 1, 0, 1}, + {CTX_yellow, 1, 1, 0, 1}, + {CTX_blue, 0, 0, 1, 1}, + {CTX_fuchsia, 1, 0, 1, 1}, + {CTX_cyan, 0, 1, 1, 1}, + {CTX_white, 1, 1, 1, 1}, + {CTX_silver, 0.75294, 0.75294, 0.75294, 1}, + {CTX_gray, 0.50196, 0.50196, 0.50196, 1}, + {CTX_magenta, 0.50196, 0, 0.50196, 1}, + {CTX_maroon, 0.50196, 0, 0, 1}, + {CTX_purple, 0.50196, 0, 0.50196, 1}, + {CTX_green, 0, 0.50196, 0, 1}, + {CTX_lime, 0, 1, 0, 1}, + {CTX_olive, 0.50196, 0.50196, 0, 1}, + {CTX_navy, 0, 0, 0.50196, 1}, + {CTX_teal, 0, 0.50196, 0.50196, 1}, + {CTX_aqua, 0, 1, 1, 1}, + {CTX_transparent, 0, 0, 0, 0}, + {CTX_none, 0, 0, 0, 0}, +}; + +static int xdigit_value(const char xdigit) +{ + if (xdigit >= '0' && xdigit <= '9') + return xdigit - '0'; + switch (xdigit) + { + case 'A':case 'a': return 10; + case 'B':case 'b': return 11; + case 'C':case 'c': return 12; + case 'D':case 'd': return 13; + case 'E':case 'e': return 14; + case 'F':case 'f': return 15; + } + return 0; +} + +static int +ctx_color_parse_rgb (CtxState *ctxstate, CtxColor *color, const char *color_string) +{ + float dcolor[4] = {0,0,0,1}; + while (*color_string && *color_string != '(') + color_string++; + if (*color_string) color_string++; + + { + int n_floats = 0; + char *p = (char*)color_string; + char *prev = (char*)NULL; + for (; p && n_floats < 4 && p != prev && *p; ) + { + float val; + prev = p; + val = _ctx_parse_float (p, &p); + if (p != prev) + { + if (n_floats < 3) + dcolor[n_floats++] = val/255.0; + else + dcolor[n_floats++] = val; + + while (*p == ' ' || *p == ',') + { + p++; + prev++; + } + } + } + } + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + return 0; +} + +static int ctx_isxdigit (uint8_t ch) +{ + if (ch >= '0' && ch <= '9') return 1; + if (ch >= 'a' && ch <= 'f') return 1; + if (ch >= 'A' && ch <= 'F') return 1; + return 0; +} + +static int +mrg_color_parse_hex (CtxState *ctxstate, CtxColor *color, const char *color_string) +{ + float dcolor[4]={0,0,0,1}; + int string_length = strlen (color_string); + int i; + dcolor[3] = 1.0; + + if (string_length == 7 || /* #rrggbb */ + string_length == 9) /* #rrggbbaa */ + { + int num_iterations = (string_length - 1) / 2; + + for (i = 0; i < num_iterations; ++i) + { + if (ctx_isxdigit (color_string[2 * i + 1]) && + ctx_isxdigit (color_string[2 * i + 2])) + { + dcolor[i] = (xdigit_value (color_string[2 * i + 1]) << 4 | + xdigit_value (color_string[2 * i + 2])) / 255.f; + } + else + { + return 0; + } + } + /* Successful #rrggbb(aa) parsing! */ + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + return 1; + } + else if (string_length == 4 || /* #rgb */ + string_length == 5) /* #rgba */ + { + int num_iterations = string_length - 1; + for (i = 0; i < num_iterations; ++i) + { + if (ctx_isxdigit (color_string[i + 1])) + { + dcolor[i] = (xdigit_value (color_string[i + 1]) << 4 | + xdigit_value (color_string[i + 1])) / 255.f; + } + else + { + return 0; + } + } + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + /* Successful #rgb(a) parsing! */ + return 0; + } + /* String was of unsupported length. */ + return 1; +} + +//#define CTX_currentColor CTX_STRH('c','u','r','r','e','n','t','C','o','l','o','r',0,0) + +int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string) +{ + int i; + uint64_t hash = ctx_strhash (string, 0); +// ctx_color_set_rgba (&(ctx->state), color, 0.4,0.1,0.9,1.0); +// return 0; + //rgba[0], rgba[1], rgba[2], rgba[3]); + + if (hash == CTX_currentColor) + { + float rgba[4]; + CtxColor ccolor; + ctx_get_color (ctx, CTX_color, &ccolor); + ctx_color_get_rgba (&(ctx->state), &ccolor, rgba); + ctx_color_set_rgba (&(ctx->state), color, rgba[0], rgba[1], rgba[2], rgba[3]); + return 0; + } + + for (i = (sizeof(_ctx_colors)/sizeof(_ctx_colors[0]))-1; i>=0; i--) + { + if (hash == _ctx_colors[i].name) + { + ctx_color_set_rgba (&(ctx->state), color, + _ctx_colors[i].r, _ctx_colors[i].g, _ctx_colors[i].b, _ctx_colors[i].a); + return 0; + } + } + + if (string[0] == '#') + mrg_color_parse_hex (&(ctx->state), color, string); + else if (string[0] == 'r' && + string[1] == 'g' && + string[2] == 'b' + ) + ctx_color_parse_rgb (&(ctx->state), color, string); + + return 0; +} + +int ctx_color (Ctx *ctx, const char *string) +{ + CtxColor color = {0,}; + ctx_color_set_from_string (ctx, &color, string); + float rgba[4]; + ctx_color_get_rgba (&(ctx->state), &color, rgba); + ctx_color_raw (ctx, CTX_RGBA, rgba, 0); + return 0; +} + +void +ctx_rgba8 (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ +#if 0 + CtxEntry command = ctx_u8 (CTX_SET_RGBA_U8, r, g, b, a, 0, 0, 0, 0); + + uint8_t rgba[4]; + ctx_color_get_rgba8 (&ctx->state, &ctx->state.gstate.source.color, rgba); + if (rgba[0] == r && rgba[1] == g && rgba[2] == b && rgba[3] == a) + return; + + ctx_process (ctx, &command); +#else + ctx_rgba (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f); +#endif +} + +void ctx_rgba8_stroke (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + ctx_rgba_stroke (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f); +} + + +#endif + +#if CTX_BABL +void ctx_rasterizer_colorspace_babl (CtxState *state, + CtxColorSpace space_slot, + const Babl *space) +{ + switch (space_slot) + { + case CTX_COLOR_SPACE_DEVICE_RGB: + state->gstate.device_space = space; + break; + case CTX_COLOR_SPACE_DEVICE_CMYK: + state->gstate.device_space = space; + break; + case CTX_COLOR_SPACE_USER_RGB: + state->gstate.rgb_space = space; + break; + case CTX_COLOR_SPACE_USER_CMYK: + state->gstate.cmyk_space = space; + break; + case CTX_COLOR_SPACE_TEXTURE: + state->gstate.texture_space = space; + break; + } + + const Babl *srgb = babl_space ("sRGB"); + if (!state->gstate.texture_space) + state->gstate.texture_space = srgb; + if (!state->gstate.device_space) + state->gstate.device_space = srgb; + if (!state->gstate.rgb_space) + state->gstate.rgb_space = srgb; + + //fprintf (stderr, "%s\n", babl_get_name (state->gstate.device_space)); + + state->gstate.fish_rgbaf_device_to_user = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.device_space), + babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space)); + state->gstate.fish_rgbaf_user_to_device = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space), + babl_format_with_space ("R'G'B'A float", state->gstate.device_space)); + state->gstate.fish_rgbaf_texture_to_device = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.texture_space), + babl_format_with_space ("R'G'B'A float", state->gstate.device_space)); +} +#endif + +void ctx_rasterizer_colorspace_icc (CtxState *state, + CtxColorSpace space_slot, + char *icc_data, + int icc_length) +{ +#if CTX_BABL + const char *error = NULL; + const Babl *space = NULL; + + if (icc_data == NULL) space = babl_space ("sRGB"); + else if (icc_length < 32) + { + if (icc_data[0] == '0' && icc_data[1] == 'x') + sscanf (icc_data, "%p", &space); + else + { + char tmp[24]; + int i; + for (i = 0; i < icc_length; i++) + tmp[i]= (icc_data[i]>='A' && icc_data[i]<='Z')?icc_data[i]+('a'-'A'):icc_data[i]; + tmp[icc_length]=0; + if (!strcmp (tmp, "srgb")) space = babl_space ("sRGB"); + else if (!strcmp (tmp, "scrgb")) space = babl_space ("scRGB"); + else if (!strcmp (tmp, "acescg")) space = babl_space ("ACEScg"); + else if (!strcmp (tmp, "adobe")) space = babl_space ("Adobe"); + else if (!strcmp (tmp, "apple")) space = babl_space ("Apple"); + else if (!strcmp (tmp, "rec2020")) space = babl_space ("Rec2020"); + else if (!strcmp (tmp, "aces2065-1")) space = babl_space ("ACES2065-1"); + } + } + + if (!space) + { + space = babl_space_from_icc (icc_data, icc_length, BABL_ICC_INTENT_RELATIVE_COLORIMETRIC, &error); + } + if (space) + { + ctx_rasterizer_colorspace_babl (state, space_slot, space); + } +#endif +} + +void ctx_colorspace (Ctx *ctx, + CtxColorSpace space_slot, + unsigned char *data, + int data_length) +{ + if (data) + { + if (data_length <= 0) data_length = (int)strlen ((char*)data); + ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, (char*)data, space_slot, 0, data_length); + } + else + { + ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, "sRGB", space_slot, 0, 4); + } +} + +void ctx_gradient_add_stop_u8 +(Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + CtxEntry entry = ctx_f (CTX_GRADIENT_STOP, pos, 0); + entry.data.u8[4+0] = r; + entry.data.u8[4+1] = g; + entry.data.u8[4+2] = b; + entry.data.u8[4+3] = a; + ctx_process (ctx, &entry); +} + +void ctx_gradient_add_stop +(Ctx *ctx, float pos, float r, float g, float b, float a) +{ + int ir = r * 255; + int ig = g * 255; + int ib = b * 255; + int ia = a * 255; + ir = CTX_CLAMP (ir, 0,255); + ig = CTX_CLAMP (ig, 0,255); + ib = CTX_CLAMP (ib, 0,255); + ia = CTX_CLAMP (ia, 0,255); + ctx_gradient_add_stop_u8 (ctx, pos, ir, ig, ib, ia); +} + +void ctx_gradient_add_stop_string +(Ctx *ctx, float pos, const char *string) +{ + CtxColor color = {0,}; + ctx_color_set_from_string (ctx, &color, string); + float rgba[4]; + ctx_color_get_rgba (&(ctx->state), &color, rgba); + ctx_gradient_add_stop (ctx, pos, rgba[0], rgba[1], rgba[2], rgba[3]); +} + +// deviceRGB .. settable when creating an RGB image surface.. +// queryable when running in terminal - is it really needed? +// though it is settable ; and functional for changing this state at runtime.. +// +// userRGB - settable at any time, stored in save|restore +// texture - set as the space of data on subsequent + +static float ctx_state_get (CtxState *state, uint64_t hash) +{ + for (int i = state->gstate.keydb_pos-1; i>=0; i--) + { + if (state->keydb[i].key == hash) + { return state->keydb[i].value; } + } + return -0.0; +} + +static void ctx_state_set (CtxState *state, uint64_t key, float value) +{ + if (key != CTX_new_state) + { + if (ctx_state_get (state, key) == value) + { return; } + for (int i = state->gstate.keydb_pos-1; + state->keydb[i].key != CTX_new_state && i >=0; + i--) + { + if (state->keydb[i].key == key) + { + state->keydb[i].value = value; + return; + } + } + } + if (state->gstate.keydb_pos >= CTX_MAX_KEYDB) + { return; } + state->keydb[state->gstate.keydb_pos].key = key; + state->keydb[state->gstate.keydb_pos].value = value; + state->gstate.keydb_pos++; +} + + +#define CTX_KEYDB_STRING_START (-90000.0) +#define CTX_KEYDB_STRING_END (CTX_KEYDB_STRING_START + CTX_STRINGPOOL_SIZE) + +static int ctx_float_is_string (float val) +{ + return val >= CTX_KEYDB_STRING_START && val <= CTX_KEYDB_STRING_END; +} + +static int ctx_float_to_string_index (float val) +{ + int idx = -1; + if (ctx_float_is_string (val)) + { + idx = val - CTX_KEYDB_STRING_START; + } + return idx; +} + +static float ctx_string_index_to_float (int index) +{ + return CTX_KEYDB_STRING_START + index; +} + +void *ctx_state_get_blob (CtxState *state, uint64_t key) +{ + float stored = ctx_state_get (state, key); + int idx = ctx_float_to_string_index (stored); + if (idx >= 0) + { + // can we know length? + return &state->stringpool[idx]; + } + + // format number as string? + return NULL; +} + +const char *ctx_state_get_string (CtxState *state, uint64_t key) +{ + const char *ret = (char*)ctx_state_get_blob (state, key); + if (ret && ret[0] == 127) + return NULL; + return ret; +} + + +static void ctx_state_set_blob (CtxState *state, uint64_t key, uint8_t *data, int len) +{ + int idx = state->gstate.stringpool_pos; + + if (idx + len > CTX_STRINGPOOL_SIZE) + { + ctx_log ("blowing varpool size [%c..]\n", data[0]); + //fprintf (stderr, "blowing varpool size [%c%c%c..]\n", data[0],data[1], data[1]?data[2]:0); +#if 0 + for (int i = 0; i< CTX_STRINGPOOL_SIZE; i++) + { + if (i==0) fprintf (stderr, "\n%i ", i); + else fprintf (stderr, "%c", state->stringpool[i]); + } +#endif + return; + } + + memcpy (&state->stringpool[idx], data, len); + state->gstate.stringpool_pos+=len; + state->stringpool[state->gstate.stringpool_pos++]=0; + ctx_state_set (state, key, ctx_string_index_to_float (idx)); +} + +static void ctx_state_set_string (CtxState *state, uint64_t key, const char *string) +{ + float old_val = ctx_state_get (state, key); + int old_idx = ctx_float_to_string_index (old_val); + + if (old_idx >= 0) + { + const char *old_string = ctx_state_get_string (state, key); + if (old_string && !strcmp (old_string, string)) + return; + } + + if (ctx_str_is_number (string)) + { + ctx_state_set (state, key, strtod (string, NULL)); + return; + } + // should do same with color + + // XXX should special case when the string modified is at the + // end of the stringpool. + // + // for clips the behavior is howevre ideal, since + // we can have more than one clip per save/restore level + ctx_state_set_blob (state, key, (uint8_t*)string, strlen(string)); +} + +static int ctx_state_get_color (CtxState *state, uint64_t key, CtxColor *color) +{ + CtxColor *stored = (CtxColor*)ctx_state_get_blob (state, key); + if (stored) + { + if (stored->magic == 127) + { + *color = *stored; + return 0; + } + } + return -1; +} + +static void ctx_state_set_color (CtxState *state, uint64_t key, CtxColor *color) +{ + CtxColor mod_color; + CtxColor old_color; + mod_color = *color; + mod_color.magic = 127; + if (ctx_state_get_color (state, key, &old_color)==0) + { + if (!memcmp (&mod_color, &old_color, sizeof (mod_color))) + return; + } + ctx_state_set_blob (state, key, (uint8_t*)&mod_color, sizeof (CtxColor)); +} + +const char *ctx_get_string (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get_string (&ctx->state, hash); +} +float ctx_get_float (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get (&ctx->state, hash); +} +int ctx_get_int (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get (&ctx->state, hash); +} +void ctx_set_float (Ctx *ctx, uint64_t hash, float value) +{ + ctx_state_set (&ctx->state, hash, value); +} +void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value) +{ + ctx_state_set_string (&ctx->state, hash, value); +} +void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color) +{ + ctx_state_set_color (&ctx->state, hash, color); +} +int ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color) +{ + return ctx_state_get_color (&ctx->state, hash, color); +} +int ctx_is_set (Ctx *ctx, uint64_t hash) +{ + return ctx_get_float (ctx, hash) != -0.0f; +} +int ctx_is_set_now (Ctx *ctx, uint64_t hash) +{ + return ctx_is_set (ctx, hash); +} + +#if CTX_COMPOSITE + +#define CTX_REFERENCE 0 + + +#define CTX_RGBA8_R_SHIFT 0 +#define CTX_RGBA8_G_SHIFT 8 +#define CTX_RGBA8_B_SHIFT 16 +#define CTX_RGBA8_A_SHIFT 24 + +#define CTX_RGBA8_R_MASK (0xff << CTX_RGBA8_R_SHIFT) +#define CTX_RGBA8_G_MASK (0xff << CTX_RGBA8_G_SHIFT) +#define CTX_RGBA8_B_MASK (0xff << CTX_RGBA8_B_SHIFT) +#define CTX_RGBA8_A_MASK (0xff << CTX_RGBA8_A_SHIFT) + +#define CTX_RGBA8_RB_MASK (CTX_RGBA8_R_MASK | CTX_RGBA8_B_MASK) +#define CTX_RGBA8_GA_MASK (CTX_RGBA8_G_MASK | CTX_RGBA8_A_MASK) + + +CTX_INLINE static void +ctx_RGBA8_associate_alpha (uint8_t *u8) +{ +#if 1 + uint32_t val = *((uint32_t*)(u8)); + uint8_t a = u8[3]; + uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT); +#else + uint32_t a = u8[3]; + u8[0] = (u8[0] * a + 255) >> 8; + u8[1] = (u8[1] * a + 255) >> 8; + u8[2] = (u8[2] * a + 255) >> 8; +#endif +} + +CTX_INLINE static void +ctx_RGBA8_associate_alpha_probably_opaque (uint8_t *u8) +{ + uint32_t val = *((uint32_t*)(u8)); + uint32_t a = val>>24;//u8[3]; + //if (CTX_UNLIKELY(a==0)) + // *((uint32_t*)(u8)) = 0; + if (CTX_UNLIKELY(a!=255)) + { + uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT); + } +} + +CTX_INLINE static uint32_t ctx_bi_RGBA8 (uint32_t isrc00, uint32_t isrc01, uint32_t isrc10, uint32_t isrc11, uint8_t dx, uint8_t dy) +{ +#if 0 + 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 +} + +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE +static uint8_t ctx_gradient_cache_u8[CTX_GRADIENT_CACHE_ELEMENTS][4]; +extern int ctx_gradient_cache_valid; + + +inline static int ctx_grad_index (float v) +{ + int ret = v * (CTX_GRADIENT_CACHE_ELEMENTS - 1.0f) + 0.5f; + if (CTX_UNLIKELY(ret >= CTX_GRADIENT_CACHE_ELEMENTS)) + return CTX_GRADIENT_CACHE_ELEMENTS - 1; + if (CTX_LIKELY(ret >= 0 && ret < CTX_GRADIENT_CACHE_ELEMENTS)) + return ret; + return 0; +} + + +//static void +//ctx_gradient_cache_reset (void) +//{ +// ctx_gradient_cache_valid = 0; +//} +#endif + + + +CTX_INLINE static void +_ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ + float v = x; + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v * 255; + rgba[3] = 255; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + uint8_t stop_rgba[4]; + uint8_t next_rgba[4]; + ctx_color_get_rgba8 (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_rgba8 (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos); +#if 1 + ((uint32_t*)rgba)[0] = ctx_lerp_RGBA8 (((uint32_t*)stop_rgba)[0], + ((uint32_t*)next_rgba)[0], dx); +#else + for (int c = 0; c < 4; c++) + { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); } +#endif + ctx_RGBA8_associate_alpha (rgba); + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_rgba8 (rasterizer->state, color, rgba); + if (rasterizer->swap_red_green) + { + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; + } + ctx_RGBA8_associate_alpha (rgba); +} + +#if CTX_GRADIENT_CACHE +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer); +#endif + +CTX_INLINE static void +ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ +#if CTX_GRADIENT_CACHE + *((uint32_t*)rgba) = *((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(x)][0])); +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, x, y, rgba); +#endif +} +#endif + +CTX_INLINE static void +ctx_u8_associate_alpha (int components, uint8_t *u8) +{ + for (int c = 0; c < components-1; c++) + u8[c] = (u8[c] * u8[components-1] + 255)>>8; +} + +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer) +{ + if (ctx_gradient_cache_valid) + return; + for (int u = 0; u < CTX_GRADIENT_CACHE_ELEMENTS; u++) + { + float v = u / (CTX_GRADIENT_CACHE_ELEMENTS - 1.0f); + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0f, &ctx_gradient_cache_u8[u][0]); + //*((uint32_t*)(&ctx_gradient_cache_u8_a[u][0]))= *((uint32_t*)(&ctx_gradient_cache_u8[u][0])); + //memcpy(&ctx_gradient_cache_u8_a[u][0], &ctx_gradient_cache_u8[u][0], 4); + //ctx_RGBA8_associate_alpha (&ctx_gradient_cache_u8_a[u][0]); + } + ctx_gradient_cache_valid = 1; +} +#endif + +CTX_INLINE static void +ctx_fragment_gradient_1d_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ + float v = x; + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v * 255; + rgba[1] = 255; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + uint8_t stop_rgba[4]; + uint8_t next_rgba[4]; + ctx_color_get_graya_u8 (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_graya_u8 (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos); + for (int c = 0; c < 2; c++) + { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); } + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_graya_u8 (rasterizer->state, color, rgba); +} + +CTX_INLINE static void +ctx_fragment_gradient_1d_RGBAF (CtxRasterizer *rasterizer, float v, float y, float *rgba) +{ + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v; + rgba[3] = 1.0; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + float stop_rgba[4]; + float next_rgba[4]; + ctx_color_get_rgba (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_rgba (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) / (next_stop->pos - stop->pos); + for (int c = 0; c < 4; c++) + { rgba[c] = ctx_lerpf (stop_rgba[c], next_rgba[c], dx); } + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_rgba (rasterizer->state, color, rgba); +} +#endif + +static void +ctx_fragment_image_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + ctx_assert (rasterizer); + ctx_assert (g); + ctx_assert (buffer); + + for (int i = 0; i < count; i ++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + int width = buffer->width; + int height = buffer->height; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba)) = 0; + } + else + { + int bpp = buffer->format->bpp/8; + if (rasterizer->state->gstate.image_smoothing) + { + uint8_t *src00 = (uint8_t *) buffer->data; + src00 += v * buffer->stride + u * bpp; + uint8_t *src01 = src00; + if ( u + 1 < width) + { + src01 = src00 + bpp; + } + uint8_t *src11 = src01; + uint8_t *src10 = src00; + if ( v + 1 < height) + { + src10 = src00 + buffer->stride; + src11 = src01 + buffer->stride; + } + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; + + switch (bpp) + { + case 1: + rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx), + ctx_lerp_u8 (src10[0], src11[0], dx), dy); + rgba[3] = 255; + break; + case 2: + rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx), + ctx_lerp_u8 (src10[0], src11[0], dx), dy); + rgba[3] = ctx_lerp_u8 (ctx_lerp_u8 (src00[1], src01[1], dx), + ctx_lerp_u8 (src10[1], src11[1], dx), dy); + break; + case 3: + for (int c = 0; c < bpp; c++) + { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + + } + rgba[3]=255; + break; + break; + case 4: + for (int c = 0; c < bpp; c++) + { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + + } + } + } + else + { + uint8_t *src = (uint8_t *) buffer->data; + src += v * buffer->stride + u * bpp; + switch (bpp) + { + case 1: + for (int c = 0; c < 3; c++) + { rgba[c] = src[0]; } + rgba[3] = 255; + break; + case 2: + for (int c = 0; c < 3; c++) + { rgba[c] = src[0]; } + rgba[3] = src[1]; + break; + case 3: + for (int c = 0; c < 3; c++) + { rgba[c] = src[c]; } + rgba[3] = 255; + break; + case 4: + for (int c = 0; c < 4; c++) + { rgba[c] = src[c]; } + break; + } + } + if (rasterizer->swap_red_green) + { + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; + } + } + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + rgba += 4; + x += dx; + y += dy; + } +} + +#if CTX_DITHER +static inline int ctx_dither_mask_a (int x, int y, int c, int divisor) +{ + /* https://pippin.gimp.org/a_dither/ */ + return ( ( ( ( (x + c * 67) + y * 236) * 119) & 255 )-127) / divisor; +} + +inline static void +ctx_dither_rgba_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green) +{ + if (dither_red_blue == 0) + { return; } + for (int c = 0; c < 3; c ++) + { + int val = rgba[c] + ctx_dither_mask_a (x, y, 0, c==1?dither_green:dither_red_blue); + rgba[c] = CTX_CLAMP (val, 0, 255); + } +} + +inline static void +ctx_dither_graya_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green) +{ + if (dither_red_blue == 0) + { return; } + for (int c = 0; c < 1; c ++) + { + int val = rgba[c] + ctx_dither_mask_a (x, y, 0, dither_red_blue); + rgba[c] = CTX_CLAMP (val, 0, 255); + } +} +#endif + +CTX_INLINE static void +ctx_RGBA8_deassociate_alpha (const uint8_t *in, uint8_t *out) +{ + uint32_t val = *((uint32_t*)(in)); + int a = val >> CTX_RGBA8_A_SHIFT; + if (a) + { + if (a ==255) + { + *((uint32_t*)(out)) = val; + } else + { + uint32_t g = (((val & CTX_RGBA8_G_MASK) * 255 / a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * 255 / a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(out)) = g|rb|(a << CTX_RGBA8_A_SHIFT); + } + } + else + { + *((uint32_t*)(out)) = 0; + } +} + +CTX_INLINE static void +ctx_u8_deassociate_alpha (int components, const uint8_t *in, uint8_t *out) +{ + if (in[components-1]) + { + if (in[components-1] != 255) + for (int c = 0; c < components-1; c++) + out[c] = (in[c] * 255) / in[components-1]; + else + for (int c = 0; c < components-1; c++) + out[c] = in[c]; + out[components-1] = in[components-1]; + } + else + { + for (int c = 0; c < components; c++) + out[c] = 0; + } +} + +CTX_INLINE static void +ctx_float_associate_alpha (int components, float *rgba) +{ + float alpha = rgba[components-1]; + for (int c = 0; c < components-1; c++) + rgba[c] *= alpha; +} + +CTX_INLINE static void +ctx_float_deassociate_alpha (int components, float *rgba, float *dst) +{ + float ralpha = rgba[components-1]; + if (ralpha != 0.0) ralpha = 1.0/ralpha; + + for (int c = 0; c < components-1; c++) + dst[c] = (rgba[c] * ralpha); + dst[components-1] = rgba[components-1]; +} + +CTX_INLINE static void +ctx_RGBAF_associate_alpha (float *rgba) +{ + ctx_float_associate_alpha (4, rgba); +} + +CTX_INLINE static void +ctx_RGBAF_deassociate_alpha (float *rgba, float *dst) +{ + ctx_float_deassociate_alpha (4, rgba, dst); +} + + +static inline void ctx_swap_red_green_u8 (void *data) +{ + uint8_t *rgba = (uint8_t*)data; + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; +} + +static void +ctx_fragment_swap_red_green_u8 (void *out, int count) +{ + uint8_t *rgba = (uint8_t*)out; + for (int x = 0; x < count; x++) + { + ctx_swap_red_green_u8 (rgba); + rgba += 4; + } +} + +/**** rgb8 ***/ + +static void +ctx_fragment_image_rgb8_RGBA8_box (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int width = buffer->width; + int height = buffer->height; + + for (int i = 0; i < count; i++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 3; + rgba[3]=255; + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + int dim = (1.0 / factor) / 2; + uint64_t sum[4]={0,0,0,0}; + int count = 0; + for (int ou = - dim; ou < dim; ou++) + for (int ov = - dim; ov < dim; ov++) + { + uint8_t *src = (uint8_t *) buffer->data; + int o = (v+ov) * width + (u + ou); + + if (o>=0 && o < width * height) + { + src += o * bpp; + + for (int c = 0; c < bpp; c++) + sum[c] += src[c]; + count ++; + } + } + if (count) + for (int c = 0; c < bpp; c++) + rgba[c] = sum[c]/count; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + rgba += 4; + x += dx; + y += dy; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgb8_RGBA8_bi (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int width = buffer->width; + int height = buffer->height; + + for (int i = 0; i < count; i++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 3; + rgba[3]=255; + uint8_t *src00 = (uint8_t *) buffer->data; + int stride = buffer->stride; + src00 += v * stride + u * bpp; + uint8_t *src01 = src00; + if ( u + 1 < width) + { + src01 = src00 + bpp; + } + uint8_t *src11 = src01; + uint8_t *src10 = src00; + if ( v + 1 < height) + { + src10 = src00 + stride; + src11 = src01 + stride; + } + float dx = (x-(int)(x)) * 255.9f; + float dy = (y-(int)(y)) * 255.9f; + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + x += dx; + y += dy; + rgba += 4; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static CTX_INLINE void +ctx_fragment_image_rgb8_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint8_t *rgba = (uint8_t *) out; + uint8_t *src = (uint8_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + int stride = buffer->stride; + + x += 0.5f; + y += 0.5f; + + if (CTX_UNLIKELY (dy == 0.0f && dx > 0.999f && dx < 1.001f)) + { + int v = y - g->texture.y0; + int u = x - g->texture.x0; + + if (v < buffer->height && v > 0) + { + int o = v * stride + u * 3; + int i; + for (i = 0; i < count && u < bwidth && u <0; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + o += 3; + u+=1; + } + + for (; i < count && u < bwidth; i++) + { + rgba[0] = src[o]; + rgba[1] = src[o+1]; + rgba[2] = src[o+2]; + rgba[3]=255; + rgba += 4; + o += 3; + u+=1; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } + else + { + for (int i = 0; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba+=4; + } + } + } + else + { + int tx0 = g->texture.x0; + int ty0 = g->texture.y0; + int u = x - tx0; + int v = y - ty0; + int i; + for (i = 0; i < count && u < bwidth && u <0; i++) + { + u = x - tx0; + v = y - ty0; + *((uint32_t*)(rgba))= 0; + x += dx; + y += dy; + rgba += 4; + } + for (; i < count && u < bwidth; i++) + { + u = x - tx0; + v = y - ty0; + if (CTX_UNLIKELY(v < 0 || v >= bheight)) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int o = v * stride + u * 3; + rgba[0] = src[o]; + rgba[1] = src[o+1]; + rgba[2] = src[o+2]; + rgba[3]=255; + } + + rgba += 4; + x += dx; + y += dy; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +#if CTX_DITHER + //ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + // rasterizer->format->dither_green); +#endif +} + + +static CTX_INLINE void +ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgb8_RGBA8 (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + if (rasterizer->state->gstate.image_smoothing) + { + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + if (factor <= 0.50f) + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + // XXX missing translate test + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#endif + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + } + } + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_DITHER + { + uint8_t *rgba = (uint8_t*)out; + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); + } +#endif +} + + +/************** rgba8 */ + +static void +ctx_fragment_image_rgba8_RGBA8_box (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + + for (int i = 0; i < count; i ++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= buffer->width || + v >= buffer->height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 4; + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + int dim = (1.0 / factor) / 2; + uint64_t sum[4]={0,0,0,0}; + int count = 0; + int width = buffer->width; + int height = buffer->height; + for (int ou = - dim; ou < dim; ou++) + for (int ov = - dim; ov < dim; ov++) + { + uint8_t *src = (uint8_t *) buffer->data; + int o = (v+ov) * width + (u + ou); + + if (o>=0 && o < width * height) + { + src += o * bpp; + + for (int c = 0; c < bpp; c++) + sum[c] += src[c]; + count ++; + } + } + if (count) + for (int c = 0; c < bpp; c++) + rgba[c] = sum[c]/count; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + rgba += 4; + x += dx; + y += dy; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgba8_RGBA8_bi (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int bwidth = buffer->width; + int bheight = buffer->height; + int i = 0; + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int u0 = x - tx0; + int v0 = y - ty0; + int u1 = x - tx0 + dx * (count-1); + int v1 = y - ty0 + dy * (count-1); + + if (u0 >= 0 && v0 >= 0 && u0 < bwidth && v0 < bheight && + u1 >= 0 && v1 >= 0 && u1 < bwidth && v1 < bheight) + { + for (i = 0; i < count;i++) + { + int u = x - tx0; + int v = y - ty0; + { + int bpp = 4; + int stride = buffer->stride; + uint8_t *src00 = ((uint8_t *) buffer->data) + v * stride + u * bpp; + uint8_t *src01 = src00 + bpp * (u + 1 < bwidth); + int got_next_row = ( v + 1 < bheight); + uint8_t *src11 = src01 + stride * got_next_row; + uint8_t *src10 = src00 + stride * got_next_row; + + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 1 + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); + +#else + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#endif + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + + x += dx; + y += dy; + rgba += 4; + } + return; + } + + + for (i= 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + int ut = x - tx0 + 1.5; + int vt = y - ty0 + 1.5; + if ( ut <= 0 || + vt <= 0 || + u >= buffer->width || + v >= buffer->height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + int ut = x - tx0 + 1.5; + int vt = y - ty0 + 1.5; + if (CTX_UNLIKELY( + u >= buffer->width || + vt <= 0 || + ut <= 0 || + v >= buffer->height)) + { + break; + } + else if (u < 0 || v < 0) // default to next sample down and to right + { + int bpp = 4; + uint8_t *src11 = (uint8_t *) buffer->data; + int stride = buffer->stride; + src11 += (v+1) * stride + (u+1) * bpp; + uint8_t *src10 = src11; + int got_prev_pix = (u >= 0); + src10 = src11 - bpp * got_prev_pix; + int got_prev_row = (v>=0); + uint8_t *src01 = src11 - stride * got_prev_row; + uint8_t *src00 = src10 - stride * got_prev_row; + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 1 + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); + +#else + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#endif +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + //*((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 4; + int stride = buffer->stride; + uint8_t *src00 = ((uint8_t *) buffer->data) + v * stride + u * bpp; + uint8_t *src01 = src00 + bpp * (u + 1 < bwidth); + int got_next_row = ( v + 1 < bheight); + uint8_t *src11 = src01 + stride * got_next_row; + uint8_t *src10 = src00 + stride * got_next_row; + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 0 + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#else + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); +#endif +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i ++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } +} + +static void +ctx_fragment_image_rgba8_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint32_t *src = (uint32_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + x += 0.5f; + y += 0.5f; + +#if 1 + if (CTX_UNLIKELY(dy == 0.0f && dx > 0.99f && dx < 1.01f)) + { + int i = 0; + int u = x - g->texture.x0; + int v = y - g->texture.y0; + uint32_t *dst = (uint32_t*)out; + src += bwidth * v + u; + while (count && !(u >= 0 && v >= 0 && u < bwidth && v < bheight)) + { + dst[0] = 0; + dst++; + src ++; + u++; + count--; + } + for (i = 0 ; i < count && u < bwidth; i++) + { + dst[i] = ((uint32_t*)src)[i]; + ctx_RGBA8_associate_alpha_probably_opaque ((uint8_t*)&dst[i]); + u++; + } + for (;i < count; i++) + dst[i] = 0; + return; + } +#endif + + { + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int i = 0; + + int u0 = x - tx0; + int v0 = y - ty0; + int u1 = x - tx0 + dx * (count-1); + int v1 = y - ty0 + dy * (count-1); + + + if (u0 >= 0 && v0 >= 0 && u0 < bwidth && v0 < bheight && + u1 >= 0 && v1 >= 0 && u1 < bwidth && v1 < bheight) + { + for (i = 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + { + *((uint32_t*)(rgba))= src[v * bwidth + u]; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + x += dx; + y += dy; + rgba += 4; + } + return; + } + + for (i = 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if ((u < 0 || v < 0 || u >= bwidth || v >= bheight)) + { + *((uint32_t*)(rgba))= 0; + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if (CTX_LIKELY(u < bwidth && u >= 0 && v >= 0 && v < bheight)) + { + *((uint32_t*)(rgba))= src[v * bwidth + u]; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +} + +#define ctx_clampi(val,min,max) \ + ctx_mini (ctx_maxi ((val), (min)), (max)) + +static inline uint32_t ctx_yuv_to_rgba32 (uint8_t y, uint8_t u, uint8_t v) +{ + int cy = ((y - 16) * 76309) >> 16; + int cr = (v - 128); + int cb = (u - 128); + int red = cy + ((cr * 104597) >> 16); + int green = cy - ((cb * 25674 + cr * 53278) >> 16); + int blue = cy + ((cb * 132201) >> 16); + return ctx_clampi (red, 0, 255) | + (ctx_clampi (green, 0, 255) << 8) | + (ctx_clampi (blue, 0, 255) << 16) | + (0xff << 24); +} + +static void +ctx_fragment_image_yuv420_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint8_t *src = (uint8_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + int bwidth_div_2 = bwidth/2; + int bheight_div_2 = bheight/2; + x += 0.5f; + y += 0.5f; + + { + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int i = 0; + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + 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; + } + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if (u >= 0 && v >= 0 && u < bwidth && v < bheight) + { + uint32_t y = v * bwidth + u; + uint32_t uv = (v / 2) * bwidth_div_2 + (u / 2); + + + + *((uint32_t*)(rgba))= ctx_yuv_to_rgba32 (src[y], + //127, 127); + src[u_offset+uv], src[v_offset+uv]); + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +} + +static void +ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8 (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + if (rasterizer->state->gstate.image_smoothing) + { + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + if (factor <= 0.50f) + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + // XXX: also verify translate == 0 for this fast path to be valid + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#endif + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + } + } + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } + //ctx_fragment_swap_red_green_u8 (out, count); +#if CTX_DITHER + uint8_t *rgba = (uint8_t*)out; + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_gray1_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + ctx_assert (rasterizer); + ctx_assert (g); + ctx_assert (buffer); + for (int i = 0; i < count; i ++) + { + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= buffer->width || + v >= buffer->height) + { + rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0; + } + else + { + uint8_t *src = (uint8_t *) buffer->data; + src += v * buffer->stride + u / 8; + if (*src & (1<< (u & 7) ) ) + { + rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0; + } + else + { + for (int c = 0; c < 4; c++) + { rgba[c] = 255; + }//g->texture.rgba[c]; + //} + } + } + + rgba += 4; + x += dx; + y += dy; + } +} + +#if CTX_GRADIENTS +static void +ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = (ctx_hypotf_fast (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); +#if CTX_GRADIENT_CACHE + uint32_t *rgbap = (uint32_t*)&ctx_gradient_cache_u8[ctx_grad_index(v)][0]; + *((uint32_t*)rgba) = *rgbap; +#else + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba); +#endif +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + rgba += 4; + x += dx; + y += dy; + } +} + +static void +ctx_fragment_linear_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ +#if 0 + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); +#if CTX_GRADIENT_CACHE + uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(v)][0])); + *((uint32_t*)rgba) = *rgbap; +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); +#endif +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + rgba += 4; + x += dx; + y += dy; + } +#else + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + float u0 = x; float v0 = y; + float ud = dx; float vd = dy; + float linear_gradient_dx = g->linear_gradient.dx; + float linear_gradient_dy = g->linear_gradient.dy; + float linear_gradient_rdelta = g->linear_gradient.rdelta; + float linear_gradient_start = g->linear_gradient.start; + float linear_gradient_length = g->linear_gradient.length; + float linear_gradient_length_recip = 1.0f/linear_gradient_length; +#if CTX_DITHER + int dither_red_blue = rasterizer->format->dither_red_blue; + int dither_green = rasterizer->format->dither_green; +#endif + linear_gradient_dx *=linear_gradient_length_recip; + linear_gradient_dy *=linear_gradient_length_recip; + + u0 *= linear_gradient_dx; + v0 *= linear_gradient_dy; + ud *= linear_gradient_dx; + vd *= linear_gradient_dy; + + u0 *= linear_gradient_rdelta; + v0 *= linear_gradient_rdelta; + ud *= linear_gradient_rdelta; + vd *= linear_gradient_rdelta; + linear_gradient_start *= linear_gradient_rdelta; + + float vv = ((u0 + v0) - linear_gradient_start); + float ud_plus_vd = ud + vd; + for (int x = 0; x < count ; x++) + { + // float vv = ((u0 + v0) - linear_gradient_start); +#if CTX_GRADIENT_CACHE + uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(vv)][0])); + *((uint32_t*)rgba) = *rgbap; +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, vv, 1.0, rgba); +#endif + +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, u0, v0, dither_red_blue, dither_green); +#endif + + rgba+= 4; + // u0 += ud; + // v0 += vd; + vv += ud_plus_vd; + } +#endif +} + +#endif + +static void +ctx_fragment_color_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba_out = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + _ctx_color_get_rgba8 (rasterizer->state, &g->color, rgba_out); + ctx_RGBA8_associate_alpha (rgba_out); + if (rasterizer->swap_red_green) + { + int tmp = rgba_out[0]; + rgba_out[0] = rgba_out[2]; + rgba_out[2] = tmp; + } + for (int i = 1; i < count; i++, rgba_out+=4) + memcpy (rgba_out + count * 4, rgba_out, 4); +} +#if CTX_ENABLE_FLOAT + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0f, rgba); + x += dx; + y += dy; + rgba += 4; + } +} + +static void +ctx_fragment_radial_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + float v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y); + v = (v - g->radial_gradient.r0) * (g->radial_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0f, rgba); + x+=dx; + y+=dy; + rgba +=4; + } +} +#endif + + +static void +ctx_fragment_color_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + for (int i = 0; i < count; i++) + { + CtxSource *g = &rasterizer->state->gstate.source_fill; + ctx_color_get_rgba (rasterizer->state, &g->color, rgba); + rgba += 4; + } +} + +static void ctx_fragment_image_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *outf = (float *) out; + uint8_t rgba[4]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int c = 0; c < 4 * count; c ++) { outf[c] = ctx_u8_to_float (rgba[c]); } +} + +static CtxFragment ctx_rasterizer_get_fragment_RGBAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_RGBAF; + case CTX_SOURCE_COLOR: return ctx_fragment_color_RGBAF; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBAF; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBAF; +#endif + } + return ctx_fragment_color_RGBAF; +} +#endif + +static CtxFragment ctx_rasterizer_get_fragment_RGBA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: + if (!buffer || !buffer->format) + return ctx_fragment_color_RGBA8; + + if (buffer->format->pixel_format == CTX_FORMAT_YUV420) + { + return ctx_fragment_image_yuv420_RGBA8_nearest; + } + else + switch (buffer->format->bpp) + { + case 1: return ctx_fragment_image_gray1_RGBA8; + case 24: + { + if (gstate->image_smoothing) + { + float factor = ctx_matrix_get_scale (&gstate->transform); + //fprintf (stderr, "{%.3f}", factor); + if (factor < 0.5f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_box_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_box; + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_nearest; + } +#endif + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_bi; + } + } + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_nearest; + } + } + break; + case 32: + { + if (gstate->image_smoothing) + { + float factor = ctx_matrix_get_scale (&gstate->transform); + //fprintf (stderr, "[%.3f]", factor); + if (factor < 0.5f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_box_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_box; + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_nearest; + } +#endif + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_bi; + } + } + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_nearest; + } + } + default: return ctx_fragment_image_RGBA8; + } + case CTX_SOURCE_COLOR: return ctx_fragment_color_RGBA8; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBA8; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBA8; +#endif + } + return ctx_fragment_color_RGBA8; +} + +static void +ctx_init_uv (CtxRasterizer *rasterizer, + int x0, int count, + float *u0, float *v0, float *ud, float *vd) +{ + CtxGState *gstate = &rasterizer->state->gstate; + *u0 = x0; + *v0 = rasterizer->scanline / 15;//rasterizer->aa; + float u1 = *u0 + count; + float v1 = *v0; + + _ctx_matrix_apply_transform (&gstate->source_fill.transform, u0, v0); + _ctx_matrix_apply_transform (&gstate->source_fill.transform, &u1, &v1); + + *ud = (u1-*u0) / (count); + *vd = (v1-*v0) / (count); +} + + +static void +ctx_u8_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + if (CTX_UNLIKELY(rasterizer->fragment)) + { + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + while (count--) + { + uint8_t cov = *coverage; + if (CTX_UNLIKELY(cov == 0)) + { + u0+=ud; + v0+=vd; + } + else + { + rasterizer->fragment (rasterizer, u0, v0, src, 1, ud, vd); + u0+=ud; + v0+=vd; + if (cov == 255) + { + for (int c = 0; c < components; c++) + dst[c] = src[c]; + } + else + { + uint8_t rcov = 255 - cov; + for (int c = 0; c < components; c++) + { dst[c] = (src[c]*cov + dst[c]*rcov)/255; } + } + } + dst += components; + coverage ++; + } + return; + } + + while (count--) + { + uint8_t cov = *coverage; + uint8_t rcov = 255-cov; + for (int c = 0; c < components; c++) + { dst[c] = (src[c]*cov+dst[c]*rcov)/255; } + dst += components; + coverage ++; + } +} + +static void +ctx_u8_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + while (count--) + { + uint8_t cov = *coverage; + for (int c = 0; c < components; c++) + { dst[c] = (dst[c] * (256-cov)) >> 8; } + coverage ++; + dst += components; + } +} + +typedef enum { + CTX_PORTER_DUFF_0, + CTX_PORTER_DUFF_1, + CTX_PORTER_DUFF_ALPHA, + CTX_PORTER_DUFF_1_MINUS_ALPHA, +} CtxPorterDuffFactor; + +#define \ +ctx_porter_duff_factors(mode, foo, bar)\ +{\ + switch (mode)\ + {\ + case CTX_COMPOSITE_SOURCE_ATOP:\ + f_s = CTX_PORTER_DUFF_ALPHA;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_1;\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + f_s = CTX_PORTER_DUFF_1;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_1;\ + break;\ + case CTX_COMPOSITE_XOR:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + f_s = CTX_PORTER_DUFF_ALPHA;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + case CTX_COMPOSITE_COPY:\ + f_s = CTX_PORTER_DUFF_1;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + default:\ + case CTX_COMPOSITE_CLEAR:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + }\ +} + +static void +ctx_u8_source_over_normal_color (int components, + CtxRasterizer *rasterizer, + uint8_t * __restrict__ dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * __restrict__ coverage, + int count) +{ + uint8_t tsrc[5]; + *((uint32_t*)tsrc) = *((uint32_t*)src); + //ctx_u8_associate_alpha (components, tsrc); + + while (count--) + { + for (int c = 0; c < components; c++) + //dst[c] = ((tsrc[c] * *coverage)>>8) + (dst[c] * (((65536)-(tsrc[components-1] * *coverage)))>>16); + dst[c] = ((((tsrc[c] * *coverage)) + (dst[c] * (((255)-(((255+(tsrc[components-1] * *coverage))>>8))))))>>8); + coverage ++; + dst+=components; + } +} + +static void +ctx_u8_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + while (count--) + { + for (int c = 0; c < components; c++) + dst[c] = ctx_lerp_u8(dst[c],src[c],coverage[0]); + coverage ++; + dst+=components; + } +} + +static inline void +ctx_RGBA8_source_over_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc) +{ + while (count--) + { + uint32_t si_ga = ((*((uint32_t*)tsrc)) & 0xff00ff00) >> 8; + uint32_t si_rb = (*((uint32_t*)tsrc)) & 0x00ff00ff; +// uint32_t di_ga = ((*((uint32_t*)dst)) & 0xff00ff00) >> 8; +// uint32_t di_rb = (*((uint32_t*)dst)) & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + uint32_t cov = *coverage; + uint32_t racov = (256-((255+si_a*cov)>>8)); + *((uint32_t*)(dst)) = + + (((si_rb*cov+(((*((uint32_t*)(dst)))&0x00ff00ff)*racov))>>8)&0x00ff00ff)| + ((si_ga*cov+((((*((uint32_t*)(dst)))&0xff00ff00)>>8)*racov))&0xff00ff00); + + coverage ++; + tsrc += 4; + dst += 4; + } +} + +static void +ctx_RGBA8_source_copy_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc) +{ + while (count--) + { + ((uint32_t*)dst)[0]=ctx_lerp_RGBA8 (((uint32_t*)dst)[0], + ((uint32_t*)tsrc)[0], coverage[0]); + coverage ++; + tsrc += 4; + dst += 4; + } +} + +static void +ctx_RGBA8_source_over_normal_fragment (CTX_COMPOSITE_ARGUMENTS) +{ + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + uint8_t _tsrc[4 * (count)]; + rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd); + ctx_RGBA8_source_over_normal_buf (rasterizer, + dst, src, x0, coverage, count, &_tsrc[0]); +} + +static void +ctx_RGBA8_source_copy_normal_fragment (CTX_COMPOSITE_ARGUMENTS) +{ + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + uint8_t _tsrc[4 * (count)]; + rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd); + ctx_RGBA8_source_copy_normal_buf (rasterizer, + dst, src, x0, coverage, count, &_tsrc[0]); +} + + +static void +ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ +#if CTX_REFERENCE + ctx_u8_source_over_normal_color (4, rasterizer, dst, src, x0, coverage, count); +#else + uint32_t si = *((uint32_t*)src); + uint32_t si_ga = (si & 0xff00ff00) >> 8; + uint32_t si_rb = si & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + while (count--) + { + uint32_t cov = *coverage++; + uint32_t rcov = (256-((255+si_a * cov)>>8)); + 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 + di_rb * rcov) & 0xff00ff00) >> 8) | + ((si_ga * cov + 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 = *((uint32_t*)src); + uint32_t si_ga = (si & 0xff00ff00) >> 8; + uint32_t si_rb = si & 0x00ff00ff; + + while (count--) + { + uint32_t cov = *coverage++; + uint32_t di = *((uint32_t*)dst); + uint32_t di_ga = (di & 0xff00ff00); + uint32_t di_rb = (di & 0x00ff00ff); + + uint32_t d_rb = si_rb - di_rb; + uint32_t d_ga = si_ga - (di_ga>>8); + + *((uint32_t*)(dst)) = + + (((di_rb + ((d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((d_ga * cov) & 0xff00ff00))); + dst +=4; + } +#endif +} + +static void +ctx_RGBA8_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_clear_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_u8_blend_normal (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count) +{ + for (int j = 0; j < count; j++) + { + switch (components) + { + case 3: + ((uint8_t*)(blended))[2] = ((uint8_t*)(src))[2]; + *((uint16_t*)(blended)) = *((uint16_t*)(src)); + break; + case 2: + *((uint16_t*)(blended)) = *((uint16_t*)(src)); + break; + case 5: + *((uint32_t*)(blended)) = *((uint32_t*)(src)); + ((uint8_t*)(blended))[4] = ((uint8_t*)(src))[4]; + break; + case 4: + *((uint32_t*)(blended)) = *((uint32_t*)(src)); + break; + default: + { + for (int i = 0; i<components;i++) + blended[i] = src[i]; + } + break; + } + blended+=components; + src+=components; + } +} + +/* branchless 8bit add that maxes out at 255 */ +static inline uint8_t ctx_sadd8(uint8_t a, uint8_t b) +{ + uint16_t s = (uint16_t)a+b; + return -(s>>8) | (uint8_t)s; +} + +#if CTX_BLENDING_AND_COMPOSITING + +#define ctx_u8_blend_define(name, CODE) \ +static void \ +ctx_u8_blend_##name (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count)\ +{\ + for (int j = 0; j < count; j++) { \ + uint8_t *s=src; uint8_t b[components];\ + ctx_u8_deassociate_alpha (components, dst, b);\ + CODE;\ + blended[components-1] = src[components-1];\ + ctx_u8_associate_alpha (components, blended);\ + src += components;\ + dst += components;\ + blended += components;\ + }\ +} + +#define ctx_u8_blend_define_seperable(name, CODE) \ + ctx_u8_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \ + +ctx_u8_blend_define_seperable(multiply, blended[c] = (b[c] * s[c])/255;) +ctx_u8_blend_define_seperable(screen, blended[c] = s[c] + b[c] - (s[c] * b[c])/255;) +ctx_u8_blend_define_seperable(overlay, blended[c] = b[c] < 127 ? (s[c] * b[c])/255 : + s[c] + b[c] - (s[c] * b[c])/255;) +ctx_u8_blend_define_seperable(darken, blended[c] = ctx_mini (b[c], s[c])) +ctx_u8_blend_define_seperable(lighten, blended[c] = ctx_maxi (b[c], s[c])) +ctx_u8_blend_define_seperable(color_dodge, blended[c] = b[c] == 0 ? 0 : + s[c] == 255 ? 255 : ctx_mini(255, (255 * b[c]) / (255-s[c]))) +ctx_u8_blend_define_seperable(color_burn, blended[c] = b[c] == 1 ? 1 : + s[c] == 0 ? 0 : 255 - ctx_mini(255, (255*(255 - b[c])) / s[c])) +ctx_u8_blend_define_seperable(hard_light, blended[c] = s[c] < 127 ? (b[c] * s[c])/255 : + b[c] + s[c] - (b[c] * s[c])/255;) +ctx_u8_blend_define_seperable(difference, blended[c] = (b[c] - s[c])) +ctx_u8_blend_define_seperable(divide, blended[c] = s[c]?(255 * b[c]) / s[c]:0) +ctx_u8_blend_define_seperable(addition, blended[c] = ctx_sadd8 (s[c], b[c])) +ctx_u8_blend_define_seperable(subtract, blended[c] = ctx_maxi(0, s[c]-b[c])) +ctx_u8_blend_define_seperable(exclusion, blended[c] = b[c] + s[c] - 2 * (b[c] * s[c]/255)) +ctx_u8_blend_define_seperable(soft_light, + if (s[c] <= 255/2) + { + blended[c] = b[c] - (255 - 2 * s[c]) * b[c] * (255 - b[c]) / (255 * 255); + } + else + { + int d; + if (b[c] <= 255/4) + d = (((16 * b[c] - 12 * 255)/255 * b[c] + 4 * 255) * b[c])/255; + else + d = ctx_sqrtf(b[c]/255.0) * 255.4; + blended[c] = (b[c] + (2 * s[c] - 255) * (d - b[c]))/255; + } +) + +static int ctx_int_get_max (int components, int *c) +{ + int max = 0; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] > max) max = c[i]; + } + return max; +} + +static int ctx_int_get_min (int components, int *c) +{ + int min = 400; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + } + return min; +} + +static int ctx_int_get_lum (int components, int *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + int sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + break; + } +} + +static int ctx_u8_get_lum (int components, uint8_t *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + int sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + break; + } +} +static int ctx_u8_get_sat (int components, uint8_t *c) +{ + switch (components) + { + case 3: + case 4: + { int r = c[0]; + int g = c[1]; + int b = c[2]; + return ctx_maxi(r, ctx_maxi(g,b)) - ctx_mini(r,ctx_mini(g,b)); + } + break; + case 1: + case 2: + return 0.0; + break; + default: + { + int min = 1000; + int max = -1000; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + if (c[i] > max) max = c[i]; + } + return max-min; + } + break; + } +} + +static void ctx_u8_set_lum (int components, uint8_t *c, uint8_t lum) +{ + int d = lum - ctx_u8_get_lum (components, c); + int tc[components]; + for (int i = 0; i < components - 1; i++) + { + tc[i] = c[i] + d; + } + + int l = ctx_int_get_lum (components, tc); + int n = ctx_int_get_min (components, tc); + int x = ctx_int_get_max (components, tc); + + if (n < 0 && l!=n) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * l) / (l-n)); + } + + if (x > 255 && x!=l) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * (255 - l)) / (x-l)); + } + for (int i = 0; i < components - 1; i++) + c[i] = tc[i]; +} + +static void ctx_u8_set_sat (int components, uint8_t *c, uint8_t sat) +{ + int max = 0, mid = 1, min = 2; + + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + if (c[mid] > c[max]){int t = mid; mid = max; max = t;} + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + + if (c[max] > c[min]) + { + c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]); + c[max] = sat; + } + else + { + c[mid] = c[max] = 0; + } + c[min] = 0; +} + +ctx_u8_blend_define(color, + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_u8_set_lum(components, blended, ctx_u8_get_lum (components, s)); +) + +ctx_u8_blend_define(hue, + int in_sat = ctx_u8_get_sat(components, b); + int in_lum = ctx_u8_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_u8_set_sat(components, blended, in_sat); + ctx_u8_set_lum(components, blended, in_lum); +) + +ctx_u8_blend_define(saturation, + int in_sat = ctx_u8_get_sat(components, s); + int in_lum = ctx_u8_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_u8_set_sat(components, blended, in_sat); + ctx_u8_set_lum(components, blended, in_lum); +) + +ctx_u8_blend_define(luminosity, + int in_lum = ctx_u8_get_lum(components, s); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_u8_set_lum(components, blended, in_lum); +) +#endif + +CTX_INLINE static void +ctx_u8_blend (int components, CtxBlend blend, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count) +{ +#if CTX_BLENDING_AND_COMPOSITING + switch (blend) + { + case CTX_BLEND_NORMAL: ctx_u8_blend_normal (components, dst, src, blended, count); break; + case CTX_BLEND_MULTIPLY: ctx_u8_blend_multiply (components, dst, src, blended, count); break; + case CTX_BLEND_SCREEN: ctx_u8_blend_screen (components, dst, src, blended, count); break; + case CTX_BLEND_OVERLAY: ctx_u8_blend_overlay (components, dst, src, blended, count); break; + case CTX_BLEND_DARKEN: ctx_u8_blend_darken (components, dst, src, blended, count); break; + case CTX_BLEND_LIGHTEN: ctx_u8_blend_lighten (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR_DODGE: ctx_u8_blend_color_dodge (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR_BURN: ctx_u8_blend_color_burn (components, dst, src, blended, count); break; + case CTX_BLEND_HARD_LIGHT: ctx_u8_blend_hard_light (components, dst, src, blended, count); break; + case CTX_BLEND_SOFT_LIGHT: ctx_u8_blend_soft_light (components, dst, src, blended, count); break; + case CTX_BLEND_DIFFERENCE: ctx_u8_blend_difference (components, dst, src, blended, count); break; + case CTX_BLEND_EXCLUSION: ctx_u8_blend_exclusion (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR: ctx_u8_blend_color (components, dst, src, blended, count); break; + case CTX_BLEND_HUE: ctx_u8_blend_hue (components, dst, src, blended, count); break; + case CTX_BLEND_SATURATION: ctx_u8_blend_saturation (components, dst, src, blended, count); break; + case CTX_BLEND_LUMINOSITY: ctx_u8_blend_luminosity (components, dst, src, blended, count); break; + case CTX_BLEND_ADDITION: ctx_u8_blend_addition (components, dst, src, blended, count); break; + case CTX_BLEND_DIVIDE: ctx_u8_blend_divide (components, dst, src, blended, count); break; + case CTX_BLEND_SUBTRACT: ctx_u8_blend_subtract (components, dst, src, blended, count); break; + } +#else + switch (blend) + { + default: ctx_u8_blend_normal (components, dst, src, blended, count); break; + } + +#endif +} + +CTX_INLINE static void +__ctx_u8_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * dst, + uint8_t * src, + int x0, + uint8_t * __restrict__ coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + CtxPorterDuffFactor f_s, f_d; + ctx_porter_duff_factors (compositing_mode, &f_s, &f_d); + CtxGState *gstate = &rasterizer->state->gstate; + uint8_t global_alpha_u8 = gstate->global_alpha_u8; + uint8_t tsrc[components * count]; + int src_step = 0; + + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + src = &tsrc[0]; + fragment (rasterizer, 0, 0, src, 1, 0, 0); + if (blend != CTX_BLEND_NORMAL) + ctx_u8_blend (components, blend, dst, src, src, 1); + } + else + { + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + src = &tsrc[0]; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + fragment (rasterizer, u0, v0, src, count, ud, vd); + if (blend != CTX_BLEND_NORMAL) + ctx_u8_blend (components, blend, dst, src, src, count); + src_step = components; + } + + while (count--) + { + uint32_t cov = *coverage; + + if (CTX_UNLIKELY(global_alpha_u8 != 255)) + cov = (cov * global_alpha_u8 + 255) >> 8; + + uint8_t csrc[components]; + for (int c = 0; c < components; c++) + csrc[c] = (src[c] * cov + 255) >> 8; + + for (int c = 0; c < components; c++) + { + uint32_t res = 0; +#if 1 + switch (f_s) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += (csrc[c] ); break; + case CTX_PORTER_DUFF_ALPHA: res += (csrc[c] * dst[components-1] + 255) >> 8; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (256-dst[components-1])) >> 8; break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += dst[c]; break; + case CTX_PORTER_DUFF_ALPHA: res += (dst[c] * csrc[components-1] + 255) >> 8; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (256-csrc[components-1])) >> 8; break; + } +#else + switch (f_s) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += (csrc[c] ); break; + case CTX_PORTER_DUFF_ALPHA: res += (csrc[c] * dst[components-1])/255; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (255-dst[components-1]))/255; break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += dst[c]; break; + case CTX_PORTER_DUFF_ALPHA: res += (dst[c] * csrc[components-1])/255; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (255-csrc[components-1]))/255; break; + } +#endif + dst[c] = res; + } + coverage ++; + src+=src_step; + dst+=components; + } +} + +CTX_INLINE static void +_ctx_u8_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + __ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, compositing_mode, fragment, blend); +} + +#define _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend) \ + switch (rasterizer->state->gstate.compositing_mode) \ + { \ + case CTX_COMPOSITE_SOURCE_ATOP: \ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \ + CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_XOR:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_XOR, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_COPY:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_COPY, fragment, blend);\ + break;\ + case CTX_COMPOSITE_CLEAR:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_CLEAR, fragment, blend);\ + break;\ + } + +/* generating one function per compositing_mode would be slightly more efficient, + * but on embedded targets leads to slightly more code bloat, + * here we trade off a slight amount of performance + */ +#define ctx_u8_porter_duff(comp_format, components, source, fragment, blend) \ +static void \ +ctx_##comp_format##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \ +{ \ + _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend);\ +} + +ctx_u8_porter_duff(RGBA8, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +//ctx_u8_porter_duff(comp_name, components,color_##blend_name, NULL, blend_mode) + +static void +ctx_RGBA8_nop (CTX_COMPOSITE_ARGUMENTS) +{ +} + + +static void +ctx_setup_RGBA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 4; + rasterizer->fragment = ctx_rasterizer_get_fragment_RGBA8 (rasterizer); + rasterizer->comp_op = ctx_RGBA8_porter_duff_generic; + + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + ctx_fragment_color_RGBA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0); + if (gstate->global_alpha_u8 != 255) + { + for (int c = 0; c < 4; c ++) + rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8 + 255)>>8; + } + + if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color; + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (rasterizer->color[components-1] == 255) + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color; + else + rasterizer->comp_op = ctx_RGBA8_source_over_normal_color; + } + else if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + { + rasterizer->comp_op = ctx_RGBA8_clear_normal; + } + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + rasterizer->comp_op = ctx_RGBA8_source_over_normal_fragment; + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_fragment; + } +} + +static void +ctx_composite_convert (CTX_COMPOSITE_ARGUMENTS) +{ + uint8_t pixels[count * rasterizer->format->ebpp]; + rasterizer->format->to_comp (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + rasterizer->format->from_comp (rasterizer, x0, &pixels[0], dst, count); +} + +#if CTX_ENABLE_FLOAT +static void +ctx_float_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + + while (count--) + { + uint8_t cov = *coverage; + float covf = ctx_u8_to_float (cov); + for (int c = 0; c < components; c++) + dstf[c] = dstf[c]*(1.0-covf) + srcf[c]*covf; + dstf += components; + coverage ++; + } +} + +static void +ctx_float_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + while (count--) + { +#if 0 + uint8_t cov = *coverage; + if (cov == 0) + { + } + else if (cov == 255) + { +#endif + switch (components) + { + case 2: + ((uint64_t*)(dst))[0] = 0; + break; + case 4: + ((uint64_t*)(dst))[0] = 0; + ((uint64_t*)(dst))[1] = 0; + break; + default: + for (int c = 0; c < components; c++) + dstf[c] = 0.0f; + } +#if 0 + } + else + { + float ralpha = 1.0 - ctx_u8_to_float (cov); + for (int c = 0; c < components; c++) + { dstf[c] = (dstf[c] * ralpha); } + } + coverage ++; +#endif + dstf += components; + } +} + + +static void +ctx_float_source_over_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + while (count--) + { + uint8_t cov = *coverage; + float fcov = ctx_u8_to_float (cov); + float ralpha = 1.0f - fcov * srcf[components-1]; + for (int c = 0; c < components-1; c++) + dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha); + coverage ++; + dstf+= components; + } +} + +static void +ctx_float_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + + while (count--) + { + uint8_t cov = *coverage; + float fcov = ctx_u8_to_float (cov); + float ralpha = 1.0f - fcov; + for (int c = 0; c < components-1; c++) + dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha); + coverage ++; + dstf+= components; + } +} + +inline static void +ctx_float_blend_normal (int components, float *dst, float *src, float *blended) +{ + float a = src[components-1]; + for (int c = 0; c < components - 1; c++) + blended[c] = src[c] * a; + blended[components-1]=a; +} + +static float ctx_float_get_max (int components, float *c) +{ + float max = -1000.0f; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] > max) max = c[i]; + } + return max; +} + +static float ctx_float_get_min (int components, float *c) +{ + float min = 400.0; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + } + return min; +} + +static float ctx_float_get_lum (int components, float *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + float sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + } +} + +static float ctx_float_get_sat (int components, float *c) +{ + switch (components) + { + case 3: + case 4: + { float r = c[0]; + float g = c[1]; + float b = c[2]; + return ctx_maxf(r, ctx_maxf(g,b)) - ctx_minf(r,ctx_minf(g,b)); + } + break; + case 1: + case 2: return 0.0; + break; + default: + { + float min = 1000; + float max = -1000; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + if (c[i] > max) max = c[i]; + } + return max-min; + } + } +} + +static void ctx_float_set_lum (int components, float *c, float lum) +{ + float d = lum - ctx_float_get_lum (components, c); + float tc[components]; + for (int i = 0; i < components - 1; i++) + { + tc[i] = c[i] + d; + } + + float l = ctx_float_get_lum (components, tc); + float n = ctx_float_get_min (components, tc); + float x = ctx_float_get_max (components, tc); + + if (n < 0.0f && l != n) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * l) / (l-n)); + } + + if (x > 1.0f && x != l) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * (1.0f - l)) / (x-l)); + } + for (int i = 0; i < components - 1; i++) + c[i] = tc[i]; +} + +static void ctx_float_set_sat (int components, float *c, float sat) +{ + int max = 0, mid = 1, min = 2; + + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + if (c[mid] > c[max]){int t = mid; mid = max; max = t;} + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + + if (c[max] > c[min]) + { + c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]); + c[max] = sat; + } + else + { + c[mid] = c[max] = 0.0f; + } + c[min] = 0.0f; + +} + +#define ctx_float_blend_define(name, CODE) \ +static void \ +ctx_float_blend_##name (int components, float * __restrict__ dst, float *src, float *blended)\ +{\ + float *s = src; float b[components];\ + ctx_float_deassociate_alpha (components, dst, b);\ + CODE;\ + blended[components-1] = s[components-1];\ + ctx_float_associate_alpha (components, blended);\ +} + +#define ctx_float_blend_define_seperable(name, CODE) \ + ctx_float_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \ + +ctx_float_blend_define_seperable(multiply, blended[c] = (b[c] * s[c]);) +ctx_float_blend_define_seperable(screen, blended[c] = b[c] + s[c] - (b[c] * s[c]);) +ctx_float_blend_define_seperable(overlay, blended[c] = b[c] < 0.5f ? (s[c] * b[c]) : + s[c] + b[c] - (s[c] * b[c]);) +ctx_float_blend_define_seperable(darken, blended[c] = ctx_minf (b[c], s[c])) +ctx_float_blend_define_seperable(lighten, blended[c] = ctx_maxf (b[c], s[c])) +ctx_float_blend_define_seperable(color_dodge, blended[c] = (b[c] == 0.0f) ? 0.0f : + s[c] == 1.0f ? 1.0f : ctx_minf(1.0f, (b[c]) / (1.0f-s[c]))) +ctx_float_blend_define_seperable(color_burn, blended[c] = (b[c] == 1.0f) ? 1.0f : + s[c] == 0.0f ? 0.0f : 1.0f - ctx_minf(1.0f, ((1.0f - b[c])) / s[c])) +ctx_float_blend_define_seperable(hard_light, blended[c] = s[c] < 0.f ? (b[c] * s[c]) : + b[c] + s[c] - (b[c] * s[c]);) +ctx_float_blend_define_seperable(difference, blended[c] = (b[c] - s[c])) + +ctx_float_blend_define_seperable(divide, blended[c] = s[c]?(b[c]) / s[c]:0.0f) +ctx_float_blend_define_seperable(addition, blended[c] = s[c]+b[c]) +ctx_float_blend_define_seperable(subtract, blended[c] = s[c]-b[c]) + +ctx_float_blend_define_seperable(exclusion, blended[c] = b[c] + s[c] - 2.0f * b[c] * s[c]) +ctx_float_blend_define_seperable(soft_light, + if (s[c] <= 0.5f) + { + blended[c] = b[c] - (1.0f - 2.0f * s[c]) * b[c] * (1.0f - b[c]); + } + else + { + int d; + if (b[c] <= 255/4) + d = (((16 * b[c] - 12.0f) * b[c] + 4.0f) * b[c]); + else + d = ctx_sqrtf(b[c]); + blended[c] = (b[c] + (2.0f * s[c] - 1.0f) * (d - b[c])); + } +) + + +ctx_float_blend_define(color, + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_float_set_lum(components, blended, ctx_float_get_lum (components, s)); +) + +ctx_float_blend_define(hue, + float in_sat = ctx_float_get_sat(components, b); + float in_lum = ctx_float_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_float_set_sat(components, blended, in_sat); + ctx_float_set_lum(components, blended, in_lum); +) + +ctx_float_blend_define(saturation, + float in_sat = ctx_float_get_sat(components, s); + float in_lum = ctx_float_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_float_set_sat(components, blended, in_sat); + ctx_float_set_lum(components, blended, in_lum); +) + +ctx_float_blend_define(luminosity, + float in_lum = ctx_float_get_lum(components, s); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_float_set_lum(components, blended, in_lum); +) + +inline static void +ctx_float_blend (int components, CtxBlend blend, float * __restrict__ dst, float *src, float *blended) +{ + switch (blend) + { + case CTX_BLEND_NORMAL: ctx_float_blend_normal (components, dst, src, blended); break; + case CTX_BLEND_MULTIPLY: ctx_float_blend_multiply (components, dst, src, blended); break; + case CTX_BLEND_SCREEN: ctx_float_blend_screen (components, dst, src, blended); break; + case CTX_BLEND_OVERLAY: ctx_float_blend_overlay (components, dst, src, blended); break; + case CTX_BLEND_DARKEN: ctx_float_blend_darken (components, dst, src, blended); break; + case CTX_BLEND_LIGHTEN: ctx_float_blend_lighten (components, dst, src, blended); break; + case CTX_BLEND_COLOR_DODGE: ctx_float_blend_color_dodge (components, dst, src, blended); break; + case CTX_BLEND_COLOR_BURN: ctx_float_blend_color_burn (components, dst, src, blended); break; + case CTX_BLEND_HARD_LIGHT: ctx_float_blend_hard_light (components, dst, src, blended); break; + case CTX_BLEND_SOFT_LIGHT: ctx_float_blend_soft_light (components, dst, src, blended); break; + case CTX_BLEND_DIFFERENCE: ctx_float_blend_difference (components, dst, src, blended); break; + case CTX_BLEND_EXCLUSION: ctx_float_blend_exclusion (components, dst, src, blended); break; + case CTX_BLEND_COLOR: ctx_float_blend_color (components, dst, src, blended); break; + case CTX_BLEND_HUE: ctx_float_blend_hue (components, dst, src, blended); break; + case CTX_BLEND_SATURATION: ctx_float_blend_saturation (components, dst, src, blended); break; + case CTX_BLEND_LUMINOSITY: ctx_float_blend_luminosity (components, dst, src, blended); break; + case CTX_BLEND_ADDITION: ctx_float_blend_addition (components, dst, src, blended); break; + case CTX_BLEND_SUBTRACT: ctx_float_blend_subtract (components, dst, src, blended); break; + case CTX_BLEND_DIVIDE: ctx_float_blend_divide (components, dst, src, blended); break; + } +} + +/* this is the grunt working function, when inlined code-path elimination makes + * it produce efficient code. + */ +CTX_INLINE static void +ctx_float_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * __restrict__ dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * __restrict__ coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + float *dstf = (float*)dst; + + CtxPorterDuffFactor f_s, f_d; + ctx_porter_duff_factors (compositing_mode, &f_s, &f_d); + uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8; + float global_alpha_f = rasterizer->state->gstate.global_alpha_f; + + { + float tsrc[components]; + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + + while (count--) + { + uint8_t cov = *coverage; +#if 1 + if ( + CTX_UNLIKELY((compositing_mode == CTX_COMPOSITE_DESTINATION_OVER && dst[components-1] == 1.0f)|| + (cov == 0 && (compositing_mode == CTX_COMPOSITE_SOURCE_OVER || + compositing_mode == CTX_COMPOSITE_XOR || + compositing_mode == CTX_COMPOSITE_DESTINATION_OUT || + compositing_mode == CTX_COMPOSITE_SOURCE_ATOP + )))) + { + u0 += ud; + v0 += vd; + coverage ++; + dstf+=components; + continue; + } +#endif + + fragment (rasterizer, u0, v0, tsrc, 1, ud, vd); + if (blend != CTX_BLEND_NORMAL) + ctx_float_blend (components, blend, dstf, tsrc, tsrc); + u0 += ud; + v0 += vd; + float covf = ctx_u8_to_float (cov); + + if (global_alpha_u8 != 255) + covf = covf * global_alpha_f; + + if (covf != 1.0f) + { + for (int c = 0; c < components; c++) + tsrc[c] *= covf; + } + + for (int c = 0; c < components; c++) + { + float res; + /* these switches and this whole function is written to be + * inlined when compiled when the enum values passed in are + * constants. + */ + switch (f_s) + { + case CTX_PORTER_DUFF_0: res = 0.0f; break; + case CTX_PORTER_DUFF_1: res = (tsrc[c]); break; + case CTX_PORTER_DUFF_ALPHA: res = (tsrc[c] * dstf[components-1]); break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res = (tsrc[c] * (1.0f-dstf[components-1])); break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: dstf[c] = res; break; + case CTX_PORTER_DUFF_1: dstf[c] = res + (dstf[c]); break; + case CTX_PORTER_DUFF_ALPHA: dstf[c] = res + (dstf[c] * tsrc[components-1]); break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: dstf[c] = res + (dstf[c] * (1.0f-tsrc[components-1])); break; + } + } + coverage ++; + dstf +=components; + } + } +} + +/* generating one function per compositing_mode would be slightly more efficient, + * but on embedded targets leads to slightly more code bloat, + * here we trade off a slight amount of performance + */ +#define ctx_float_porter_duff(compformat, components, source, fragment, blend) \ +static void \ +ctx_##compformat##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \ +{ \ + switch (rasterizer->state->gstate.compositing_mode) \ + { \ + case CTX_COMPOSITE_SOURCE_ATOP: \ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \ + CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_XOR:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_XOR, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_COPY:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_COPY, fragment, blend);\ + break;\ + case CTX_COMPOSITE_CLEAR:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_CLEAR, fragment, blend);\ + break;\ + }\ +} +#endif + +#if CTX_ENABLE_RGBAF + +ctx_float_porter_duff(RGBAF, 4,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(RGBAF, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +#if CTX_GRADIENTS +ctx_float_porter_duff(RGBAF, 4,linear_gradient, ctx_fragment_linear_gradient_RGBAF, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(RGBAF, 4,radial_gradient, ctx_fragment_radial_gradient_RGBAF, rasterizer->state->gstate.blend_mode) +#endif +ctx_float_porter_duff(RGBAF, 4,image, ctx_fragment_image_RGBAF, rasterizer->state->gstate.blend_mode) + + +#if CTX_GRADIENTS +#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\ +ctx_float_porter_duff(comp_name, components,color_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,generic_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,linear_gradient_##blend_name, ctx_fragment_linear_gradient_RGBA8, blend_mode)\ +ctx_float_porter_duff(comp_name, components,radial_gradient_##blend_name, ctx_fragment_radial_gradient_RGBA8, blend_mode)\ +ctx_float_porter_duff(comp_name, components,image_##blend_name, ctx_fragment_image_RGBAF, blend_mode) +#else +#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\ +ctx_float_porter_duff(comp_name, components,color_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,generic_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,image_##blend_name, ctx_fragment_image_RGBAF, blend_mode) +#endif + +ctx_float_porter_duff_blend(RGBAF, 4, CTX_BLEND_NORMAL, normal) + + +static void +ctx_RGBAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_RGBAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +#if 1 +static void +ctx_RGBAF_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_over_normal_color (4, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif +#endif + +static void +ctx_setup_RGBAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 4; + rasterizer->fragment = ctx_rasterizer_get_fragment_RGBAF (rasterizer); +#if 1 + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_RGBAF_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; + } + else +#endif + { + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_RGBAF_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + { + rasterizer->comp_op = ctx_RGBA8_nop; + } + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)(rasterizer->color))[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; + // else if (((float*)(rasterizer->color))[components-1] == 0.0f) + else + rasterizer->comp_op = ctx_RGBAF_source_over_normal_color; + //rasterizer->comp_op = ctx_RGBAF_source_over_normal_color; + } + else + { + rasterizer->comp_op = ctx_RGBAF_porter_duff_color_normal; + } + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient_normal; + break; + case CTX_SOURCE_RADIAL_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient_normal; + break; +#endif + case CTX_SOURCE_TEXTURE: + rasterizer->comp_op = ctx_RGBAF_porter_duff_image_normal; + break; + default: + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_RGBAF_porter_duff_color; + //rasterizer->fragment = NULL; + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient; + break; + case CTX_SOURCE_RADIAL_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient; + break; +#endif + case CTX_SOURCE_TEXTURE: + rasterizer->comp_op = ctx_RGBAF_porter_duff_image; + break; + default: + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic; + break; + } + break; + } +#endif +} + +#endif +#if CTX_ENABLE_GRAYAF + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float rgba[4]; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0 ; i < count; i++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0, rgba); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba); + ((float*)out)[1] = rgba[3]; + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} + +static void +ctx_fragment_radial_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float rgba[4]; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = 0.0f; + if ((g->radial_gradient.r1-g->radial_gradient.r0) > 0.0f) + { + v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y); + v = (v - g->radial_gradient.r0) / (g->radial_gradient.rdelta); + } + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0, rgba); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba); + ((float*)out)[1] = rgba[3]; + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} +#endif + +static void +ctx_fragment_color_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + ctx_color_get_graya (rasterizer->state, &g->color, (float*)out); + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} + +static void ctx_fragment_image_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t rgba[4]; + float rgbaf[4]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int c = 0; c < 2 * count; c ++) { + rgbaf[c] = ctx_u8_to_float (rgba[c]); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgbaf); + ((float*)out)[1] = rgbaf[3]; + out = ((float*)out) + 2; + } +} + +static CtxFragment ctx_rasterizer_get_fragment_GRAYAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_GRAYAF; + case CTX_SOURCE_COLOR: return ctx_fragment_color_GRAYAF; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYAF; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYAF; +#endif + } + return ctx_fragment_color_GRAYAF; +} + +ctx_float_porter_duff(GRAYAF, 2,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(GRAYAF, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +ctx_float_porter_duff(GRAYAF, 2,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_float_porter_duff(GRAYAF, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_GRAYAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +static void +ctx_setup_GRAYAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 2; + rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYAF (rasterizer); + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color; + // rasterizer->fragment = NULL; + ctx_color_get_rgba (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color); + if (gstate->global_alpha_u8 != 255) + for (int c = 0; c < components; c ++) + ((float*)rasterizer->color)[c] *= gstate->global_alpha_f; + } + else + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_GRAYAF_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_GRAYAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; +#if 1 + else //if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_GRAYAF_source_copy_normal_color; +#endif + //else + // rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal; +// rasterizer->fragment = NULL; + } + else + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal; +// rasterizer->fragment = NULL; + } + break; + default: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color; +// rasterizer->fragment = NULL; + break; + default: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic; + break; + } + break; + } +#endif +} + +#endif +#if CTX_ENABLE_GRAYF + +static void +ctx_composite_GRAYF (CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + + float temp[count*2]; + for (int i = 0; i < count; i++) + { + temp[i*2] = dstf[i]; + temp[i*2+1] = 1.0f; + } + rasterizer->comp_op (rasterizer, (uint8_t*)temp, rasterizer->color, x0, coverage, count); + for (int i = 0; i < count; i++) + { + dstf[i] = temp[i*2]; + } +} + +#endif +#if CTX_ENABLE_BGRA8 + +inline static void +ctx_swap_red_green (uint8_t *rgba) +{ + uint32_t *buf = (uint32_t *) rgba; + uint32_t orig = *buf; + uint32_t green_alpha = (orig & 0xff00ff00); + uint32_t red_blue = (orig & 0x00ff00ff); + uint32_t red = red_blue << 16; + uint32_t blue = red_blue >> 16; + *buf = green_alpha | red | blue; +} + +static void +ctx_BGRA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + uint32_t *srci = (uint32_t *) buf; + uint32_t *dsti = (uint32_t *) rgba; + while (count--) + { + uint32_t val = *srci++; + ctx_swap_red_green ( (uint8_t *) &val); + *dsti++ = val; + } +} + +static void +ctx_RGBA8_to_BGRA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + ctx_BGRA8_to_RGBA8 (rasterizer, x, rgba, (uint8_t *) buf, count); +} + +static void +ctx_composite_BGRA8 (CTX_COMPOSITE_ARGUMENTS) +{ + // for better performance, this could be done without a pre/post conversion, + // by swapping R and B of source instead... as long as it is a color instead + // of gradient or image + // + // + uint8_t pixels[count * 4]; + ctx_BGRA8_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_BGRA8_to_RGBA8 (rasterizer, x0, &pixels[0], dst, count); +} + + +#endif +#if CTX_ENABLE_CMYKAF + +static void +ctx_fragment_other_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *cmyka = (float*)out; + float _rgba[4 * count]; + float *rgba = &_rgba[0]; + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: + ctx_fragment_image_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; + case CTX_SOURCE_COLOR: + ctx_fragment_color_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + ctx_fragment_linear_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; + case CTX_SOURCE_RADIAL_GRADIENT: + ctx_fragment_radial_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; +#endif + default: + rgba[0]=rgba[1]=rgba[2]=rgba[3]=0.0f; + break; + } + for (int i = 0; i < count; i++) + { + cmyka[4]=rgba[3]; + ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2], &cmyka[0], &cmyka[1], &cmyka[2], &cmyka[3]); + cmyka += 5; + rgba += 4; + } +} + +static void +ctx_fragment_color_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxGState *gstate = &rasterizer->state->gstate; + float *cmyka = (float*)out; + float cmyka_in[5]; + ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, cmyka_in); + for (int i = 0; i < count; i++) + { + for (int c = 0; c < 4; c ++) + { + cmyka[c] = (1.0f - cmyka_in[c]); + } + cmyka[4] = cmyka_in[4]; + cmyka += 5; + } +} + +static CtxFragment ctx_rasterizer_get_fragment_CMYKAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + return ctx_fragment_color_CMYKAF; + } + return ctx_fragment_other_CMYKAF; +} + +ctx_float_porter_duff (CMYKAF, 5,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff (CMYKAF, 5,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +ctx_float_porter_duff (CMYKAF, 5,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_float_porter_duff (CMYKAF, 5,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_CMYKAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (5, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_CMYKAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (5, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_CMYKAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_copy_normal_color (5, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +static void +ctx_setup_CMYKAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 5; + rasterizer->fragment = ctx_rasterizer_get_fragment_CMYKAF (rasterizer); + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color; + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + // rasterizer->fragment = NULL; + ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color); + if (gstate->global_alpha_u8 != 255) + ((float*)rasterizer->color)[components-1] *= gstate->global_alpha_f; + } + else + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_CMYKAF_clear_normal; +#if 1 + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_CMYKAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; +#if 1 + else //if (((float*)rasterizer->color)[components-1] == 1.0f) + rasterizer->comp_op = ctx_CMYKAF_source_copy_normal_color; + // else + // rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal; + rasterizer->fragment = NULL; +#endif + } + else + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal; + // rasterizer->fragment = NULL; + } + break; + default: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color; + // rasterizer->fragment = NULL; + break; + default: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + break; + } + break; + } +#endif +#endif +} + +#endif +#if CTX_ENABLE_CMYKA8 + +static void +ctx_CMYKA8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + for (int c = 0; c < 4; c ++) + { dst[c] = ctx_u8_to_float ( (255-src[c]) ); } + dst[4] = ctx_u8_to_float (src[4]); + for (int c = 0; c < 4; c++) + { dst[c] *= dst[4]; } + src += 5; + dst += 5; + } +} +static void +ctx_CMYKAF_to_CMYKA8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + int a = ctx_float_to_u8 (src[4]); + if (a != 0 && a != 255) + { + float recip = 1.0f/src[4]; + for (int c = 0; c < 4; c++) + { + dst[c] = ctx_float_to_u8 (1.0f - src[c] * recip); + } + } + else + { + for (int c = 0; c < 4; c++) + dst[c] = 255 - ctx_float_to_u8 (src[c]); + } + dst[4]=a; + + src += 5; + dst += 5; + } +} + +static void +ctx_composite_CMYKA8 (CTX_COMPOSITE_ARGUMENTS) +{ + float pixels[count * 5]; + ctx_CMYKA8_to_CMYKAF (rasterizer, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], rasterizer->color, x0, coverage, count); + ctx_CMYKAF_to_CMYKA8 (rasterizer, &pixels[0], dst, count); +} + +#endif +#if CTX_ENABLE_CMYK8 + +static void +ctx_CMYK8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + dst[0] = ctx_u8_to_float (255-src[0]); + dst[1] = ctx_u8_to_float (255-src[1]); + dst[2] = ctx_u8_to_float (255-src[2]); + dst[3] = ctx_u8_to_float (255-src[3]); + dst[4] = 1.0f; + src += 4; + dst += 5; + } +} +static void +ctx_CMYKAF_to_CMYK8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + float c = src[0]; + float m = src[1]; + float y = src[2]; + float k = src[3]; + float a = src[4]; + if (a != 0.0f && a != 1.0f) + { + float recip = 1.0f/a; + c *= recip; + m *= recip; + y *= recip; + k *= recip; + } + c = 1.0 - c; + m = 1.0 - m; + y = 1.0 - y; + k = 1.0 - k; + dst[0] = ctx_float_to_u8 (c); + dst[1] = ctx_float_to_u8 (m); + dst[2] = ctx_float_to_u8 (y); + dst[3] = ctx_float_to_u8 (k); + src += 5; + dst += 4; + } +} + +static void +ctx_composite_CMYK8 (CTX_COMPOSITE_ARGUMENTS) +{ + float pixels[count * 5]; + ctx_CMYK8_to_CMYKAF (rasterizer, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], src, x0, coverage, count); + ctx_CMYKAF_to_CMYK8 (rasterizer, &pixels[0], dst, count); +} +#endif + +#if CTX_ENABLE_RGB8 + +inline static void +ctx_RGB8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (const uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[1]; + rgba[2] = pixel[2]; + rgba[3] = 255; + pixel+=3; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_RGB8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = rgba[0]; + pixel[1] = rgba[1]; + pixel[2] = rgba[2]; + pixel+=3; + rgba +=4; + } +} + +#endif +#if CTX_ENABLE_GRAY1 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY1_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = 255 * (*pixel & (1<< (x&7) ) ); + rgba[1] = 255; + pixel+= ( (x&7) ==7); + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + *pixel = 0; + while (count--) + { + int gray = rgba[0]; + //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127); + if (gray >= 127) + { + *pixel = *pixel | ((1<< (x&7) ) * (gray >= 127)); + } +#if 0 + else + { + *pixel = *pixel & (~ (1<< (x&7) ) ); + } +#endif + if ( (x&7) ==7) + { pixel+=1; + if(count>0)*pixel = 0; + } + x++; + rgba +=2; + } +} + +#else + +inline static void +ctx_GRAY1_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + *((uint32_t*)(rgba))=0xff000000 + 0x00ffffff * ((*pixel & (1<< (x&7) ) )!=0); + pixel+= ( (x&7) ==7); + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + *pixel = 0; + while (count--) + { + int gray = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127); + if (gray <= 127) + { + //*pixel = *pixel & (~ (1<< (x&7) ) ); + } + else + { + *pixel = *pixel | (1<< (x&7) ); + } + if ( (x&7) ==7) + { pixel+=1; + if(count>0)*pixel = 0; + } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY2 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY2_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1); + val <<= 6; + rgba[0] = val; + rgba[1] = 255; + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = rgba[0]; + val >>= 6; + *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) ); + *pixel = *pixel | ( (val << ( (x&3) <<1) ) ); + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=2; + } +} +#else + +inline static void +ctx_GRAY2_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1); + val <<= 6; + rgba[0] = val; + rgba[1] = val; + rgba[2] = val; + rgba[3] = 255; + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + val >>= 6; + *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) ); + *pixel = *pixel | ( (val << ( (x&3) <<1) ) ); + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY4 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY4_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2); + val <<= 4; + rgba[0] = val; + rgba[1] = 255; + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = rgba[0]; + val >>= 4; + *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) ); + *pixel = *pixel | ( (val << ( (x&1) <<2) ) ); + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=2; + } +} +#else +inline static void +ctx_GRAY4_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2); + val <<= 4; + rgba[0] = val; + rgba[1] = val; + rgba[2] = val; + rgba[3] = 255; + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + val >>= 4; + *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) ); + *pixel = *pixel | ( (val << ( (x&1) <<2) ) ); + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY8 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = 255; + pixel+=1; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = rgba[0]; + pixel+=1; + rgba +=2; + } +} +#else +inline static void +ctx_GRAY8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[0]; + rgba[2] = pixel[0]; + rgba[3] = 255; + pixel+=1; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + for (int i = 0; i < count; i ++) + { + pixel[i] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba + i * 4); + } +} +#endif + +#endif +#if CTX_ENABLE_GRAYA8 + +inline static void +ctx_GRAYA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (const uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[0]; + rgba[2] = pixel[0]; + rgba[3] = pixel[1]; + pixel+=2; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + pixel[1] = rgba[3]; + pixel+=2; + rgba +=4; + } +} + +#if CTX_NATIVE_GRAYA8 +CTX_INLINE static void ctx_rgba_to_graya_u8 (CtxState *state, uint8_t *in, uint8_t *out) +{ + out[0] = ctx_u8_color_rgb_to_gray (state, in); + out[1] = in[3]; +} + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + uint8_t *dst = (uint8_t*)out; + for (int i = 0; i < count;i ++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + { + uint8_t rgba[4]; + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); + ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst); + + } + + +#if CTX_DITHER + ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + dst += 2; + x += dx; + y += dy; + } +} + +#if 0 +static void +ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif +} +#endif + + +static void +ctx_fragment_radial_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *dst = (uint8_t*)out; + for (int i = 0; i < count;i ++) + { + CtxSource *g = &rasterizer->state->gstate.source_fill; + float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); + { + uint8_t rgba[4]; + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); + ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst); + } +#if CTX_DITHER + ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + dst += 2; + x += dx; + y += dy; + } +} +#endif + +static void +ctx_fragment_color_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + uint16_t *dst = (uint16_t*)out; + uint16_t pix; + ctx_color_get_graya_u8 (rasterizer->state, &g->color, (void*)&pix); + for (int i = 0; i <count; i++) + { + dst[i]=pix; + } +} + +static void ctx_fragment_image_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t rgba[4*count]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int i = 0; i < count; i++) + ctx_rgba_to_graya_u8 (rasterizer->state, &rgba[i*4], &((uint8_t*)out)[i*2]); +} + +static CtxFragment ctx_rasterizer_get_fragment_GRAYA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_GRAYA8; + case CTX_SOURCE_COLOR: return ctx_fragment_color_GRAYA8; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYA8; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYA8; +#endif + } + return ctx_fragment_color_GRAYA8; +} + +//ctx_u8_porter_duff(GRAYA8, 2,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_u8_porter_duff(GRAYA8, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +//ctx_u8_porter_duff(GRAYA8, 2,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_u8_porter_duff(GRAYA8, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_GRAYA8_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_copy_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYA8_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_clear_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_source_over_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} + +static void +ctx_GRAYA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +inline static int +ctx_is_opaque_color (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + if (gstate->global_alpha_u8 != 255) + return 0; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + uint8_t ga[2]; + ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga); + return ga[1] == 255; + } + return 0; +} + +static void +ctx_setup_GRAYA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 2; + rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYA8 (rasterizer); + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + ctx_fragment_color_GRAYA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0); + if (gstate->global_alpha_u8 != 255) + for (int c = 0; c < components; c ++) + rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8)/255; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_GRAYA8_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_GRAYA8_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (rasterizer->color[components-1] == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else if (rasterizer->color[components-1] == 255) + rasterizer->comp_op = ctx_GRAYA8_source_copy_normal_color; + else + rasterizer->comp_op = ctx_GRAYA8_source_over_normal_color; + } + else + { + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal; + } + break; + default: + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal; + break; + } + break; + default: + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic; + break; + } +#endif +} +#endif + +#endif +#if CTX_ENABLE_RGB332 + +inline static void +ctx_332_unpack (uint8_t pixel, + uint8_t *red, + uint8_t *green, + uint8_t *blue) +{ + *blue = (pixel & 3) <<6; + *green = ( (pixel >> 2) & 7) <<5; + *red = ( (pixel >> 5) & 7) <<5; + if (*blue > 223) { *blue = 255; } + if (*green > 223) { *green = 255; } + if (*red > 223) { *red = 255; } +} + +static inline uint8_t +ctx_332_pack (uint8_t red, + uint8_t green, + uint8_t blue) +{ + uint8_t c = (red >> 5) << 5; + c |= (green >> 5) << 2; + c |= (blue >> 6); + return c; +} + +static inline void +ctx_RGB332_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + ctx_332_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2]); +#if CTX_RGB332_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } + else +#endif + { rgba[3] = 255; } + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB332 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { +#if CTX_RGB332_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_332_pack (255, 0, 255); } + else +#endif + { pixel[0] = ctx_332_pack (rgba[0], rgba[1], rgba[2]); } + pixel+=1; + rgba +=4; + } +} + +#endif +#if CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED + +static inline void +ctx_565_unpack (const uint16_t pixel, + uint8_t *red, + uint8_t *green, + uint8_t *blue, + const int byteswap) +{ + uint16_t byteswapped; + if (byteswap) + { byteswapped = (pixel>>8) | (pixel<<8); } + else + { byteswapped = pixel; } + *blue = (byteswapped & 31) <<3; + *green = ( (byteswapped>>5) & 63) <<2; + *red = ( (byteswapped>>11) & 31) <<3; +#if 0 + if (*blue > 248) { *blue = 255; } + if (*green > 248) { *green = 255; } + if (*red > 248) { *red = 255; } +#endif +} + +static inline uint32_t +ctx_565_unpack_32 (const uint16_t pixel, + const int byteswap) +{ + uint16_t byteswapped; + if (byteswap) + { byteswapped = (pixel>>8) | (pixel<<8); } + else + { byteswapped = pixel; } + uint8_t blue = (byteswapped & 31) <<3; + uint8_t green = ( (byteswapped>>5) & 63) <<2; + uint8_t red = ( (byteswapped>>11) & 31) <<3; +#if 0 + if (*blue > 248) { *blue = 255; } + if (*green > 248) { *green = 255; } + if (*red > 248) { *red = 255; } +#endif + return red + (green << 8) + (blue << 16) + (0xff << 24); +} + +static inline uint16_t +ctx_565_pack (const uint8_t red, + const uint8_t green, + const uint8_t blue, + const int byteswap) +{ + uint32_t c = (red >> 3) << 11; + c |= (green >> 2) << 5; + c |= blue >> 3; + if (byteswap) + { return (c>>8) | (c<<8); } /* swap bytes */ + return c; +} + +static inline uint16_t +ctx_888_to_565 (uint32_t in, int byteswap) +{ + uint8_t *rgb=(uint8_t*)(&in); + return ctx_565_pack (rgb[0],rgb[1],rgb[2], byteswap); +} + +static inline uint32_t +ctx_565_to_888 (uint16_t in, int byteswap) +{ + uint32_t ret = 0; + uint8_t *rgba=(uint8_t*)&ret; + ctx_565_unpack (in, + &rgba[0], + &rgba[1], + &rgba[2], + byteswap); + return ret; +} + +#endif +#if CTX_ENABLE_RGB565 + + +static inline void +ctx_RGB565_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint16_t *pixel = (uint16_t *) buf; + while (count--) + { + ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 0); +#if CTX_RGB565_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } +#endif + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB565 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint16_t *pixel = (uint16_t *) buf; + while (count--) + { +#if CTX_RGB565_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_565_pack (255, 0, 255, 0); } + else +#endif + { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 0); } + pixel+=1; + rgba +=4; + } +} + +static void +ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS); +static void +ctx_RGBA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS); + +static void +ctx_composite_RGB565 (CTX_COMPOSITE_ARGUMENTS) +{ +#if 0 + if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color)) + { + int byteswap = 0; + uint32_t si = *((uint32_t*)(src)); + uint16_t si_16 = ctx_888_to_565 (si, byteswap); + uint32_t sval = (si_16 & ( (31 << 11 ) | 31)); + uint32_t sg = (si_16 & (63 << 5)) >> 5; + uint32_t si_a = si >> (24 + 3); + while (count--) + { + uint32_t di_16 = *((uint16_t*)(dst)); + uint32_t cov = (*coverage) >> 3; + uint32_t racov = (32-((31+si_a*cov)>>5)); + uint32_t dval = (di_16 & ( (31 << 11 ) | 31)); + uint32_t dg = (di_16 >> 5) & 63; // faster outside than + // remerged as part of dval + *((uint16_t*)(dst)) = + (( + (((sval * cov) + (dval * racov)) >> 5) + ) & ((31 << 11 )|31)) | + ((((sg * cov) + (dg * racov)) & 63) ); + + } + return; + } +#endif + uint8_t pixels[count * 4]; + ctx_RGB565_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_RGBA8_to_RGB565 (rasterizer, x0, &pixels[0], dst, count); +} +#endif +#if CTX_ENABLE_RGB565_BYTESWAPPED + +static inline void +ctx_RGB565_BS_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint16_t *pixel = (uint16_t *) buf; + while (count--) + { + //ctx_565_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2], 1); + ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 1); +#if CTX_RGB565_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } + else + { rgba[3] = 255; } +#endif + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB565_BS (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint16_t *pixel = (uint16_t *) buf; + while (count--) + { +#if CTX_RGB565_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_565_pack (255, 0, 255, 1); } + else +#endif + { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 1); } + pixel+=1; + rgba +=4; + } +} + +static void +ctx_composite_RGB565_BS (CTX_COMPOSITE_ARGUMENTS) +{ + uint8_t pixels[count * 4]; + ctx_RGB565_BS_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_RGBA8_to_RGB565_BS (rasterizer, x0, &pixels[0], dst, count); +} +#endif + +static CtxPixelFormatInfo ctx_pixel_formats[]= +{ +#if CTX_ENABLE_RGBA8 + { + CTX_FORMAT_RGBA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8, + NULL, NULL, NULL, ctx_setup_RGBA8 + }, +#endif +#if CTX_ENABLE_BGRA8 + { + CTX_FORMAT_BGRA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_BGRA8_to_RGBA8, ctx_RGBA8_to_BGRA8, ctx_composite_BGRA8, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_GRAYF + { + CTX_FORMAT_GRAYF, 1, 32, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF, + NULL, NULL, ctx_composite_GRAYF, ctx_setup_GRAYAF, + }, +#endif +#if CTX_ENABLE_GRAYAF + { + CTX_FORMAT_GRAYAF, 2, 64, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF, + NULL, NULL, NULL, ctx_setup_GRAYAF, + }, +#endif +#if CTX_ENABLE_RGBAF + { + CTX_FORMAT_RGBAF, 4, 128, 4 * 4, 0, 0, CTX_FORMAT_RGBAF, + NULL, NULL, NULL, ctx_setup_RGBAF, + }, +#endif +#if CTX_ENABLE_RGB8 + { + CTX_FORMAT_RGB8, 3, 24, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_RGB8_to_RGBA8, ctx_RGBA8_to_RGB8, ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_GRAY1 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY1, 1, 1, 2, 1, 1, CTX_FORMAT_GRAYA8, + ctx_GRAY1_to_GRAYA8, ctx_GRAYA8_to_GRAY1, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY1, 1, 1, 4, 1, 1, CTX_FORMAT_RGBA8, + ctx_GRAY1_to_RGBA8, ctx_RGBA8_to_GRAY1, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY2 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY2, 1, 2, 2, 4, 4, CTX_FORMAT_GRAYA8, + ctx_GRAY2_to_GRAYA8, ctx_GRAYA8_to_GRAY2, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY2, 1, 2, 4, 4, 4, CTX_FORMAT_RGBA8, + ctx_GRAY2_to_RGBA8, ctx_RGBA8_to_GRAY2, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY4 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY4, 1, 4, 2, 16, 16, CTX_FORMAT_GRAYA8, + ctx_GRAY4_to_GRAYA8, ctx_GRAYA8_to_GRAY4, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY4, 1, 4, 4, 16, 16, CTX_FORMAT_GRAYA8, + ctx_GRAY4_to_RGBA8, ctx_RGBA8_to_GRAY4, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY8 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY8, 1, 8, 2, 0, 0, CTX_FORMAT_GRAYA8, + ctx_GRAY8_to_GRAYA8, ctx_GRAYA8_to_GRAY8, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY8, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_GRAY8_to_RGBA8, ctx_RGBA8_to_GRAY8, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAYA8 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAYA8, 2, 16, 2, 0, 0, CTX_FORMAT_GRAYA8, + ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, NULL, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAYA8, 2, 16, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_RGB332 + { + CTX_FORMAT_RGB332, 3, 8, 4, 10, 12, CTX_FORMAT_RGBA8, + ctx_RGB332_to_RGBA8, ctx_RGBA8_to_RGB332, + ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_RGB565 + { + CTX_FORMAT_RGB565, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8, + ctx_RGB565_to_RGBA8, ctx_RGBA8_to_RGB565, + ctx_composite_RGB565, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_RGB565_BYTESWAPPED + { + CTX_FORMAT_RGB565_BYTESWAPPED, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8, + ctx_RGB565_BS_to_RGBA8, + ctx_RGBA8_to_RGB565_BS, + ctx_composite_RGB565_BS, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_CMYKAF + { + CTX_FORMAT_CMYKAF, 5, 160, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, NULL, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_CMYKA8 + { + CTX_FORMAT_CMYKA8, 5, 40, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, ctx_composite_CMYKA8, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_CMYK8 + { + CTX_FORMAT_CMYK8, 5, 32, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, ctx_composite_CMYK8, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_YUV420 + { + CTX_FORMAT_YUV420, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8, + NULL, NULL, ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif + { + CTX_FORMAT_NONE + } +}; + + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format) +{ + for (unsigned int i = 0; ctx_pixel_formats[i].pixel_format; i++) + { + if (ctx_pixel_formats[i].pixel_format == format) + { + return &ctx_pixel_formats[i]; + } + } + return NULL; +} + +#endif +#if CTX_RASTERIZER +#define CTX_AA_HALFSTEP2 (CTX_FULL_AA/2) +#define CTX_AA_HALFSTEP ((CTX_FULL_AA/2)+1) + +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer); + +static inline void +_ctx_setup_compositor (CtxRasterizer *rasterizer) +{ + if (CTX_UNLIKELY (rasterizer->comp_op==0)) + { + rasterizer->format->setup (rasterizer); + } +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE + switch (rasterizer->state->gstate.source_fill.type) + { + case CTX_SOURCE_LINEAR_GRADIENT: + case CTX_SOURCE_RADIAL_GRADIENT: + ctx_gradient_cache_prime (rasterizer); + break; + case CTX_SOURCE_TEXTURE: + if (!rasterizer->state->gstate.source_fill.texture.buffer->color_managed) + _ctx_texture_prepare_color_management (rasterizer, + rasterizer->state->gstate.source_fill.texture.buffer); + break; + } +#endif +#endif +} + +#define CTX_FULL_AA 15 +inline static void +ctx_rasterizer_apply_coverage (CtxRasterizer *rasterizer, + uint8_t * dst, + int x, + uint8_t * coverage, + int count) +{ + if (CTX_UNLIKELY(rasterizer->format->apply_coverage)) + rasterizer->format->apply_coverage(rasterizer, dst, rasterizer->color, x, coverage, count); + else + /* it is faster to dispatch in this condition, than using a shared + * direct trampoline + */ + rasterizer->comp_op (rasterizer, dst, rasterizer->color, x, coverage, count); +} + +static void +ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba) +{ + /* FIXME XXX we only have one gradient, but might need separate gradients + * for fill/stroke ! + * + */ + CtxGradient *gradient = &rasterizer->state->gradient; + CtxGradientStop *stop = &gradient->stops[gradient->n_stops]; + stop->pos = pos; + ctx_color_set_rgba (rasterizer->state, & (stop->color), rgba[0], rgba[1], rgba[2], rgba[3]); + if (gradient->n_stops < 15) //we'll keep overwriting the last when out of stops + { gradient->n_stops++; } +} + +static inline int ctx_rasterizer_add_point (CtxRasterizer *rasterizer, int x1, int y1) +{ + CtxSegment entry = {CTX_EDGE, {{0},}}; + rasterizer->scan_min = ctx_mini (y1, rasterizer->scan_min); + rasterizer->scan_max = ctx_maxi (y1, rasterizer->scan_max); + + rasterizer->col_min = ctx_mini (x1, rasterizer->col_min); + rasterizer->col_max = ctx_maxi (x1, rasterizer->col_max); + + entry.data.s16[2]=x1; + entry.data.s16[3]=y1; + return ctx_drawlist_add_single (&rasterizer->edge_list, (CtxEntry*)&entry); +} + +#if 0 +#define CTX_SHAPE_CACHE_PRIME1 7853 +#define CTX_SHAPE_CACHE_PRIME2 4129 +#define CTX_SHAPE_CACHE_PRIME3 3371 +#define CTX_SHAPE_CACHE_PRIME4 4221 +#else +#define CTX_SHAPE_CACHE_PRIME1 283 +#define CTX_SHAPE_CACHE_PRIME2 599 +#define CTX_SHAPE_CACHE_PRIME3 101 +#define CTX_SHAPE_CACHE_PRIME4 661 +#endif + +float ctx_shape_cache_rate = 0.0; +#if CTX_SHAPE_CACHE +int _ctx_shape_cache_enabled = 1; + +//static CtxShapeCache ctx_cache = {{NULL,}, 0}; + +static long ctx_shape_cache_hits = 0; +static long ctx_shape_cache_misses = 0; + + +/* this returns the buffer to use for rendering, it always + succeeds.. + */ +static inline CtxShapeEntry *ctx_shape_entry_find (CtxRasterizer *rasterizer, uint32_t hash, int width, int height) +{ + /* use both some high and some low bits */ + int entry_no = ( (hash >> 10) ^ (hash & 1023) ) % CTX_SHAPE_CACHE_ENTRIES; + int i; + { + static int i = 0; + i++; + if (i>1000) + { + ctx_shape_cache_rate = ctx_shape_cache_hits * 100.0 / (ctx_shape_cache_hits+ctx_shape_cache_misses); + i = 0; + ctx_shape_cache_hits = 0; + ctx_shape_cache_misses = 0; + } + } +// XXX : this 1 one is needed to silence a false positive: +// ==90718== Invalid write of size 1 +// ==90718== at 0x1189EF: ctx_rasterizer_generate_coverage (ctx.h:4786) +// ==90718== by 0x118E57: ctx_rasterizer_rasterize_edges (ctx.h:4907) +// + int size = sizeof (CtxShapeEntry) + width * height + 1; + + i = entry_no; + if (rasterizer->shape_cache.entries[i]) + { + CtxShapeEntry *entry = rasterizer->shape_cache.entries[i]; + int old_size = sizeof (CtxShapeEntry) + width + height + 1; + if (entry->hash == hash && + entry->width == width && + entry->height == height) + { + if (entry->uses < 1<<30) + { entry->uses++; } + ctx_shape_cache_hits ++; + return entry; + } + + if (old_size >= size) + { + } + else + { + rasterizer->shape_cache.entries[i] = NULL; + rasterizer->shape_cache.size -= entry->width * entry->height; + rasterizer->shape_cache.size -= sizeof (CtxShapeEntry); + free (entry); + rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1); + } + } + else + { + rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1); + } + + ctx_shape_cache_misses ++; + rasterizer->shape_cache.size += size; + rasterizer->shape_cache.entries[i]->hash = hash; + rasterizer->shape_cache.entries[i]->width = width; + rasterizer->shape_cache.entries[i]->height = height; + rasterizer->shape_cache.entries[i]->uses = 0; + return rasterizer->shape_cache.entries[i]; +} + +#endif + +static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer) +{ + int x = 0; + int y = 0; + + CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0]; + + int ox = entry->data.s16[2]; + int oy = entry->data.s16[3]; + uint32_t hash = rasterizer->edge_list.count; + hash = ox;//(ox % CTX_SUBDIV); + hash *= CTX_SHAPE_CACHE_PRIME1; + hash += oy; //(oy % CTX_RASTERIZER_AA); + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[i]; + x = entry->data.s16[2]; + y = entry->data.s16[3]; + int dx = x-ox; + int dy = y-oy; + ox = x; + oy = y; + hash *= CTX_SHAPE_CACHE_PRIME3; + hash += dx; + hash *= CTX_SHAPE_CACHE_PRIME4; + hash += dy; + } + return hash; +} + +static uint32_t ctx_rasterizer_poly_to_edges (CtxRasterizer *rasterizer) +{ + int x = 0; + int y = 0; + if (rasterizer->edge_list.count == 0) + return 0; +#if CTX_SHAPE_CACHE + CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0]; + int ox = entry->data.s16[2]; + int oy = entry->data.s16[3]; + uint32_t hash = rasterizer->edge_list.count; + hash = (ox & CTX_SUBDIV); + hash *= CTX_SHAPE_CACHE_PRIME1; + hash += (oy & CTX_SUBDIV); +#endif + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[i]; + if (CTX_UNLIKELY (entry->code == CTX_NEW_EDGE)) + { + entry->code = CTX_EDGE; +#if CTX_SHAPE_CACHE + hash *= CTX_SHAPE_CACHE_PRIME2; +#endif + } + else + { + entry->data.s16[0] = x; + entry->data.s16[1] = y; + } + x = entry->data.s16[2]; + y = entry->data.s16[3]; +#if CTX_SHAPE_CACHE + 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 (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]); + } + } +#if CTX_SHAPE_CACHE + return hash; +#else + return 0; +#endif +} + +static inline void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer) +{ + if (rasterizer->has_shape && rasterizer->has_prev) + { + ctx_rasterizer_line_to (rasterizer, rasterizer->first_x, rasterizer->first_y); + rasterizer->has_prev = 0; + } +} + +static inline void ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y) +{ + float tx = x; float ty = y; + int aa = 15;//rasterizer->aa; + rasterizer->x = x; + rasterizer->y = y; + rasterizer->first_x = x; + rasterizer->first_y = y; + rasterizer->has_prev = -1; + if (rasterizer->uses_transforms) + { + _ctx_user_to_device (rasterizer->state, &tx, &ty); + } + + tx = (tx - rasterizer->blit_x) * CTX_SUBDIV; + ty = ty * aa; + + rasterizer->scan_min = ctx_mini (ty, rasterizer->scan_min); + rasterizer->scan_max = ctx_maxi (ty, rasterizer->scan_max); + + rasterizer->col_min = ctx_mini (tx, rasterizer->col_min); + rasterizer->col_max = ctx_maxi (tx, rasterizer->col_max); +} + +static inline void ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y) +{ + float tx = x; + float ty = y; + float ox = rasterizer->x; + float oy = rasterizer->y; + if ((rasterizer->uses_transforms)) + { + _ctx_user_to_device (rasterizer->state, &tx, &ty); + } + tx -= rasterizer->blit_x; +#define MIN_Y -1000 +#define MAX_Y 1400 + + ty = ctx_maxf (MIN_Y, ty); + ty = ctx_minf (MAX_Y, ty); + + + ctx_rasterizer_add_point (rasterizer, tx * CTX_SUBDIV, ty * CTX_FULL_AA);//rasterizer->aa); + + if (CTX_UNLIKELY(rasterizer->has_prev<=0)) + { + if ((rasterizer->uses_transforms)) + { + // storing transformed would save some processing for a tiny + // amount of runtime RAM XXX + _ctx_user_to_device (rasterizer->state, &ox, &oy); + } + ox -= rasterizer->blit_x; + oy = ctx_maxf (oy, MIN_Y); + oy = ctx_minf (oy, MAX_Y); + + CtxSegment *entry = & ((CtxSegment*)rasterizer->edge_list.entries)[rasterizer->edge_list.count-1]; + + entry->data.s16[0] = ox * CTX_SUBDIV; + entry->data.s16[1] = oy * CTX_FULL_AA; + entry->code = CTX_NEW_EDGE; + rasterizer->has_prev = 1; + } + rasterizer->has_shape = 1; + rasterizer->y = y; + rasterizer->x = x; +} + + +CTX_INLINE static float +ctx_bezier_sample_1d (float x0, float x1, float x2, float x3, float dt) +{ + float ab = ctx_lerpf (x0, x1, dt); + float bc = ctx_lerpf (x1, x2, dt); + float cd = ctx_lerpf (x2, x3, dt); + float abbc = ctx_lerpf (ab, bc, dt); + float bccd = ctx_lerpf (bc, cd, dt); + return ctx_lerpf (abbc, bccd, dt); +} + +inline static void +ctx_bezier_sample (float x0, float y0, + float x1, float y1, + float x2, float y2, + float x3, float y3, + float dt, float *x, float *y) +{ + *x = ctx_bezier_sample_1d (x0, x1, x2, x3, dt); + *y = ctx_bezier_sample_1d (y0, y1, y2, y3, dt); +} + +static inline void +ctx_rasterizer_bezier_divide (CtxRasterizer *rasterizer, + float ox, float oy, + float x0, float y0, + float x1, float y1, + float x2, float y2, + float sx, float sy, + float ex, float ey, + float s, + float e, + int iteration, + float tolerance) +{ + float t = (s + e) * 0.5f; + float x, y, lx, ly, dx, dy; + ctx_bezier_sample (ox, oy, x0, y0, x1, y1, x2, y2, t, &x, &y); + if (iteration) + { + lx = ctx_lerpf (sx, ex, t); + ly = ctx_lerpf (sy, ey, t); + dx = lx - x; + dy = ly - y; + if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance)) + /* bailing - because for the mid-point straight line difference is + tiny */ + { return; } + dx = sx - ex; + dy = ey - ey; + if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance)) + /* bailing on tiny segments */ + { return; } + } + if (iteration < 8) + { + ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2, + sx, sy, x, y, s, t, iteration + 1, + tolerance); + ctx_rasterizer_line_to (rasterizer, x, y); + ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2, + x, y, ex, ey, t, e, iteration + 1, + tolerance); + } +} + +static void +ctx_rasterizer_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + float tolerance = + 2.0f/(ctx_pow2 (rasterizer->state->gstate.transform.m[0][0]) + + ctx_pow2 (rasterizer->state->gstate.transform.m[1][1])); + //float tolerance = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + float ox = rasterizer->x; + float oy = rasterizer->y; + ox = rasterizer->state->x; + oy = rasterizer->state->y; + //tolerance = 10.0/(tolerance*tolerance); + //tolerance = 10.0f/tolerance; +#if 0 // skipping this to preserve hash integrity + if (tolerance == 1.0f || 1) + { + float maxx = ctx_maxf (x1,x2); + maxx = ctx_maxf (maxx, ox); + maxx = ctx_maxf (maxx, x0); + float maxy = ctx_maxf (y1,y2); + maxy = ctx_maxf (maxy, oy); + maxy = ctx_maxf (maxy, y0); + float minx = ctx_minf (x1,x2); + minx = ctx_minf (minx, ox); + minx = ctx_minf (minx, x0); + float miny = ctx_minf (y1,y2); + miny = ctx_minf (miny, oy); + miny = ctx_minf (miny, y0); + + _ctx_user_to_device (rasterizer->state, &minx, &miny); + _ctx_user_to_device (rasterizer->state, &maxx, &maxy); +#if 1 + if( + (minx > rasterizer->blit_x + rasterizer->blit_width) || + (miny > rasterizer->blit_y + rasterizer->blit_height) || + (maxx < rasterizer->blit_x) || + (maxy < rasterizer->blit_y) ) + { + } + else +#endif + { + ctx_rasterizer_bezier_divide (rasterizer, + ox, oy, x0, y0, + x1, y1, x2, y2, + ox, oy, x2, y2, + 0.0f, 1.0f, 0.0f, tolerance); + } + } + else +#endif + { + ctx_rasterizer_bezier_divide (rasterizer, + ox, oy, x0, y0, + x1, y1, x2, y2, + ox, oy, x2, y2, + 0.0f, 1.0f, 0.0f, tolerance); + } + ctx_rasterizer_line_to (rasterizer, x2, y2); +} + +static void +ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y) +{ + //if (CTX_UNLIKELY(x == 0.f && y == 0.f)) + //{ return; } + x += rasterizer->x; + y += rasterizer->y; + ctx_rasterizer_move_to (rasterizer, x, y); +} + +static void +ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y) +{ + //if (CTX_UNLIKELY(x== 0.f && y==0.f)) + // { return; } + x += rasterizer->x; + y += rasterizer->y; + ctx_rasterizer_line_to (rasterizer, x, y); +} + +static void +ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, float x1, float y1, float x2, float y2) +{ + x0 += rasterizer->x; + y0 += rasterizer->y; + x1 += rasterizer->x; + y1 += rasterizer->y; + x2 += rasterizer->x; + y2 += rasterizer->y; + ctx_rasterizer_curve_to (rasterizer, x0, y0, x1, y1, x2, y2); +} + + +static int +ctx_rasterizer_find_texture (CtxRasterizer *rasterizer, + const char *eid) +{ + int no; + for (no = 0; no < CTX_MAX_TEXTURES; no++) + { + if (rasterizer->texture_source->texture[no].data && + rasterizer->texture_source->texture[no].eid && + !strcmp (rasterizer->texture_source->texture[no].eid, eid)) + return no; + } + return -1; +} + +static void +ctx_rasterizer_set_texture (CtxRasterizer *rasterizer, + const char *eid, + float x, + float y) +{ + int is_stroke = (rasterizer->state->source != 0); + CtxSource *source = is_stroke && (rasterizer->state->gstate.source_stroke.type != CTX_SOURCE_INHERIT_FILL)? + &rasterizer->state->gstate.source_stroke: + &rasterizer->state->gstate.source_fill; + rasterizer->state->source = 0; + + int no = ctx_rasterizer_find_texture (rasterizer, eid); + if (no < 0 || no >= CTX_MAX_TEXTURES) { no = 0; } + if (rasterizer->texture_source->texture[no].data == NULL) + { + fprintf (stderr, "ctx tex fail %p %s %i\n", rasterizer->texture_source, eid, no); + return; + } + else + { + rasterizer->texture_source->texture[no].frame = rasterizer->texture_source->frame; + } + source->type = CTX_SOURCE_TEXTURE; + source->texture.buffer = &rasterizer->texture_source->texture[no]; + source->texture.x0 = 0; + source->texture.y0 = 0; + source->transform = rasterizer->state->gstate.transform; + ctx_matrix_translate (&source->transform, x, y); + ctx_matrix_invert (&source->transform); +} + + +static void ctx_rasterizer_define_texture (CtxRasterizer *rasterizer, + const char *eid, + int width, + int height, + int format, + char unsigned *data) +{ + _ctx_texture_lock (); // we're using the same texture_source from all threads, keeping allocaitons down + // need synchronizing (it could be better to do a pre-pass) + ctx_texture_init (rasterizer->texture_source, + eid, + width, + height, + ctx_pixel_format_get_stride ((CtxPixelFormat)format, width), + (CtxPixelFormat)format, +#if CTX_ENABLE_CM + (void*)rasterizer->state->gstate.texture_space, +#else + NULL, +#endif + data, + ctx_buffer_pixels_free, (void*)23); + /* when userdata for ctx_buffer_pixels_free is 23, texture_init dups the data on + * use + */ + + ctx_rasterizer_set_texture (rasterizer, eid, 0.0, 0.0); + _ctx_texture_unlock (); +} + + +CTX_INLINE static int ctx_compare_edges (const void *ap, const void *bp) +{ + const CtxSegment *a = (const CtxSegment *) ap; + const CtxSegment *b = (const CtxSegment *) bp; + return a->data.s16[1] - b->data.s16[1]; +} + +CTX_INLINE static int ctx_edge_qsort_partition (CtxSegment *A, int low, int high) +{ + CtxSegment pivot = A[ (high+low) /2]; + int i = low; + int j = high; + while (i <= j) + { + while (ctx_compare_edges (&A[i], &pivot) < 0) { i ++; } + while (ctx_compare_edges (&pivot, &A[j]) < 0) { j --; } + if (i <= j) + { + CtxSegment tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + i++; + j--; + } + } + return i; +} + +static inline void ctx_edge_qsort (CtxSegment *entries, int low, int high) +{ + { + int p = ctx_edge_qsort_partition (entries, low, high); + if (low < p -1 ) + { ctx_edge_qsort (entries, low, p - 1); } + if (low < high) + { ctx_edge_qsort (entries, p, high); } + } +} + +static CTX_INLINE void ctx_rasterizer_sort_edges (CtxRasterizer *rasterizer) +{ + ctx_edge_qsort ((CtxSegment*)& (rasterizer->edge_list.entries[0]), 0, rasterizer->edge_list.count-1); +} + +//extern int _ctx_fast_aa; + +static inline void ctx_rasterizer_discard_edges (CtxRasterizer *rasterizer) +{ + int scanline = rasterizer->scanline; +#if CTX_ONLY_FAST_AA + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#else + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3; + if (rasterizer->fast_aa) + limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#endif + for (int i = 0; i < rasterizer->active_edges; i++) + { + int edge_end = ((CtxSegment*)(rasterizer->edge_list.entries))[rasterizer->edges[i].index].data.s16[3]-1; + if (CTX_LIKELY(edge_end < scanline)) + { + + int dx_dy = abs(rasterizer->edges[i].delta); + rasterizer->needs_aa3 -= (dx_dy > limit3); +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5); + rasterizer->needs_aa15 -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15); +#endif + rasterizer->edges[i] = rasterizer->edges[rasterizer->active_edges-1]; + rasterizer->active_edges--; + i--; + } + else if (edge_end < scanline + CTX_FULL_AA) + rasterizer->ending_edges++; + } +#if 0 + // we should - but for 99% of the cases we do not need to, so we skip it + for (int i = 0; i < rasterizer->pending_edges; i++) + { + int edge_end = ((CtxSegment*)(rasterizer->edge_list.entries))[rasterizer->edges[CTX_MAX_EDGES-1-i].index].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; + for (int i = 0; i < rasterizer->active_edges; i++) + { + rasterizer->edges[i].val += rasterizer->edges[i].delta * count; + } + for (int i = 0; i < rasterizer->pending_edges; i++) + { + rasterizer->edges[CTX_MAX_EDGES-1-i].val += rasterizer->edges[CTX_MAX_EDGES-1-i].delta * count; + } +} + +/* feeds up to rasterizer->scanline, + keeps a pending buffer of edges - that encompass + the full incoming scanline, + feed until the start of the scanline and check for need for aa + in all of pending + active edges, then + again feed_edges until middle of scanline if doing non-AA + or directly render when doing AA +*/ +CTX_INLINE static void ctx_edge2_insertion_sort (CtxEdge *entries, int count) +{ + for(int i=1; i<count; i++) + { + CtxEdge temp = entries[i]; + int j = i-1; + while (j >= 0 && temp.val - entries[j].val < 0) + { + entries[j+1] = entries[j]; + j--; + } + entries[j+1] = temp; + } +} + +CTX_INLINE static int ctx_edge2_compare2 (const CtxEdge *a, const CtxEdge *b) +{ + int minval_a = ctx_mini (a->val - a->delta * CTX_AA_HALFSTEP2, a->val + a->delta * CTX_AA_HALFSTEP); + int minval_b = ctx_mini (b->val - b->delta * CTX_AA_HALFSTEP2, b->val + b->delta * CTX_AA_HALFSTEP); + return minval_a - minval_b; +} + +CTX_INLINE static void ctx_edge2_insertion_sort2 (CtxEdge *entries, int count) +{ + for(int i=1; i<count; i++) + { + CtxEdge temp = entries[i]; + int j = i-1; + while (j >= 0 && ctx_edge2_compare2 (&temp, &entries[j]) < 0) + { + entries[j+1] = entries[j]; + j--; + } + entries[j+1] = temp; + } +} + +inline static void ctx_rasterizer_feed_edges (CtxRasterizer *rasterizer, int apply2_sort) +{ + int miny; + CtxSegment *entries = (CtxSegment*)&rasterizer->edge_list.entries[0]; + rasterizer->horizontal_edges = 0; + rasterizer->ending_edges = 0; + for (int i = 0; i < rasterizer->pending_edges; i++) + { + if (entries[rasterizer->edges[CTX_MAX_EDGES-1-i].index].data.s16[1] - 1 <= rasterizer->scanline) + { + if (CTX_LIKELY(rasterizer->active_edges < CTX_MAX_EDGES-2)) + { + int no = rasterizer->active_edges; + rasterizer->active_edges++; + rasterizer->edges[no] = rasterizer->edges[CTX_MAX_EDGES-1-i]; + rasterizer->edges[CTX_MAX_EDGES-1-i] = + rasterizer->edges[CTX_MAX_EDGES-1-rasterizer->pending_edges + 1]; + rasterizer->pending_edges--; + i--; + } + } + } +#if CTX_ONLY_FAST_AA + const int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#else + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3; + if (rasterizer->fast_aa) + limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#endif + int scanline = rasterizer->scanline; + while (CTX_LIKELY(rasterizer->edge_pos < rasterizer->edge_list.count && + (miny=entries[rasterizer->edge_pos].data.s16[1]-1) <= scanline + 15)) + { + int maxy=entries[rasterizer->edge_pos].data.s16[3]-1; + if (CTX_LIKELY(rasterizer->active_edges < CTX_MAX_EDGES-2 && + maxy >= scanline)) + { + int dy = (entries[rasterizer->edge_pos].data.s16[3] - 1 - miny); + if (CTX_LIKELY(dy)) + { + int yd = scanline - miny; + int no = rasterizer->active_edges; + rasterizer->active_edges++; + rasterizer->edges[no].index = rasterizer->edge_pos; + int index = rasterizer->edges[no].index; + int x0 = entries[index].data.s16[0]; + int x1 = entries[index].data.s16[2]; + int dx_dy; + dx_dy = CTX_RASTERIZER_EDGE_MULTIPLIER * (x1 - x0) / dy; + rasterizer->edges[no].delta = dx_dy; + rasterizer->edges[no].val = x0 * CTX_RASTERIZER_EDGE_MULTIPLIER + + (yd * dx_dy); + + { + int abs_dx_dy = abs(dx_dy); + rasterizer->needs_aa3 += (abs_dx_dy > limit3); +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5); + rasterizer->needs_aa15 += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15); +#endif + } + + if ((miny > scanline) ) + { + /* it is a pending edge - we add it to the end of the array + and keep a different count for items stored here, like + a heap and stack growing against each other + */ + if (CTX_LIKELY(rasterizer->pending_edges < CTX_MAX_PENDING-1)) + { + rasterizer->edges[CTX_MAX_EDGES-1-rasterizer->pending_edges] = + rasterizer->edges[no]; + rasterizer->pending_edges++; + rasterizer->active_edges--; + } + } + } + else + rasterizer->horizontal_edges ++; + } + rasterizer->edge_pos++; + } + ctx_rasterizer_discard_edges (rasterizer); + if (apply2_sort) + ctx_edge2_insertion_sort2 (rasterizer->edges, rasterizer->active_edges); + else + ctx_edge2_insertion_sort (rasterizer->edges, rasterizer->active_edges); +} + + +#undef CTX_CMPSWP + +static inline void ctx_coverage_post_process (CtxRasterizer *rasterizer, int minx, int maxx, uint8_t *coverage, int *first_col, int *last_col) +{ + int scanline = rasterizer->scanline; +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (CTX_UNLIKELY (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM)) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + { + uint16_t temp[maxx-minx+1]; + memset (temp, 0, sizeof (temp)); + for (int x = dim/2; x < maxx-minx + 1 - dim/2; x ++) + for (int u = 0; u < dim; u ++) + { + temp[x] += coverage[minx+x+u-dim/2] * rasterizer->kernel[u] * 256; + } + for (int x = 0; x < maxx-minx + 1; x ++) + coverage[minx+x] = temp[x] >> 8; + } + } +#endif + +#if CTX_ENABLE_CLIP + if (CTX_UNLIKELY(rasterizer->clip_buffer && !rasterizer->clip_rectangle)) + { + /* perhaps not working right for clear? */ + int y = scanline / 15;//rasterizer->aa; + uint8_t *clip_line = &((uint8_t*)(rasterizer->clip_buffer->data))[rasterizer->blit_width*y]; + // XXX SIMD candidate + for (int x = minx; x <= maxx; x ++) + { +#if CTX_1BIT_CLIP + coverage[x] = (coverage[x] * ((clip_line[x/8]&(1<<(x&8)))?255:0))/255; +#else + coverage[x] = (255 + coverage[x] * clip_line[x])>>8; +#endif + } + } + if (CTX_UNLIKELY(rasterizer->aa == 1)) + { + for (int x = minx; x <= maxx; x ++) + coverage[x] = coverage[x] > 127?255:0; + } +#endif +} + +inline static void +ctx_rasterizer_generate_coverage (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *coverage, + const int fill_rule, + uint8_t aa_factor) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int active_edges = rasterizer->active_edges; + int parity = 0; + coverage -= minx; + uint8_t fraction = 255/aa_factor; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + if (!active_edges) + return; + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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 fill_rule) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int active_edges = rasterizer->active_edges; + int parity = 0; + if (!active_edges) + return; + coverage -= minx; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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) + { + coverage[first] += graystart; +#if 0 + for (int x = first + 1; x < last; x++) + coverage[x] = 255; +#else + memset(&coverage[first+1], 255, last-(first+1)); +#endif + coverage[last] += grayend; + } + else if (first == last) + coverage[first] += (graystart-(255-grayend)); + } + } +} + +static inline uint32_t +ctx_over_RGBA8 (uint32_t dst, uint32_t src, uint32_t cov) +{ + uint32_t si_ga = (src & 0xff00ff00) >> 8; + uint32_t si_rb = src & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + uint32_t rcov = (256-((255+si_a * cov)>>8)); + uint32_t di_ga = ( dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb * cov) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga * cov) + (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 = (256-si_a); + uint32_t di_ga = (dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb << 8) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga << 8) + (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 = (256-((si_a * cov)/255)); + uint32_t di_ga = (dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb * cov) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga * cov) + (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 = (256-si_a); + 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); +} + +#if 0 + +how to check for no intersections within line? + + iterate through edges and check that order is still increasing after increment + bail at first that isnt + +compute the horizontal coverage of each intersection as if it was a slow intersect, +process this with one dispatch + + +#endif + + +inline static void +ctx_rasterizer_generate_coverage_apply (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *coverage, + int fill_rule, + int fast_source_copy, + int fast_source_over) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int bpp = rasterizer->format->bpp/8; + int active_edges = rasterizer->active_edges; + int parity = 0; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + + uint32_t src_pix = ((uint32_t*)rasterizer->color)[0]; + uint32_t si_ga_full = (src_pix & 0xff00ff00); + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb = src_pix & 0x00ff00ff; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + uint8_t *dst = ( (uint8_t *) rasterizer->buf) + + (rasterizer->blit_stride * (scanline / CTX_FULL_AA)); + int accumulator_x=0; + uint8_t accumulated = 0; + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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 (accumulated) + { + if (accumulator_x == first) + { + graystart += accumulated; + } + else + { + uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]); + if (fast_source_copy) + { + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated); + } + else if (fast_source_over) + { + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated); + } + else + { + ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1); + } + } + accumulated = 0; + } + + if (first < last) + { + if (fast_source_copy) + { +#if 1 + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, graystart); + + dst_pix++; + for (int i = first + 1; i < last; i++) + { + *dst_pix = src_pix; + dst_pix++; + } +#endif + } + else if (fast_source_over) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, graystart); + dst_pix++; + for (int i = first + 1; i < last; i++) + { + *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a); + dst_pix++; + } + } + else + { + rasterizer->opaque[0] = graystart; + ctx_rasterizer_apply_coverage (rasterizer, + &dst[(first * bpp)], + first, + &rasterizer->opaque[0], + last-first); + rasterizer->opaque[0] = 255; + } + accumulated = grayend; + } + else if (first == last) + { + accumulated = (graystart-(255-grayend)); + } + accumulator_x = last; + } + } + + if (accumulated) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]); + if (fast_source_copy) + { + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated); + } + else if (fast_source_over) + { + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated); + } + else + { + ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1); + } + accumulated = 0; + } +} +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) +#define CTX_EDGE_DELTA(no) (edges[no].delta) + +inline static int ctx_rasterizer_is_simple (CtxRasterizer *rasterizer) +{ + if (rasterizer->fast_aa == 0 || + rasterizer->ending_edges || + rasterizer->pending_edges) + return 0; +#if 0 + // crossing edges is what we do worst, perhaps we should do better on + // them specifically? just ignoring them also works + // + CtxEdge *edges = rasterizer->edges; + + int active_edges = rasterizer->active_edges; + for (int t = 0; t < active_edges -1;t++) + { + int delta0 = CTX_EDGE_DELTA (t); + int delta1 = CTX_EDGE_DELTA (t+1); + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + // reverse the edge for other side proper? + int x0_end = x0 + delta0 * CTX_AA_HALFSTEP; + int x1_end = x1 + delta1 * CTX_AA_HALFSTEP; + if (x1_end < x0_end) + return 0; + } +#endif + return 1; +} + + +inline static void +ctx_rasterizer_generate_coverage_apply2 (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *ignored_coverage, + int fill_rule, + int fast_source_copy, + int fast_source_over) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int bpp = rasterizer->format->bpp/8; + int active_edges = rasterizer->active_edges; + int parity = 0; + + uint32_t src_pix = ((uint32_t*)rasterizer->color)[0]; + uint32_t si_ga_full = (src_pix & 0xff00ff00); + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb = src_pix & 0x00ff00ff; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + uint8_t *dst = ( (uint8_t *) rasterizer->buf) + + (rasterizer->blit_stride * (scanline / CTX_FULL_AA)); + + uint8_t _coverage[maxx-minx+1+2]; + uint8_t *coverage=_coverage - minx; + memset(_coverage,0, sizeof(_coverage)); + + int minx_ = minx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV; + int maxx_ = maxx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV; + + int accumulated_x0 = 65536; + int accumulated_x1 = 65536; + + for (int t = 0; t < active_edges -1;t++) + { + int delta0 = CTX_EDGE_DELTA (t); + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + int delta1 = CTX_EDGE_DELTA (t+1); + + int x0_start = x0 - delta0 * CTX_AA_HALFSTEP2; + int x1_start = x1 - delta1 * CTX_AA_HALFSTEP2; + int x0_end = x0 + delta0 * CTX_AA_HALFSTEP; + int x1_end = x1 + delta1 * CTX_AA_HALFSTEP; + + int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256); + int grayend = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256); + int first = graystart >> 8; + int last = grayend >> 8; + + first = ctx_maxi (first, minx); + last = ctx_mini (last, maxx); + + if (first < last) + { + int pre = 1; + int post = 1; + + if (abs(delta0) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA) + { + graystart = 255 - (graystart&0xff); + //grayend = (grayend & 0xff); + coverage[first] += graystart; + + accumulated_x1 = first; + accumulated_x0 = ctx_mini (accumulated_x0, first); +#if 0 + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((first) * bpp)], + first, + &coverage[first], + 1); +#endif + } + else + { + int u0 = x0_start; + int u1 = x0_end; + if (u0 > u1) + { + int tmp = u0; + u0 = u1;u1=tmp; + } + u0 = ctx_maxi (u0, minx_); + u1 = ctx_mini (u1, maxx_); + int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV); + int count = 0; + for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) + { + coverage[us + count] += (u - u0 + (0.5f)*CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) / (u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 1.0) * 255; + count++; + } + pre = (us+count-1)-first+1; + + + + if (accumulated_x1 != 65536 && us - accumulated_x1 > 4 && + accumulated_x1-accumulated_x0+1>0 + ) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + } + + accumulated_x0 = ctx_mini (accumulated_x0, us); + accumulated_x1 = us + count - 1; + } + + + if (accumulated_x0 != 65536 && accumulated_x1-accumulated_x0+1>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + accumulated_x0 = 65536; + accumulated_x1 = 65536; + } + +#if 1 + if (abs(delta1) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA) + { + //graystart = 255 - (graystart&0xff); + grayend = (grayend & 0xff); + coverage[last] += grayend; + accumulated_x1 = last; + accumulated_x0 = last; + } + else + { + int u0 = x1_start; + int u1 = x1_end; + if (u0 > u1) + { + int tmp = u0; + u0 = u1;u1=tmp; + } + u0 = ctx_maxi (u0, minx_); + u1 = ctx_mini (u1, maxx_); + int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV); + int count = 0; + for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) + { + coverage[us + count] += 255-(u - u0 * 1.0 + ((127)/255.0)*(CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)) / (u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 0.5) * 255; + count++; + } + post = count/2; + post = last-us+1;// + + accumulated_x1 = us + count; + accumulated_x0 = us; + } +#endif + if (fast_source_copy) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + dst_pix+=pre; + for (int i = first + pre; i <= last - post; i++) + { + *dst_pix = src_pix; + dst_pix++; + } + } + else if (fast_source_over) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + dst_pix+=pre; + for (int i = first + pre; i <= last - post; i++) + { + *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a); + dst_pix++; + } + } + else if (last-first-pre>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((first + pre) * bpp)], + first + pre, + rasterizer->opaque, + last-first-pre); + } + } + else if (first == last) + { + graystart = 255 - (graystart&0xff); + grayend = (grayend & 0xff); + coverage[last]+=(graystart-(255-grayend)); + + accumulated_x1 = last; + accumulated_x0 = ctx_maxi (accumulated_x0, last); + } + } + } + + if (accumulated_x0 != 65536 && accumulated_x1-accumulated_x0+1>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + } +} + +#undef CTX_EDGE_Y0 +#undef CTX_EDGE + +static inline void +ctx_rasterizer_reset (CtxRasterizer *rasterizer) +{ + rasterizer->pending_edges = 0; + rasterizer->active_edges = 0; + rasterizer->has_shape = 0; + rasterizer->has_prev = 0; + rasterizer->edge_list.count = 0; // ready for new edges + rasterizer->edge_pos = 0; + rasterizer->needs_aa3 = 0; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 = 0; + rasterizer->needs_aa15 = 0; +#endif + rasterizer->scanline = 0; + if (CTX_LIKELY(!rasterizer->preserve)) + { + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + rasterizer->col_min = 5000; + rasterizer->col_max = -5000; + } + //rasterizer->comp_op = NULL; // keep comp_op cached + // between rasterizations where rendering attributes are + // nonchanging +} + + +static void +ctx_rasterizer_rasterize_edges (CtxRasterizer *rasterizer, const int fill_rule +#if CTX_SHAPE_CACHE + ,CtxShapeEntry *shape +#endif + ) +{ + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + + int real_aa = rasterizer->aa; + + int scan_start = rasterizer->blit_y * CTX_FULL_AA; + int scan_end = scan_start + rasterizer->blit_height * CTX_FULL_AA; + int blit_width = rasterizer->blit_width; + 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; + + int fast_source_copy = + (rasterizer->format->components == 4 && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR && + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_COPY && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->format->bpp == 32); + int fast_source_over = + (rasterizer->format->components == 4 && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR && + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OVER && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->format->bpp == 32); + + if (fast_source_over && rasterizer->color[3]==255) + { + fast_source_over = 0; + fast_source_copy = 1; + } + + rasterizer->prev_active_edges = -1; + if ( +#if CTX_SHAPE_CACHE + !shape && +#endif + maxx > blit_max_x - 1) + { maxx = blit_max_x - 1; } + + + minx = ctx_maxi (rasterizer->state->gstate.clip_min_x, minx); + maxx = ctx_mini (rasterizer->state->gstate.clip_max_x, maxx); + minx = ctx_maxi (0, minx); // redundant? + if (minx >= maxx) + { + ctx_rasterizer_reset (rasterizer); + return; + } +#if CTX_SHAPE_CACHE + uint8_t _coverage[shape?2:maxx-minx+1]; +#else + uint8_t _coverage[maxx-minx+1]; +#endif + uint8_t *coverage = &_coverage[0]; + + int coverage_size = +#if CTX_SHAPE_CACHE + shape?shape->width: +#endif + sizeof (_coverage); + +#if CTX_SHAPE_CACHE + if (shape) + { + coverage = &shape->data[0]; + } +#endif + //ctx_assert (coverage); + rasterizer->scan_min -= (rasterizer->scan_min % CTX_FULL_AA); +#if CTX_SHAPE_CACHE + if (shape) + { + scan_start = rasterizer->scan_min; + scan_end = rasterizer->scan_max; + } + else +#endif + { + if (rasterizer->scan_min > scan_start) + { + dst += (rasterizer->blit_stride * (rasterizer->scan_min-scan_start) / CTX_FULL_AA); + scan_start = rasterizer->scan_min; + } + scan_end = ctx_mini (rasterizer->scan_max, scan_end); + } + if (CTX_UNLIKELY(rasterizer->state->gstate.clip_min_y * CTX_FULL_AA > scan_start )) + { + dst += (rasterizer->blit_stride * (rasterizer->state->gstate.clip_min_y * CTX_FULL_AA -scan_start) / CTX_FULL_AA); + scan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + } + scan_end = ctx_mini (rasterizer->state->gstate.clip_max_y * CTX_FULL_AA, scan_end); + if (CTX_UNLIKELY(scan_start > scan_end || + (scan_start > (rasterizer->blit_y + rasterizer->blit_height) * CTX_FULL_AA) || + (scan_end < (rasterizer->blit_y) * CTX_FULL_AA))) + { + /* not affecting this rasterizers scanlines */ + ctx_rasterizer_reset (rasterizer); + return; + } + + rasterizer->horizontal_edges = 0; + ctx_rasterizer_sort_edges (rasterizer); + { + rasterizer->needs_aa3 = 0; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 = 0; + rasterizer->needs_aa15 = 0; +#endif + rasterizer->scanline = scan_start; + ctx_rasterizer_feed_edges (rasterizer, 0); + + int enable_aa3 = (real_aa >= 3); +#if CTX_ONLY_FAST_AA==0 + int enable_aa5 = (real_aa >= 5); + int enable_aa15 = (real_aa >= 15); +#endif + + int avoid_direct = (0 +#if CTX_ENABLE_CLIP + || rasterizer->clip_buffer +#endif +#if CTX_SHAPE_CACHE + || shape != NULL +#endif + ); + + for (; rasterizer->scanline <= scan_end;) + { + + rasterizer->needs_aa3 *= enable_aa3; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 *= enable_aa5; + rasterizer->needs_aa15 *= enable_aa15; +#endif + + int needs_full_aa = + (rasterizer->horizontal_edges!=0) + || (rasterizer->active_edges != rasterizer->prev_active_edges + || (rasterizer->active_edges + rasterizer->pending_edges == rasterizer->ending_edges) + // || rasterizer->needs_aa15 + ); + +#if 0 + if (1) + { + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + else +#endif + if (CTX_UNLIKELY (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, fill_rule, real_aa); + ctx_rasterizer_increment_edges (rasterizer, increment); + } + } + else if (CTX_LIKELY(!rasterizer->needs_aa3)) // if it doesnt need aa3 it doesnt need aa5 or aa15 either + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2); + ctx_rasterizer_feed_edges (rasterizer, 0); + + if (CTX_LIKELY(rasterizer->active_edges)) + { + if (!avoid_direct) + { +#if CTX_RASTERIZER_INLINED_FAST_COPY_OVER + + if (fast_source_copy) + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 1, 0); + else if (fast_source_over) + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 0, 1); + else + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 0, 0); +#else + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, fast_source_copy, fast_source_over); +#endif + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } + else + { + memset (coverage, 0, coverage_size); + ctx_rasterizer_generate_coverage_set (rasterizer, minx, maxx, coverage, fill_rule); + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + } + } + else + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } + } +#if 1 + else if (!avoid_direct && ctx_rasterizer_is_simple (rasterizer)) + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2); + ctx_rasterizer_feed_edges (rasterizer, 1); + + if (CTX_LIKELY(rasterizer->active_edges)) + { +#if CTX_RASTERIZER_INLINED_FAST_COPY_OVER + if (fast_source_copy) + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 1, 0); + else if (fast_source_over) + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 0, 1); + else + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 0, 0); +#else + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, fast_source_copy, fast_source_over); +#endif + } + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } +#endif +#if CTX_ONLY_FAST_AA==0 +#if 1 + else if (rasterizer->needs_aa15) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i++) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA); + ctx_rasterizer_increment_edges (rasterizer, 1); + } + } +#endif + else if (rasterizer->needs_aa5) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + else if (rasterizer->needs_aa3) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=5) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/5); + ctx_rasterizer_increment_edges (rasterizer, 5); + } + } +#endif + else + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + + ctx_coverage_post_process (rasterizer, minx, maxx, coverage - minx, + NULL, NULL); +#if CTX_SHAPE_CACHE + if (shape == NULL) +#endif + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[(minx * rasterizer->format->bpp) /8], + minx, + coverage, + maxx-minx+ 1); + } +#if CTX_SHAPE_CACHE + if (shape) + { + coverage += shape->width; + } +#endif + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + } + } + + if (CTX_UNLIKELY(rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OUT || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_IN || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_IN || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_ATOP || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_CLEAR)) + { + /* fill in the rest of the blitrect when compositing mode permits it */ + uint8_t nocoverage[rasterizer->blit_width]; + //int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + int gscan_end = rasterizer->state->gstate.clip_max_y * CTX_FULL_AA; + memset (nocoverage, 0, sizeof(nocoverage)); + int startx = rasterizer->state->gstate.clip_min_x; + int endx = rasterizer->state->gstate.clip_max_x; + int clipw = endx-startx + 1; + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (gscan_start / CTX_FULL_AA); + for (rasterizer->scanline = gscan_start; rasterizer->scanline < scan_start;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, clipw); + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } + if (minx < startx) + { + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA); + for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, minx-startx); + dst += rasterizer->blit_stride; + } + } + + if (endx > maxx) + { + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA); + for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (maxx * rasterizer->format->bpp) /8], + 0, + nocoverage, endx-maxx); + + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } + } +#if 1 + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_end / CTX_FULL_AA); + // XXX valgrind/asan this + if(0)for (rasterizer->scanline = scan_end; rasterizer->scanline/CTX_FULL_AA < gscan_end-1;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, clipw-1); + + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } +#endif + } + ctx_rasterizer_reset (rasterizer); +} + +inline static int +ctx_is_transparent (CtxRasterizer *rasterizer, int stroke) +{ + CtxGState *gstate = &rasterizer->state->gstate; + if (gstate->global_alpha_u8 == 0) + return 1; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + uint8_t ga[2]; + ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga); + if (ga[1] == 0) + return 1; + } + return 0; +} + +#define CTX_RECT_FILL 1 + +#if CTX_RECT_FILL +static void +ctx_rasterizer_fill_rect (CtxRasterizer *rasterizer, + int x0, + int y0, + int x1, + int y1, + uint8_t cov) +{ + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + CtxGState *gstate = &rasterizer->state->gstate; + int blit_stride = rasterizer->blit_stride; + x0 = ctx_maxi (x0, rasterizer->blit_x); + y0 = ctx_maxi (y0, rasterizer->blit_y); + x1 = ctx_mini (x1, rasterizer->blit_x + rasterizer->blit_width); + y1 = ctx_mini (y1, rasterizer->blit_y + rasterizer->blit_height); + + dst += (y0 - rasterizer->blit_y) * rasterizer->blit_stride; + int width = x1 - x0; + + if (CTX_UNLIKELY(width <=0)) + return; + _ctx_setup_compositor (rasterizer); + + if (cov == 255 && rasterizer->format->components == 4 && + gstate->source_fill.type == CTX_SOURCE_COLOR && + rasterizer->format->bpp == 32 && + gstate->blend_mode == CTX_BLEND_NORMAL) + { + if ( (rasterizer->color[3]==255 && gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + ||(gstate->compositing_mode == CTX_COMPOSITE_COPY) + ) + { + if (CTX_UNLIKELY(width == 1)) + { + for (int y = y0; y < y1; y++) + { + memcpy (&dst[(x0)*4], rasterizer->color, 4); + dst += blit_stride; + } + } + else + { + uint32_t color; + memcpy(&color, rasterizer->color, 4); + dst += x0 * 4; + for (int y = y0; y < y1; y++) + { + uint32_t *dst_i = (uint32_t*)&dst[0]; + for (int i = 0; i < width; i++) + { + dst_i[i] = color; + } + dst += blit_stride; + } + } + return; + } + else if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + uint32_t *src = (uint32_t*)&rasterizer->color[0]; + uint32_t si_ga_full = (*src & 0xff00ff00); + uint32_t si_rb = *src & 0x00ff00ff; + + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + dst += x0 * 4; + if (CTX_UNLIKELY(width == 1)) + { + for (int y = y0; y < y1; y++) + { + ((uint32_t*)(dst))[0] = ctx_over_RGBA8_full_2 ( + ((uint32_t*)(dst))[0], si_ga_full, si_rb_full, si_a); + dst += blit_stride; + } + } + else + { + for (int y = y0; y < y1; y++) + { + uint32_t *dst_i = (uint32_t*)&dst[0]; + for (int i = 0; i < width; i++) + { + dst_i[i] = ctx_over_RGBA8_full_2 (dst_i[i], si_ga_full, si_rb_full, si_a); + } + dst += blit_stride; + } + } + return; + } + } + + if (CTX_UNLIKELY(width == 1)) + { + uint8_t coverage[1]={cov}; + dst += (x0) * rasterizer->format->bpp/8; + for (int y = y0; y < y1; y++) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[0], + x0, coverage, 1); + dst += blit_stride; + } + } + else + { + uint8_t coverage[width]; + memset (coverage, cov, sizeof (coverage) ); + rasterizer->scanline = y0 * CTX_FULL_AA; + dst += (x0) * rasterizer->format->bpp/8; + for (int y = y0; y < y1; y++) + { + rasterizer->scanline += CTX_FULL_AA; + ctx_rasterizer_apply_coverage (rasterizer, + &dst[0], + x0, + coverage, width); + dst += blit_stride; + } + } + return; +} +#endif + +static inline float ctx_fmod1f (float val) +{ + int vali = val; + return val - vali; +} + +static void +ctx_rasterizer_fill (CtxRasterizer *rasterizer) +{ + int count = rasterizer->preserve?rasterizer->edge_list.count:0; + + CtxSegment temp[count]; /* copy of already built up path's poly line + XXX - by building a large enough path + the stack can be smashed! + */ + if (CTX_UNLIKELY(rasterizer->preserve)) + { memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) ); } + +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &((CtxSegment*)rasterizer->edge_list.entries)[i]; + entry->data.s16[2] += rasterizer->shadow_x * CTX_SUBDIV; + entry->data.s16[3] += rasterizer->shadow_y * CTX_FULL_AA; + } + rasterizer->scan_min += rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->scan_max += rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->col_min += (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + rasterizer->col_max += (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + } +#endif + + if (CTX_UNLIKELY(ctx_is_transparent (rasterizer, 0) || + rasterizer->scan_min / CTX_FULL_AA > rasterizer->blit_y + rasterizer->blit_height || + rasterizer->scan_max / CTX_FULL_AA < rasterizer->blit_y || + rasterizer->col_min / CTX_SUBDIV > rasterizer->blit_x + rasterizer->blit_width || + rasterizer->col_max / CTX_SUBDIV < rasterizer->blit_x)) + { + ctx_rasterizer_reset (rasterizer); + } + else + { + _ctx_setup_compositor (rasterizer); + +#if 1 + rasterizer->state->min_x = ctx_mini (rasterizer->state->min_x, rasterizer->col_min / CTX_SUBDIV); + rasterizer->state->max_x = ctx_maxi (rasterizer->state->min_x, rasterizer->col_max / CTX_SUBDIV); + rasterizer->state->min_y = ctx_mini (rasterizer->state->min_y, rasterizer->scan_min / CTX_FULL_AA); + rasterizer->state->max_y = ctx_maxi (rasterizer->state->min_y, rasterizer->scan_max / CTX_FULL_AA); +#else + if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV < rasterizer->state->min_x)) + rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV; + if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV > rasterizer->state->max_x)) + rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV; + + if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA < rasterizer->state->min_y)) + rasterizer->state->min_y = rasterizer->scan_min / CTX_FULL_AA; + if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA > rasterizer->state->max_y)) + rasterizer->state->max_y = rasterizer->scan_max / CTX_FULL_AA; +#endif + +#if CTX_RECT_FILL + if (rasterizer->edge_list.count == 6) + { + 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 + ) + { + if(((entry1->data.s16[2] % (CTX_SUBDIV)) == 0) && + ((entry1->data.s16[3] % (CTX_FULL_AA)) == 0) && + ((entry3->data.s16[2] % (CTX_SUBDIV)) == 0) && + ((entry3->data.s16[3] % (CTX_FULL_AA)) == 0)) + { + /* best-case axis aligned rectangle */ + int x0 = entry3->data.s16[2] / CTX_SUBDIV; + int y0 = entry3->data.s16[3] / CTX_FULL_AA; + int x1 = entry1->data.s16[2] / CTX_SUBDIV; + int y1 = entry1->data.s16[3] / CTX_FULL_AA; + + ctx_rasterizer_fill_rect (rasterizer, x0, y0, x1, y1, 255); + ctx_rasterizer_reset (rasterizer); + goto done; + } + else + { + float x0 = entry3->data.s16[2] * 1.0f / CTX_SUBDIV; + float y0 = entry3->data.s16[3] * 1.0f / CTX_FULL_AA; + float x1 = entry1->data.s16[2] * 1.0f / CTX_SUBDIV; + float y1 = entry1->data.s16[3] * 1.0f / CTX_FULL_AA; + + x0 = ctx_maxf (x0, rasterizer->blit_x); + y0 = ctx_maxf (y0, rasterizer->blit_y); + x1 = ctx_minf (x1, rasterizer->blit_x + rasterizer->blit_width); + y1 = ctx_minf (y1, rasterizer->blit_y + rasterizer->blit_height); + + uint8_t left = 255-ctx_fmod1f (x0) * 255; + uint8_t top = 255-ctx_fmod1f (y0) * 255; + uint8_t right = ctx_fmod1f (x1) * 255; + uint8_t bottom = ctx_fmod1f (y1) * 255; + + x0 = ctx_floorf (x0); + y0 = ctx_floorf (y0); + x1 = ctx_floorf (x1+7/8.0f); + y1 = ctx_floorf (y1+14/15.0f); + + int has_top = (top < 255); + int has_bottom = (bottom <255); + int has_right = (right >0); + int has_left = (left >0); + + int width = x1 - x0; + + if (CTX_LIKELY(width >0)) + { + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + uint8_t coverage[width+2]; + dst += (((int)y0) - rasterizer->blit_y) * rasterizer->blit_stride; + dst += ((int)x0) * rasterizer->format->bpp/8; + + if (has_top) + { + int i = 0; + if (has_left) + { + coverage[i++] = top * left / 255; + } + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = top; + coverage[i++]= top * right / 255; + + ctx_rasterizer_apply_coverage (rasterizer,dst, + x0, + coverage, width); + dst += rasterizer->blit_stride; + } + + if (!( + (rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_COPY|| + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OVER) && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR + )) + { + int i = 0; + if (has_left) + { + coverage[i++] = left; + } + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = 255; + coverage[i++] = right; + + for (int ty = y0+has_top; ty < y1-has_bottom; ty++) + { + ctx_rasterizer_apply_coverage (rasterizer, dst, x0, coverage, width); + dst += rasterizer->blit_stride; + } + } + else + { + if (has_left) + ctx_rasterizer_fill_rect (rasterizer, x0, y0 + has_top, x0+1, y1 - has_bottom, left); + if (has_right) + ctx_rasterizer_fill_rect (rasterizer, x1-1, y0 + has_top, x1, y1 - has_bottom, right); + x0 += has_left; + y0 += has_top; + y1 -= has_bottom; + x1 -= has_right; + ctx_rasterizer_fill_rect (rasterizer, x0,y0,x1,y1,255); + + dst += rasterizer->blit_stride * ((((int)y1)-has_bottom) - (((int)y0)+has_top)); + } + + if (has_bottom) + { + int i = 0; + if (has_left) + coverage[i++] = bottom * left / 255; + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = bottom; + coverage[i++]= bottom * right / 255; + + ctx_rasterizer_apply_coverage (rasterizer,dst, x0, coverage, width); + } + } + + ctx_rasterizer_reset (rasterizer); + goto done; + } + } + } +#endif + ctx_rasterizer_finish_shape (rasterizer); + + uint32_t hash = ctx_rasterizer_poly_to_edges (rasterizer); + if (hash){}; + +#if CTX_SHAPE_CACHE + int width = (rasterizer->col_max + (CTX_SUBDIV-1) ) / CTX_SUBDIV - rasterizer->col_min/CTX_SUBDIV + 1; + int height = (rasterizer->scan_max + (CTX_FULL_AA-1) ) / CTX_FULL_AA - rasterizer->scan_min / CTX_FULL_AA + 1; + if (width * height < CTX_SHAPE_CACHE_DIM && width >=1 && height >= 1 + && width < CTX_SHAPE_CACHE_MAX_DIM + && height < CTX_SHAPE_CACHE_MAX_DIM +#if CTX_ENABLE_SHADOW_BLUR + && !rasterizer->in_shadow +#endif + ) + { + int scan_min = rasterizer->scan_min; + int col_min = rasterizer->col_min; + scan_min -= (scan_min % CTX_FULL_AA); + int y0 = scan_min / CTX_FULL_AA; + int y1 = y0 + height; + int x0 = col_min / CTX_SUBDIV; + int ymin = y0; + int x1 = x0 + width; + int clip_x_min = rasterizer->blit_x; + int clip_x_max = rasterizer->blit_x + rasterizer->blit_width - 1; + int clip_y_min = rasterizer->blit_y; + int clip_y_max = rasterizer->blit_y + rasterizer->blit_height - 1; + + int dont_cache = 0; + if (CTX_UNLIKELY(x1 >= clip_x_max)) + { x1 = clip_x_max; + dont_cache = 1; + } + int xo = 0; + if (CTX_UNLIKELY(x0 < clip_x_min)) + { + xo = clip_x_min - x0; + x0 = clip_x_min; + dont_cache = 1; + } + if (CTX_UNLIKELY(y0 < clip_y_min || y1 >= clip_y_max)) + dont_cache = 1; + if (dont_cache || !_ctx_shape_cache_enabled) + { + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule +#if CTX_SHAPE_CACHE + , NULL +#endif + ); + } + else + { + rasterizer->scanline = scan_min; + CtxShapeEntry *shape = ctx_shape_entry_find (rasterizer, hash, width, height); + + if (shape->uses == 0) + { + CtxBuffer *buffer_backup = rasterizer->clip_buffer; + rasterizer->clip_buffer = NULL; + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule, shape); + rasterizer->clip_buffer = buffer_backup; + } + rasterizer->scanline = scan_min; + + int ewidth = x1 - x0; + if (ewidth>0) + { + if (rasterizer->clip_buffer && !rasterizer->clip_rectangle) + { + uint8_t composite[ewidth]; + 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*((uint8_t*)rasterizer->clip_buffer->data) [ + ((y-rasterizer->blit_y) * rasterizer->blit_width) + x0 + x])/255; + composite[x] = val; + } + ctx_rasterizer_apply_coverage (rasterizer, + ( (uint8_t *) rasterizer->buf) + (y-rasterizer->blit_y) * rasterizer->blit_stride + (int) (x0) * rasterizer->format->bpp/8, + x0, // is 0 + composite, + ewidth ); + rasterizer->scanline += CTX_FULL_AA; + } + } + } + else + for (int y = y0; y < y1; y++) + { + if (CTX_LIKELY((y >= clip_y_min) && (y <= clip_y_max) )) + { + ctx_rasterizer_apply_coverage (rasterizer, + ( (uint8_t *) rasterizer->buf) + (y-rasterizer->blit_y) * rasterizer->blit_stride + (int) (x0) * rasterizer->format->bpp/8, + x0, + &shape->data[shape->width * (int) (y-ymin) + xo], + ewidth ); + } + rasterizer->scanline += CTX_FULL_AA; + } + } + if (shape->uses != 0) + { + ctx_rasterizer_reset (rasterizer); + } + } + } + else +#endif + { + + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule +#if CTX_SHAPE_CACHE + , NULL +#endif + ); + } + } +done: + if (CTX_UNLIKELY(rasterizer->preserve)) + { + memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) ); + rasterizer->edge_list.count = count; + } +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + rasterizer->scan_min -= rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->scan_max -= rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->col_min -= (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + rasterizer->col_max -= (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + } +#endif + rasterizer->preserve = 0; +} + +#if 0 +static void +ctx_rasterizer_triangle (CtxRasterizer *rasterizer, + int x0, int y0, + int x1, int y1, + int x2, int y2, + int r0, int g0, int b0, int a0, + int r1, int g1, int b1, int a1, + int r2, int g2, int b2, int a2, + int u0, int v0, + int u1, int v1) +{ + +} +#endif + + +typedef struct _CtxTermGlyph CtxTermGlyph; + +struct _CtxTermGlyph +{ + uint32_t unichar; + int col; + int row; + uint8_t rgba_bg[4]; + uint8_t rgba_fg[4]; +}; + +static int _ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke); +static void +ctx_rasterizer_glyph (CtxRasterizer *rasterizer, uint32_t unichar, int stroke) +{ + float tx = rasterizer->state->x; + float ty = rasterizer->state->y - rasterizer->state->gstate.font_size; + float tx2 = rasterizer->state->x + rasterizer->state->gstate.font_size; + float ty2 = rasterizer->state->y + rasterizer->state->gstate.font_size; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device (rasterizer->state, &tx2, &ty2); + + if (tx2 < rasterizer->blit_x || ty2 < rasterizer->blit_y) return; + if (tx > rasterizer->blit_x + rasterizer->blit_width || + ty > rasterizer->blit_y + rasterizer->blit_height) + return; + +#if CTX_BRAILLE_TEXT + float font_size = 0; + int ch = 1; + int cw = 1; + + if (rasterizer->term_glyphs) + { + float tx = 0; + float ty = rasterizer->state->gstate.font_size; + float txb = 0; + float tyb = 0; + + ch = ctx_term_get_cell_height (rasterizer->ctx); + cw = ctx_term_get_cell_width (rasterizer->ctx); + + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device (rasterizer->state, &txb, &tyb); + font_size = ty-tyb; + } + if (rasterizer->term_glyphs && !stroke && + fabs (font_size - ch) < 0.5) + { + float tx = rasterizer->x; + float ty = rasterizer->y; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + int col = tx / cw + 1; + int row = ty / ch + 1; + CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1); + ctx_list_append (&rasterizer->glyphs, glyph); + glyph->unichar = unichar; + glyph->col = col; + glyph->row = row; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, + &glyph->rgba_fg[0]); + } + else +#endif + _ctx_glyph (rasterizer->ctx, unichar, stroke); +} + + +static void +_ctx_text (Ctx *ctx, + const char *string, + int stroke, + int visible); +static void +ctx_rasterizer_text (CtxRasterizer *rasterizer, const char *string, int stroke) +{ +#if CTX_BRAILLE_TEXT + float font_size = 0; + if (rasterizer->term_glyphs) + { + float tx = 0; + float ty = rasterizer->state->gstate.font_size; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + font_size = ty; + } + int ch = ctx_term_get_cell_height (rasterizer->ctx); + int cw = ctx_term_get_cell_width (rasterizer->ctx); + + if (rasterizer->term_glyphs && !stroke && + fabs (font_size - ch) < 0.5) + { + float tx = rasterizer->x; + float ty = rasterizer->y; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + int col = tx / cw + 1; + int row = ty / ch + 1; + for (int i = 0; string[i]; i++, col++) + { + CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1); + ctx_list_prepend (&rasterizer->glyphs, glyph); + glyph->unichar = string[i]; + glyph->col = col; + glyph->row = row; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, + glyph->rgba_fg); + } + } + else +#endif + { + _ctx_text (rasterizer->ctx, string, stroke, 1); + } +} + +void +_ctx_font (Ctx *ctx, const char *name); +static void +ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name) +{ + _ctx_font (rasterizer->ctx, font_name); +} + +static void +ctx_rasterizer_arc (CtxRasterizer *rasterizer, + float x, + float y, + float radius, + float start_angle, + float end_angle, + int anticlockwise) +{ + int full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; + full_segments = radius * CTX_PI * 2 / 4.0; + if (full_segments > CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS) + { full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; } + if (full_segments < 24) full_segments = 24; + float step = CTX_PI*2.0/full_segments; + int steps; + + if (end_angle < -30.0) + end_angle = -30.0; + if (start_angle < -30.0) + start_angle = -30.0; + if (end_angle > 30.0) + end_angle = 30.0; + if (start_angle > 30.0) + start_angle = 30.0; + + if (radius <= 0.0001) + return; + + if (end_angle == start_angle) + // XXX also detect arcs fully outside render view + { + if (rasterizer->has_prev!=0) + ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); + else + ctx_rasterizer_move_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); + return; + } +#if 1 + if ( (!anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f) || + ( (anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f ) ) + || (anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f) || (!anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f ) ) + { + start_angle = start_angle; + steps = full_segments - 1; + } + else +#endif + { + steps = (end_angle - start_angle) / (CTX_PI*2) * full_segments; + if (anticlockwise) + { steps = full_segments - steps; }; + // if (steps > full_segments) + // steps = full_segments; + } + if (anticlockwise) { step = step * -1; } + int first = 1; + if (steps == 0 /* || steps==full_segments -1 || (anticlockwise && steps == full_segments) */) + { + float xv = x + ctx_cosf (start_angle) * radius; + float yv = y + ctx_sinf (start_angle) * radius; + if (!rasterizer->has_prev) + { ctx_rasterizer_move_to (rasterizer, xv, yv); } + first = 0; + } + else + { + for (float angle = start_angle, i = 0; i < steps; angle += step, i++) + { + float xv = x + ctx_cosf (angle) * radius; + float yv = y + ctx_sinf (angle) * radius; + if (first && !rasterizer->has_prev) + { ctx_rasterizer_move_to (rasterizer, xv, yv); } + else + { ctx_rasterizer_line_to (rasterizer, xv, yv); } + first = 0; + } + } + ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); +} + +static void +ctx_rasterizer_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y) +{ + /* XXX : it is probably cheaper/faster to do quad interpolation directly - + * though it will increase the code-size, an + * alternative is to turn everything into cubic + * and deal with cubics more directly during + * rasterization + */ + ctx_rasterizer_curve_to (rasterizer, + (cx * 2 + rasterizer->x) / 3.0f, (cy * 2 + rasterizer->y) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); +} + +static void +ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer, + float cx, float cy, + float x, float y) +{ + ctx_rasterizer_quad_to (rasterizer, cx + rasterizer->x, cy + rasterizer->y, + x + rasterizer->x, y + rasterizer->y); +} + +#define LENGTH_OVERSAMPLE 1 +static void +ctx_rasterizer_pset (CtxRasterizer *rasterizer, int x, int y, uint8_t cov) +{ + // XXX - we avoid rendering here x==0 - to keep with + // an off-by one elsewhere + // + // XXX onlt works in rgba8 formats + if (x <= 0 || y < 0 || x >= rasterizer->blit_width || + y >= rasterizer->blit_height) + { return; } + uint8_t fg_color[4]; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, fg_color); + uint8_t pixel[4]; + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + dst += y * rasterizer->blit_stride; + dst += x * rasterizer->format->bpp / 8; + if (!rasterizer->format->to_comp || + !rasterizer->format->from_comp) + { return; } + if (cov == 255) + { + for (int c = 0; c < 4; c++) + { + pixel[c] = fg_color[c]; + } + } + else + { + rasterizer->format->to_comp (rasterizer, x, dst, &pixel[0], 1); + for (int c = 0; c < 4; c++) + { + pixel[c] = ctx_lerp_u8 (pixel[c], fg_color[c], cov); + } + } + rasterizer->format->from_comp (rasterizer, x, &pixel[0], dst, 1); +} + +static void +ctx_rasterizer_stroke_1px (CtxRasterizer *rasterizer) +{ + int count = rasterizer->edge_list.count; + CtxSegment *temp = (CtxSegment*)rasterizer->edge_list.entries; + float prev_x = 0.0f; + float prev_y = 0.0f; + int aa = 15;//rasterizer->aa; + int start = 0; + int end = 0; +#if 0 + float factor = ctx_matrix_get_scale (&state->gstate.transform); +#endif + + while (start < count) + { + int started = 0; + int i; + for (i = start; i < count; i++) + { + CtxSegment *entry = &temp[i]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + if (started) + { + end = i - 1; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + int dx = x - prev_x; + int dy = y - prev_y; + int length = ctx_maxf (abs (dx), abs (dy) ); + if (length) + { + length *= LENGTH_OVERSAMPLE; + int len = length; + int tx = prev_x * 256; + int ty = prev_y * 256; + dx *= 256; + dy *= 256; + dx /= length; + dy /= length; + for (int i = 0; i < len; i++) + { + ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255); + tx += dx; + ty += dy; + ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255); + } + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + start = end+1; + } + ctx_rasterizer_reset (rasterizer); +} + +static void +ctx_rasterizer_stroke (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + CtxSource source_backup; + if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL) + { + source_backup = gstate->source_fill; + gstate->source_fill = rasterizer->state->gstate.source_stroke; + } + int count = rasterizer->edge_list.count; + int preserved = rasterizer->preserve; + float factor = ctx_matrix_get_scale (&gstate->transform); + float line_width = gstate->line_width * factor; + + CtxSegment temp[count]; /* copy of already built up path's poly line */ + memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) ); + + if (rasterizer->edge_list.count == 6) + { + CtxSegment *entry0 = &((CtxSegment*)rasterizer->edge_list.entries)[0]; + CtxSegment *entry1 = &((CtxSegment*)rasterizer->edge_list.entries)[1]; + CtxSegment *entry2 = &((CtxSegment*)rasterizer->edge_list.entries)[2]; + CtxSegment *entry3 = &((CtxSegment*)rasterizer->edge_list.entries)[3]; + //fprintf (stderr, "{%i %.2f %.2f}", lw, lwmod, line_width); + + if (!rasterizer->state->gstate.clipped && + (entry0->data.s16[2] == entry1->data.s16[2]) && + (entry0->data.s16[3] == entry3->data.s16[3]) && + (entry1->data.s16[3] == entry2->data.s16[3]) && + (entry2->data.s16[2] == entry3->data.s16[2]) +#if CTX_ENABLE_SHADOW_BLUR + && !rasterizer->in_shadow +#endif + ) + { + float lwmod = ctx_fmod1f (line_width); + int lw = ctx_floorf (line_width + 0.5f); + int is_compat_even = (lw % 2 == 0) && (lwmod < 0.1); // only even linewidths implemented properly + int is_compat_odd = (lw % 2 == 1) && (lwmod < 0.1); // only even linewidths implemented properly + + int off_x = 0; + int off_y = 0; + + + if (is_compat_odd) + { + off_x = CTX_SUBDIV/2; + off_y = CTX_FULL_AA/2; + } + + if((is_compat_odd || is_compat_even) && + (((entry1->data.s16[2]-off_x) % (CTX_SUBDIV)) == 0) && + (((entry1->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0) && + (((entry3->data.s16[2]-off_x) % (CTX_SUBDIV)) == 0) && + (((entry3->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0)) + { + float x0 = entry3->data.s16[2] * 1.0f / CTX_SUBDIV; + float y0 = entry3->data.s16[3] * 1.0f / CTX_FULL_AA; + float x1 = entry1->data.s16[2] * 1.0f / CTX_SUBDIV; + float y1 = entry1->data.s16[3] * 1.0f / CTX_FULL_AA; + + int bw = lw/2+1; + int bwb = lw/2; + + if (is_compat_even) + { + bw = lw/2; + } + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0-bwb, x1+bw, y0+bw, 255); + + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y1-bwb, x1-bwb, y1+bw, 255); + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0, x0+bw, y1, 255); + ctx_rasterizer_fill_rect (rasterizer, x1-bwb, y0, x1+bw, y1+bw, 255); + ctx_rasterizer_reset (rasterizer); + goto done; + } + } + } + + { + + int aa = CTX_FULL_AA; +#if 0 + if (CTX_UNLIKELY(gstate->line_width * factor <= 0.0f && + gstate->line_width * factor > -10.0f)) + { + ctx_rasterizer_stroke_1px (rasterizer); + } + else +#endif + { + if (line_width < 5.0f) + { + factor *= 0.89; /* this hack adjustment makes sharp 1px and 2px strokewidths + // end up sharp without erronious AA; we seem to be off by + // one somewhere else, causing the need for this + // */ + line_width *= 0.89f; + } + ctx_rasterizer_reset (rasterizer); /* then start afresh with our stroked shape */ + CtxMatrix transform_backup = gstate->transform; + _ctx_matrix_identity (&gstate->transform); + float prev_x = 0.0f; + float prev_y = 0.0f; + float half_width_x = line_width/2; + float half_width_y = line_width/2; + if (CTX_UNLIKELY(line_width <= 0.0f)) + { // makes 0 width be hairline + half_width_x = .5f; + half_width_y = .5f; + } + int start = 0; + int end = 0; + while (start < count) + { + int started = 0; + int i; + for (i = start; i < count; i++) + { + CtxSegment *entry = &temp[i]; + float x, y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (CTX_LIKELY(started)) + { + end = i - 1; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + float dx = x - prev_x; + float dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + if (CTX_LIKELY(length>0.001f)) + { + float recip_length = 1.0/length; + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + ctx_rasterizer_finish_shape (rasterizer); + ctx_rasterizer_move_to (rasterizer, prev_x+dy, prev_y-dx); + } + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + + // we need to know the slope of the other side + + // XXX possible miter line-to + //ctx_rasterizer_line_to (rasterizer, prev_x-dy+4, prev_y+dx+10); + //ctx_rasterizer_line_to (rasterizer, prev_x-dy+8, prev_y+dx+0); + + + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + for (int i = end; i >= start; i--) + { + CtxSegment *entry = &temp[i]; + float x, y, dx, dy; + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + dx = x - prev_x; + dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + float recip_length = 1.0f/length; + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + if (CTX_LIKELY(length>0.001f)) + { + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + // XXX possible miter line-to + // ctx_rasterizer_line_to (rasterizer, prev_x-dy+10, prev_y+dx+10); + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + prev_x = x; + prev_y = y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + dx = x - prev_x; + dy = y - prev_y; + length = ctx_fast_hypotf (dx, dy); + recip_length = 1.0f/length; + if (CTX_LIKELY(length>0.001f)) + { + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + } + if ( (prev_x != x) && (prev_y != y) ) + { + prev_x = x; + prev_y = y; + } + } + start = end+1; + } + ctx_rasterizer_finish_shape (rasterizer); + switch (gstate->line_cap) + { + case CTX_CAP_SQUARE: // XXX: incorrect - if rectangles were in + // reverse order - rotation would be off + // better implement correct here + { + float x = 0, y = 0; + int has_prev = 0; + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &temp[i]; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (has_prev) + { + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x, half_width_y); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + has_prev = 1; + } + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2); + ctx_rasterizer_finish_shape (rasterizer); + } + break; + case CTX_CAP_NONE: /* nothing to do */ + break; + case CTX_CAP_ROUND: + { + float x = 0, y = 0; + int has_prev = 0; + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &temp[i]; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (has_prev) + { + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + has_prev = 1; + } + ctx_rasterizer_move_to (rasterizer, x, y); + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + break; + } + } + switch (gstate->line_join) + { + case CTX_JOIN_BEVEL: + case CTX_JOIN_MITER: + break; + case CTX_JOIN_ROUND: + { + float x = 0, y = 0; + for (int i = 0; i < count-1; i++) + { + CtxSegment *entry = &temp[i]; + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + if (CTX_UNLIKELY(entry[1].code == CTX_EDGE)) + { + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*2, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + } + break; + } + } + CtxFillRule rule_backup = gstate->fill_rule; + gstate->fill_rule = CTX_FILL_RULE_WINDING; + rasterizer->preserve = 0; // so fill isn't tripped + ctx_rasterizer_fill (rasterizer); + gstate->fill_rule = rule_backup; + gstate->transform = transform_backup; + } + } +done: + if (preserved) + { + memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) ); + rasterizer->edge_list.count = count; + rasterizer->preserve = 0; + } + if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL) + gstate->source_fill = source_backup; +} + +#if CTX_1BIT_CLIP +#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY1 +#else +#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY8 +#endif + + +static void +ctx_rasterizer_clip_reset (CtxRasterizer *rasterizer) +{ +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + ctx_buffer_free (rasterizer->clip_buffer); + rasterizer->clip_buffer = NULL; +#endif + rasterizer->state->gstate.clip_min_x = rasterizer->blit_x; + rasterizer->state->gstate.clip_min_y = rasterizer->blit_y; + + rasterizer->state->gstate.clip_max_x = rasterizer->blit_x + rasterizer->blit_width - 1; + rasterizer->state->gstate.clip_max_y = rasterizer->blit_y + rasterizer->blit_height - 1; +} + +static void +ctx_rasterizer_clip_apply (CtxRasterizer *rasterizer, + CtxSegment *edges) +{ + int count = edges[0].data.u32[0]; + + int minx = 5000; + int miny = 5000; + int maxx = -5000; + int maxy = -5000; + int prev_x = 0; + int prev_y = 0; + int blit_width = rasterizer->blit_width; + int blit_height = rasterizer->blit_height; + + int aa = 15;//rasterizer->aa; + float coords[6][2]; + + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &edges[i+1]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + if (prev_x < minx) { minx = prev_x; } + if (prev_y < miny) { miny = prev_y; } + if (prev_x > maxx) { maxx = prev_x; } + if (prev_y > maxy) { maxy = prev_y; } + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + if (x < minx) { minx = x; } + if (y < miny) { miny = y; } + if (x > maxx) { maxx = x; } + if (y > maxy) { maxy = y; } + + if (i < 6) + { + coords[i][0] = x; + coords[i][1] = y; + } + } + +#if CTX_ENABLE_CLIP + + if ((rasterizer->clip_rectangle==1 + || !rasterizer->clip_buffer) + ) + { + if (count == 6) + { + if (coords[3][0] == coords[5][0] && + coords[3][1] == coords[5][1]) + { +#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; + } + } + } + rasterizer->clip_rectangle = 0; + + if ((minx == maxx) || (miny == maxy)) // XXX : reset hack + { + ctx_rasterizer_clip_reset (rasterizer); + return;//goto done; + } + + int we_made_it = 0; + CtxBuffer *clip_buffer; + + if (!rasterizer->clip_buffer) + { + rasterizer->clip_buffer = ctx_buffer_new (blit_width, + blit_height, + CTX_CLIP_FORMAT); + clip_buffer = rasterizer->clip_buffer; + we_made_it = 1; + if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1) + memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height/8); + else + memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height); + } + else + { + clip_buffer = ctx_buffer_new (blit_width, blit_height, + CTX_CLIP_FORMAT); + } + + { + + int prev_x = 0; + int prev_y = 0; + + Ctx *ctx = ctx_new_for_framebuffer (clip_buffer->data, blit_width, blit_height, + blit_width, + CTX_CLIP_FORMAT); + + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &edges[i+1]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + ctx_move_to (ctx, prev_x, prev_y); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + ctx_line_to (ctx, x, y); + } + ctx_gray (ctx, 1.0f); + ctx_fill (ctx); + ctx_free (ctx); + } + + int maybe_rect = 1; + rasterizer->clip_rectangle = 0; + + if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1) + { + int count = blit_width * blit_height / 8; + for (int i = 0; i < count; i++) + { + ((uint8_t*)rasterizer->clip_buffer->data)[i] = + (((uint8_t*)rasterizer->clip_buffer->data)[i] & + ((uint8_t*)clip_buffer->data)[i]); + } + } + else + { + int count = blit_width * blit_height; + + + int i; + int x0 = 0; + int y0 = 0; + int width = -1; + int next_stage = 0; + uint8_t *p_data = (uint8_t*)rasterizer->clip_buffer->data; + uint8_t *data = (uint8_t*)clip_buffer->data; + + i=0; + /* find upper left */ + for (; i < count && maybe_rect && !next_stage; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + switch (val) + { + case 255: + x0 = i % blit_width; + y0 = i / blit_width; + next_stage = 1; + break; + case 0: break; + default: + maybe_rect = 0; + break; + } + } + + next_stage = 0; + /* figure out with */ + for (; i < count && !next_stage && maybe_rect; i++) + { + int x = i % blit_width; + int y = i / blit_width; + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (y == y0) + { + switch (val) + { + case 255: + width = x - x0 + 1; + break; + case 0: + next_stage = 1; + break; + default: + maybe_rect = 0; + break; + } + if (x % blit_width == blit_width - 1) next_stage = 1; + } + else next_stage = 1; + } + + next_stage = 0; + /* body */ + for (; i < count && maybe_rect && !next_stage; i++) + { + int x = i % blit_width; + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (x < x0) + { + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } else if (x < x0 + width) + { + if (val != 255){ if (val != 0) maybe_rect = 0; next_stage = 1; } + } else { + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } + } + + next_stage = 0; + /* foot */ + for (; i < count && maybe_rect && !next_stage; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } + + + for (; i < count; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + } + + if (maybe_rect) + rasterizer->clip_rectangle = 1; + } + if (!we_made_it) + ctx_buffer_free (clip_buffer); +#else + if (coords[0][0]){}; +#endif + + rasterizer->state->gstate.clip_min_x = ctx_maxi (minx, + rasterizer->state->gstate.clip_min_x); + rasterizer->state->gstate.clip_min_y = ctx_maxi (miny, + rasterizer->state->gstate.clip_min_y); + rasterizer->state->gstate.clip_max_x = ctx_mini (maxx, + rasterizer->state->gstate.clip_max_x); + rasterizer->state->gstate.clip_max_y = ctx_mini (maxy, + rasterizer->state->gstate.clip_max_y); +} + +static void +ctx_rasterizer_clip (CtxRasterizer *rasterizer) +{ + int count = rasterizer->edge_list.count; + CtxSegment temp[count+1]; /* copy of already built up path's poly line */ + rasterizer->state->has_clipped=1; + rasterizer->state->gstate.clipped=1; + //if (rasterizer->preserve) + { memcpy (temp + 1, rasterizer->edge_list.entries, sizeof (temp) - sizeof (temp[0])); + temp[0].code = CTX_NOP; + temp[0].data.u32[0] = count; + ctx_state_set_blob (rasterizer->state, CTX_clip, (uint8_t*)temp, sizeof(temp)); + } + ctx_rasterizer_clip_apply (rasterizer, temp); + ctx_rasterizer_reset (rasterizer); + if (rasterizer->preserve) + { + memcpy (rasterizer->edge_list.entries, temp + 1, sizeof (temp) - sizeof(temp[0])); + rasterizer->edge_list.count = count; + rasterizer->preserve = 0; + } +} + + +#if 0 +static void +ctx_rasterizer_load_image (CtxRasterizer *rasterizer, + const char *path, + float x, + float y) +{ + // decode PNG, put it in image is slot 1, + // magic width height stride format data + ctx_buffer_load_png (&rasterizer->ctx->texture[0], path); + ctx_rasterizer_set_texture (rasterizer, 0, x, y); +} +#endif + + +CTX_INLINE void +ctx_rasterizer_rectangle (CtxRasterizer *rasterizer, + float x, + float y, + float width, + float height) +{ + ctx_rasterizer_move_to (rasterizer, x, y); + ctx_rasterizer_rel_line_to (rasterizer, width, 0); + ctx_rasterizer_rel_line_to (rasterizer, 0, height); + ctx_rasterizer_rel_line_to (rasterizer, -width, 0); + ctx_rasterizer_rel_line_to (rasterizer, 0, -height); + ctx_rasterizer_rel_line_to (rasterizer, width/2, 0); + ctx_rasterizer_finish_shape (rasterizer); +} + +static void +ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer, + uint16_t x, + uint16_t y, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a) +{ + rasterizer->state->gstate.source_fill.type = CTX_SOURCE_COLOR; + ctx_color_set_RGBA8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, r, g, b, a); + rasterizer->comp_op = NULL; +#if 0 + // XXX : doesn't take transforms into account - and has + // received less testing than code paths part of protocol, + // using rectangle properly will trigger the fillrect fastpath + ctx_rasterizer_pset (rasterizer, x, y, 255); +#else + ctx_rasterizer_rectangle (rasterizer, x, y, 1.0, 1.0); + ctx_rasterizer_fill (rasterizer); +#endif +} + +#if CTX_ENABLE_SHADOW_BLUR +static float +ctx_gaussian (float x, float mu, float sigma) +{ + float a = ( x- mu) / sigma; + return ctx_expf (-0.5 * a * a); +} + +static void +ctx_compute_gaussian_kernel (int dim, float radius, float *kernel) +{ + float sigma = radius / 2; + float sum = 0.0; + int i = 0; + //for (int row = 0; row < dim; row ++) + for (int col = 0; col < dim; col ++, i++) + { + float val = //ctx_gaussian (row, radius, sigma) * + ctx_gaussian (col, radius, sigma); + kernel[i] = val; + sum += val; + } + i = 0; + //for (int row = 0; row < dim; row ++) + for (int col = 0; col < dim; col ++, i++) + kernel[i] /= sum; +} +#endif + +static void +ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius) +{ + float aspect = 1.0f; + float radius = corner_radius / aspect; + float degrees = CTX_PI / 180.0f; + + if (radius > width*0.5f) radius = width/2; + if (radius > height*0.5f) radius = height/2; + + ctx_rasterizer_finish_shape (rasterizer); + ctx_rasterizer_arc (rasterizer, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + radius, y + radius, radius, 180 * degrees, 270 * degrees, 0); + + ctx_rasterizer_finish_shape (rasterizer); +} + +static void +ctx_rasterizer_process (void *user_data, CtxCommand *command); + +int +_ctx_is_rasterizer (Ctx *ctx) +{ + if (ctx->renderer && ctx->renderer->process == ctx_rasterizer_process) + return 1; + return 0; +} + +#if CTX_COMPOSITING_GROUPS +static void +ctx_rasterizer_start_group (CtxRasterizer *rasterizer) +{ + CtxEntry save_command = ctx_void(CTX_SAVE); + // allocate buffer, and set it as temporary target + int no; + if (rasterizer->group[0] == NULL) // first group + { + rasterizer->saved_buf = rasterizer->buf; + } + for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++); + + if (no >= CTX_GROUP_MAX) + return; + rasterizer->group[no] = ctx_buffer_new (rasterizer->blit_width, + rasterizer->blit_height, + rasterizer->format->composite_format); + rasterizer->buf = rasterizer->group[no]->data; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); +} + +static void +ctx_rasterizer_end_group (CtxRasterizer *rasterizer) +{ + CtxEntry restore_command = ctx_void(CTX_RESTORE); + CtxEntry save_command = ctx_void(CTX_SAVE); + int no = 0; + for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++); + no--; + + if (no < 0) + return; + + CtxCompositingMode comp = rasterizer->state->gstate.compositing_mode; + CtxBlend blend = rasterizer->state->gstate.blend_mode; + float global_alpha = rasterizer->state->gstate.global_alpha_f; + // fetch compositing, blending, global alpha + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + CtxEntry set_state[3]= + { + ctx_u32 (CTX_COMPOSITING_MODE, comp, 0), + ctx_u32 (CTX_BLEND_MODE, blend, 0), + ctx_f (CTX_GLOBAL_ALPHA, global_alpha, 0.0) + }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[0]); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[1]); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[2]); + if (no == 0) + { + rasterizer->buf = rasterizer->saved_buf; + } + else + { + rasterizer->buf = rasterizer->group[no-1]->data; + } + // XXX use texture_source ? + ctx_texture_init (rasterizer->ctx, ".ctx-group", // XXX ? count groups.. + rasterizer->blit_width, // or have group based on thread-id? + rasterizer->blit_height, // .. this would mean threadsafe + // allocation + rasterizer->blit_width * rasterizer->format->bpp/8, + rasterizer->format->pixel_format, + NULL, // space + (uint8_t*)rasterizer->group[no]->data, + NULL, NULL); + { + const char *eid = ".ctx-group"; + int eid_len = strlen (eid); + + CtxEntry commands[4] = + { + ctx_f (CTX_TEXTURE, rasterizer->blit_x, rasterizer->blit_y), + ctx_u32 (CTX_DATA, eid_len, eid_len/9+1), + ctx_u32 (CTX_CONT, 0,0), + ctx_u32 (CTX_CONT, 0,0) + }; + memcpy( (char *) &commands[2].data.u8[0], eid, eid_len); + ( (char *) (&commands[2].data.u8[0]) ) [eid_len]=0; + + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + { + CtxEntry commands[2]= + { + ctx_f (CTX_RECTANGLE, rasterizer->blit_x, rasterizer->blit_y), + ctx_f (CTX_CONT, rasterizer->blit_width, rasterizer->blit_height) + }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + { + CtxEntry commands[1]= { ctx_void (CTX_FILL) }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + //ctx_texture_release (rasterizer->ctx, ".ctx-group"); + ctx_buffer_free (rasterizer->group[no]); + rasterizer->group[no] = 0; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} +#endif + +#if CTX_ENABLE_SHADOW_BLUR +static void +ctx_rasterizer_shadow_stroke (CtxRasterizer *rasterizer) +{ + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + { + int i = 0; + for (int v = 0; v < dim; v += 1, i++) + { + float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2; + set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command[0]); +#if CTX_ENABLE_SHADOW_BLUR + rasterizer->in_shadow = 1; +#endif + rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x; + rasterizer->shadow_y = dy; + rasterizer->preserve = 1; + ctx_rasterizer_stroke (rasterizer); +#if CTX_ENABLE_SHADOW_BLUR + rasterizer->in_shadow = 0; +#endif + } + } + //free (kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} + +static void +ctx_rasterizer_shadow_text (CtxRasterizer *rasterizer, const char *str) +{ + float x = rasterizer->state->x; + float y = rasterizer->state->y; + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry move_to_command [1]= + { + ctx_f (CTX_MOVE_TO, x, y), + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + + { + { + move_to_command[0].data.f[0] = x; + move_to_command[0].data.f[1] = y; + set_color_command[2].data.f[0] = rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command); + rasterizer->in_shadow=1; + ctx_rasterizer_text (rasterizer, str, 0); + rasterizer->in_shadow=0; + } + } + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); + move_to_command[0].data.f[0] = x; + move_to_command[0].data.f[1] = y; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command); +} + +static void +ctx_rasterizer_shadow_fill (CtxRasterizer *rasterizer) +{ + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + + { + for (int v = 0; v < dim; v ++) + { + int i = v; + float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2; + set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command); + rasterizer->in_shadow = 1; + rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x; + rasterizer->shadow_y = dy; + rasterizer->preserve = 1; + ctx_rasterizer_fill (rasterizer); + rasterizer->in_shadow = 0; + } + } + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} +#endif + +static void +ctx_rasterizer_line_dash (CtxRasterizer *rasterizer, int count, float *dashes) +{ + if (!dashes) + { + rasterizer->state->gstate.n_dashes = 0; + return; + } + count = CTX_MIN(count, CTX_PARSER_MAX_ARGS-1); + rasterizer->state->gstate.n_dashes = count; + memcpy(&rasterizer->state->gstate.dashes[0], dashes, count * sizeof(float)); + for (int i = 0; i < count; i ++) + { + if (rasterizer->state->gstate.dashes[i] < 0.0001f) + rasterizer->state->gstate.dashes[i] = 0.0001f; // hang protection + } +} + + +static void +ctx_rasterizer_process (void *user_data, CtxCommand *command) +{ + CtxEntry *entry = &command->entry; + CtxRasterizer *rasterizer = (CtxRasterizer *) user_data; + CtxState *state = rasterizer->state; + CtxCommand *c = (CtxCommand *) entry; + int clear_clip = 0; + ctx_interpret_style (state, entry, NULL); + switch (c->code) + { +#if CTX_ENABLE_SHADOW_BLUR + case CTX_SHADOW_COLOR: + { + CtxColor col; + CtxColor *color = &col; + //state->gstate.source_fill.type = CTX_SOURCE_COLOR; + switch ((int)c->rgba.model) + { + case CTX_RGB: + ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f); + break; + case CTX_RGBA: + //ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; + case CTX_DRGBA: + ctx_color_set_drgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYKA: + ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_CMYK: + ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; + case CTX_DCMYKA: + ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; +#endif + case CTX_GRAYA: + ctx_color_set_graya (state, color, c->graya.g, c->graya.a); + break; + case CTX_GRAY: + ctx_color_set_graya (state, color, c->graya.g, 1.0f); + break; + } + ctx_set_color (rasterizer->ctx, CTX_shadowColor, color); + } + break; +#endif + case CTX_LINE_DASH: + if (c->line_dash.count) + { + ctx_rasterizer_line_dash (rasterizer, c->line_dash.count, c->line_dash.data); + } + else + ctx_rasterizer_line_dash (rasterizer, 0, NULL); + break; + + case CTX_LINE_TO: + ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_LINE_TO: + ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_MOVE_TO: + ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_MOVE_TO: + ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_CURVE_TO: + ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_REL_CURVE_TO: + ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_QUAD_TO: + ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_REL_QUAD_TO: + ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_ARC: + ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction); + break; + case CTX_RECTANGLE: + ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_ROUND_RECTANGLE: + ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height, + c->rectangle.radius); + break; + case CTX_SET_PIXEL: + ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y, + c->set_pixel.rgba[0], + c->set_pixel.rgba[1], + c->set_pixel.rgba[2], + c->set_pixel.rgba[3]); + break; + case CTX_DEFINE_TEXTURE: + { + uint8_t *pixel_data = ctx_define_texture_pixel_data (entry); + ctx_rasterizer_define_texture (rasterizer, c->define_texture.eid, + c->define_texture.width, c->define_texture.height, + c->define_texture.format, + pixel_data); + rasterizer->comp_op = NULL; + rasterizer->fragment = NULL; + } + break; + case CTX_TEXTURE: + ctx_rasterizer_set_texture (rasterizer, c->texture.eid, + c->texture.x, c->texture.y); + rasterizer->comp_op = NULL; + rasterizer->fragment = NULL; + break; +#if 0 + case CTX_LOAD_IMAGE: + ctx_rasterizer_load_image (rasterizer, ctx_arg_string(), + ctx_arg_float (0), ctx_arg_float (1) ); + break; +#endif +#if CTX_GRADIENTS + case CTX_GRADIENT_STOP: + { + float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (4+1) ), + ctx_u8_to_float (ctx_arg_u8 (4+2) ), + ctx_u8_to_float (ctx_arg_u8 (4+3) ) + }; + ctx_rasterizer_gradient_add_stop (rasterizer, + ctx_arg_float (0), rgba); + rasterizer->comp_op = NULL; + } + break; + case CTX_LINEAR_GRADIENT: + ctx_state_gradient_clear_stops (state); + rasterizer->comp_op = NULL; + break; + case CTX_RADIAL_GRADIENT: + ctx_state_gradient_clear_stops (state); + rasterizer->comp_op = NULL; + break; +#endif + case CTX_PRESERVE: + rasterizer->preserve = 1; + break; + case CTX_COLOR: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + rasterizer->comp_op = NULL; + //_ctx_setup_compositor (rasterizer); + break; +#if CTX_COMPOSITING_GROUPS + case CTX_START_GROUP: + ctx_rasterizer_start_group (rasterizer); + break; + case CTX_END_GROUP: + ctx_rasterizer_end_group (rasterizer); + break; +#endif + + case CTX_RESTORE: + for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0; + i < state->gstate.keydb_pos; i++) + { + if (state->keydb[i].key == CTX_clip) + { + clear_clip = 1; + } + } + /* FALLTHROUGH */ + case CTX_ROTATE: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_IDENTITY: + case CTX_SAVE: + rasterizer->comp_op = NULL; + rasterizer->uses_transforms = 1; + ctx_interpret_transforms (state, entry, NULL); + if (clear_clip) + { + ctx_rasterizer_clip_reset (rasterizer); + for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0; + i < state->gstate.keydb_pos; i++) + { + if (state->keydb[i].key == CTX_clip) + { + int idx = ctx_float_to_string_index (state->keydb[i].value); + if (idx >=0) + { + CtxSegment *edges = (CtxSegment*)&state->stringpool[idx]; + ctx_rasterizer_clip_apply (rasterizer, edges); + } + } + } + } + break; + case CTX_STROKE: +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0 && + !rasterizer->in_text) + ctx_rasterizer_shadow_stroke (rasterizer); +#endif + { + int count = rasterizer->edge_list.count; + if (state->gstate.n_dashes) + { + int n_dashes = state->gstate.n_dashes; + float *dashes = state->gstate.dashes; + float factor = ctx_matrix_get_scale (&state->gstate.transform); + + int aa = 15;//rasterizer->aa; + CtxEntry temp[count]; /* copy of already built up path's poly line */ + memcpy (temp, rasterizer->edge_list.entries, sizeof (temp)); + int start = 0; + int end = 0; + CtxMatrix transform_backup = state->gstate.transform; + _ctx_matrix_identity (&state->gstate.transform); + ctx_rasterizer_reset (rasterizer); /* for dashing we create + a dashed path to stroke */ + float prev_x = 0.0f; + float prev_y = 0.0f; + float pos = 0.0; + + int dash_no = 0.0; + float dash_lpos = state->gstate.line_dash_offset * factor; + int is_down = 0; + + while (start < count) + { + int started = 0; + int i; + is_down = 0; + + if (!is_down) + { + CtxEntry *entry = &temp[0]; + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_move_to (rasterizer, prev_x, prev_y); + is_down = 1; + } + + + for (i = start; i < count; i++) + { + CtxEntry *entry = &temp[i]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + if (started) + { + end = i - 1; + dash_no = 0; + dash_lpos = 0.0; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + is_down = 1; + ctx_rasterizer_move_to (rasterizer, prev_x, prev_y); + } + +again: + + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + float dx = x - prev_x; + float dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + + if (dash_lpos + length >= dashes[dash_no] * factor) + { + float p = (dashes[dash_no] * factor - dash_lpos) / length; + float splitx = x * p + (1.0f - p) * prev_x; + float splity = y * p + (1.0f - p) * prev_y; + if (is_down) + { + ctx_rasterizer_line_to (rasterizer, splitx, splity); + is_down = 0; + } + else + { + ctx_rasterizer_move_to (rasterizer, splitx, splity); + is_down = 1; + } + prev_x = splitx; + prev_y = splity; + dash_no++; + dash_lpos=0; + if (dash_no >= n_dashes) dash_no = 0; + goto again; + } + else + { + pos += length; + dash_lpos += length; + { + if (is_down) + ctx_rasterizer_line_to (rasterizer, x, y); + } + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + start = end+1; + } + state->gstate.transform = transform_backup; + } + ctx_rasterizer_stroke (rasterizer); + } + + break; + case CTX_FONT: + ctx_rasterizer_set_font (rasterizer, ctx_arg_string() ); + break; + case CTX_TEXT: + rasterizer->in_text++; +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0) + ctx_rasterizer_shadow_text (rasterizer, ctx_arg_string ()); +#endif + ctx_rasterizer_text (rasterizer, ctx_arg_string(), 0); + rasterizer->in_text--; + ctx_rasterizer_reset (rasterizer); + break; + case CTX_STROKE_TEXT: + ctx_rasterizer_text (rasterizer, ctx_arg_string(), 1); + ctx_rasterizer_reset (rasterizer); + break; + case CTX_GLYPH: + ctx_rasterizer_glyph (rasterizer, entry[0].data.u32[0], entry[0].data.u8[4]); + break; + case CTX_FILL: +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0 && + !rasterizer->in_text) + ctx_rasterizer_shadow_fill (rasterizer); +#endif + ctx_rasterizer_fill (rasterizer); + break; + case CTX_RESET: + case CTX_BEGIN_PATH: + ctx_rasterizer_reset (rasterizer); + break; + case CTX_CLIP: + ctx_rasterizer_clip (rasterizer); + break; + case CTX_CLOSE_PATH: + ctx_rasterizer_finish_shape (rasterizer); + break; + case CTX_IMAGE_SMOOTHING: + rasterizer->comp_op = NULL; + break; + } + ctx_interpret_pos_bare (state, entry, NULL); +} + +void +ctx_rasterizer_deinit (CtxRasterizer *rasterizer) +{ + ctx_drawlist_deinit (&rasterizer->edge_list); +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + { + ctx_buffer_free (rasterizer->clip_buffer); + rasterizer->clip_buffer = NULL; + } +#endif +#if CTX_SHAPE_CACHE + for (int i = 0; i < CTX_SHAPE_CACHE_ENTRIES; i ++) + if (rasterizer->shape_cache.entries[i]) + { + free (rasterizer->shape_cache.entries[i]); + rasterizer->shape_cache.entries[i] = NULL; + } + +#endif + if (rasterizer->opaque) + free (rasterizer->opaque); + free (rasterizer); +} + + +CtxAntialias ctx_get_antialias (Ctx *ctx) +{ +#if CTX_EVENTS + if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx)) + { + CtxTiled *fb = (CtxTiled*)(ctx->renderer); + return fb->antialias; + } +#endif + if (!_ctx_is_rasterizer (ctx)) return CTX_ANTIALIAS_DEFAULT; + + switch (((CtxRasterizer*)(ctx->renderer))->aa) + { + case 1: return CTX_ANTIALIAS_NONE; + case 3: return CTX_ANTIALIAS_FAST; + //case 5: return CTX_ANTIALIAS_GOOD; + default: + case 5: return CTX_ANTIALIAS_DEFAULT; + case 17: return CTX_ANTIALIAS_BEST; + } +} + +int _ctx_antialias_to_aa (CtxAntialias antialias) +{ + switch (antialias) + { + case CTX_ANTIALIAS_NONE: return 1; + case CTX_ANTIALIAS_FAST: return 3; + case CTX_ANTIALIAS_GOOD: return 5; + default: + case CTX_ANTIALIAS_DEFAULT: return CTX_RASTERIZER_AA; + case CTX_ANTIALIAS_BEST: return 17; + } +} + +void +ctx_set_antialias (Ctx *ctx, CtxAntialias antialias) +{ +#if CTX_EVENTS + if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx)) + { + CtxTiled *fb = (CtxTiled*)(ctx->renderer); + fb->antialias = antialias; + for (int i = 0; i < _ctx_max_threads; i++) + { + ctx_set_antialias (fb->host[i], antialias); + } + return; + } +#endif + if (!_ctx_is_rasterizer (ctx)) return; + + ((CtxRasterizer*)(ctx->renderer))->aa = + _ctx_antialias_to_aa (antialias); + ((CtxRasterizer*)(ctx->renderer))->fast_aa = 0; + if (antialias == CTX_ANTIALIAS_DEFAULT|| + antialias == CTX_ANTIALIAS_FAST) + ((CtxRasterizer*)(ctx->renderer))->fast_aa = 1; +} + +CtxRasterizer * +ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias) +{ +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + ctx_buffer_free (rasterizer->clip_buffer); +#endif + if (rasterizer->edge_list.size) + ctx_drawlist_deinit (&rasterizer->edge_list); + + memset (rasterizer, 0, sizeof (CtxRasterizer) ); + rasterizer->vfuncs.process = ctx_rasterizer_process; + rasterizer->vfuncs.free = (CtxDestroyNotify)ctx_rasterizer_deinit; + rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST; + rasterizer->state = state; + rasterizer->ctx = ctx; + rasterizer->texture_source = texture_source?texture_source:ctx; + + rasterizer->aa = _ctx_antialias_to_aa (antialias); + rasterizer->fast_aa = (antialias == CTX_ANTIALIAS_DEFAULT||antialias == CTX_ANTIALIAS_FAST); + ctx_state_init (rasterizer->state); + rasterizer->buf = data; + rasterizer->blit_x = x; + rasterizer->blit_y = y; + rasterizer->blit_width = width; + rasterizer->blit_height = height; + rasterizer->state->gstate.clip_min_x = x; + rasterizer->state->gstate.clip_min_y = y; + rasterizer->state->gstate.clip_max_x = x + width - 1; + rasterizer->state->gstate.clip_max_y = y + height - 1; + rasterizer->blit_stride = stride; + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + + if (pixel_format == CTX_FORMAT_BGRA8) + { + pixel_format = CTX_FORMAT_RGBA8; + rasterizer->swap_red_green = 1; + } + + rasterizer->format = ctx_pixel_format_info (pixel_format); + + if (rasterizer->opaque==NULL) + { + rasterizer->opaque=(uint8_t*)malloc(CTX_MAX_FRAMEBUFFER_WIDTH); + memset (rasterizer->opaque, 255, CTX_MAX_FRAMEBUFFER_WIDTH); + } + + return rasterizer; +} + +Ctx * +ctx_new_for_buffer (CtxBuffer *buffer) +{ + Ctx *ctx = ctx_new (); + ctx_set_renderer (ctx, + ctx_rasterizer_init ( (CtxRasterizer *) malloc (sizeof (CtxRasterizer) ), + ctx, NULL, &ctx->state, + buffer->data, 0, 0, buffer->width, buffer->height, + buffer->stride, buffer->format->pixel_format, + CTX_ANTIALIAS_DEFAULT)); + return ctx; +} + +Ctx * +ctx_new_for_framebuffer (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format) +{ + Ctx *ctx = ctx_new (); + CtxRasterizer *r = ctx_rasterizer_init ( (CtxRasterizer *) ctx_calloc (sizeof (CtxRasterizer), 1), + ctx, NULL, &ctx->state, data, 0, 0, width, height, + stride, pixel_format, CTX_ANTIALIAS_DEFAULT); + ctx_set_renderer (ctx, r); + return ctx; +} + +// ctx_new_for_stream (FILE *stream); + +#if 0 +CtxRasterizer *ctx_rasterizer_new (void *data, int x, int y, int width, int height, + int stride, CtxPixelFormat pixel_format) +{ + CtxState *state = (CtxState *) malloc (sizeof (CtxState) ); + CtxRasterizer *rasterizer = (CtxRasterizer *) malloc (sizeof (CtxRenderer) ); + ctx_rasterizer_init (rasterizer, state, data, x, y, width, height, + stride, pixel_format, CTX_ANTIALIAS_DEFAULT); +} +#endif + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format); + +#else + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format) +{ + return NULL; +} +#endif + +void +ctx_current_point (Ctx *ctx, float *x, float *y) +{ + if (!ctx) + { + if (x) { *x = 0.0f; } + if (y) { *y = 0.0f; } + } +#if CTX_RASTERIZER + if (ctx->renderer) + { + if (x) { *x = ( (CtxRasterizer *) (ctx->renderer) )->x; } + if (y) { *y = ( (CtxRasterizer *) (ctx->renderer) )->y; } + return; + } +#endif + if (x) { *x = ctx->state.x; } + if (y) { *y = ctx->state.y; } +} + +float ctx_x (Ctx *ctx) +{ + float x = 0, y = 0; + ctx_current_point (ctx, &x, &y); + return x; +} + +float ctx_y (Ctx *ctx) +{ + float x = 0, y = 0; + ctx_current_point (ctx, &x, &y); + return y; +} + +static void +ctx_process (Ctx *ctx, CtxEntry *entry) +{ +#if CTX_CURRENT_PATH + switch (entry->code) + { + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_BEGIN_PATH: + ctx->current_path.count = 0; + break; + case CTX_CLIP: + case CTX_FILL: + case CTX_STROKE: + // XXX unless preserve + ctx->current_path.count = 0; + break; + case CTX_CLOSE_PATH: + case CTX_LINE_TO: + case CTX_MOVE_TO: + case CTX_QUAD_TO: + case CTX_SMOOTH_TO: + case CTX_SMOOTHQ_TO: + case CTX_REL_QUAD_TO: + case CTX_REL_SMOOTH_TO: + case CTX_REL_SMOOTHQ_TO: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: + ctx_drawlist_add_entry (&ctx->current_path, entry); + break; + default: + break; + } +#endif +#if CTX_RASTERIZER + if (CTX_LIKELY(ctx->renderer && ctx->renderer->process == ctx_rasterizer_process)) + { + ctx_rasterizer_process (ctx->renderer, (CtxCommand *) entry); + } + else +#endif + if (CTX_LIKELY(ctx->renderer && ctx->renderer->process)) + { + ctx->renderer->process (ctx->renderer, (CtxCommand *) entry); + } + else + { + /* these functions might alter the code and coordinates of + command that in the end gets added to the drawlist + */ + ctx_interpret_style (&ctx->state, entry, ctx); + ctx_interpret_transforms (&ctx->state, entry, ctx); + ctx_interpret_pos (&ctx->state, entry, ctx); + ctx_drawlist_add_entry (&ctx->drawlist, entry); + } +} + + +int ctx_gradient_cache_valid = 0; + +void +ctx_state_gradient_clear_stops (CtxState *state) +{ +//#if CTX_GRADIENT_CACHE +// ctx_gradient_cache_reset (); +//#endif + ctx_gradient_cache_valid = 0; + state->gradient.n_stops = 0; +} + + +/**** end of engine ****/ + +CtxBuffer *ctx_buffer_new_bare (void) +{ + CtxBuffer *buffer = (CtxBuffer *) ctx_calloc (sizeof (CtxBuffer), 1); + return buffer; +} + +void ctx_buffer_set_data (CtxBuffer *buffer, + void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + if (buffer->free_func) + { buffer->free_func (buffer->data, buffer->user_data); } + if (stride <= 0) + stride = ctx_pixel_format_get_stride (pixel_format, width); + buffer->data = data; + buffer->width = width; + buffer->height = height; + buffer->stride = stride; + buffer->format = ctx_pixel_format_info (pixel_format); + buffer->free_func = freefunc; + buffer->user_data = user_data; +} + +CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + CtxBuffer *buffer = ctx_buffer_new_bare (); + ctx_buffer_set_data (buffer, data, width, height, stride, pixel_format, + freefunc, user_data); + return buffer; +} + +void ctx_buffer_pixels_free (void *pixels, void *userdata) +{ + free (pixels); +} + +CtxBuffer *ctx_buffer_new (int width, int height, + CtxPixelFormat pixel_format) +{ + //CtxPixelFormatInfo *info = ctx_pixel_format_info (pixel_format); + CtxBuffer *buffer = ctx_buffer_new_bare (); + int stride = ctx_pixel_format_get_stride (pixel_format, width); + int data_len = stride * height; + if (pixel_format == CTX_FORMAT_YUV420) + data_len = width * height + ((width/2) * (height/2)) * 2; + + uint8_t *pixels = (uint8_t*)ctx_calloc (data_len, 1); + + ctx_buffer_set_data (buffer, pixels, width, height, stride, pixel_format, + ctx_buffer_pixels_free, NULL); + return buffer; +} + +void ctx_buffer_deinit (CtxBuffer *buffer) +{ + if (buffer->free_func) + buffer->free_func (buffer->data, buffer->user_data); + if (buffer->eid) + { + free (buffer->eid); + } + buffer->eid = NULL; + buffer->data = NULL; + buffer->free_func = NULL; + buffer->user_data = NULL; + if (buffer->color_managed) + { + if (buffer->color_managed != buffer) + { + ctx_buffer_free (buffer->color_managed); + } + buffer->color_managed = NULL; + } +} + +void ctx_buffer_free (CtxBuffer *buffer) +{ + ctx_buffer_deinit (buffer); + free (buffer); +} + +static int +ctx_texture_check_eid (Ctx *ctx, const char *eid, int *tw, int *th) +{ + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data && + ctx->texture[i].eid && + !strcmp (ctx->texture[i].eid, eid)) + { + if (tw) *tw = ctx->texture[i].width; + if (th) *th = ctx->texture[i].height; + ctx->texture[i].frame = ctx->texture_cache->frame; + return i; + } + } + return -1; +} + +const char* ctx_texture_init (Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + CtxPixelFormat format, + void *space, + uint8_t *pixels, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + int id = 0; + if (eid) + { + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data && + ctx->texture[i].eid && + !strcmp (ctx->texture[i].eid, eid)) + { + ctx->texture[i].frame = ctx->texture_cache->frame; + if (freefunc && user_data != (void*)23) + freefunc (pixels, user_data); + return ctx->texture[i].eid; + } + if (ctx->texture[i].data == NULL + || (ctx->texture_cache->frame - ctx->texture[i].frame >= 2)) + id = i; + } + } else + { + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data == NULL + || (ctx->texture_cache->frame - ctx->texture[i].frame > 2)) + id = i; + } + } + //int bpp = ctx_pixel_format_bits_per_pixel (format); + ctx_buffer_deinit (&ctx->texture[id]); + + if (stride<=0) + { + stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width); + } + + int data_len = stride * height; + if (format == CTX_FORMAT_YUV420) + data_len = width * height + + 2 * ((width/2)*(height/2)); + + if (freefunc == ctx_buffer_pixels_free && user_data == (void*)23) + { + uint8_t *tmp = (uint8_t*)malloc (data_len); + memcpy (tmp, pixels, data_len); + pixels = tmp; + } + + ctx_buffer_set_data (&ctx->texture[id], + pixels, width, height, + stride, format, + freefunc, user_data); +#if CTX_ENABLE_CM + ctx->texture[id].space = space; +#endif + ctx->texture[id].frame = ctx->texture_cache->frame; + if (eid) + { + /* we got an eid, this is the fast path */ + ctx->texture[id].eid = strdup (eid); + } + else + { + uint8_t hash[20]; + char ascii[41]; + + CtxSHA1 *sha1 = ctx_sha1_new (); + ctx_sha1_process (sha1, pixels, stride * height); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + ctx->texture[id].eid = strdup (ascii); + } + return ctx->texture[id].eid; +} + +static void +_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer, + CtxBuffer *buffer) +{ + switch (buffer->format->pixel_format) + { +#ifndef NO_BABL +#if CTX_BABL + case CTX_FORMAT_RGBA8: + if (buffer->space == rasterizer->state->gstate.device_space) + { + buffer->color_managed = buffer; + } + else + { + buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height, + CTX_FORMAT_RGBA8); + babl_process ( + babl_fish (babl_format_with_space ("R'G'B'A u8", buffer->space), + babl_format_with_space ("R'G'B'A u8", rasterizer->state->gstate.device_space)), + buffer->data, buffer->color_managed->data, + buffer->width * buffer->height + ); + } + break; + case CTX_FORMAT_RGB8: + if (buffer->space == rasterizer->state->gstate.device_space) + { + buffer->color_managed = buffer; + } + else + { + buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height, + CTX_FORMAT_RGB8); + babl_process ( + babl_fish (babl_format_with_space ("R'G'B' u8", buffer->space), + babl_format_with_space ("R'G'B' u8", rasterizer->state->gstate.device_space)), + buffer->data, buffer->color_managed->data, + buffer->width * buffer->height + ); + } + break; +#endif +#endif + default: + buffer->color_managed = buffer; + } +} + + + +int ctx_utf8_len (const unsigned char first_byte) +{ + if ( (first_byte & 0x80) == 0) + { return 1; } /* ASCII */ + else if ( (first_byte & 0xE0) == 0xC0) + { return 2; } + else if ( (first_byte & 0xF0) == 0xE0) + { return 3; } + else if ( (first_byte & 0xF8) == 0xF0) + { return 4; } + return 1; +} + + +const char *ctx_utf8_skip (const char *s, int utf8_length) +{ + int count; + if (!s) + { return NULL; } + for (count = 0; *s; s++) + { + if ( (*s & 0xC0) != 0x80) + { count++; } + if (count == utf8_length + 1) + { return s; } + } + return s; +} + +// XXX : unused +int ctx_utf8_strlen (const char *s) +{ + int count; + if (!s) + { return 0; } + for (count = 0; *s; s++) + if ( (*s & 0xC0) != 0x80) + { count++; } + return count; +} + +int +ctx_unichar_to_utf8 (uint32_t ch, + uint8_t *dest) +{ + /* http://www.cprogramming.com/tutorial/utf8.c */ + /* Basic UTF-8 manipulation routines + by Jeff Bezanson + placed in the public domain Fall 2005 ... */ + if (ch < 0x80) + { + dest[0] = (char) ch; + return 1; + } + if (ch < 0x800) + { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) + { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ( (ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) + { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ( (ch>>12) & 0x3F) | 0x80; + dest[2] = ( (ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} + +uint32_t +ctx_utf8_to_unichar (const char *input) +{ + const uint8_t *utf8 = (const uint8_t *) input; + uint8_t c = utf8[0]; + if ( (c & 0x80) == 0) + { return c; } + else if ( (c & 0xE0) == 0xC0) + return ( (utf8[0] & 0x1F) << 6) | + (utf8[1] & 0x3F); + else if ( (c & 0xF0) == 0xE0) + return ( (utf8[0] & 0xF) << 12) | + ( (utf8[1] & 0x3F) << 6) | + (utf8[2] & 0x3F); + else if ( (c & 0xF8) == 0xF0) + return ( (utf8[0] & 0x7) << 18) | + ( (utf8[1] & 0x3F) << 12) | + ( (utf8[2] & 0x3F) << 6) | + (utf8[3] & 0x3F); + else if ( (c & 0xFC) == 0xF8) + return ( (utf8[0] & 0x3) << 24) | + ( (utf8[1] & 0x3F) << 18) | + ( (utf8[2] & 0x3F) << 12) | + ( (utf8[3] & 0x3F) << 6) | + (utf8[4] & 0x3F); + else if ( (c & 0xFE) == 0xFC) + return ( (utf8[0] & 0x1) << 30) | + ( (utf8[1] & 0x3F) << 24) | + ( (utf8[2] & 0x3F) << 18) | + ( (utf8[3] & 0x3F) << 12) | + ( (utf8[4] & 0x3F) << 6) | + (utf8[5] & 0x3F); + return 0; +} + +#if CTX_RASTERIZER + + + +static int +ctx_rect_intersect (const CtxIntRectangle *a, const CtxIntRectangle *b) +{ + if (a->x >= b->x + b->width || + b->x >= a->x + a->width || + a->y >= b->y + b->height || + b->y >= a->y + a->height) return 0; + + return 1; +} + +static void +_ctx_add_hash (CtxHasher *hasher, CtxIntRectangle *shape_rect, char *hash) +{ + CtxIntRectangle rect = {0,0, hasher->rasterizer.blit_width/hasher->cols, + hasher->rasterizer.blit_height/hasher->rows}; + int hno = 0; + for (int row = 0; row < hasher->rows; row++) + for (int col = 0; col < hasher->cols; col++, hno++) + { + rect.x = col * rect.width; + rect.y = row * rect.height; + if (ctx_rect_intersect (shape_rect, &rect)) + { + int temp = hasher->hashes[(row * hasher->cols + col) *20 + 0]; + for (int i = 0; i <19;i++) + hasher->hashes[(row * hasher->cols + col) *20 + i] = + hasher->hashes[(row * hasher->cols + col) *20 + i+1]^ + hash[i]; + hasher->hashes[(row * hasher->cols + col) *20 + 19] = + temp ^ hash[19]; + } + } +} + + +static void +ctx_hasher_process (void *user_data, CtxCommand *command) +{ + CtxEntry *entry = &command->entry; + CtxRasterizer *rasterizer = (CtxRasterizer *) user_data; + CtxHasher *hasher = (CtxHasher*) user_data; + CtxState *state = rasterizer->state; + CtxCommand *c = (CtxCommand *) entry; + int aa = 15;//rasterizer->aa; + + ctx_interpret_pos_bare (rasterizer->state, entry, NULL); + ctx_interpret_style (rasterizer->state, entry, NULL); + + switch (c->code) + { + case CTX_TEXT: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + float width = ctx_text_width (rasterizer->ctx, ctx_arg_string()); + + + float height = ctx_get_font_size (rasterizer->ctx); + CtxIntRectangle shape_rect; + + shape_rect.x=rasterizer->x; + shape_rect.y=rasterizer->y - height, + shape_rect.width = width; + shape_rect.height = height * 2; + switch ((int)ctx_state_get (rasterizer->state, CTX_text_align)) + { + case CTX_TEXT_ALIGN_LEFT: + case CTX_TEXT_ALIGN_START: + break; + case CTX_TEXT_ALIGN_END: + case CTX_TEXT_ALIGN_RIGHT: + shape_rect.x -= shape_rect.width; + break; + case CTX_TEXT_ALIGN_CENTER: + shape_rect.x -= shape_rect.width/2; + break; + // XXX : doesn't take all text-alignments into account + } + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, (const unsigned char*)ctx_arg_string(), strlen (ctx_arg_string())); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + } + ctx_rasterizer_reset (rasterizer); + break; + case CTX_STROKE_TEXT: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + float width = ctx_text_width (rasterizer->ctx, ctx_arg_string()); + float height = ctx_get_font_size (rasterizer->ctx); + + CtxIntRectangle shape_rect = { + (int)rasterizer->x, (int)(rasterizer->y - height), + (int)width, (int)(height * 2) + }; + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, (unsigned char*)ctx_arg_string(), strlen (ctx_arg_string())); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + } + ctx_rasterizer_reset (rasterizer); + break; + case CTX_GLYPH: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + + char ctx_sha1_hash[20]; + uint8_t string[8]; + string[ctx_unichar_to_utf8 (c->u32.a0, string)]=0; + float width = ctx_text_width (rasterizer->ctx, (char*)string); + float height = ctx_get_font_size (rasterizer->ctx); + + float tx = rasterizer->x; + float ty = rasterizer->y; + float tw = width; + float th = height * 2; + + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device_distance (rasterizer->state, &tw, &th); + CtxIntRectangle shape_rect = {(int)tx,(int)(ty-th/2),(int)tw,(int)th}; + + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, string, strlen ((const char*)string)); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + ctx_rasterizer_reset (rasterizer); + } + break; + + case CTX_FILL: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + + /* we eant this hasher to be as good as possible internally, + * since it is also used in the small shapes rasterization + * cache + */ + uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer); // + hasher->salt; + CtxIntRectangle shape_rect = { + (int)(rasterizer->col_min / CTX_SUBDIV - 2), + (int)(rasterizer->scan_min / aa - 2), + (int)(3+(rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV), + (int)(3+(rasterizer->scan_max - rasterizer->scan_min + 1) / aa) + }; + + hash ^= (rasterizer->state->gstate.fill_rule * 23); + + ctx_sha1_process(&sha1, (unsigned char*)&hash, 8); + + { + int is = rasterizer->state->gstate.image_smoothing; + ctx_sha1_process(&sha1, (uint8_t*)&is, sizeof(int)); + } + + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + if (!rasterizer->preserve) + ctx_rasterizer_reset (rasterizer); + rasterizer->preserve = 0; + } + break; + case CTX_STROKE: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer); + CtxIntRectangle shape_rect = { + (int)(rasterizer->col_min / CTX_SUBDIV - rasterizer->state->gstate.line_width), + (int)(rasterizer->scan_min / aa - rasterizer->state->gstate.line_width), + (int)((rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV + rasterizer->state->gstate.line_width), + (int)((rasterizer->scan_max - rasterizer->scan_min + 1) / aa + rasterizer->state->gstate.line_width) + }; + + shape_rect.width += rasterizer->state->gstate.line_width * 2; + shape_rect.height += rasterizer->state->gstate.line_width * 2; + shape_rect.x -= rasterizer->state->gstate.line_width; + shape_rect.y -= rasterizer->state->gstate.line_width; + + hash ^= (int)(rasterizer->state->gstate.line_width * 110); + hash ^= (rasterizer->state->gstate.line_cap * 23); + hash ^= (rasterizer->state->gstate.source_stroke.type * 117); + + ctx_sha1_process(&sha1, (unsigned char*)&hash, 8); + + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); + + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); + + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + } + if (!rasterizer->preserve) + ctx_rasterizer_reset (rasterizer); + rasterizer->preserve = 0; + break; + /* the above cases are the painting cases and + * the only ones differing from the rasterizer's process switch + */ + + case CTX_LINE_TO: + ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_LINE_TO: + ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_MOVE_TO: + ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_MOVE_TO: + ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_CURVE_TO: + ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_REL_CURVE_TO: + ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_QUAD_TO: + ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_REL_QUAD_TO: + ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_ARC: + ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction); + break; + case CTX_RECTANGLE: + ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_ROUND_RECTANGLE: + ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height, + c->rectangle.radius); + break; + case CTX_SET_PIXEL: + ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y, + c->set_pixel.rgba[0], + c->set_pixel.rgba[1], + c->set_pixel.rgba[2], + c->set_pixel.rgba[3]); + break; + case CTX_PRESERVE: + rasterizer->preserve = 1; + break; + case CTX_ROTATE: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_SAVE: + case CTX_RESTORE: + rasterizer->uses_transforms = 1; + ctx_interpret_transforms (rasterizer->state, entry, NULL); + + + break; + case CTX_FONT: + ctx_rasterizer_set_font (rasterizer, ctx_arg_string() ); + break; + case CTX_BEGIN_PATH: + ctx_rasterizer_reset (rasterizer); + break; + case CTX_CLIP: + // should perhaps modify a global state to include + // in hash? + ctx_rasterizer_clip (rasterizer); + break; + case CTX_CLOSE_PATH: + ctx_rasterizer_finish_shape (rasterizer); + break; + case CTX_DEFINE_TEXTURE: + { + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->define_texture.eid, strlen (c->define_texture.eid)); + ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + + rasterizer->comp_op = NULL; // why? + } + break; + case CTX_TEXTURE: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->texture.eid, strlen (c->texture.eid)); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + rasterizer->comp_op = NULL; // why? + break; + case CTX_COLOR: + { + uint32_t color; + if (((int)(ctx_arg_float(0))&512)) + { + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); + ctx_sha1_init (&hasher->sha1_stroke); + ctx_sha1_process(&hasher->sha1_stroke, (unsigned char*)&color, 4); + } + else + { + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)&color, 4); + } + } + break; + case CTX_LINEAR_GRADIENT: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*)c, sizeof (c->linear_gradient)); + ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + break; + case CTX_RADIAL_GRADIENT: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*)c, sizeof (c->radial_gradient)); + ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + //ctx_state_gradient_clear_stops (rasterizer->state); + break; +#if CTX_GRADIENTS + case CTX_GRADIENT_STOP: + { + float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (4+1) ), + ctx_u8_to_float (ctx_arg_u8 (4+2) ), + ctx_u8_to_float (ctx_arg_u8 (4+3) ) + }; + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*) &rgba[0], sizeof(rgba)); + } + break; +#endif + } + if (command->code == CTX_LINE_WIDTH) + { + float x = state->gstate.line_width; + /* normalize line width according to scaling factor + */ + x = x * ctx_maxf (ctx_maxf (ctx_fabsf (state->gstate.transform.m[0][0]), + ctx_fabsf (state->gstate.transform.m[0][1]) ), + ctx_maxf (ctx_fabsf (state->gstate.transform.m[1][0]), + ctx_fabsf (state->gstate.transform.m[1][1]) ) ); + state->gstate.line_width = x; + } +} + +static CtxRasterizer * +ctx_hasher_init (CtxRasterizer *rasterizer, Ctx *ctx, CtxState *state, int width, int height, int cols, int rows) +{ + CtxHasher *hasher = (CtxHasher*)rasterizer; + ctx_memset (rasterizer, 0, sizeof (CtxHasher) ); + rasterizer->vfuncs.process = ctx_hasher_process; + rasterizer->vfuncs.free = (CtxDestroyNotify)ctx_rasterizer_deinit; + // XXX need own destructor to not leak ->hashes + rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST; + rasterizer->state = state; + rasterizer->ctx = ctx; + ctx_state_init (rasterizer->state); + rasterizer->blit_x = 0; + rasterizer->blit_y = 0; + rasterizer->blit_width = width; + rasterizer->blit_height = height; + rasterizer->state->gstate.clip_min_x = 0; + rasterizer->state->gstate.clip_min_y = 0; + rasterizer->state->gstate.clip_max_x = width - 1; + rasterizer->state->gstate.clip_max_y = height - 1; + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + //rasterizer->aa = 15; + + hasher->rows = rows; + hasher->cols = cols; + + hasher->hashes = (uint8_t*)ctx_calloc (20, rows * cols); + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_init (&hasher->sha1_stroke); + + return rasterizer; +} + +Ctx *ctx_hasher_new (int width, int height, int cols, int rows) +{ + Ctx *ctx = ctx_new (); + CtxState *state = &ctx->state; + CtxRasterizer *rasterizer = (CtxRasterizer *) ctx_calloc (sizeof (CtxHasher), 1); + ctx_hasher_init (rasterizer, ctx, state, width, height, cols, rows); + ctx_set_renderer (ctx, (void*)rasterizer); + return ctx; +} +uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row) +{ + CtxHasher *hasher = (CtxHasher*)ctx->renderer; + if (row < 0) row =0; + if (col < 0) col =0; + if (row >= hasher->rows) row = hasher->rows-1; + if (col >= hasher->cols) col = hasher->cols-1; + + return &hasher->hashes[(row*hasher->cols+col)*20]; +} + +#endif +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <termios.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +int ctx_terminal_width (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[14t"); + //tcflush(STDIN_FILENO, 1); +#if __COSMOPOLITAN__ + /// XXX ? +#else + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi) {semi++; semi = strchr (semi, ';');} + if (semi) + { + return atoi(semi + 1); + } + return 0; +} + +int ctx_terminal_height (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[14t"); + //tcflush(STDIN_FILENO, 1); +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi) + { + return atoi(semi + 1); + } + return 0; +} + +int ctx_terminal_cols (void) +{ + struct winsize ws; + if (ioctl(0,TIOCGWINSZ,&ws)!=0) + return 80; + return ws.ws_col; +} + +int ctx_terminal_rows (void) +{ + struct winsize ws; + if (ioctl(0,TIOCGWINSZ,&ws)!=0) + return 25; + return ws.ws_row; +} + + + + + +#define DECTCEM_CURSOR_SHOW "\033[?25h" +#define DECTCEM_CURSOR_HIDE "\033[?25l" +#define TERMINAL_MOUSE_OFF "\033[?1000l\033[?1003l" +#define TERMINAL_MOUSE_ON_BASIC "\033[?1000h" +#define TERMINAL_MOUSE_ON_DRAG "\033[?1000h\033[?1003h" /* +ON_BASIC for wider */ +#define TERMINAL_MOUSE_ON_FULL "\033[?1000h\033[?1004h" /* compatibility */ +#define XTERM_ALTSCREEN_ON "\033[?47h" +#define XTERM_ALTSCREEN_OFF "\033[?47l" + +/*************************** input handling *************************/ + +#if !__COSMOPOLITAN__ +#include <termios.h> +#include <errno.h> +#include <signal.h> +#endif + +#define DELAY_MS 100 + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +static int size_changed = 0; /* XXX: global state */ +static int signal_installed = 0; /* XXX: global state */ + +static const char *mouse_modes[]= +{TERMINAL_MOUSE_OFF, + TERMINAL_MOUSE_ON_BASIC, + TERMINAL_MOUSE_ON_DRAG, + TERMINAL_MOUSE_ON_FULL, + NULL}; + +/* note that a nick can have multiple occurences, the labels + * should be kept the same for all occurences of a combination. */ +typedef struct NcKeyCode { + const char *nick; /* programmers name for key (combo) */ + const char *label; /* utf8 label for key */ + const char sequence[10]; /* terminal sequence */ +} NcKeyCode; +static const NcKeyCode keycodes[]={ + + {"up", "↑", "\033[A"}, + {"down", "↓", "\033[B"}, + {"right", "→", "\033[C"}, + {"left", "←", "\033[D"}, + + {"shift-up", "⇧↑", "\033[1;2A"}, + {"shift-down", "⇧↓", "\033[1;2B"}, + {"shift-right", "⇧→", "\033[1;2C"}, + {"shift-left", "⇧←", "\033[1;2D"}, + + {"alt-up", "^↑", "\033[1;3A"}, + {"alt-down", "^↓", "\033[1;3B"}, + {"alt-right", "^→", "\033[1;3C"}, + {"alt-left", "^←", "\033[1;3D"}, + + {"alt-shift-up", "alt-s↑", "\033[1;4A"}, + {"alt-shift-down", "alt-s↓", "\033[1;4B"}, + {"alt-shift-right", "alt-s→", "\033[1;4C"}, + {"alt-shift-left", "alt-s←", "\033[1;4D"}, + + {"control-up", "^↑", "\033[1;5A"}, + {"control-down", "^↓", "\033[1;5B"}, + {"control-right", "^→", "\033[1;5C"}, + {"control-left", "^←", "\033[1;5D"}, + + /* putty */ + {"control-up", "^↑", "\033OA"}, + {"control-down", "^↓", "\033OB"}, + {"control-right", "^→", "\033OC"}, + {"control-left", "^←", "\033OD"}, + + {"control-shift-up", "^⇧↑", "\033[1;6A"}, + {"control-shift-down", "^⇧↓", "\033[1;6B"}, + {"control-shift-right", "^⇧→", "\033[1;6C"}, + {"control-shift-left", "^⇧←", "\033[1;6D"}, + + {"control-up", "^↑", "\033Oa"}, + {"control-down", "^↓", "\033Ob"}, + {"control-right", "^→", "\033Oc"}, + {"control-left", "^←", "\033Od"}, + + {"shift-up", "⇧↑", "\033[a"}, + {"shift-down", "⇧↓", "\033[b"}, + {"shift-right", "⇧→", "\033[c"}, + {"shift-left", "⇧←", "\033[d"}, + + {"insert", "ins", "\033[2~"}, + {"delete", "del", "\033[3~"}, + {"page-up", "PgUp", "\033[5~"}, + {"page-down", "PdDn", "\033[6~"}, + {"home", "Home", "\033OH"}, + {"end", "End", "\033OF"}, + {"home", "Home", "\033[H"}, + {"end", "End", "\033[F"}, + {"control-delete", "^del", "\033[3;5~"}, + {"shift-delete", "⇧del", "\033[3;2~"}, + {"control-shift-delete","^⇧del", "\033[3;6~"}, + + {"F1", "F1", "\033[11~"}, + {"F2", "F2", "\033[12~"}, + {"F3", "F3", "\033[13~"}, + {"F4", "F4", "\033[14~"}, + {"F1", "F1", "\033OP"}, + {"F2", "F2", "\033OQ"}, + {"F3", "F3", "\033OR"}, + {"F4", "F4", "\033OS"}, + {"F5", "F5", "\033[15~"}, + {"F6", "F6", "\033[16~"}, + {"F7", "F7", "\033[17~"}, + {"F8", "F8", "\033[18~"}, + {"F9", "F9", "\033[19~"}, + {"F9", "F9", "\033[20~"}, + {"F10", "F10", "\033[21~"}, + {"F11", "F11", "\033[22~"}, + {"F12", "F12", "\033[23~"}, + {"tab", "↹", {9, '\0'}}, + {"shift-tab", "shift+↹", "\033[Z"}, + {"backspace", "⌫", {127, '\0'}}, + {"space", "␣", " "}, + {"esc", "␛", "\033"}, + {"return", "⏎", {10,0}}, + {"return", "⏎", {13,0}}, + /* this section could be autogenerated by code */ + {"control-a", "^A", {1,0}}, + {"control-b", "^B", {2,0}}, + {"control-c", "^C", {3,0}}, + {"control-d", "^D", {4,0}}, + {"control-e", "^E", {5,0}}, + {"control-f", "^F", {6,0}}, + {"control-g", "^G", {7,0}}, + {"control-h", "^H", {8,0}}, /* backspace? */ + {"control-i", "^I", {9,0}}, /* tab */ + {"control-j", "^J", {10,0}}, + {"control-k", "^K", {11,0}}, + {"control-l", "^L", {12,0}}, + {"control-n", "^N", {14,0}}, + {"control-o", "^O", {15,0}}, + {"control-p", "^P", {16,0}}, + {"control-q", "^Q", {17,0}}, + {"control-r", "^R", {18,0}}, + {"control-s", "^S", {19,0}}, + {"control-t", "^T", {20,0}}, + {"control-u", "^U", {21,0}}, + {"control-v", "^V", {22,0}}, + {"control-w", "^W", {23,0}}, + {"control-x", "^X", {24,0}}, + {"control-y", "^Y", {25,0}}, + {"control-z", "^Z", {26,0}}, + {"alt-0", "%0", "\0330"}, + {"alt-1", "%1", "\0331"}, + {"alt-2", "%2", "\0332"}, + {"alt-3", "%3", "\0333"}, + {"alt-4", "%4", "\0334"}, + {"alt-5", "%5", "\0335"}, + {"alt-6", "%6", "\0336"}, + {"alt-7", "%7", "\0337"}, /* backspace? */ + {"alt-8", "%8", "\0338"}, + {"alt-9", "%9", "\0339"}, + {"alt-+", "%+", "\033+"}, + {"alt--", "%-", "\033-"}, + {"alt-/", "%/", "\033/"}, + {"alt-a", "%A", "\033a"}, + {"alt-b", "%B", "\033b"}, + {"alt-c", "%C", "\033c"}, + {"alt-d", "%D", "\033d"}, + {"alt-e", "%E", "\033e"}, + {"alt-f", "%F", "\033f"}, + {"alt-g", "%G", "\033g"}, + {"alt-h", "%H", "\033h"}, /* backspace? */ + {"alt-i", "%I", "\033i"}, + {"alt-j", "%J", "\033j"}, + {"alt-k", "%K", "\033k"}, + {"alt-l", "%L", "\033l"}, + {"alt-n", "%N", "\033m"}, + {"alt-n", "%N", "\033n"}, + {"alt-o", "%O", "\033o"}, + {"alt-p", "%P", "\033p"}, + {"alt-q", "%Q", "\033q"}, + {"alt-r", "%R", "\033r"}, + {"alt-s", "%S", "\033s"}, + {"alt-t", "%T", "\033t"}, + {"alt-u", "%U", "\033u"}, + {"alt-v", "%V", "\033v"}, + {"alt-w", "%W", "\033w"}, + {"alt-x", "%X", "\033x"}, + {"alt-y", "%Y", "\033y"}, + {"alt-z", "%Z", "\033z"}, + {"shift-tab", "shift-↹", {27, 9, 0}}, + /* Linux Console */ + {"home", "Home", "\033[1~"}, + {"end", "End", "\033[4~"}, + {"F1", "F1", "\033[[A"}, + {"F2", "F2", "\033[[B"}, + {"F3", "F3", "\033[[C"}, + {"F4", "F4", "\033[[D"}, + {"F5", "F5", "\033[[E"}, + {"F6", "F6", "\033[[F"}, + {"F7", "F7", "\033[[G"}, + {"F8", "F8", "\033[[H"}, + {"F9", "F9", "\033[[I"}, + {"F10", "F10", "\033[[J"}, + {"F11", "F11", "\033[[K"}, + {"F12", "F12", "\033[[L"}, + {"ok", "", "\033[0n"}, + {NULL, } +}; + +static struct termios orig_attr; /* in order to restore at exit */ +static int nc_is_raw = 0; +static int atexit_registered = 0; +static int mouse_mode = NC_MOUSE_NONE; + +static void _nc_noraw (void) +{ + if (nc_is_raw && tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr) != -1) + nc_is_raw = 0; +} + +void +nc_at_exit (void) +{ + printf (TERMINAL_MOUSE_OFF); + printf (XTERM_ALTSCREEN_OFF); + _nc_noraw(); + fprintf (stdout, "\e[?25h"); + //if (ctx_native_events) + fprintf (stdout, "\e[?201l"); + fprintf (stdout, "\e[?1049l"); +} + +static const char *mouse_get_event_int (Ctx *n, int *x, int *y) +{ + static int prev_state = 0; + const char *ret = "mouse-motion"; + float relx, rely; + signed char buf[3]; + read (n->mouse_fd, buf, 3); + relx = buf[1]; + rely = -buf[2]; + + n->mouse_x += relx * 0.1; + n->mouse_y += rely * 0.1; + + if (n->mouse_x < 1) n->mouse_x = 1; + if (n->mouse_y < 1) n->mouse_y = 1; + if (n->mouse_x >= n->events.width) n->mouse_x = n->events.width; + if (n->mouse_y >= n->events.height) n->mouse_y = n->events.height; + + if (x) *x = n->mouse_x; + if (y) *y = n->mouse_y; + + if ((prev_state & 1) != (buf[0] & 1)) + { + if (buf[0] & 1) ret = "mouse-press"; + } + else if (buf[0] & 1) + ret = "mouse-drag"; + + if ((prev_state & 2) != (buf[0] & 2)) + { + if (buf[0] & 2) ret = "mouse2-press"; + } + else if (buf[0] & 2) + ret = "mouse2-drag"; + + if ((prev_state & 4) != (buf[0] & 4)) + { + if (buf[0] & 4) ret = "mouse1-press"; + } + else if (buf[0] & 4) + ret = "mouse1-drag"; + + prev_state = buf[0]; + return ret; +} + +static const char *mev_type = NULL; +static int mev_x = 0; +static int mev_y = 0; +static int mev_q = 0; + +static const char *mouse_get_event (Ctx *n, int *x, int *y) +{ + if (!mev_q) + return NULL; + *x = mev_x; + *y = mev_y; + mev_q = 0; + return mev_type; +} + +static int mouse_has_event (Ctx *n) +{ + struct timeval tv; + int retval; + + if (mouse_mode == NC_MOUSE_NONE) + return 0; + + if (mev_q) + return 1; + + if (n->mouse_fd == 0) + return 0; + return 0; + + { + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(n->mouse_fd, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (n->mouse_fd+1, &rfds, NULL, NULL, &tv); + } + + if (retval != 0) + { + int nx = 0, ny = 0; + const char *type = mouse_get_event_int (n, &nx, &ny); + + if ((mouse_mode < NC_MOUSE_DRAG && mev_type && !strcmp (mev_type, "drag")) || + (mouse_mode < NC_MOUSE_ALL && mev_type && !strcmp (mev_type, "motion"))) + { + mev_q = 0; + return mouse_has_event (n); + } + + if ((mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse-motion")) || + (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse1-drag")) || + (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse2-drag"))) + { + if (nx == mev_x && ny == mev_y) + { + mev_q = 0; + return mouse_has_event (n); + } + } + mev_x = nx; + mev_y = ny; + mev_type = type; + mev_q = 1; + } + return retval != 0; +} + + +static int _nc_raw (void) +{ + struct termios raw; + if (!isatty (STDIN_FILENO)) + return -1; + if (!atexit_registered) + { + atexit (nc_at_exit); + atexit_registered = 1; + } + if (tcgetattr (STDIN_FILENO, &orig_attr) == -1) + return -1; + raw = orig_attr; /* modify the original mode */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return -1; + nc_is_raw = 1; +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); + tcflush(STDIN_FILENO, 1); +#endif + return 0; +} + +static int match_keycode (const char *buf, int length, const NcKeyCode **ret) +{ + int i; + int matches = 0; + + if (!strncmp (buf, "\033[M", MIN(length,3))) + { + if (length >= 6) + return 9001; + return 2342; + } + for (i = 0; keycodes[i].nick; i++) + if (!strncmp (buf, keycodes[i].sequence, length)) + { + matches ++; + if ((int)strlen (keycodes[i].sequence) == length && ret) + { + *ret = &keycodes[i]; + return 1; + } + } + if (matches != 1 && ret) + *ret = NULL; + return matches==1?2:matches; +} + +static void nc_resize_term (int dummy) +{ + size_changed = 1; +} + +int ctx_nct_has_event (Ctx *n, int delay_ms) +{ + struct timeval tv; + int retval; + fd_set rfds; + + if (size_changed) + return 1; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; tv.tv_usec = delay_ms * 1000; + retval = select (1, &rfds, NULL, NULL, &tv); + if (size_changed) + return 1; + return retval == 1 && retval != -1; +} + +const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y) +{ + unsigned char buf[20]; + int length; + + + if (x) *x = -1; + if (y) *y = -1; + + if (!signal_installed) + { + _nc_raw (); + signal_installed = 1; + signal (SIGWINCH, nc_resize_term); + } + if (mouse_mode) // XXX too often to do it all the time! + printf("%s", mouse_modes[mouse_mode]); + + { + int elapsed = 0; + int got_event = 0; + + do { + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + got_event = mouse_has_event (n); + if (!got_event) + got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed)); + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + /* only do this if the client has asked for idle events, + * and perhaps programmed the ms timer? + */ + elapsed += MIN(DELAY_MS, timeoutms-elapsed); + if (!got_event && timeoutms && elapsed >= timeoutms) + return "idle"; + } while (!got_event); + } + + if (mouse_has_event (n)) + return mouse_get_event (n, x, y); + + for (length = 0; length < 10; length ++) + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + const NcKeyCode *match = NULL; + + /* special case ESC, so that we can use it alone in keybindings */ + if (length == 0 && buf[0] == 27) + { + struct timeval tv; + fd_set rfds; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1000 * DELAY_MS; + if (select (1, &rfds, NULL, NULL, &tv) == 0) + return "esc"; + } + + switch (match_keycode ((const char*)buf, length + 1, &match)) + { + case 1: /* unique match */ + if (!match) + return NULL; + if (!strcmp(match->nick, "ok")) + { + ctx_frame_ack = 1; + return NULL; + } + return match->nick; + break; + case 9001: /* mouse event */ + if (x) *x = ((unsigned char)buf[4]-32)*1.0; + if (y) *y = ((unsigned char)buf[5]-32)*1.0; + switch (buf[3]) + { + /* XXX : todo reduce this to less string constants */ + case 32: return "mouse-press"; + case 33: return "mouse1-press"; + case 34: return "mouse2-press"; + case 40: return "alt-mouse-press"; + case 41: return "alt-mouse1-press"; + case 42: return "alt-mouse2-press"; + case 48: return "control-mouse-press"; + case 49: return "control-mouse1-press"; + case 50: return "control-mouse2-press"; + case 56: return "alt-control-mouse-press"; + case 57: return "alt-control-mouse1-press"; + case 58: return "alt-control-mouse2-press"; + case 64: return "mouse-drag"; + case 65: return "mouse1-drag"; + case 66: return "mouse2-drag"; + case 71: return "mouse-motion"; /* shift+motion */ + case 72: return "alt-mouse-drag"; + case 73: return "alt-mouse1-drag"; + case 74: return "alt-mouse2-drag"; + case 75: return "mouse-motion"; /* alt+motion */ + case 80: return "control-mouse-drag"; + case 81: return "control-mouse1-drag"; + case 82: return "control-mouse2-drag"; + case 83: return "mouse-motion"; /* ctrl+motion */ + case 91: return "mouse-motion"; /* ctrl+alt+motion */ + case 95: return "mouse-motion"; /* ctrl+alt+shift+motion */ + case 96: return "scroll-up"; + case 97: return "scroll-down"; + case 100: return "shift-scroll-up"; + case 101: return "shift-scroll-down"; + case 104: return "alt-scroll-up"; + case 105: return "alt-scroll-down"; + case 112: return "control-scroll-up"; + case 113: return "control-scroll-down"; + case 116: return "control-shift-scroll-up"; + case 117: return "control-shift-scroll-down"; + case 35: /* (or release) */ + case 51: /* (or ctrl-release) */ + case 43: /* (or alt-release) */ + case 67: return "mouse-motion"; + /* have a separate mouse-drag ? */ + default: { + static char rbuf[100]; + sprintf (rbuf, "mouse (unhandled state: %i)", buf[3]); + return rbuf; + } + } + case 0: /* no matches, bail*/ + { + static char ret[256]; + if (length == 0 && ctx_utf8_len (buf[0])>1) /* single unicode + char */ + { + int n_read = + read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1); + if (n_read) + { + buf[ctx_utf8_len(buf[0])]=0; + strcpy (ret, (const char*)buf); + } + return ret; + } + if (length == 0) /* ascii */ + { + buf[1]=0; + strcpy (ret, (const char*)buf); + return ret; + } + sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'", + length>=0? buf[0]: 0, length>=0? buf[0]>31?buf[0]:'?': ' ', + length>=1? buf[1]: 0, length>=1? buf[1]>31?buf[1]:'?': ' ', + length>=2? buf[2]: 0, length>=2? buf[2]>31?buf[2]:'?': ' ', + length>=3? buf[3]: 0, length>=3? buf[3]>31?buf[3]:'?': ' ', + length>=4? buf[4]: 0, length>=4? buf[4]>31?buf[4]:'?': ' ', + length>=5? buf[5]: 0, length>=5? buf[5]>31?buf[5]:'?': ' ', + length>=6? buf[6]: 0, length>=6? buf[6]>31?buf[6]:'?': ' '); + return ret; + } + return NULL; + default: /* continue */ + break; + } + } + else + return "key read eek"; + return "fail"; +} + +int ctx_nct_consume_events (Ctx *ctx) +{ + int ix, iy; + CtxCtx *ctxctx = (CtxCtx*)ctx->renderer; + const char *event = NULL; + + { + float x, y; + event = ctx_nct_get_event (ctx, 50, &ix, &iy); + + x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width; + y = (iy - 1.0) / ctxctx->rows * ctx->events.height; + + if (!strcmp (event, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, 0, 0); + ctxctx->was_down = 1; + } else if (!strcmp (event, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } else if (!strcmp (event, "mouse-motion")) + { + //nct_set_cursor_pos (backend->term, ix, iy); + //nct_flush (backend->term); + if (ctxctx->was_down) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-drag")) + { + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "size-changed")) + { +#if 0 + int width = nct_sys_terminal_width (); + int height = nct_sys_terminal_height (); + nct_set_size (backend->term, width, height); + width *= CPX; + height *= CPX; + free (mrg->glyphs); + free (mrg->styles); + free (backend->nct_pixels); + backend->nct_pixels = calloc (width * height * 4, 1); + mrg->glyphs = calloc ((width/CPX) * (height/CPX) * 4, 1); + mrg->styles = calloc ((width/CPX) * (height/CPX) * 1, 1); + mrg_set_size (mrg, width, height); + mrg_queue_draw (mrg, NULL); +#endif + + } + else + { + if (!strcmp (event, "esc")) + ctx_key_press (ctx, 0, "escape", 0); + else if (!strcmp (event, "space")) + ctx_key_press (ctx, 0, "space", 0); + else if (!strcmp (event, "enter")) + ctx_key_press (ctx, 0, "\n", 0); + else if (!strcmp (event, "return")) + ctx_key_press (ctx, 0, "return", 0); + else if (!strcmp (event, "idle")) + { + } + else + ctx_key_press (ctx, 0, event, 0); + } + } + + return 1; +} + +const char *ctx_native_get_event (Ctx *n, int timeoutms) +{ + static unsigned char buf[256]; + int length; + + if (!signal_installed) + { + _nc_raw (); + signal_installed = 1; + signal (SIGWINCH, nc_resize_term); + } +//if (mouse_mode) // XXX too often to do it all the time! +// printf("%s", mouse_modes[mouse_mode]); + + int got_event = 0; + { + int elapsed = 0; + + do { + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed)); + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + /* only do this if the client has asked for idle events, + * and perhaps programmed the ms timer? + */ + elapsed += MIN(DELAY_MS, timeoutms-elapsed); + if (!got_event && timeoutms && elapsed >= timeoutms) + { + return "idle"; + } + } while (!got_event); + } + + for (length = 0; got_event && length < 200; length ++) + { + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + buf[length+1] = 0; + if (!strcmp ((char*)buf, "\e[0n")) + { + ctx_frame_ack = 1; + return NULL; + } + else if (buf[length]=='\n') + { + buf[length]=0; + return (const char*)buf; + } + } + got_event = ctx_nct_has_event (n, 5); + } + return NULL; +} + +const char *ctx_key_get_label (Ctx *n, const char *nick) +{ + int j; + int found = -1; + for (j = 0; keycodes[j].nick; j++) + if (found == -1 && !strcmp (keycodes[j].nick, nick)) + return keycodes[j].label; + return NULL; +} + +void _ctx_mouse (Ctx *term, int mode) +{ + //if (term->is_st && mode > 1) + // mode = 1; + if (mode != mouse_mode) + { + printf ("%s", mouse_modes[mode]); + fflush (stdout); + } + mouse_mode = mode; +} + + +#endif + +#if !__COSMOPOLITAN__ +#include <sys/time.h> +#endif + + +#define usecs(time) ((uint64_t)(time.tv_sec - start_time.tv_sec) * 1000000 + time. tv_usec) + +#if !__COSMOPOLITAN__ +static struct timeval start_time; + +static void +_ctx_init_ticks (void) +{ + static int done = 0; + if (done) + return; + done = 1; + gettimeofday (&start_time, NULL); +} + +static inline unsigned long +_ctx_ticks (void) +{ + struct timeval measure_time; + gettimeofday (&measure_time, NULL); + return usecs (measure_time) - usecs (start_time); +} + +unsigned long +ctx_ticks (void) +{ + _ctx_init_ticks (); + return _ctx_ticks (); +} + +uint32_t ctx_ms (Ctx *ctx) +{ + return _ctx_ticks () / 1000; +} + + + +enum _CtxFlags { + CTX_FLAG_DIRECT = (1<<0), +}; +typedef enum _CtxFlags CtxFlags; + + +int _ctx_max_threads = 1; +int _ctx_enable_hash_cache = 1; +#if CTX_SHAPE_CACHE +extern int _ctx_shape_cache_enabled; +#endif + +#if CTX_THREADS +static mtx_t _ctx_texture_mtx; +#endif + +void _ctx_texture_lock (void) +{ +#if CTX_THREADS + mtx_lock (&_ctx_texture_mtx); +#endif +} + +void _ctx_texture_unlock (void) +{ +#if CTX_THREADS + mtx_unlock (&_ctx_texture_mtx); +#endif +} + + +void +ctx_init (int *argc, char ***argv) +{ +#if 0 + if (!getenv ("CTX_VERSION")) + { + int i; + char *new_argv[*argc+3]; + new_argv[0] = "ctx"; + for (i = 0; i < *argc; i++) + { + new_argv[i+1] = *argv[i]; + } + new_argv[i+1] = NULL; + execvp (new_argv[0], new_argv); + // if this fails .. we continue normal startup + // and end up in self-hosted braille + } +#endif +} + +int ctx_count (Ctx *ctx) +{ + return ctx->drawlist.count; +} + + + +extern int _ctx_damage_control; + +static void ctx_list_backends() +{ + fprintf (stderr, "possible values for CTX_BACKEND:\n"); + fprintf (stderr, " ctx"); +#if CTX_SDL + fprintf (stderr, " SDL"); +#endif +#if CTX_FB + fprintf (stderr, " fb"); + fprintf (stderr, " drm"); +#endif + fprintf (stderr, " term"); + fprintf (stderr, " termimg"); + fprintf (stderr, "\n"); +} + +#if CTX_EVENTS +static int is_in_ctx (void); +Ctx *ctx_new_ui (int width, int height) +{ +#if CTX_TILED + if (getenv ("CTX_DAMAGE_CONTROL")) + { + const char * val = getenv ("CTX_DAMAGE_CONTROL"); + if (!strcmp (val, "0") || + !strcmp (val, "off")) + _ctx_damage_control = 0; + else + _ctx_damage_control = 1; + } +#endif + + if (getenv ("CTX_HASH_CACHE")) + { + const char * val = getenv ("CTX_HASH_CACHE"); + if (!strcmp (val, "0")) + _ctx_enable_hash_cache = 0; + if (!strcmp (val, "off")) + _ctx_enable_hash_cache = 0; + } +#if CTX_SHAPE_CACHE + if (getenv ("CTX_SHAPE_CACHE")) + { + const char * val = getenv ("CTX_SHAPE_CACHE"); + if (!strcmp (val, "0")) + _ctx_shape_cache_enabled = 0; + if (!strcmp (val, "off")) + _ctx_shape_cache_enabled = 0; + } +#endif + + if (getenv ("CTX_THREADS")) + { + int val = atoi (getenv ("CTX_THREADS")); + _ctx_max_threads = val; + } + else + { + _ctx_max_threads = 2; +#ifdef _SC_NPROCESSORS_ONLN + _ctx_max_threads = sysconf (_SC_NPROCESSORS_ONLN) / 2; +#endif + } + +#if CTX_THREADS + mtx_init (&_ctx_texture_mtx, mtx_plain); +#endif + + if (_ctx_max_threads < 1) _ctx_max_threads = 1; + if (_ctx_max_threads > CTX_MAX_THREADS) _ctx_max_threads = CTX_MAX_THREADS; + + //fprintf (stderr, "ctx using %i threads\n", _ctx_max_threads); + const char *backend = getenv ("CTX_BACKEND"); + + if (backend && !strcmp (backend, "")) + backend = NULL; + if (backend && !strcmp (backend, "auto")) + backend = NULL; + if (backend && !strcmp (backend, "list")) + { + ctx_list_backends (); + exit (-1); + } + + Ctx *ret = NULL; + + /* we do the query on auto but not on directly set ctx + * + */ + if ((backend && !strcmp(backend, "ctx")) || + (backend == NULL && is_in_ctx ())) + { + if (!backend || !strcmp (backend, "ctx")) + { + // full blown ctx protocol - in terminal or standalone + ret = ctx_new_ctx (width, height); + } + } + +#if CTX_SDL + if (!ret && getenv ("DISPLAY")) + { + if ((backend==NULL) || (!strcmp (backend, "SDL"))) + ret = ctx_new_sdl (width, height); + } +#endif + +#if CTX_FB + if (!ret && !getenv ("DISPLAY")) + { + if ((backend==NULL) || (!strcmp (backend, "drm"))) + ret = ctx_new_fb (width, height, 1); + + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "fb"))) + ret = ctx_new_fb (width, height, 0); + } + } +#endif + +#if CTX_RASTERIZER + // braille in terminal + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "term"))) + ret = ctx_new_term (width, height); + } + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "termimg"))) + ret = ctx_new_termimg (width, height); + } +#endif + if (!ret) + { + fprintf (stderr, "no interactive ctx backend\n"); + ctx_list_backends (); + exit (2); + } + ctx_get_event (ret); // enables events + return ret; +} +#endif +#else +void _ctx_texture_unlock (void) +{ +} +void _ctx_texture_lock (void) +{ +} + +#endif +void _ctx_resized (Ctx *ctx, int width, int height, long time); + +void ctx_set_size (Ctx *ctx, int width, int height) +{ +#if CTX_EVENTS + if (ctx->events.width != width || ctx->events.height != height) + { + ctx->events.width = width; + ctx->events.height = height; + _ctx_resized (ctx, width, height, 0); + } +#endif +} + +#if CTX_EVENTS + + +static int is_in_ctx (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[?200$p"); + //tcflush(STDIN_FILENO, 1); +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi && semi[1] == '2') + { + return 1; + } + return 0; +} + +typedef struct CtxIdleCb { + int (*cb) (Ctx *ctx, void *idle_data); + void *idle_data; + + void (*destroy_notify)(void *destroy_data); + void *destroy_data; + + int ticks_full; + int ticks_remaining; + int is_idle; + int id; +} CtxIdleCb; + +void _ctx_events_init (Ctx *ctx) +{ + CtxEvents *events = &ctx->events; + _ctx_init_ticks (); + events->tap_delay_min = 40; + events->tap_delay_max = 800; + events->tap_delay_max = 8000000; /* quick reflexes needed making it hard for some is an argument against very short values */ + + events->tap_delay_hold = 1000; + events->tap_hysteresis = 32; /* XXX: should be ppi dependent */ +} + + +void _ctx_idle_iteration (Ctx *ctx) +{ + static unsigned long prev_ticks = 0; + CtxList *l; + CtxList *to_remove = NULL; + unsigned long ticks = _ctx_ticks (); + unsigned long tick_delta = (prev_ticks == 0) ? 0 : ticks - prev_ticks; + prev_ticks = ticks; + + if (!ctx->events.idles) + { + return; + } + for (l = ctx->events.idles; l; l = l->next) + { + CtxIdleCb *item = l->data; + + if (item->ticks_remaining >= 0) + item->ticks_remaining -= tick_delta; + + if (item->ticks_remaining < 0) + { + if (item->cb (ctx, item->idle_data) == 0) + ctx_list_prepend (&to_remove, item); + else + item->ticks_remaining = item->ticks_full; + } + } + for (l = to_remove; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->destroy_notify) + item->destroy_notify (item->destroy_data); + ctx_list_remove (&ctx->events.idles, l->data); + } +} + + +void ctx_add_key_binding_full (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data, + CtxDestroyNotify destroy_notify, + void *destroy_data) +{ + CtxEvents *events = &ctx->events; + if (events->n_bindings +1 >= CTX_MAX_KEYBINDINGS) + { + fprintf (stderr, "warning: binding overflow\n"); + return; + } + events->bindings[events->n_bindings].nick = strdup (key); + strcpy (events->bindings[events->n_bindings].nick, key); + + if (action) + events->bindings[events->n_bindings].command = action ? strdup (action) : NULL; + if (label) + events->bindings[events->n_bindings].label = label ? strdup (label) : NULL; + events->bindings[events->n_bindings].cb = cb; + events->bindings[events->n_bindings].cb_data = cb_data; + events->bindings[events->n_bindings].destroy_notify = destroy_notify; + events->bindings[events->n_bindings].destroy_data = destroy_data; + events->n_bindings++; +} + +void ctx_add_key_binding (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data) +{ + ctx_add_key_binding_full (ctx, key, action, label, cb, cb_data, NULL, NULL); +} + +void ctx_clear_bindings (Ctx *ctx) +{ + CtxEvents *events = &ctx->events; + int i; + for (i = 0; events->bindings[i].nick; i ++) + { + if (events->bindings[i].destroy_notify) + events->bindings[i].destroy_notify (events->bindings[i].destroy_data); + free (events->bindings[i].nick); + if (events->bindings[i].command) + free (events->bindings[i].command); + if (events->bindings[i].label) + free (events->bindings[i].label); + } + memset (&events->bindings, 0, sizeof (events->bindings)); + events->n_bindings = 0; +} + +static void +ctx_collect_events (CtxEvent *event, void *data, void *data2); +static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2) +{ + Ctx *ctx = event->ctx; + CtxEvents *events = &ctx->events; + int i; + int handled = 0; + + for (i = events->n_bindings-1; i>=0; i--) + if (!strcmp (events->bindings[i].nick, event->string)) + { + if (events->bindings[i].cb) + { + events->bindings[i].cb (event, events->bindings[i].cb_data, NULL); + if (event->stop_propagate) + return; + handled = 1; + } + } + if (!handled) + for (i = events->n_bindings-1; i>=0; i--) + if (!strcmp (events->bindings[i].nick, "unhandled")) + { + if (events->bindings[i].cb) + { + events->bindings[i].cb (event, events->bindings[i].cb_data, NULL); + if (event->stop_propagate) + return; + } + } + ctx_collect_events (event, data1, data2); +} + +CtxBinding *ctx_get_bindings (Ctx *ctx) +{ + return &ctx->events.bindings[0]; +} + +void ctx_remove_idle (Ctx *ctx, int handle) +{ + CtxList *l; + CtxList *to_remove = NULL; + + if (!ctx->events.idles) + { + return; + } + for (l = ctx->events.idles; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->id == handle) + ctx_list_prepend (&to_remove, item); + } + for (l = to_remove; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->destroy_notify) + item->destroy_notify (item->destroy_data); + ctx_list_remove (&ctx->events.idles, l->data); + } +} + +int ctx_add_timeout_full (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data) +{ + CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1); + item->cb = idle_cb; + item->idle_data = idle_data; + item->id = ++ctx->events.idle_id; + item->ticks_full = + item->ticks_remaining = ms * 1000; + item->destroy_notify = destroy_notify; + item->destroy_data = destroy_data; + ctx_list_append (&ctx->events.idles, item); + return item->id; +} + +int ctx_add_timeout (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data) +{ + return ctx_add_timeout_full (ctx, ms, idle_cb, idle_data, NULL, NULL); +} + +int ctx_add_idle_full (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data) +{ + CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1); + item->cb = idle_cb; + item->idle_data = idle_data; + item->id = ++ctx->events.idle_id; + item->ticks_full = + item->ticks_remaining = -1; + item->is_idle = 1; + item->destroy_notify = destroy_notify; + item->destroy_data = destroy_data; + ctx_list_append (&ctx->events.idles, item); + return item->id; +} + +int ctx_add_idle (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data) +{ + return ctx_add_idle_full (ctx, idle_cb, idle_data, NULL, NULL); +} + +#endif +/* using bigger primes would be a good idea, this falls apart due to rounding + * when zoomed in close + */ +static inline double ctx_path_hash (void *path) +{ + double ret = 0; +#if 0 + int i; + cairo_path_data_t *data; + if (!path) + return 0.99999; + for (i = 0; i <path->num_data; i += path->data[i].header.length) + { + data = &path->data[i]; + switch (data->header.type) { + case CAIRO_PATH_MOVE_TO: + ret *= 17; + ret += data[1].point.x; + ret *= 113; + ret += data[1].point.y; + break; + case CAIRO_PATH_LINE_TO: + ret *= 121; + ret += data[1].point.x; + ret *= 1021; + ret += data[1].point.y; + break; + case CAIRO_PATH_CURVE_TO: + ret *= 3111; + ret += data[1].point.x; + ret *= 23; + ret += data[1].point.y; + ret *= 107; + ret += data[2].point.x; + ret *= 739; + ret += data[2].point.y; + ret *= 3; + ret += data[3].point.x; + ret *= 51; + ret += data[3].point.y; + break; + case CAIRO_PATH_CLOSE_PATH: + ret *= 51; + break; + } + } +#endif + return ret; +} + +#if CTX_EVENTS +void _ctx_item_ref (CtxItem *item) +{ + if (item->ref_count < 0) + { + fprintf (stderr, "EEEEK!\n"); + } + item->ref_count++; +} + + +void _ctx_item_unref (CtxItem *item) +{ + if (item->ref_count <= 0) + { + fprintf (stderr, "EEEEK!\n"); + return; + } + item->ref_count--; + if (item->ref_count <=0) + { + { + int i; + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].finalize) + item->cb[i].finalize (item->cb[i].data1, item->cb[i].data2, + item->cb[i].finalize_data); + } + } + if (item->path) + { + //cairo_path_destroy (item->path); + } + free (item); + } +} + + +static int +path_equal (void *path, + void *path2) +{ + // XXX + return 0; +} + +void ctx_listen_set_cursor (Ctx *ctx, + CtxCursor cursor) +{ + if (ctx->events.last_item) + { + ctx->events.last_item->cursor = cursor; + } +} + +void ctx_listen_full (Ctx *ctx, + float x, + float y, + float width, + float height, + CtxEventType types, + CtxCb cb, + void *data1, + void *data2, + void (*finalize)(void *listen_data, + void *listen_data2, + void *finalize_data), + void *finalize_data) +{ + if (!ctx->events.frozen) + { + CtxItem *item; + + /* early bail for listeners outside screen */ + /* XXX: fixme respect clipping */ + { + float tx = x; + float ty = y; + float tw = width; + float th = height; + _ctx_user_to_device (&ctx->state, &tx, &ty); + _ctx_user_to_device_distance (&ctx->state, &tw, &th); + if (ty > ctx->events.height * 2 || + tx > ctx->events.width * 2 || + tx + tw < 0 || + ty + th < 0) + { + if (finalize) + finalize (data1, data2, finalize_data); + return; + } + } + + item = calloc (sizeof (CtxItem), 1); + item->x0 = x; + item->y0 = y; + item->x1 = x + width; + item->y1 = y + height; + item->cb[0].types = types; + item->cb[0].cb = cb; + item->cb[0].data1 = data1; + item->cb[0].data2 = data2; + item->cb[0].finalize = finalize; + item->cb[0].finalize_data = finalize_data; + item->cb_count = 1; + item->types = types; + //item->path = cairo_copy_path (cr); // XXX + item->path_hash = ctx_path_hash (item->path); + ctx_get_matrix (ctx, &item->inv_matrix); + ctx_matrix_invert (&item->inv_matrix); + + if (ctx->events.items) + { + CtxList *l; + for (l = ctx->events.items; l; l = l->next) + { + CtxItem *item2 = l->data; + + /* store multiple callbacks for one entry when the paths + * are exact matches, reducing per event traversal checks at the + * cost of a little paint-hit (XXX: is this the right tradeoff, + * perhaps it is better to spend more time during event processing + * than during paint?) + */ + if (item->path_hash == item2->path_hash && + path_equal (item->path, item2->path)) + { + /* found an item, copy over cb data */ + item2->cb[item2->cb_count] = item->cb[0]; + free (item); + item2->cb_count++; + item2->types |= types; + return; + } + } + } + item->ref_count = 1; + ctx->events.last_item = item; + ctx_list_prepend_full (&ctx->events.items, item, (void*)_ctx_item_unref, NULL); + } +} + +void ctx_event_stop_propagate (CtxEvent *event) +{ + if (event) + event->stop_propagate = 1; +} + +void ctx_listen (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2) +{ + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + if (types & CTX_KEY) + { + x = 0; + y = 0; + width = 0; + height = 0; + } + else + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + if (types == CTX_DRAG_MOTION) + types = CTX_DRAG_MOTION | CTX_DRAG_PRESS; + return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, NULL, NULL); +} + +void ctx_listen_with_finalize (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data) +{ + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + if (types & CTX_KEY) + { + x = 0; + y = 0; + width = 0; + height = 0; + } + else + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + if (types == CTX_DRAG_MOTION) + types = CTX_DRAG_MOTION | CTX_DRAG_PRESS; + return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, finalize, finalize_data); +} + + +static void ctx_report_hit_region (CtxEvent *event, + void *data, + void *data2) +{ + const char *id = data; + + fprintf (stderr, "hit region %s\n", id); + // XXX: NYI +} + +void ctx_add_hit_region (Ctx *ctx, const char *id) +{ + char *id_copy = strdup (id); + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + return ctx_listen_full (ctx, x, y, width, height, + CTX_POINTER, ctx_report_hit_region, + id_copy, NULL, (void*)free, NULL); +} + +typedef struct _CtxGrab CtxGrab; + +struct _CtxGrab +{ + CtxItem *item; + int device_no; + int timeout_id; + int start_time; + float x; // for tap and hold + float y; + CtxEventType type; +}; + +static void grab_free (Ctx *ctx, CtxGrab *grab) +{ + if (grab->timeout_id) + { + ctx_remove_idle (ctx, grab->timeout_id); + grab->timeout_id = 0; + } + _ctx_item_unref (grab->item); + free (grab); +} + +static void device_remove_grab (Ctx *ctx, CtxGrab *grab) +{ + ctx_list_remove (&ctx->events.grabs, grab); + grab_free (ctx, grab); +} + +static CtxGrab *device_add_grab (Ctx *ctx, int device_no, CtxItem *item, CtxEventType type) +{ + CtxGrab *grab = calloc (1, sizeof (CtxGrab)); + grab->item = item; + grab->type = type; + _ctx_item_ref (item); + grab->device_no = device_no; + ctx_list_append (&ctx->events.grabs, grab); + return grab; +} + +static CtxList *_ctx_device_get_grabs (Ctx *ctx, int device_no) +{ + CtxList *ret = NULL; + CtxList *l; + for (l = ctx->events.grabs; l; l = l->next) + { + CtxGrab *grab = l->data; + if (grab->device_no == device_no) + ctx_list_append (&ret, grab); + } + return ret; +} + +static void _mrg_restore_path (Ctx *ctx, void *path) //XXX +{ + //int i; + //cairo_path_data_t *data; + //cairo_new_path (cr); + //cairo_append_path (cr, path); +} + +CtxList *_ctx_detect_list (Ctx *ctx, float x, float y, CtxEventType type) +{ + CtxList *a; + CtxList *ret = NULL; + + if (type == CTX_KEY_DOWN || + type == CTX_KEY_UP || + type == CTX_KEY_PRESS || + type == CTX_MESSAGE || + type == (CTX_KEY_DOWN|CTX_MESSAGE) || + type == (CTX_KEY_DOWN|CTX_KEY_UP) || + type == (CTX_KEY_DOWN|CTX_KEY_UP|CTX_MESSAGE)) + { + for (a = ctx->events.items; a; a = a->next) + { + CtxItem *item = a->data; + if (item->types & type) + { + ctx_list_prepend (&ret, item); + return ret; + } + } + return NULL; + } + + for (a = ctx->events.items; a; a = a->next) + { + CtxItem *item= a->data; + + float u, v; + u = x; + v = y; + _ctx_matrix_apply_transform (&item->inv_matrix, &u, &v); + + if (u >= item->x0 && v >= item->y0 && + u < item->x1 && v < item->y1 && + ((item->types & type) || ((type == CTX_SET_CURSOR) && + item->cursor))) + { + if (item->path) + { + _mrg_restore_path (ctx, item->path); + if (ctx_in_fill (ctx, u, v)) + { + ctx_begin_path (ctx); + ctx_list_prepend (&ret, item); + } + ctx_begin_path (ctx); + } + else + { + ctx_list_prepend (&ret, item); + } + } + } + return ret; +} + +CtxItem *_ctx_detect (Ctx *ctx, float x, float y, CtxEventType type) +{ + CtxList *l = _ctx_detect_list (ctx, x, y, type); + if (l) + { + ctx_list_reverse (&l); + CtxItem *ret = l->data; + ctx_list_free (&l); + return ret; + } + return NULL; +} + +static int +_ctx_emit_cb_item (Ctx *ctx, CtxItem *item, CtxEvent *event, CtxEventType type, float x, float y) +{ + static CtxEvent s_event; + CtxEvent transformed_event; + int i; + + + if (!event) + { + event = &s_event; + event->type = type; + event->x = x; + event->y = y; + } + event->ctx = ctx; + transformed_event = *event; + transformed_event.device_x = event->x; + transformed_event.device_y = event->y; + + { + float tx, ty; + tx = transformed_event.x; + ty = transformed_event.y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.x = tx; + transformed_event.y = ty; + + if ((type & CTX_DRAG_PRESS) || + (type & CTX_DRAG_MOTION) || + (type & CTX_MOTION)) /* probably a worthwhile check for the performance + benefit + */ + { + tx = transformed_event.start_x; + ty = transformed_event.start_y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.start_x = tx; + transformed_event.start_y = ty; + } + + + tx = transformed_event.delta_x; + ty = transformed_event.delta_y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.delta_x = tx; + transformed_event.delta_y = ty; + } + + transformed_event.state = ctx->events.modifier_state; + transformed_event.type = type; + + for (i = item->cb_count-1; i >= 0; i--) + { + if (item->cb[i].types & type) + { + item->cb[i].cb (&transformed_event, item->cb[i].data1, item->cb[i].data2); + event->stop_propagate = transformed_event.stop_propagate; /* copy back the response */ + if (event->stop_propagate) + return event->stop_propagate; + } + } + return 0; +} +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <stdatomic.h> +#endif + +int ctx_native_events = 0; +#if CTX_SDL +int ctx_sdl_events = 0; +int ctx_sdl_consume_events (Ctx *ctx); +#endif + +#if CTX_FB +int ctx_fb_events = 0; +int ctx_fb_consume_events (Ctx *ctx); +#endif + +int ctx_nct_consume_events (Ctx *ctx); +int ctx_nct_has_event (Ctx *n, int delay_ms); +int ctx_ctx_consume_events (Ctx *ctx); + + + +void ctx_consume_events (Ctx *ctx) +{ +#if CTX_SDL + if (ctx_sdl_events) + ctx_sdl_consume_events (ctx); + else +#endif +#if CTX_FB + if (ctx_fb_events) + ctx_fb_consume_events (ctx); + else +#endif + if (ctx_native_events) + ctx_ctx_consume_events (ctx); + else + ctx_nct_consume_events (ctx); +} + +int ctx_has_event (Ctx *ctx, int timeout) +{ +#if CTX_SDL + if (ctx_sdl_events) + { + return SDL_WaitEventTimeout (NULL, timeout); + } + else +#endif +#if CTX_FB + if (ctx_fb_events) + { + return ctx_nct_has_event (ctx, timeout); + } + else +#endif + if (ctx_native_events) + { + return ctx_nct_has_event (ctx, timeout); + } + else + { + return ctx_nct_has_event (ctx, timeout); + } + + ctx_consume_events (ctx); + if (ctx->events.events) + return 1; + return 0; +} + +#if CTX_FB +static int ctx_fb_get_mice_fd (Ctx *ctx); +#endif + +void ctx_get_event_fds (Ctx *ctx, int *fd, int *count) +{ +#if CTX_SDL + if (ctx_sdl_events) + { + *count = 0; + } + else +#endif +#if CTX_FB + if (ctx_fb_events) + { + int mice_fd = ctx_fb_get_mice_fd (ctx); + fd[0] = STDIN_FILENO; + if (mice_fd) + { + fd[1] = mice_fd; + *count = 2; + } + else + { + *count = 1; + } + } + else +#endif + if (ctx_native_events) + { + fd[0] = STDIN_FILENO; + *count = 1; + } + else + { + fd[0] = STDIN_FILENO; + *count = 1; + } +} + +CtxEvent *ctx_get_event (Ctx *ctx) +{ + static CtxEvent event_copy; + _ctx_idle_iteration (ctx); + if (!ctx->events.ctx_get_event_enabled) + ctx->events.ctx_get_event_enabled = 1; + + ctx_consume_events (ctx); + + if (ctx->events.events) + { + event_copy = *((CtxEvent*)(ctx->events.events->data)); + ctx_list_remove (&ctx->events.events, ctx->events.events->data); + return &event_copy; + } + return NULL; +} + +static int +_ctx_emit_cb (Ctx *ctx, CtxList *items, CtxEvent *event, CtxEventType type, float x, float y) +{ + CtxList *l; + event->stop_propagate = 0; + for (l = items; l; l = l->next) + { + _ctx_emit_cb_item (ctx, l->data, event, type, x, y); + if (event->stop_propagate) + return event->stop_propagate; + } + return 0; +} + +/* + * update what is the currently hovered item and returns it.. and the list of hits + * a well. + * + */ +static CtxItem *_ctx_update_item (Ctx *ctx, int device_no, float x, float y, CtxEventType type, CtxList **hitlist) +{ + CtxItem *current = NULL; + + CtxList *l = _ctx_detect_list (ctx, x, y, type); + if (l) + { + ctx_list_reverse (&l); + current = l->data; + } + if (hitlist) + *hitlist = l; + else + ctx_list_free (&l); + + if (ctx->events.prev[device_no] == NULL || current == NULL || (current->path_hash != ctx->events.prev[device_no]->path_hash)) + { +// enter/leave should snapshot chain to root +// and compare with previous snapshotted chain to root +// and emit/enter/leave as appropriate.. +// +// leave might be registered for emission on enter..emission? + + + //int focus_radius = 2; + if (current) + _ctx_item_ref (current); + + if (ctx->events.prev[device_no]) + { + { +#if 0 + CtxIntRectangle rect = {floor(ctx->events.prev[device_no]->x0-focus_radius), + floor(ctx->events.prev[device_no]->y0-focus_radius), + ceil(ctx->events.prev[device_no]->x1)-floor(ctx->events.prev[device_no]->x0) + focus_radius * 2, + ceil(ctx->events.prev[device_no]->y1)-floor(ctx->events.prev[device_no]->y0) + focus_radius * 2}; + mrg_queue_draw (mrg, &rect); +#endif + } + + _ctx_emit_cb_item (ctx, ctx->events.prev[device_no], NULL, CTX_LEAVE, x, y); + _ctx_item_unref (ctx->events.prev[device_no]); + ctx->events.prev[device_no] = NULL; + } + if (current) + { +#if 0 + { + CtxIntRectangle rect = {floor(current->x0-focus_radius), + floor(current->y0-focus_radius), + ceil(current->x1)-floor(current->x0) + focus_radius * 2, + ceil(current->y1)-floor(current->y0) + focus_radius * 2}; + mrg_queue_draw (mrg, &rect); + } +#endif + _ctx_emit_cb_item (ctx, current, NULL, CTX_ENTER, x, y); + ctx->events.prev[device_no] = current; + } + } + current = _ctx_detect (ctx, x, y, type); + //fprintf (stderr, "%p\n", current); + return current; +} + +static int tap_and_hold_fire (Ctx *ctx, void *data) +{ + CtxGrab *grab = data; + CtxList *list = NULL; + ctx_list_prepend (&list, grab->item); + CtxEvent event = {0, }; + + event.ctx = ctx; + event.time = ctx_ms (ctx); + + event.device_x = + event.x = ctx->events.pointer_x[grab->device_no]; + event.device_y = + event.y = ctx->events.pointer_y[grab->device_no]; + + // XXX: x and y coordinates + int ret = _ctx_emit_cb (ctx, list, &event, CTX_TAP_AND_HOLD, + ctx->events.pointer_x[grab->device_no], ctx->events.pointer_y[grab->device_no]); + + ctx_list_free (&list); + + grab->timeout_id = 0; + + return 0; + + return ret; +} + +int ctx_pointer_drop (Ctx *ctx, float x, float y, int device_no, uint32_t time, + char *string) +{ + CtxList *l; + CtxList *hitlist = NULL; + + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + if (device_no <= 3) + { + ctx->events.pointer_x[0] = x; + ctx->events.pointer_y[0] = y; + } + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &ctx->events.drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->ctx = ctx; + event->x = x; + event->y = y; + + event->delta_x = event->delta_y = 0; + + event->device_no = device_no; + event->string = string; + event->time = time; + event->stop_propagate = 0; + + _ctx_update_item (ctx, device_no, x, y, CTX_DROP, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + _ctx_emit_cb_item (ctx, item, event, CTX_DROP, x, y); + + if (event->stop_propagate) + { + ctx_list_free (&hitlist); + return 0; + } + } + + //mrg_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + + return 0; +} + +int ctx_pointer_press (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxEvents *events = &ctx->events; + CtxList *hitlist = NULL; + events->pointer_x[device_no] = x; + events->pointer_y[device_no] = y; + if (device_no <= 3) + { + events->pointer_x[0] = x; + events->pointer_y[0] = y; + } + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &events->drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->x = event->start_x = event->prev_x = x; + event->y = event->start_y = event->prev_y = y; + + event->delta_x = event->delta_y = 0; + + event->device_no = device_no; + event->time = time; + event->stop_propagate = 0; + + if (events->pointer_down[device_no] == 1) + { + fprintf (stderr, "events thought device %i was already down\n", device_no); + } + /* doing just one of these two should be enough? */ + events->pointer_down[device_no] = 1; + switch (device_no) + { + case 1: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON1; + break; + case 2: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON2; + break; + case 3: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON3; + break; + default: + break; + } + + CtxGrab *grab = NULL; + CtxList *l; + + _ctx_update_item (ctx, device_no, x, y, + CTX_PRESS | CTX_DRAG_PRESS | CTX_TAP | CTX_TAP_AND_HOLD, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + if (item && + ((item->types & CTX_DRAG)|| + (item->types & CTX_TAP) || + (item->types & CTX_TAP_AND_HOLD))) + { + grab = device_add_grab (ctx, device_no, item, item->types); + grab->start_time = time; + + if (item->types & CTX_TAP_AND_HOLD) + { + grab->timeout_id = ctx_add_timeout (ctx, events->tap_delay_hold, tap_and_hold_fire, grab); + } + } + _ctx_emit_cb_item (ctx, item, event, CTX_PRESS, x, y); + if (!event->stop_propagate) + _ctx_emit_cb_item (ctx, item, event, CTX_DRAG_PRESS, x, y); + + if (event->stop_propagate) + { + ctx_list_free (&hitlist); + return 0; + } + } + + //events_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + return 0; +} + +void _ctx_resized (Ctx *ctx, int width, int height, long time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS); + CtxEvent event = {0, }; + + if (!time) + time = ctx_ms (ctx); + + event.ctx = ctx; + event.time = time; + event.string = "resize-event"; /* gets delivered to clients as a key_down event, maybe message shouldbe used instead? + */ + + if (item) + { + event.stop_propagate = 0; + _ctx_emit_cb_item (ctx, item, &event, CTX_KEY_PRESS, 0, 0); + } + +} + +int ctx_pointer_release (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxEvents *events = &ctx->events; + if (time == 0) + time = ctx_ms (ctx); + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &events->drag_event[device_no]; + + event->time = time; + event->x = x; + event->ctx = ctx; + event->y = y; + event->device_no = device_no; + event->stop_propagate = 0; + + switch (device_no) + { + case 1: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON1) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON1; + break; + case 2: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON2) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON2; + break; + case 3: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON3) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON3; + break; + default: + break; + } + + //events_queue_draw (mrg, NULL); /* in case of style change */ + + if (events->pointer_down[device_no] == 0) + { + fprintf (stderr, "device %i already up\n", device_no); + } + events->pointer_down[device_no] = 0; + + events->pointer_x[device_no] = x; + events->pointer_y[device_no] = y; + if (device_no <= 3) + { + events->pointer_x[0] = x; + events->pointer_y[0] = y; + } + CtxList *hitlist = NULL; + CtxList *grablist = NULL , *g= NULL; + CtxGrab *grab; + + _ctx_update_item (ctx, device_no, x, y, CTX_RELEASE | CTX_DRAG_RELEASE, &hitlist); + grablist = _ctx_device_get_grabs (ctx, device_no); + + for (g = grablist; g; g = g->next) + { + grab = g->data; + + if (!event->stop_propagate) + { + if (grab->item->types & CTX_TAP) + { + long delay = time - grab->start_time; + + if (delay > events->tap_delay_min && + delay < events->tap_delay_max && + ( + (event->start_x - x) * (event->start_x - x) + + (event->start_y - y) * (event->start_y - y)) < ctx_pow2(events->tap_hysteresis) + ) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_TAP, x, y); + } + } + + if (!event->stop_propagate && grab->item->types & CTX_DRAG_RELEASE) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_RELEASE, x, y); + } + } + + device_remove_grab (ctx, grab); + } + + if (hitlist) + { + if (!event->stop_propagate) + _ctx_emit_cb (ctx, hitlist, event, CTX_RELEASE, x, y); + ctx_list_free (&hitlist); + } + ctx_list_free (&grablist); + return 0; +} + +/* for multi-touch, we use a list of active grabs - thus a grab corresponds to + * a device id. even during drag-grabs events propagate; to stop that stop + * propagation. + */ +int ctx_pointer_motion (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxList *hitlist = NULL; + CtxList *grablist = NULL, *g; + CtxGrab *grab; + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &ctx->events.drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->ctx = ctx; + event->x = x; + event->y = y; + event->time = time; + event->device_no = device_no; + event->stop_propagate = 0; + + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + + if (device_no <= 3) + { + ctx->events.pointer_x[0] = x; + ctx->events.pointer_y[0] = y; + } + + grablist = _ctx_device_get_grabs (ctx, device_no); + _ctx_update_item (ctx, device_no, x, y, CTX_MOTION, &hitlist); + + { + CtxItem *cursor_item = _ctx_detect (ctx, x, y, CTX_SET_CURSOR); + if (cursor_item) + { + ctx_set_cursor (ctx, cursor_item->cursor); + } + else + { + ctx_set_cursor (ctx, CTX_CURSOR_ARROW); + } + CtxItem *hovered_item = _ctx_detect (ctx, x, y, CTX_ANY); + static CtxItem *prev_hovered_item = NULL; + if (prev_hovered_item != hovered_item) + { + ctx_set_dirty (ctx, 1); + } + prev_hovered_item = hovered_item; + } + + event->delta_x = x - event->prev_x; + event->delta_y = y - event->prev_y; + event->prev_x = x; + event->prev_y = y; + + CtxList *remove_grabs = NULL; + + for (g = grablist; g; g = g->next) + { + grab = g->data; + + if ((grab->type & CTX_TAP) || + (grab->type & CTX_TAP_AND_HOLD)) + { + if ( + ( + (event->start_x - x) * (event->start_x - x) + + (event->start_y - y) * (event->start_y - y)) > + ctx_pow2(ctx->events.tap_hysteresis) + ) + { + //fprintf (stderr, "-"); + ctx_list_prepend (&remove_grabs, grab); + } + else + { + //fprintf (stderr, ":"); + } + } + + if (grab->type & CTX_DRAG_MOTION) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_MOTION, x, y); + if (event->stop_propagate) + break; + } + } + if (remove_grabs) + { + for (g = remove_grabs; g; g = g->next) + device_remove_grab (ctx, g->data); + ctx_list_free (&remove_grabs); + } + if (hitlist) + { + if (!event->stop_propagate) + _ctx_emit_cb (ctx, hitlist, event, CTX_MOTION, x, y); + ctx_list_free (&hitlist); + } + ctx_list_free (&grablist); + return 0; +} + +void ctx_incoming_message (Ctx *ctx, const char *message, long time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_MESSAGE); + CtxEvent event = {0, }; + + if (!time) + time = ctx_ms (ctx); + + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_MESSAGE; + event.time = time; + event.string = message; + + fprintf (stderr, "{%s|\n", message); + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_MESSAGE)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + return;// event.stop_propagate; + } + } + } +} + +int ctx_scrolled (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time) +{ + CtxList *hitlist = NULL; + CtxList *l; + + int device_no = 0; + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + + CtxEvent *event = &ctx->events.drag_event[device_no]; /* XXX: might + conflict with other code + create a sibling member + of drag_event?*/ + if (time == 0) + time = ctx_ms (ctx); + + event->x = event->start_x = event->prev_x = x; + event->y = event->start_y = event->prev_y = y; + event->delta_x = event->delta_y = 0; + event->device_no = device_no; + event->time = time; + event->stop_propagate = 0; + event->scroll_direction = scroll_direction; + + _ctx_update_item (ctx, device_no, x, y, CTX_SCROLL, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + + _ctx_emit_cb_item (ctx, item, event, CTX_SCROLL, x, y); + + if (event->stop_propagate) + l = NULL; + } + + //mrg_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + return 0; +} + +static int ctx_str_has_prefix (const char *string, const char *prefix) +{ + for (int i = 0; prefix[i]; i++) + { + if (!string[i]) return 0; + if (string[i] != prefix[i]) return 0; + } + return 0; +} + +int ctx_key_press (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + char event_type[128]=""; + float x, y; int b; + sscanf (string, "%s %f %f %i", event_type, &x, &y, &b); + if (!strcmp (event_type, "mouse-motion") || + !strcmp (event_type, "mouse-drag")) + return ctx_pointer_motion (ctx, x, y, b, 0); + else if (!strcmp (event_type, "mouse-press")) + return ctx_pointer_press (ctx, x, y, b, 0); + else if (!strcmp (event_type, "mouse-release")) + return ctx_pointer_release (ctx, x, y, b, 0); + //else if (!strcmp (event_type, "keydown")) + // return ctx_key_down (ctx, keyval, string + 8, time); + //else if (!strcmp (event_type, "keyup")) + // return ctx_key_up (ctx, keyval, string + 6, time); + + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_PRESS; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_PRESS)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; +} + +int ctx_key_down (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_DOWN); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_DOWN; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_DOWN)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; +} + +int ctx_key_up (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_UP); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_UP; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_UP)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; + + return 0; +} + +void ctx_freeze (Ctx *ctx) +{ + ctx->events.frozen ++; +} + +void ctx_thaw (Ctx *ctx) +{ + ctx->events.frozen --; +} +int ctx_events_frozen (Ctx *ctx) +{ + return ctx && ctx->events.frozen; +} +void ctx_events_clear_items (Ctx *ctx) +{ + ctx_list_free (&ctx->events.items); +} +int ctx_events_width (Ctx *ctx) +{ + return ctx->events.width; +} +int ctx_events_height (Ctx *ctx) +{ + return ctx->events.height; +} + +float ctx_pointer_x (Ctx *ctx) +{ + return ctx->events.pointer_x[0]; +} + +float ctx_pointer_y (Ctx *ctx) +{ + return ctx->events.pointer_y[0]; +} + +int ctx_pointer_is_down (Ctx *ctx, int no) +{ + if (no < 0 || no > CTX_MAX_DEVICES) return 0; + return ctx->events.pointer_down[no]; +} + +void _ctx_debug_overlays (Ctx *ctx) +{ + CtxList *a; + ctx_save (ctx); + + ctx_line_width (ctx, 2); + ctx_rgba (ctx, 0,0,0.8,0.5); + for (a = ctx->events.items; a; a = a->next) + { + float current_x = ctx_pointer_x (ctx); + float current_y = ctx_pointer_y (ctx); + CtxItem *item = a->data; + CtxMatrix matrix = item->inv_matrix; + + _ctx_matrix_apply_transform (&matrix, ¤t_x, ¤t_y); + + if (current_x >= item->x0 && current_x < item->x1 && + current_y >= item->y0 && current_y < item->y1) + { + ctx_matrix_invert (&matrix); + ctx_set_matrix (ctx, &matrix); + _mrg_restore_path (ctx, item->path); + ctx_stroke (ctx); + } + } + ctx_restore (ctx); +} + +void ctx_set_render_threads (Ctx *ctx, int n_threads) +{ + // XXX +} +int ctx_get_render_threads (Ctx *ctx) +{ + return _ctx_max_threads; +} +void ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache) +{ + _ctx_enable_hash_cache = enable_hash_cache; +} +int ctx_get_hash_cache (Ctx *ctx) +{ + return _ctx_enable_hash_cache; +} + +int ctx_is_dirty (Ctx *ctx) +{ + return ctx->dirty; +} +void ctx_set_dirty (Ctx *ctx, int dirty) +{ + ctx->dirty = dirty; +} + +/* + * centralized global API for managing file descriptors that + * wake us up, this to remove sleeping and polling + */ + +#define CTX_MAX_LISTEN_FDS 128 // becomes max clients.. + +static int _ctx_listen_fd[CTX_MAX_LISTEN_FDS]; +static int _ctx_listen_fds = 0; +static int _ctx_listen_max_fd = 0; + +void _ctx_add_listen_fd (int fd) +{ + _ctx_listen_fd[_ctx_listen_fds++]=fd; + if (fd > _ctx_listen_max_fd) + _ctx_listen_max_fd = fd; +} + +void _ctx_remove_listen_fd (int fd) +{ + for (int i = 0; i < _ctx_listen_fds; i++) + { + if (_ctx_listen_fd[i] == fd) + { + _ctx_listen_fd[i] = _ctx_listen_fd[_ctx_listen_fds-1]; + _ctx_listen_fds--; + return; + } + } +} + +int ctx_input_pending (Ctx *ctx, int timeout) +{ + struct timeval tv; + fd_set fdset; + FD_ZERO (&fdset); + for (int i = 0; i < _ctx_listen_fds; i++) + { + FD_SET (_ctx_listen_fd[i], &fdset); + } + int input_fds[5]; + int n_fds; + ctx_get_event_fds (ctx, input_fds, &n_fds); + for (int i = 0; i < n_fds; i++) + { + FD_SET (input_fds[i], &fdset); + } + + tv.tv_sec = 0; + tv.tv_usec = timeout; + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + int retval = select (_ctx_listen_max_fd + 1, &fdset, NULL, NULL, &tv); + if (retval == -1) + { + perror ("select"); + return 0; + } + return retval; +} + +void ctx_sdl_set_title (void *self, const char *new_title); +void ctx_set_title (Ctx *ctx, const char *title) +{ +#if CTX_SDL + // XXX also check we're first/only client? + if (ctx_renderer_is_sdl (ctx)) + ctx_sdl_set_title (ctx_get_renderer (ctx), title); +#endif +} + +#endif +/* the parser comes in the end, nothing in ctx knows about the parser */ + +#if CTX_PARSER + +/* ctx parser, */ + +#define CTX_ID_MAXLEN 64 // in use should not be more than 40! + // to offer headroom for multiplexing + + +#define CTX_REPORT_COL_ROW 0 + +struct + _CtxParser +{ + Ctx *ctx; + int t_args; // total number of arguments seen for current command + int state; +#if CTX_PARSER_FIXED_TEMP + uint8_t holding[CTX_PARSER_MAXLEN]; /* */ +#else + uint8_t *holding; +#endif + int hold_len; + int pos; + +#if CTX_REPORT_COL_ROW + int line; /* for error reporting */ + int col; /* for error reporting */ +#endif + float numbers[CTX_PARSER_MAX_ARGS+1]; + int n_numbers; + int decimal; + CtxCode command; + int expected_args; /* low digits are literal higher values + carry special meaning */ + int n_args; + int texture_done; + uint8_t texture_id[CTX_ID_MAXLEN]; // used in defineTexture only + uint64_t set_key_hash; + float pcx; + float pcy; + int color_components; + int color_stroke; // 0 is fill source 1 is stroke source + CtxColorModel color_model; // 1 gray 3 rgb 4 cmyk + float left_margin; // set by last user provided move_to + int width; // <- maybe should be float + int height; + float cell_width; + float cell_height; + int cursor_x; // <- leaking in from terminal + int cursor_y; + + int translate_origin; + + CtxColorSpace color_space_slot; + + void (*exit) (void *exit_data); + void *exit_data; + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len); + int (*get_prop)(void *prop_data, const char *key, char **data, int *len); + void *prop_data; + int prev_byte; +}; + +void +ctx_parser_set_size (CtxParser *parser, + int width, + int height, + float cell_width, + float cell_height) +{ + if (cell_width > 0) + parser->cell_width = cell_width; + if (cell_height > 0) + parser->cell_height = cell_height; + if (width > 0) + parser->width = width; + if (height > 0) + parser->height = height; +} + +static CtxParser * +ctx_parser_init (CtxParser *parser, + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data + ) +{ + ctx_memset (parser, 0, sizeof (CtxParser) ); +#if CTX_REPORT_COL_ROW + parser->line = 1; +#endif + parser->ctx = ctx; + parser->cell_width = cell_width; + parser->cell_height = cell_height; + parser->cursor_x = cursor_x; + parser->cursor_y = cursor_y; + parser->width = width; + parser->height = height; + parser->exit = exit; + parser->exit_data = exit_data; + parser->color_model = CTX_RGBA; + parser->color_stroke = 0; + parser->color_components = 4; + parser->command = CTX_MOVE_TO; + parser->set_prop = set_prop; + parser->get_prop = get_prop; + parser->prop_data = prop_data; + return parser; +} + +CtxParser *ctx_parser_new ( + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data) +{ + return ctx_parser_init ( (CtxParser *) ctx_calloc (sizeof (CtxParser), 1), + ctx, + width, height, + cell_width, cell_height, + cursor_x, cursor_y, set_prop, get_prop, prop_data, + exit, exit_data); +} + +void ctx_parser_free (CtxParser *parser) +{ +#if !CTX_PARSER_FIXED_TEMP + if (parser->holding) + free (parser->holding); +#endif + free (parser); +} + +#define CTX_ARG_COLLECT_NUMBERS 50 +#define CTX_ARG_STRING_OR_NUMBER 100 +#define CTX_ARG_NUMBER_OF_COMPONENTS 200 +#define CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1 201 + +static int ctx_arguments_for_code (CtxCode code) +{ + switch (code) + { + case CTX_SAVE: + case CTX_START_GROUP: + case CTX_END_GROUP: + case CTX_IDENTITY: + case CTX_CLOSE_PATH: + case CTX_BEGIN_PATH: + case CTX_RESET: + case CTX_FLUSH: + case CTX_RESTORE: + case CTX_STROKE: + case CTX_FILL: + case CTX_NEW_PAGE: + case CTX_CLIP: + case CTX_EXIT: + return 0; + case CTX_GLOBAL_ALPHA: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_FONT_SIZE: + case CTX_LINE_JOIN: + case CTX_LINE_CAP: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_IMAGE_SMOOTHING: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_FILL_RULE: + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_MITER_LIMIT: + case CTX_REL_VER_LINE_TO: + case CTX_REL_HOR_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_VER_LINE_TO: + case CTX_FONT: + case CTX_ROTATE: + case CTX_GLYPH: + return 1; + case CTX_TRANSLATE: + case CTX_REL_SMOOTHQ_TO: + case CTX_LINE_TO: + case CTX_MOVE_TO: + case CTX_SCALE: + case CTX_REL_LINE_TO: + case CTX_REL_MOVE_TO: + case CTX_SMOOTHQ_TO: + return 2; + case CTX_LINEAR_GRADIENT: + case CTX_REL_QUAD_TO: + case CTX_QUAD_TO: + case CTX_RECTANGLE: + case CTX_FILL_RECT: + case CTX_STROKE_RECT: + case CTX_REL_SMOOTH_TO: + case CTX_VIEW_BOX: + case CTX_SMOOTH_TO: + return 4; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_ROUND_RECTANGLE: + return 5; + case CTX_ARC: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_RADIAL_GRADIENT: + return 6; + case CTX_STROKE_TEXT: + case CTX_TEXT: + case CTX_COLOR_SPACE: + case CTX_DEFINE_GLYPH: + case CTX_KERNING_PAIR: + case CTX_TEXTURE: + case CTX_DEFINE_TEXTURE: + return CTX_ARG_STRING_OR_NUMBER; + case CTX_LINE_DASH: /* append to current dashes for each argument encountered */ + return CTX_ARG_COLLECT_NUMBERS; + //case CTX_SET_KEY: + case CTX_COLOR: + case CTX_SHADOW_COLOR: + return CTX_ARG_NUMBER_OF_COMPONENTS; + case CTX_GRADIENT_STOP: + return CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1; + + default: +#if 1 + case CTX_SET_RGBA_U8: + case CTX_NOP: + case CTX_NEW_EDGE: + case CTX_EDGE: + case CTX_EDGE_FLIPPED: + case CTX_CONT: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_SET_PIXEL: + case CTX_REL_LINE_TO_X4: + case CTX_REL_LINE_TO_REL_CURVE_TO: + case CTX_REL_CURVE_TO_REL_LINE_TO: + case CTX_REL_CURVE_TO_REL_MOVE_TO: + case CTX_REL_LINE_TO_X2: + case CTX_MOVE_TO_REL_LINE_TO: + case CTX_REL_LINE_TO_REL_MOVE_TO: + case CTX_FILL_MOVE_TO: + case CTX_REL_QUAD_TO_REL_QUAD_TO: + case CTX_REL_QUAD_TO_S16: + case CTX_STROKE_SOURCE: +#endif + return 0; + } +} + +static int ctx_parser_set_command (CtxParser *parser, CtxCode code) +{ + if (code < 150 && code >= 32) + { + parser->expected_args = ctx_arguments_for_code (code); + parser->n_args = 0; + parser->texture_done = 0; + if (parser->expected_args >= CTX_ARG_NUMBER_OF_COMPONENTS) + { + parser->expected_args = (parser->expected_args % 100) + parser->color_components; + } + } + return code; +} + +static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke); + +static int ctx_parser_resolve_command (CtxParser *parser, const uint8_t *str) +{ + uint64_t ret = str[0]; /* if it is single char it already is the CtxCode */ + + /* this is handled outside the hashing to make it possible to be case insensitive + * with the rest. + */ + if (str[0] == CTX_SET_KEY && str[1] && str[2] == 0) + { + switch (str[1]) + { + case 'm': return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE); + case 'B': return ctx_parser_set_command (parser, CTX_BLEND_MODE); + case 'l': return ctx_parser_set_command (parser, CTX_MITER_LIMIT); + case 't': return ctx_parser_set_command (parser, CTX_TEXT_ALIGN); + case 'b': return ctx_parser_set_command (parser, CTX_TEXT_BASELINE); + case 'd': return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION); + case 'j': return ctx_parser_set_command (parser, CTX_LINE_JOIN); + case 'c': return ctx_parser_set_command (parser, CTX_LINE_CAP); + case 'w': return ctx_parser_set_command (parser, CTX_LINE_WIDTH); + case 'D': return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET); + case 'S': return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING); + case 'C': return ctx_parser_set_command (parser, CTX_SHADOW_COLOR); + case 's': return ctx_parser_set_command (parser, CTX_SHADOW_BLUR); + case 'x': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X); + case 'y': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y); + case 'a': return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA); + case 'f': return ctx_parser_set_command (parser, CTX_FONT_SIZE); + case 'r': return ctx_parser_set_command (parser, CTX_FILL_RULE); + } + } + + if (str[0] && str[1]) + { + uint64_t str_hash; + /* trim ctx_ and CTX_ prefix */ + if ( (str[0] == 'c' && str[1] == 't' && str[2] == 'x' && str[3] == '_') || + (str[0] == 'C' && str[1] == 'T' && str[2] == 'X' && str[3] == '_') ) + { + str += 4; + } + if ( (str[0] == 's' && str[1] == 'e' && str[2] == 't' && str[3] == '_') ) + { str += 4; } + str_hash = ctx_strhash ( (char *) str, 0); + switch (str_hash) + { + /* first a list of mappings to one_char hashes, handled in a + * separate fast path switch without hashing + */ + case CTX_arcTo: ret = CTX_ARC_TO; break; + case CTX_arc: ret = CTX_ARC; break; + case CTX_curveTo: ret = CTX_CURVE_TO; break; + case CTX_restore: ret = CTX_RESTORE; break; + case CTX_stroke: ret = CTX_STROKE; break; + case CTX_fill: ret = CTX_FILL; break; + case CTX_flush: ret = CTX_FLUSH; break; + case CTX_horLineTo: ret = CTX_HOR_LINE_TO; break; + case CTX_rotate: ret = CTX_ROTATE; break; + case CTX_color: ret = CTX_COLOR; break; + case CTX_lineTo: ret = CTX_LINE_TO; break; + case CTX_moveTo: ret = CTX_MOVE_TO; break; + case CTX_scale: ret = CTX_SCALE; break; + case CTX_newPage: ret = CTX_NEW_PAGE; break; + case CTX_quadTo: ret = CTX_QUAD_TO; break; + case CTX_viewBox: ret = CTX_VIEW_BOX; break; + case CTX_smooth_to: ret = CTX_SMOOTH_TO; break; + case CTX_smooth_quad_to: ret = CTX_SMOOTHQ_TO; break; + case CTX_clear: ret = CTX_COMPOSITE_CLEAR; break; + case CTX_copy: ret = CTX_COMPOSITE_COPY; break; + case CTX_destinationOver: ret = CTX_COMPOSITE_DESTINATION_OVER; break; + case CTX_destinationIn: ret = CTX_COMPOSITE_DESTINATION_IN; break; + case CTX_destinationOut: ret = CTX_COMPOSITE_DESTINATION_OUT; break; + case CTX_sourceOver: ret = CTX_COMPOSITE_SOURCE_OVER; break; + case CTX_sourceAtop: ret = CTX_COMPOSITE_SOURCE_ATOP; break; + case CTX_destinationAtop: ret = CTX_COMPOSITE_DESTINATION_ATOP; break; + case CTX_sourceOut: ret = CTX_COMPOSITE_SOURCE_OUT; break; + case CTX_sourceIn: ret = CTX_COMPOSITE_SOURCE_IN; break; + case CTX_xor: ret = CTX_COMPOSITE_XOR; break; + case CTX_darken: ret = CTX_BLEND_DARKEN; break; + case CTX_lighten: ret = CTX_BLEND_LIGHTEN; break; + //case CTX_color: ret = CTX_BLEND_COLOR; break; + // + // XXX check that he special casing for color works + // it is the first collision and it is due to our own + // color, not w3c for now unique use of it + // + case CTX_hue: ret = CTX_BLEND_HUE; break; + case CTX_multiply: ret = CTX_BLEND_MULTIPLY; break; + case CTX_normal: ret = CTX_BLEND_NORMAL;break; + case CTX_screen: ret = CTX_BLEND_SCREEN;break; + case CTX_difference: ret = CTX_BLEND_DIFFERENCE; break; + case CTX_reset: ret = CTX_RESET; break; + case CTX_verLineTo: ret = CTX_VER_LINE_TO; break; + case CTX_exit: + case CTX_done: ret = CTX_EXIT; break; + case CTX_closePath: ret = CTX_CLOSE_PATH; break; + case CTX_beginPath: + case CTX_newPath: ret = CTX_BEGIN_PATH; break; + case CTX_relArcTo: ret = CTX_REL_ARC_TO; break; + case CTX_clip: ret = CTX_CLIP; break; + case CTX_relCurveTo: ret = CTX_REL_CURVE_TO; break; + case CTX_startGroup: ret = CTX_START_GROUP; break; + case CTX_endGroup: ret = CTX_END_GROUP; break; + case CTX_save: ret = CTX_SAVE; break; + case CTX_translate: ret = CTX_TRANSLATE; break; + case CTX_linearGradient: ret = CTX_LINEAR_GRADIENT; break; + case CTX_relHorLineTo: ret = CTX_REL_HOR_LINE_TO; break; + case CTX_relLineTo: ret = CTX_REL_LINE_TO; break; + case CTX_relMoveTo: ret = CTX_REL_MOVE_TO; break; + case CTX_font: ret = CTX_FONT; break; + case CTX_radialGradient:ret = CTX_RADIAL_GRADIENT; break; + case CTX_gradientAddStop: + case CTX_addStop: ret = CTX_GRADIENT_STOP; break; + case CTX_relQuadTo: ret = CTX_REL_QUAD_TO; break; + case CTX_rectangle: + case CTX_rect: ret = CTX_RECTANGLE; break; + case CTX_roundRectangle: ret = CTX_ROUND_RECTANGLE; break; + case CTX_relSmoothTo: ret = CTX_REL_SMOOTH_TO; break; + case CTX_relSmoothqTo: ret = CTX_REL_SMOOTHQ_TO; break; + case CTX_strokeText: ret = CTX_STROKE_TEXT; break; + case CTX_strokeRect: ret = CTX_STROKE_RECT; break; + case CTX_fillRect: ret = CTX_FILL_RECT; break; + case CTX_relVerLineTo: ret = CTX_REL_VER_LINE_TO; break; + case CTX_text: ret = CTX_TEXT; break; + case CTX_identity: ret = CTX_IDENTITY; break; + case CTX_transform: ret = CTX_APPLY_TRANSFORM; break; + case CTX_texture: ret = CTX_TEXTURE; break; + case CTX_defineTexture: ret = CTX_DEFINE_TEXTURE; break; +#if 0 + case CTX_rgbSpace: + return ctx_parser_set_command (parser, CTX_SET_RGB_SPACE); + case CTX_cmykSpace: + return ctx_parser_set_command (parser, CTX_SET_CMYK_SPACE); + case CTX_drgbSpace: + return ctx_parser_set_command (parser, CTX_SET_DRGB_SPACE); +#endif + case CTX_defineGlyph: + return ctx_parser_set_command (parser, CTX_DEFINE_GLYPH); + case CTX_kerningPair: + return ctx_parser_set_command (parser, CTX_KERNING_PAIR); + + case CTX_colorSpace: + return ctx_parser_set_command (parser, CTX_COLOR_SPACE); + case CTX_fillRule: + return ctx_parser_set_command (parser, CTX_FILL_RULE); + case CTX_fontSize: + case CTX_setFontSize: + return ctx_parser_set_command (parser, CTX_FONT_SIZE); + case CTX_compositingMode: + return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE); + + case CTX_blend: + case CTX_blending: + case CTX_blendMode: + return ctx_parser_set_command (parser, CTX_BLEND_MODE); + + case CTX_miterLimit: + return ctx_parser_set_command (parser, CTX_MITER_LIMIT); + case CTX_textAlign: + return ctx_parser_set_command (parser, CTX_TEXT_ALIGN); + case CTX_textBaseline: + return ctx_parser_set_command (parser, CTX_TEXT_BASELINE); + case CTX_textDirection: + return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION); + case CTX_join: + case CTX_lineJoin: + case CTX_setLineJoin: + return ctx_parser_set_command (parser, CTX_LINE_JOIN); + case CTX_glyph: + return ctx_parser_set_command (parser, CTX_GLYPH); + case CTX_cap: + case CTX_lineCap: + case CTX_setLineCap: + return ctx_parser_set_command (parser, CTX_LINE_CAP); + case CTX_lineDash: + return ctx_parser_set_command (parser, CTX_LINE_DASH); + case CTX_lineWidth: + case CTX_setLineWidth: + return ctx_parser_set_command (parser, CTX_LINE_WIDTH); + case CTX_lineDashOffset: + return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET); + case CTX_imageSmoothing: + return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING); + case CTX_shadowColor: + return ctx_parser_set_command (parser, CTX_SHADOW_COLOR); + case CTX_shadowBlur: + return ctx_parser_set_command (parser, CTX_SHADOW_BLUR); + case CTX_shadowOffsetX: + return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X); + case CTX_shadowOffsetY: + return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y); + case CTX_globalAlpha: + return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA); + + case CTX_strokeSource: + return ctx_parser_set_command (parser, CTX_STROKE_SOURCE); + + /* strings are handled directly here, + * instead of in the one-char handler, using return instead of break + */ + case CTX_gray: + ctx_parser_set_color_model (parser, CTX_GRAY, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_graya: + ctx_parser_set_color_model (parser, CTX_GRAYA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgb: + ctx_parser_set_color_model (parser, CTX_RGB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgb: + ctx_parser_set_color_model (parser, CTX_DRGB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgba: + ctx_parser_set_color_model (parser, CTX_RGBA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgba: + ctx_parser_set_color_model (parser, CTX_DRGBA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmyk: + ctx_parser_set_color_model (parser, CTX_CMYK, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmyka: + ctx_parser_set_color_model (parser, CTX_CMYKA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lab: + ctx_parser_set_color_model (parser, CTX_LAB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_laba: + ctx_parser_set_color_model (parser, CTX_LABA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lch: + ctx_parser_set_color_model (parser, CTX_LCH, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lcha: + ctx_parser_set_color_model (parser, CTX_LCHA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + + /* and a full repeat of the above, with S for Stroke suffix */ + case CTX_grayS: + ctx_parser_set_color_model (parser, CTX_GRAY, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_grayaS: + ctx_parser_set_color_model (parser, CTX_GRAYA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgbS: + ctx_parser_set_color_model (parser, CTX_RGB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgbS: + ctx_parser_set_color_model (parser, CTX_DRGB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgbaS: + ctx_parser_set_color_model (parser, CTX_RGBA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgbaS: + ctx_parser_set_color_model (parser, CTX_DRGBA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmykS: + ctx_parser_set_color_model (parser, CTX_CMYK, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmykaS: + ctx_parser_set_color_model (parser, CTX_CMYKA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_labS: + ctx_parser_set_color_model (parser, CTX_LAB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_labaS: + ctx_parser_set_color_model (parser, CTX_LABA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lchS: + ctx_parser_set_color_model (parser, CTX_LCH, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lchaS: + ctx_parser_set_color_model (parser, CTX_LCHA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + + /* words that correspond to low integer constants + */ + case CTX_nonzero: return CTX_FILL_RULE_WINDING; + case CTX_non_zero: return CTX_FILL_RULE_WINDING; + case CTX_winding: return CTX_FILL_RULE_WINDING; + case CTX_evenOdd: + case CTX_even_odd: return CTX_FILL_RULE_EVEN_ODD; + case CTX_bevel: return CTX_JOIN_BEVEL; + case CTX_round: return CTX_JOIN_ROUND; + case CTX_miter: return CTX_JOIN_MITER; + case CTX_none: return CTX_CAP_NONE; + case CTX_square: return CTX_CAP_SQUARE; + case CTX_start: return CTX_TEXT_ALIGN_START; + case CTX_end: return CTX_TEXT_ALIGN_END; + case CTX_left: return CTX_TEXT_ALIGN_LEFT; + case CTX_right: return CTX_TEXT_ALIGN_RIGHT; + case CTX_center: return CTX_TEXT_ALIGN_CENTER; + case CTX_top: return CTX_TEXT_BASELINE_TOP; + case CTX_bottom : return CTX_TEXT_BASELINE_BOTTOM; + case CTX_middle: return CTX_TEXT_BASELINE_MIDDLE; + case CTX_alphabetic: return CTX_TEXT_BASELINE_ALPHABETIC; + case CTX_hanging: return CTX_TEXT_BASELINE_HANGING; + case CTX_ideographic: return CTX_TEXT_BASELINE_IDEOGRAPHIC; + + case CTX_userRGB: return CTX_COLOR_SPACE_USER_RGB; + case CTX_deviceRGB: return CTX_COLOR_SPACE_DEVICE_RGB; + case CTX_userCMYK: return CTX_COLOR_SPACE_USER_CMYK; + case CTX_deviceCMYK: return CTX_COLOR_SPACE_DEVICE_CMYK; +#undef STR +#undef LOWER + default: + ret = str_hash; + } + } + if (ret == CTX_CLOSE_PATH2) + { + ret = CTX_CLOSE_PATH; + } + + return ctx_parser_set_command (parser, (CtxCode) ret); +} + +enum +{ + CTX_PARSER_NEUTRAL = 0, + CTX_PARSER_NUMBER, + CTX_PARSER_NEGATIVE_NUMBER, + CTX_PARSER_WORD, + CTX_PARSER_COMMENT, + CTX_PARSER_STRING_APOS, + CTX_PARSER_STRING_QUOT, + CTX_PARSER_STRING_APOS_ESCAPED, + CTX_PARSER_STRING_QUOT_ESCAPED, + CTX_PARSER_STRING_A85, + CTX_PARSER_STRING_YENC, +} CTX_STATE; + +static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke) +{ + parser->color_model = color_model; + parser->color_stroke = stroke; + parser->color_components = ctx_color_model_get_components (color_model); +} + +static void ctx_parser_get_color_rgba (CtxParser *parser, int offset, float *red, float *green, float *blue, float *alpha) +{ + /* XXX - this function is to be deprecated */ + *alpha = 1.0; + switch (parser->color_model) + { + case CTX_GRAYA: + *alpha = parser->numbers[offset + 1]; + /* FALLTHROUGH */ + case CTX_GRAY: + *red = *green = *blue = parser->numbers[offset + 0]; + break; + default: + case CTX_LABA: // NYI - needs RGB profile + case CTX_LCHA: // NYI - needs RGB profile + case CTX_RGBA: + *alpha = parser->numbers[offset + 3]; + /* FALLTHROUGH */ + case CTX_LAB: // NYI + case CTX_LCH: // NYI + case CTX_RGB: + *red = parser->numbers[offset + 0]; + *green = parser->numbers[offset + 1]; + *blue = parser->numbers[offset + 2]; + break; + case CTX_CMYKA: + *alpha = parser->numbers[offset + 4]; + /* FALLTHROUGH */ + case CTX_CMYK: + /* should use profile instead */ + *red = (1.0-parser->numbers[offset + 0]) * + (1.0 - parser->numbers[offset + 3]); + *green = (1.0-parser->numbers[offset + 1]) * + (1.0 - parser->numbers[offset + 3]); + *blue = (1.0-parser->numbers[offset + 2]) * + (1.0 - parser->numbers[offset + 3]); + break; + } +} + +static void ctx_parser_dispatch_command (CtxParser *parser) +{ + CtxCode cmd = parser->command; + Ctx *ctx = parser->ctx; + + if (parser->expected_args != CTX_ARG_STRING_OR_NUMBER && + parser->expected_args != CTX_ARG_COLLECT_NUMBERS && + parser->expected_args != parser->n_numbers) + { +#if CTX_REPORT_COL_ROW + fprintf (stderr, "ctx:%i:%i %c got %i instead of %i args\n", + parser->line, parser->col, + cmd, parser->n_numbers, parser->expected_args); +#endif + //return; + } + +#define arg(a) (parser->numbers[a]) + parser->command = CTX_NOP; + //parser->n_args = 0; + switch (cmd) + { + default: + break; // to silence warnings about missing ones + case CTX_PRESERVE: + ctx_preserve (ctx); + break; + case CTX_FILL: + ctx_fill (ctx); + break; + case CTX_SAVE: + ctx_save (ctx); + break; + case CTX_START_GROUP: + ctx_start_group (ctx); + break; + case CTX_END_GROUP: + ctx_end_group (ctx); + break; + case CTX_STROKE: + ctx_stroke (ctx); + break; + case CTX_STROKE_SOURCE: + ctx_stroke_source (ctx); + break; + case CTX_RESTORE: + ctx_restore (ctx); + break; +#if CTX_ENABLE_CM + case CTX_COLOR_SPACE: + if (parser->n_numbers == 1) + { + parser->color_space_slot = (CtxColorSpace) arg(0); + parser->command = CTX_COLOR_SPACE; // did this work without? + } + else + { + ctx_colorspace (ctx, (CtxColorSpace)parser->color_space_slot, + parser->holding, parser->pos); + } + break; +#endif + case CTX_KERNING_PAIR: + switch (parser->n_args) + { + case 0: + parser->numbers[0] = ctx_utf8_to_unichar ((char*)parser->holding); + break; + case 1: + parser->numbers[1] = ctx_utf8_to_unichar ((char*)parser->holding); + break; + case 2: + parser->numbers[2] = strtod ((char*)parser->holding, NULL); + { + CtxEntry e = {CTX_KERNING_PAIR, }; + e.data.u16[0] = parser->numbers[0]; + e.data.u16[1] = parser->numbers[1]; + e.data.s32[1] = parser->numbers[2] * 256; + ctx_process (ctx, &e); + } + break; + } + parser->command = CTX_KERNING_PAIR; + parser->n_args ++; // make this more generic? + break; + case CTX_TEXTURE: + if (parser->texture_done) + { + } + else + if (parser->n_numbers == 2) + { + const char *eid = (char*)parser->holding; + float x0 = arg(0); + float x1 = arg(1); + ctx_texture (ctx, eid, x0, x1); + parser->texture_done = 1; + } + parser->command = CTX_TEXTURE; + //parser->n_args++; + break; + case CTX_DEFINE_TEXTURE: + if (parser->texture_done) + { + if (parser->texture_done++ == 1) + { + const char *eid = (char*)parser->texture_id; + int width = arg(0); + int height = arg(1); + CtxPixelFormat format = (CtxPixelFormat)arg(2); + int stride = ctx_pixel_format_get_stride (format, width); + int data_len = stride * height; + if (format == CTX_FORMAT_YUV420) + data_len = height * width + 2*(height/2) * (width/2); + + + if (parser->pos != data_len) + { + fprintf (stderr, "unexpected datasize for define texture %s %ix%i\n size:%i != expected:%i - start of data: %i %i %i %i\n", eid, width, height, + parser->pos, + stride * height, + parser->holding[0], + parser->holding[1], + parser->holding[2], + parser->holding[3] + ); + } + else + ctx_define_texture (ctx, eid, width, height, stride, format, parser->holding, NULL); + } + } + else + { + switch (parser->n_numbers) + { + case 0: + strncpy ((char*)parser->texture_id, (char*)parser->holding, sizeof(parser->texture_id)); + parser->texture_id[sizeof(parser->texture_id)-1]=0; + break; + case 1: + case 2: + break; + case 3: + parser->texture_done = 1; + break; + default: + fprintf (stderr, "!!%i\n", parser->n_numbers); + break; + } + } + parser->command = CTX_DEFINE_TEXTURE; + break; + + + case CTX_DEFINE_GLYPH: + /* XXX : reuse n_args logic - to enforce order */ + if (parser->n_numbers == 1) + { + CtxEntry e = {CTX_DEFINE_GLYPH, }; + e.data.u32[0] = parser->color_space_slot; + e.data.u32[1] = arg(0) * 256; + ctx_process (ctx, &e); + } + else + { + int unichar = ctx_utf8_to_unichar ((char*)parser->holding); + parser->color_space_slot = (CtxColorSpace)unichar; + } + parser->command = CTX_DEFINE_GLYPH; + break; + + case CTX_COLOR: + { + switch (parser->color_model) + { + case CTX_GRAY: + case CTX_GRAYA: + case CTX_RGB: + case CTX_RGBA: + case CTX_DRGB: + case CTX_DRGBA: + ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYK: + case CTX_CMYKA: + ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke); + break; +#else + /* when there is no cmyk support at all in rasterizer + * do a naive mapping to RGB on input. + */ + case CTX_CMYK: + case CTX_CMYKA: + case CTX_DCMYKA: + { + float rgba[4] = {1,1,1,1.0f}; + + ctx_cmyk_to_rgb (arg(0), arg(1), arg(2), arg(3), &rgba[0], &rgba[1], &rgba[2]); + if (parser->color_model == CTX_CMYKA) + { rgba[3] = arg(4); } + ctx_color_raw (ctx, CTX_RGBA, rgba, parser->color_stroke); + } + break; +#endif + case CTX_LAB: + case CTX_LCH: + default: + break; + } + } + break; + case CTX_LINE_DASH: + if (parser->n_numbers) + { + ctx_line_dash (ctx, parser->numbers, parser->n_numbers); + } + else + { + ctx_line_dash (ctx, NULL, 0); + } + //append_dash_val (ctx, arg(0)); + break; + case CTX_ARC_TO: + ctx_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4)); + break; + case CTX_REL_ARC_TO: + ctx_rel_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4) ); + break; + case CTX_REL_SMOOTH_TO: + { + float cx = parser->pcx; + float cy = parser->pcy; + float ax = 2 * ctx_x (ctx) - cx; + float ay = 2 * ctx_y (ctx) - cy; + ctx_curve_to (ctx, ax, ay, arg(0) + cx, arg(1) + cy, + arg(2) + cx, arg(3) + cy); + parser->pcx = arg(0) + cx; + parser->pcy = arg(1) + cy; + } + break; + case CTX_SMOOTH_TO: + { + float ax = 2 * ctx_x (ctx) - parser->pcx; + float ay = 2 * ctx_y (ctx) - parser->pcy; + ctx_curve_to (ctx, ax, ay, arg(0), arg(1), + arg(2), arg(3) ); + parser->pcx = arg(0); + parser->pcx = arg(1); + } + break; + case CTX_SMOOTHQ_TO: + ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0), arg(1) ); + break; + case CTX_REL_SMOOTHQ_TO: + { + float cx = parser->pcx; + float cy = parser->pcy; + parser->pcx = 2 * ctx_x (ctx) - parser->pcx; + parser->pcy = 2 * ctx_y (ctx) - parser->pcy; + ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0) + cx, arg(1) + cy); + } + break; + case CTX_VER_LINE_TO: + ctx_line_to (ctx, ctx_x (ctx), arg(0) ); + parser->command = CTX_VER_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_HOR_LINE_TO: + ctx_line_to (ctx, arg(0), ctx_y (ctx) ); + parser->command = CTX_HOR_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_REL_HOR_LINE_TO: + ctx_rel_line_to (ctx, arg(0), 0.0f); + parser->command = CTX_REL_HOR_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_REL_VER_LINE_TO: + ctx_rel_line_to (ctx, 0.0f, arg(0) ); + parser->command = CTX_REL_VER_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_ARC: + ctx_arc (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_APPLY_TRANSFORM: + ctx_apply_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_SOURCE_TRANSFORM: + ctx_source_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_CURVE_TO: + ctx_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + parser->pcx = arg(2); + parser->pcy = arg(3); + parser->command = CTX_CURVE_TO; + break; + case CTX_REL_CURVE_TO: + parser->pcx = arg(2) + ctx_x (ctx); + parser->pcy = arg(3) + ctx_y (ctx); + ctx_rel_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + parser->command = CTX_REL_CURVE_TO; + break; + case CTX_LINE_TO: + ctx_line_to (ctx, arg(0), arg(1) ); + parser->command = CTX_LINE_TO; + parser->pcx = arg(0); + parser->pcy = arg(1); + break; + case CTX_MOVE_TO: + ctx_move_to (ctx, arg(0), arg(1) ); + parser->command = CTX_LINE_TO; + parser->pcx = arg(0); + parser->pcy = arg(1); + parser->left_margin = parser->pcx; + break; + case CTX_FONT_SIZE: + ctx_font_size (ctx, arg(0) ); + break; + case CTX_MITER_LIMIT: + ctx_miter_limit (ctx, arg(0) ); + break; + case CTX_SCALE: + ctx_scale (ctx, arg(0), arg(1) ); + break; + case CTX_QUAD_TO: + parser->pcx = arg(0); + parser->pcy = arg(1); + ctx_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) ); + parser->command = CTX_QUAD_TO; + break; + case CTX_REL_QUAD_TO: + parser->pcx = arg(0) + ctx_x (ctx); + parser->pcy = arg(1) + ctx_y (ctx); + ctx_rel_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) ); + parser->command = CTX_REL_QUAD_TO; + break; + case CTX_CLIP: + ctx_clip (ctx); + break; + case CTX_TRANSLATE: + ctx_translate (ctx, arg(0), arg(1) ); + break; + case CTX_ROTATE: + ctx_rotate (ctx, arg(0) ); + break; + case CTX_FONT: + ctx_font (ctx, (char *) parser->holding); + break; + + case CTX_STROKE_TEXT: + case CTX_TEXT: + if (parser->n_numbers == 1) + { ctx_rel_move_to (ctx, -parser->numbers[0], 0.0); } // XXX : scale by font(size) + else + { + for (char *c = (char *) parser->holding; c; ) + { + char *next_nl = ctx_strchr (c, '\n'); + if (next_nl) + { *next_nl = 0; } + /* do our own layouting on a per-word basis?, to get justified + * margins? then we'd want explict margins rather than the + * implicit ones from move_to's .. making move_to work within + * margins. + */ + if (cmd == CTX_STROKE_TEXT) + { ctx_text_stroke (ctx, c); } + else + { ctx_text (ctx, c); } + if (next_nl) + { + *next_nl = '\n'; // swap it newline back in + ctx_move_to (ctx, parser->left_margin, ctx_y (ctx) + + ctx_get_font_size (ctx) ); + c = next_nl + 1; + if (c[0] == 0) + { c = NULL; } + } + else + { + c = NULL; + } + } + } + if (cmd == CTX_STROKE_TEXT) + { parser->command = CTX_STROKE_TEXT; } + else + { parser->command = CTX_TEXT; } + break; + case CTX_REL_LINE_TO: + ctx_rel_line_to (ctx, arg(0), arg(1) ); + parser->pcx += arg(0); + parser->pcy += arg(1); + break; + case CTX_REL_MOVE_TO: + ctx_rel_move_to (ctx, arg(0), arg(1) ); + parser->pcx += arg(0); + parser->pcy += arg(1); + parser->left_margin = ctx_x (ctx); + break; + case CTX_LINE_WIDTH: + ctx_line_width (ctx, arg(0)); + break; + case CTX_LINE_DASH_OFFSET: + ctx_line_dash_offset (ctx, arg(0)); + break; + case CTX_IMAGE_SMOOTHING: + ctx_image_smoothing (ctx, arg(0)); + break; + case CTX_SHADOW_COLOR: + ctx_shadow_rgba (ctx, arg(0), arg(1), arg(2), arg(3)); + break; + case CTX_SHADOW_BLUR: + ctx_shadow_blur (ctx, arg(0) ); + break; + case CTX_SHADOW_OFFSET_X: + ctx_shadow_offset_x (ctx, arg(0) ); + break; + case CTX_SHADOW_OFFSET_Y: + ctx_shadow_offset_y (ctx, arg(0) ); + break; + case CTX_LINE_JOIN: + ctx_line_join (ctx, (CtxLineJoin) arg(0) ); + break; + case CTX_LINE_CAP: + ctx_line_cap (ctx, (CtxLineCap) arg(0) ); + break; + case CTX_COMPOSITING_MODE: + ctx_compositing_mode (ctx, (CtxCompositingMode) arg(0) ); + break; + case CTX_BLEND_MODE: + { + int blend_mode = arg(0); + if (blend_mode == CTX_COLOR) blend_mode = CTX_BLEND_COLOR; + ctx_blend_mode (ctx, (CtxBlend)blend_mode); + } + break; + case CTX_FILL_RULE: + ctx_fill_rule (ctx, (CtxFillRule) arg(0) ); + break; + case CTX_TEXT_ALIGN: + ctx_text_align (ctx, (CtxTextAlign) arg(0) ); + break; + case CTX_TEXT_BASELINE: + ctx_text_baseline (ctx, (CtxTextBaseline) arg(0) ); + break; + case CTX_TEXT_DIRECTION: + ctx_text_direction (ctx, (CtxTextDirection) arg(0) ); + break; + case CTX_IDENTITY: + ctx_identity (ctx); + break; + case CTX_RECTANGLE: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_FILL_RECT: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + ctx_fill (ctx); + break; + case CTX_STROKE_RECT: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + ctx_stroke (ctx); + break; + case CTX_ROUND_RECTANGLE: + ctx_round_rectangle (ctx, arg(0), arg(1), arg(2), arg(3), arg(4)); + break; + case CTX_VIEW_BOX: + ctx_view_box (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_LINEAR_GRADIENT: + ctx_linear_gradient (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_RADIAL_GRADIENT: + ctx_radial_gradient (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_GRADIENT_STOP: + { + float red, green, blue, alpha; + ctx_parser_get_color_rgba (parser, 1, &red, &green, &blue, &alpha); + ctx_gradient_add_stop (ctx, arg(0), red, green, blue, alpha); + } + break; + case CTX_GLOBAL_ALPHA: + ctx_global_alpha (ctx, arg(0) ); + break; + case CTX_BEGIN_PATH: + ctx_begin_path (ctx); + break; + case CTX_GLYPH: + ctx_glyph (ctx, arg(0), 0); + break; + case CTX_CLOSE_PATH: + ctx_close_path (ctx); + break; + case CTX_EXIT: + if (parser->exit) + { parser->exit (parser->exit_data); + return; + } + break; + case CTX_FLUSH: + //ctx_flush (ctx); + break; + case CTX_RESET: + ctx_reset (ctx); + if (parser->translate_origin) + { + ctx_translate (ctx, + (parser->cursor_x-1) * parser->cell_width * 1.0, + (parser->cursor_y-1) * parser->cell_height * 1.0); + } + break; + } +#undef arg +// parser->n_numbers = 0; +} + +static inline void ctx_parser_holding_append (CtxParser *parser, int byte) +{ +#if !CTX_PARSER_FIXED_TEMP + if (CTX_UNLIKELY(parser->hold_len < parser->pos + 1 + 1)) + { + int new_len = parser->hold_len * 2; + if (new_len < 512) new_len = 512; + parser->holding = (uint8_t*)realloc (parser->holding, new_len); + parser->hold_len = new_len; + } +#endif + + parser->holding[parser->pos++]=byte; +#if CTX_PARSER_FIXED_TEMP + if (CTX_UNLIKELY(parser->pos > (int) sizeof (parser->holding)-2)) + { parser->pos = sizeof (parser->holding)-2; } +#endif + parser->holding[parser->pos]=0; +} + +static void ctx_parser_transform_percent (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + int big = parser->width; + int small = parser->height; + if (big < small) + { + small = parser->width; + big = parser->height; + } + switch (code) + { + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + switch (arg_no) + { + case 0: + case 3: + *value *= (parser->width/100.0); + break; + case 1: + case 4: + *value *= (parser->height/100.0); + break; + case 2: + case 5: + *value *= small/100.0; + break; + } + break; + case CTX_FONT_SIZE: + case CTX_MITER_LIMIT: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + { + *value *= (small/100.0); + } + break; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + if (arg_no > 3) + { + *value *= (small/100.0); + } + else + { + if (arg_no % 2 == 0) + { *value *= ( (parser->width) /100.0); } + else + { *value *= ( (parser->height) /100.0); } + } + break; + case CTX_ROUND_RECTANGLE: + if (arg_no == 4) + { + { *value *= ((parser->height)/100.0); } + return; + } + /* FALLTHROUGH */ + default: // even means x coord + if (arg_no % 2 == 0) + { *value *= ((parser->width)/100.0); } + else + { *value *= ((parser->height)/100.0); } + break; + } +} + +static void ctx_parser_transform_percent_height (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + *value *= (parser->height/100.0); +} + +static void ctx_parser_transform_percent_width (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + *value *= (parser->height/100.0); +} + +static void ctx_parser_transform_cell (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + float small = parser->cell_width; + if (small > parser->cell_height) + { small = parser->cell_height; } + switch (code) + { + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + switch (arg_no) + { + case 0: + case 3: + *value *= parser->cell_width; + break; + case 1: + case 4: + *value *= parser->cell_height; + break; + case 2: + case 5: + *value *= small; // use height? + break; + } + break; + case CTX_MITER_LIMIT: + case CTX_FONT_SIZE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + { + *value *= parser->cell_height; + } + break; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + if (arg_no > 3) + { + *value *= small; + } + else + { + *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height; + } + break; + case CTX_RECTANGLE: + if (arg_no % 2 == 0) + { *value *= parser->cell_width; } + else + { + if (! (arg_no > 1) ) + { (*value) -= 1.0f; } + *value *= parser->cell_height; + } + break; + default: // even means x coord odd means y coord + *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height; + break; + } +} + +// %h %v %m %M + +static void ctx_parser_number_done (CtxParser *parser) +{ + +} + +static void ctx_parser_word_done (CtxParser *parser) +{ + parser->holding[parser->pos]=0; + //int old_args = parser->expected_args; + int command = ctx_parser_resolve_command (parser, parser->holding); + if ((command >= 0 && command < 32) + || (command > 150) || (command < 0) + ) // special case low enum values + { // and enum values too high to be + // commands - permitting passing words + // for strings in some cases + parser->numbers[parser->n_numbers] = command; + + // trigger transition from number + parser->state = CTX_PARSER_NUMBER; + char c = ','; + ctx_parser_feed_bytes (parser, &c, 1); + } + else if (command > 0) + { +#if 0 + if (old_args == CTX_ARG_COLLECT_NUMBERS || + old_args == CTX_ARG_STRING_OR_NUMBER) + { + int tmp1 = parser->command; + int tmp2 = parser->expected_args; + int tmp3 = parser->n_numbers; + // int tmp4 = parser->n_args; + ctx_parser_dispatch_command (parser); + parser->command = (CtxCode)tmp1; + parser->expected_args = tmp2; + parser->n_numbers = tmp3; + // parser->n_args = tmp4; + } +#endif + + parser->command = (CtxCode) command; + parser->n_numbers = 0; + parser->n_args = 0; + if (parser->expected_args == 0) + { + ctx_parser_dispatch_command (parser); + } + } + else + { + /* interpret char by char */ + uint8_t buf[16]=" "; + for (int i = 0; parser->pos && parser->holding[i] > ' '; i++) + { + buf[0] = parser->holding[i]; + parser->command = (CtxCode) ctx_parser_resolve_command (parser, buf); + parser->n_numbers = 0; + parser->n_args = 0; + if (parser->command > 0) + { + if (parser->expected_args == 0) + { + ctx_parser_dispatch_command (parser); + } + } + else + { + ctx_log ("unhandled command '%c'\n", buf[0]); + } + } + } +} + +static void ctx_parser_string_done (CtxParser *parser) +{ + if (parser->expected_args == CTX_ARG_STRING_OR_NUMBER) + { + /* + if (parser->state != CTX_PARSER_NUMBER && + parser->state != CTX_PARSER_NEGATIVE_NUMBER && + parser->state != CTX_PARSER_STRING_A85 && + parser->state != CTX_PARSER_STRING_APOS && + parser->state != CTX_PARSER_STRING_QUOT + ) + */ + { + int tmp1 = parser->command; + int tmp2 = parser->expected_args; + int tmp3 = parser->n_numbers; + int tmp4 = parser->n_args; + ctx_parser_dispatch_command (parser); + parser->command = (CtxCode)tmp1; + parser->expected_args = tmp2; + parser->n_numbers = tmp3; + parser->n_args = tmp4; + } + } + else + { + ctx_parser_dispatch_command (parser); + } +} + +static inline void ctx_parser_feed_byte (CtxParser *parser, char byte) +{ +#if CTX_REPORT_COL_ROW + if (CTX_UNLIKELY(byte == '\n')) + { + parser->col=0; + parser->line++; + } + else + { + parser->col++; + } +#endif + + if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_YENC)) + { + if (CTX_UNLIKELY((parser->prev_byte == '=') && (byte == 'y'))) + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_ydec ((char*)parser->holding, (char*)parser->holding, parser->pos) - 1; +#if 0 + if (parser->pos > 5) + fprintf (stderr, "dec got %i %c %c %c %c\n", parser->pos, + parser->holding[0], + parser->holding[1], + parser->holding[2], + parser->holding[3] + ); +#endif + ctx_parser_string_done (parser); + } + else + { + ctx_parser_holding_append (parser, byte); + } + parser->prev_byte = byte; + return; + } + else if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_A85)) + { + /* since these are our largest bulk transfers, minimize + * overhead for this case. */ + if (CTX_LIKELY(byte!='~')) + { + ctx_parser_holding_append (parser, byte); + } + else + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos); + // fprintf (stderr, "dec got %i\n", parser->pos); + ctx_parser_string_done (parser); + } + return; + } + switch (parser->state) + { + case CTX_PARSER_NEUTRAL: + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 11: case 12: case 14: + case 15: case 16: case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: case 25: case 26: + case 27: case 28: case 29: case 30: case 31: + break; + case ' ': case '\t': case '\r': case '\n': + case ';': case ',': + case '(': case ')': + case '{': case '}': + //case '=': + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '\'': + parser->state = CTX_PARSER_STRING_APOS; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '=': + parser->state = CTX_PARSER_STRING_YENC; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '~': + parser->state = CTX_PARSER_STRING_A85; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '"': + parser->state = CTX_PARSER_STRING_QUOT; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '-': + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 0; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->numbers[parser->n_numbers] += (byte - '0'); + parser->decimal = 0; + break; + case '.': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 1; + break; + default: + parser->state = CTX_PARSER_WORD; + parser->pos = 0; + ctx_parser_holding_append (parser, byte); + break; + } + break; + case CTX_PARSER_NUMBER: + case CTX_PARSER_NEGATIVE_NUMBER: + { + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: + case 11: case 12: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: + case 22: case 23: case 24: case 25: case 26: + case 27: case 28: case 29: case 30: case 31: + parser->state = CTX_PARSER_NEUTRAL; + break; + case ' ': + case '\t': + case '\r': + case '\n': + case ';': + case ',': + case '(': + case ')': + case '{': + case '}': + case '=': + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '-': + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers+1] = 0; + parser->n_numbers ++; + parser->decimal = 0; + break; + case '.': + //if (parser->decimal) // TODO permit .13.32.43 to equivalent to .12 .32 .43 + parser->decimal = 1; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (parser->decimal) + { + parser->decimal *= 10; + parser->numbers[parser->n_numbers] += (byte - '0') / (1.0 * parser->decimal); + } + else + { + parser->numbers[parser->n_numbers] *= 10; + parser->numbers[parser->n_numbers] += (byte - '0'); + } + break; + case '@': // cells + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_cell (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '%': // percent of width/height + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '^': // percent of height + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent_height (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '~': // percent of width + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent_width (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + default: + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_WORD; + parser->pos = 0; + ctx_parser_holding_append (parser, byte); + break; + } + if ( (parser->state != CTX_PARSER_NUMBER) && + (parser->state != CTX_PARSER_NEGATIVE_NUMBER)) + { + parser->n_numbers ++; + ctx_parser_number_done (parser); + + if (parser->n_numbers == parser->expected_args || + parser->expected_args == CTX_ARG_COLLECT_NUMBERS || + parser->expected_args == CTX_ARG_STRING_OR_NUMBER) + { + int tmp1 = parser->n_numbers; + int tmp2 = parser->n_args; + CtxCode tmp3 = parser->command; + int tmp4 = parser->expected_args; + ctx_parser_dispatch_command (parser); + parser->command = tmp3; + switch (parser->command) + { + case CTX_DEFINE_TEXTURE: + case CTX_TEXTURE: + parser->n_numbers = tmp1; + parser->n_args = tmp2; + break; + default: + parser->n_numbers = 0; + parser->n_args = 0; + break; + } + parser->expected_args = tmp4; + } + if (parser->n_numbers > CTX_PARSER_MAX_ARGS) + { parser->n_numbers = CTX_PARSER_MAX_ARGS; + } + } + } + break; + case CTX_PARSER_WORD: + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + case 8: case 11: case 12: case 14: case 15: case 16: case 17: + case 18: case 19: case 20: case 21: case 22: case 23: case 24: + case 25: case 26: case 27: case 28: case 29: case 30: case 31: + case ' ': case '\t': case '\r': case '\n': + case ';': case ',': + case '(': case ')': case '=': case '{': case '}': + parser->state = CTX_PARSER_NEUTRAL; + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '-': + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 0; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->numbers[parser->n_numbers] += (byte - '0'); + parser->decimal = 0; + break; + case '.': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 1; + break; + default: + ctx_parser_holding_append (parser, byte); + break; + } + if (parser->state != CTX_PARSER_WORD) + { + ctx_parser_word_done (parser); + } + break; + case CTX_PARSER_STRING_A85: + if (CTX_LIKELY(byte!='~')) + { + ctx_parser_holding_append (parser, byte); + } + else + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos); + // fprintf (stderr, "dec got %i\n", parser->pos); + ctx_parser_string_done (parser); + } + break; + case CTX_PARSER_STRING_APOS: + switch (byte) + { + case '\\': parser->state = CTX_PARSER_STRING_APOS_ESCAPED; break; + case '\'': parser->state = CTX_PARSER_NEUTRAL; + ctx_parser_string_done (parser); + break; + default: + ctx_parser_holding_append (parser, byte); break; + } + break; + case CTX_PARSER_STRING_APOS_ESCAPED: + switch (byte) + { + case '0': byte = '\0'; break; + case 'b': byte = '\b'; break; + case 'f': byte = '\f'; break; + case 'n': byte = '\n'; break; + case 'r': byte = '\r'; break; + case 't': byte = '\t'; break; + case 'v': byte = '\v'; break; + default: break; + } + ctx_parser_holding_append (parser, byte); + parser->state = CTX_PARSER_STRING_APOS; + break; + case CTX_PARSER_STRING_QUOT_ESCAPED: + switch (byte) + { + case '0': byte = '\0'; break; + case 'b': byte = '\b'; break; + case 'f': byte = '\f'; break; + case 'n': byte = '\n'; break; + case 'r': byte = '\r'; break; + case 't': byte = '\t'; break; + case 'v': byte = '\v'; break; + default: break; + } + ctx_parser_holding_append (parser, byte); + parser->state = CTX_PARSER_STRING_QUOT; + break; + case CTX_PARSER_STRING_QUOT: + switch (byte) + { + case '\\': + parser->state = CTX_PARSER_STRING_QUOT_ESCAPED; + break; + case '"': + parser->state = CTX_PARSER_NEUTRAL; + ctx_parser_string_done (parser); + break; + default: + ctx_parser_holding_append (parser, byte); + break; + } + break; + case CTX_PARSER_COMMENT: + switch (byte) + { + case '\r': + case '\n': + parser->state = CTX_PARSER_NEUTRAL; + default: + break; + } + break; + } +} + +void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count) +{ + for (int i = 0; i < count; i++) + ctx_parser_feed_byte (parser, data[i]); +} + +void ctx_parse (Ctx *ctx, const char *string) +{ + if (!string) + return; + CtxParser *parser = ctx_parser_new (ctx, ctx_width(ctx), + ctx_height(ctx), + ctx_get_font_size(ctx), + ctx_get_font_size(ctx), + 0, 0, NULL, NULL, NULL, NULL, NULL); + ctx_parser_feed_bytes (parser, string, strlen (string)); + ctx_parser_free (parser); +} + +#endif + +static CtxFont ctx_fonts[CTX_MAX_FONTS]; +static int ctx_font_count = 0; + +#if CTX_FONT_ENGINE_STB +static float +ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar); +static float +ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB); +static int +ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke); + +CtxFontEngine ctx_font_engine_stb = +{ +#if CTX_FONTS_FROM_FILE + ctx_load_font_ttf_file, +#endif + ctx_load_font_ttf, + ctx_glyph_stb, + ctx_glyph_width_stb, + ctx_glyph_kern_stb, +}; + +int +ctx_load_font_ttf (const char *name, const void *ttf_contents, int length) +{ + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + ctx_fonts[ctx_font_count].type = 1; + ctx_fonts[ctx_font_count].name = (char *) malloc (strlen (name) + 1); + ctx_strcpy ( (char *) ctx_fonts[ctx_font_count].name, name); + if (!stbtt_InitFont (&ctx_fonts[ctx_font_count].stb.ttf_info, ttf_contents, 0) ) + { + ctx_log ( "Font init failed\n"); + return -1; + } + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_stb; + ctx_font_count ++; + return ctx_font_count-1; +} + +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ttf_file (const char *name, const char *path) +{ + uint8_t *contents = NULL; + long length = 0; + ctx_get_contents (path, &contents, &length); + if (!contents) + { + ctx_log ( "File load failed\n"); + return -1; + } + return ctx_load_font_ttf (name, contents, length); +} +#endif + +static int +ctx_glyph_stb_find (CtxFont *font, uint32_t unichar) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + int index = font->stb.cache_index; + if (font->stb.cache_unichar == unichar) + { + return index; + } + font->stb.cache_unichar = 0; + index = font->stb.cache_index = stbtt_FindGlyphIndex (ttf_info, unichar); + font->stb.cache_unichar = unichar; + return index; +} + +static float +ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + float font_size = ctx->state.gstate.font_size; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size); + int advance, lsb; + int glyph = ctx_glyph_stb_find (font, unichar); + if (glyph==0) + { return 0.0f; } + stbtt_GetGlyphHMetrics (ttf_info, glyph, &advance, &lsb); + return (advance * scale); +} + +static float +ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + float font_size = ctx->state.gstate.font_size; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size); + int glyphA = ctx_glyph_stb_find (font, unicharA); + int glyphB = ctx_glyph_stb_find (font, unicharB); + return stbtt_GetGlyphKernAdvance (ttf_info, glyphA, glyphB) * scale; +} + +static int +ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + int glyph = ctx_glyph_stb_find (font, unichar); + if (glyph==0) + { return -1; } + float font_size = ctx->state.gstate.font_size; + int baseline = ctx->state.y; + float origin_x = ctx->state.x; + float origin_y = baseline; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size);; + stbtt_vertex *vertices = NULL; + ctx_begin_path (ctx); + int num_verts = stbtt_GetGlyphShape (ttf_info, glyph, &vertices); + for (int i = 0; i < num_verts; i++) + { + stbtt_vertex *vertex = &vertices[i]; + switch (vertex->type) + { + case STBTT_vmove: + ctx_move_to (ctx, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vline: + ctx_line_to (ctx, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vcubic: + ctx_curve_to (ctx, + origin_x + vertex->cx * scale, origin_y - vertex->cy * scale, + origin_x + vertex->cx1 * scale, origin_y - vertex->cy1 * scale, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vcurve: + ctx_quad_to (ctx, + origin_x + vertex->cx * scale, origin_y - vertex->cy * scale, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + } + } + stbtt_FreeShape (ttf_info, vertices); + if (stroke) + { + ctx_stroke (ctx); + } + else + { ctx_fill (ctx); } + return 0; +} +#endif + +#if CTX_FONT_ENGINE_CTX + +/* XXX: todo remove this, and rely on a binary search instead + */ +static int ctx_font_find_glyph_cached (CtxFont *font, uint32_t glyph) +{ + for (int i = 0; i < font->ctx.glyphs; i++) + { + if (font->ctx.index[i * 2] == glyph) + { return font->ctx.index[i * 2 + 1]; } + } + return -1; +} + +static int ctx_glyph_find_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + int ret = ctx_font_find_glyph_cached (font, unichar); + if (ret >= 0) return ret; + + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH && + entry->data.u32[0] == unichar) + { + return i; + // XXX this could be prone to insertion of valid header + // data in included bitmaps.. is that an issue? + // + } + } + return -1; +} + + +static float +ctx_glyph_kern_ctx (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ + float font_size = ctx->state.gstate.font_size; + int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA); + if (first_kern < 0) return 0.0; + for (int i = first_kern + 1; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_KERNING_PAIR) + { + if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB) + { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; } + } + if (entry->code == CTX_DEFINE_GLYPH) + return 0.0; + } + return 0.0; +} +#if 0 +static int ctx_glyph_find (Ctx *ctx, CtxFont *font, uint32_t unichar) +{ + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar) + { return i; } + } + return 0; +} +#endif + + +static float +ctx_glyph_width_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + CtxState *state = &ctx->state; + float font_size = state->gstate.font_size; + int start = ctx_glyph_find_ctx (font, ctx, unichar); + if (start < 0) + { return 0.0; } // XXX : fallback + for (int i = start; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + if (entry->data.u32[0] == (unsigned) unichar) + { return (entry->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE); } + } + return 0.0; +} + +static int +ctx_glyph_drawlist (CtxFont *font, Ctx *ctx, CtxDrawlist *drawlist, uint32_t unichar, int stroke) +{ + CtxState *state = &ctx->state; + CtxIterator iterator; + float origin_x = state->x; + float origin_y = state->y; + ctx_current_point (ctx, &origin_x, &origin_y); + int in_glyph = 0; + float font_size = state->gstate.font_size; + int start = 0; + if (font->type == 0) + { + start = ctx_glyph_find_ctx (font, ctx, unichar); + if (start < 0) + { return -1; } // XXX : fallback glyph + } + ctx_iterator_init (&iterator, drawlist, start, CTX_ITERATOR_EXPAND_BITPACK); + CtxCommand *command; + + /* XXX : do a binary search instead of a linear search */ + while ( (command= ctx_iterator_next (&iterator) ) ) + { + CtxEntry *entry = &command->entry; + if (in_glyph) + { + if (entry->code == CTX_DEFINE_GLYPH) + { + if (stroke) + { ctx_stroke (ctx); } + else + { +#if CTX_RASTERIZER +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow) + { + ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer); + ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1; + } + else +#endif +#endif + ctx_fill (ctx); + + } + ctx_restore (ctx); + return 0; + } + ctx_process (ctx, entry); + } + else if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar) + { + in_glyph = 1; + ctx_save (ctx); + ctx_translate (ctx, origin_x, origin_y); + ctx_move_to (ctx, 0, 0); + ctx_begin_path (ctx); + ctx_scale (ctx, font_size / CTX_BAKE_FONT_SIZE, + font_size / CTX_BAKE_FONT_SIZE); + } + } + if (stroke) + { ctx_stroke (ctx); + } + else + { + +#if CTX_RASTERIZER +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow) + { + ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer); + ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1; + } + else +#endif +#endif + { + ctx_fill (ctx); + } + } + ctx_restore (ctx); + return -1; +} + +static int +ctx_glyph_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + CtxDrawlist drawlist = { (CtxEntry *) font->ctx.data, + font->ctx.length, + font->ctx.length, 0, 0 + }; + return ctx_glyph_drawlist (font, ctx, &drawlist, unichar, stroke); +} + +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]; +} + +static void ctx_font_init_ctx (CtxFont *font) +{ + int glyph_count = 0; + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + { glyph_count ++; } + } + font->ctx.glyphs = glyph_count; +#if CTX_DRAWLIST_STATIC + static uint32_t idx[512]; // one might have to adjust this for + // larger fonts XXX + // should probably be made a #define + font->ctx.index = &idx[0]; +#else + font->ctx.index = (uint32_t *) malloc (sizeof (uint32_t) * 2 * glyph_count); +#endif + int no = 0; + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + { + font->ctx.index[no*2] = entry->data.u32[0]; + font->ctx.index[no*2+1] = i; + no++; + } + } +} + +int +ctx_load_font_ctx (const char *name, const void *data, int length); +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ctx_file (const char *name, const char *path); +#endif + +static CtxFontEngine ctx_font_engine_ctx = +{ +#if CTX_FONTS_FROM_FILE + ctx_load_font_ctx_file, +#endif + ctx_load_font_ctx, + ctx_glyph_ctx, + ctx_glyph_width_ctx, + ctx_glyph_kern_ctx, +}; + +int +ctx_load_font_ctx (const char *name, const void *data, int length) +{ + if (length % sizeof (CtxEntry) ) + { return -1; } + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + ctx_fonts[ctx_font_count].type = 0; + ctx_fonts[ctx_font_count].name = name; + ctx_fonts[ctx_font_count].ctx.data = (CtxEntry *) data; + ctx_fonts[ctx_font_count].ctx.length = length / sizeof (CtxEntry); + ctx_font_init_ctx (&ctx_fonts[ctx_font_count]); + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx; + ctx_font_count++; + return ctx_font_count-1; +} + +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ctx_file (const char *name, const char *path) +{ + uint8_t *contents = NULL; + long length = 0; + ctx_get_contents (path, &contents, &length); + if (!contents) + { + ctx_log ( "File load failed\n"); + return -1; + } + return ctx_load_font_ctx (name, contents, length); +} +#endif +#endif + +#if CTX_FONT_ENGINE_CTX_FS + +static float +ctx_glyph_kern_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ +#if 0 + float font_size = ctx->state.gstate.font_size; + int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA); + if (first_kern < 0) return 0.0; + for (int i = first_kern + 1; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_KERNING_PAIR) + { + if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB) + { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; } + } + if (entry->code == CTX_DEFINE_GLYPH) + return 0.0; + } +#endif + return 0.0; +} + +static float +ctx_glyph_width_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + CtxState *state = &ctx->state; + char path[1024]; + sprintf (path, "%s/%010p", font->ctx_fs.path, unichar); + uint8_t *data = NULL; + long int len_bytes = 0; + ctx_get_contents (path, &data, &len_bytes); + float ret = 0.0; + float font_size = state->gstate.font_size; + if (data){ + Ctx *glyph_ctx = ctx_new (); + ctx_parse (glyph_ctx, data); + for (int i = 0; i < glyph_ctx->drawlist.count; i++) + { + CtxEntry *e = &glyph_ctx->drawlist.entries[i]; + if (e->code == CTX_DEFINE_GLYPH) + ret = e->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; + } + free (data); + ctx_free (glyph_ctx); + } + return ret; +} + +static int +ctx_glyph_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + char path[1024]; + sprintf (path, "file://%s/%010p", font->ctx_fs.path, unichar); + uint8_t *data = NULL; + long int len_bytes = 0; + ctx_get_contents (path, &data, &len_bytes); + + if (data){ + Ctx *glyph_ctx = ctx_new (); + ctx_parse (glyph_ctx, data); + int ret = ctx_glyph_drawlist (font, ctx, &(glyph_ctx->drawlist), + unichar, stroke); + free (data); + ctx_free (glyph_ctx); + return ret; + } + return -1; +} + +int +ctx_load_font_ctx_fs (const char *name, const void *data, int length); + +static CtxFontEngine ctx_font_engine_ctx_fs = +{ +#if CTX_FONTS_FROM_FILE + NULL, +#endif + ctx_load_font_ctx_fs, + ctx_glyph_ctx_fs, + ctx_glyph_width_ctx_fs, + ctx_glyph_kern_ctx_fs, +}; + +int +ctx_load_font_ctx_fs (const char *name, const void *path, int length) // length is ignored +{ + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + + ctx_fonts[ctx_font_count].type = 42; + ctx_fonts[ctx_font_count].name = name; + ctx_fonts[ctx_font_count].ctx_fs.path = strdup (path); + int path_len = strlen (path); + if (ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] == '/') + ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] = 0; + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx_fs; + ctx_font_count++; + return ctx_font_count-1; +} + +#endif + +int +_ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + // a begin-path here did not remove stray spikes in terminal + return font->engine->glyph (font, ctx, unichar, stroke); +} + +int +ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke) +{ +#if CTX_BACKEND_TEXT + CtxEntry commands[3]; // 3 to silence incorrect warning from static analysis + ctx_memset (commands, 0, sizeof (commands) ); + commands[0] = ctx_u32 (CTX_GLYPH, unichar, 0); + commands[0].data.u8[4] = stroke; + ctx_process (ctx, commands); + return 0; // XXX is return value used? +#else + return _ctx_glyph (ctx, unichar, stroke); +#endif +} + +float +ctx_glyph_width (Ctx *ctx, int unichar) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + + return font->engine->glyph_width (font, ctx, unichar); +} + +static float +ctx_glyph_kern (Ctx *ctx, int unicharA, int unicharB) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + return font->engine->glyph_kern (font, ctx, unicharA, unicharB); +} + +float +ctx_text_width (Ctx *ctx, + const char *string) +{ + float sum = 0.0; + if (!string) + return 0.0f; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) ) + { + sum += ctx_glyph_width (ctx, ctx_utf8_to_unichar (utf8) ); + } + return sum; +} + +static void +_ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs, + int stroke) +{ + for (int i = 0; i < n_glyphs; i++) + { + { + uint32_t unichar = glyphs[i].index; + ctx_move_to (ctx, glyphs[i].x, glyphs[i].y); + ctx_glyph (ctx, unichar, stroke); + } + } +} + +static void +_ctx_text (Ctx *ctx, + const char *string, + int stroke, + int visible) +{ + CtxState *state = &ctx->state; + float x = ctx->state.x; + switch ( (int) ctx_state_get (state, CTX_text_align) ) + //switch (state->gstate.text_align) + { + case CTX_TEXT_ALIGN_START: + case CTX_TEXT_ALIGN_LEFT: + break; + case CTX_TEXT_ALIGN_CENTER: + x -= ctx_text_width (ctx, string) /2; + break; + case CTX_TEXT_ALIGN_END: + case CTX_TEXT_ALIGN_RIGHT: + x -= ctx_text_width (ctx, string); + break; + } + float y = ctx->state.y; + float baseline_offset = 0.0f; + switch ( (int) ctx_state_get (state, CTX_text_baseline) ) + { + case CTX_TEXT_BASELINE_HANGING: + /* XXX : crude */ + baseline_offset = ctx->state.gstate.font_size * 0.55; + break; + case CTX_TEXT_BASELINE_TOP: + /* XXX : crude */ + baseline_offset = ctx->state.gstate.font_size * 0.7; + break; + case CTX_TEXT_BASELINE_BOTTOM: + baseline_offset = -ctx->state.gstate.font_size * 0.1; + break; + case CTX_TEXT_BASELINE_ALPHABETIC: + case CTX_TEXT_BASELINE_IDEOGRAPHIC: + baseline_offset = 0.0f; + break; + case CTX_TEXT_BASELINE_MIDDLE: + baseline_offset = ctx->state.gstate.font_size * 0.25; + break; + } + float x0 = x; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) ) + { + if (*utf8 == '\n') + { + y += ctx->state.gstate.font_size * ctx_state_get (state, CTX_line_spacing); + x = x0; + if (visible) + { ctx_move_to (ctx, x, y); } + } + else + { + uint32_t unichar = ctx_utf8_to_unichar (utf8); + if (visible) + { + ctx_move_to (ctx, x, y + baseline_offset); + _ctx_glyph (ctx, unichar, stroke); + } + const char *next_utf8 = ctx_utf8_skip (utf8, 1); + if (next_utf8) + { + x += ctx_glyph_width (ctx, unichar); + x += ctx_glyph_kern (ctx, unichar, ctx_utf8_to_unichar (next_utf8) ); + } + if (visible) + { ctx_move_to (ctx, x, y); } + } + } + if (!visible) + { ctx_move_to (ctx, x, y); } +} + + +CtxGlyph * +ctx_glyph_allocate (int n_glyphs) +{ + return (CtxGlyph *) malloc (sizeof (CtxGlyph) * n_glyphs); +} +void +gtx_glyph_free (CtxGlyph *glyphs) +{ + free (glyphs); +} + +void +ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs) +{ + _ctx_glyphs (ctx, glyphs, n_glyphs, 0); +} + +void +ctx_glyphs_stroke (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs) +{ + _ctx_glyphs (ctx, glyphs, n_glyphs, 1); +} + +void +ctx_text (Ctx *ctx, + const char *string) +{ + if (!string) + return; +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_TEXT, string, 0, 0); + _ctx_text (ctx, string, 0, 0); +#else + _ctx_text (ctx, string, 0, 1); +#endif +} + + +void +ctx_fill_text (Ctx *ctx, const char *string, + float x, float y) +{ + ctx_move_to (ctx, x, y); + ctx_text (ctx, string); +} + +void +ctx_text_stroke (Ctx *ctx, + const char *string) +{ + if (!string) + return; +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_STROKE_TEXT, string, 0, 0); + _ctx_text (ctx, string, 1, 0); +#else + _ctx_text (ctx, string, 1, 1); +#endif +} + +void +ctx_stroke_text (Ctx *ctx, const char *string, + float x, float y) +{ + ctx_move_to (ctx, x, y); + ctx_text_stroke (ctx, string); +} + +static int _ctx_resolve_font (const char *name) +{ + for (int i = 0; i < ctx_font_count; i ++) + { + if (!ctx_strcmp (ctx_fonts[i].name, name) ) + { return i; } + } + for (int i = 0; i < ctx_font_count; i ++) + { + if (ctx_strstr (ctx_fonts[i].name, name) ) + { return i; } + } + return -1; +} + +int ctx_resolve_font (const char *name) +{ + int ret = _ctx_resolve_font (name); + if (ret >= 0) + { return ret; } + if (!ctx_strcmp (name, "regular") ) + { + int ret = _ctx_resolve_font ("sans"); + if (ret >= 0) { return ret; } + ret = _ctx_resolve_font ("serif"); + if (ret >= 0) { return ret; } + } + return 0; +} + +static void ctx_font_setup () +{ + static int initialized = 0; + if (initialized) { return; } + initialized = 1; +#if CTX_FONT_ENGINE_CTX + ctx_font_count = 0; // oddly - this is needed in arduino + +#if CTX_FONT_ENGINE_CTX_FS + ctx_load_font_ctx_fs ("sans-ctx", "/tmp/ctx-regular", 0); +#else +#if CTX_FONT_ascii + ctx_load_font_ctx ("sans-ctx", ctx_font_ascii, sizeof (ctx_font_ascii) ); +#endif +#if CTX_FONT_regular + ctx_load_font_ctx ("sans-ctx", ctx_font_regular, sizeof (ctx_font_regular) ); +#endif +#endif + +#if CTX_FONT_mono + ctx_load_font_ctx ("mono-ctx", ctx_font_mono, sizeof (ctx_font_mono) ); +#endif +#if CTX_FONT_bold + ctx_load_font_ctx ("bold-ctx", ctx_font_bold, sizeof (ctx_font_bold) ); +#endif +#if CTX_FONT_italic + ctx_load_font_ctx ("italic-ctx", ctx_font_italic, sizeof (ctx_font_italic) ); +#endif +#if CTX_FONT_sans + ctx_load_font_ctx ("sans-ctx", ctx_font_sans, sizeof (ctx_font_sans) ); +#endif +#if CTX_FONT_serif + ctx_load_font_ctx ("serif-ctx", ctx_font_serif, sizeof (ctx_font_serif) ); +#endif +#if CTX_FONT_symbol + ctx_load_font_ctx ("symbol-ctx", ctx_font_symbol, sizeof (ctx_font_symbol) ); +#endif +#if CTX_FONT_emoji + ctx_load_font_ctx ("emoji-ctx", ctx_font_emoji, sizeof (ctx_font_emoji) ); +#endif +#endif + +#if NOTO_EMOJI_REGULAR + ctx_load_font_ttf ("sans-NotoEmoji_Regular", ttf_NotoEmoji_Regular_ttf, ttf_NotoEmoji_Regular_ttf_len); +#endif +#if ROBOTO_LIGHT + ctx_load_font_ttf ("sans-light-Roboto_Light", ttf_Roboto_Light_ttf, ttf_Roboto_Light_ttf_len); +#endif +#if ROBOTO_REGULAR + ctx_load_font_ttf ("sans-Roboto_Regular", ttf_Roboto_Regular_ttf, ttf_Roboto_Regular_ttf_len); +#endif +#if ROBOTO_BOLD + ctx_load_font_ttf ("sans-bold-Roboto_Bold", ttf_Roboto_Bold_ttf, ttf_Roboto_Bold_ttf_len); +#endif +#if DEJAVU_SANS + ctx_load_font_ttf ("sans-DejaVuSans", ttf_DejaVuSans_ttf, ttf_DejaVuSans_ttf_len); +#endif +#if VERA + ctx_load_font_ttf ("sans-Vera", ttf_Vera_ttf, ttf_Vera_ttf_len); +#endif +#if UNSCII_16 + ctx_load_font_ttf ("mono-unscii16", ttf_unscii_16_ttf, ttf_unscii_16_ttf_len); +#endif +#if XA000_MONO + ctx_load_font_ttf ("mono-0xA000", ttf_0xA000_Mono_ttf, ttf_0xA000_Mono_ttf_len); +#endif +#if DEJAVU_SANS_MONO + ctx_load_font_ttf ("mono-DejaVuSansMono", ttf_DejaVuSansMono_ttf, ttf_DejaVuSansMono_ttf_len); +#endif +#if NOTO_MONO_REGULAR + ctx_load_font_ttf ("mono-NotoMono_Regular", ttf_NotoMono_Regular_ttf, ttf_NotoMono_Regular_ttf_len); +#endif +} + + + +#if !__COSMOPOLITAN__ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#endif + +//#include "ctx.h" +/* instead of including ctx.h we declare the few utf8 + * functions we use + */ +uint32_t ctx_utf8_to_unichar (const char *input); +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +int ctx_utf8_strlen (const char *s); + + +void ctx_string_init (CtxString *string, int initial_size) +{ + string->allocated_length = initial_size; + string->length = 0; + string->utf8_length = 0; + string->str = (char*)malloc (string->allocated_length + 1); + string->str[0]='\0'; +} + +static void ctx_string_destroy (CtxString *string) +{ + if (string->str) + { + free (string->str); + string->str = NULL; + } +} + +void ctx_string_clear (CtxString *string) +{ + string->length = 0; + string->utf8_length = 0; + string->str[string->length]=0; +} + +static inline void _ctx_string_append_byte (CtxString *string, char val) +{ + if (CTX_LIKELY((val & 0xC0) != 0x80)) + { string->utf8_length++; } + if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length)) + { + char *old = string->str; + string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2); + string->str = (char*)realloc (old, string->allocated_length); + } + string->str[string->length++] = val; + string->str[string->length] = '\0'; +} + +void ctx_string_append_unichar (CtxString *string, unsigned int unichar) +{ + char *str; + char utf8[5]; + utf8[ctx_unichar_to_utf8 (unichar, (unsigned char *) utf8)]=0; + str = utf8; + while (str && *str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +static inline void _ctx_string_append_str (CtxString *string, const char *str) +{ + if (!str) { return; } + while (*str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +void ctx_string_append_utf8char (CtxString *string, const char *str) +{ + if (!str) { return; } + int len = ctx_utf8_len (*str); + for (int i = 0; i < len && *str; i++) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +void ctx_string_append_str (CtxString *string, const char *str) +{ + _ctx_string_append_str (string, str); +} + +CtxString *ctx_string_new_with_size (const char *initial, int initial_size) +{ + CtxString *string = (CtxString*)ctx_calloc (sizeof (CtxString), 1); + ctx_string_init (string, initial_size); + if (initial) + { _ctx_string_append_str (string, initial); } + return string; +} + +CtxString *ctx_string_new (const char *initial) +{ + return ctx_string_new_with_size (initial, 8); +} + +void ctx_string_append_data (CtxString *string, const char *str, int len) +{ + int i; + for (i = 0; i<len; i++) + { _ctx_string_append_byte (string, str[i]); } +} + +void ctx_string_append_string (CtxString *string, CtxString *string2) +{ + const char *str = ctx_string_get (string2); + while (str && *str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +const char *ctx_string_get (CtxString *string) +{ + return string->str; +} + +int ctx_string_get_utf8length (CtxString *string) +{ + return string->utf8_length; +} + +int ctx_string_get_length (CtxString *string) +{ + return string->length; +} + +void +ctx_string_free (CtxString *string, int freealloc) +{ + if (freealloc) + { + ctx_string_destroy (string); + } +#if 0 + if (string->is_line) + { + VtLine *line = (VtLine*)string; + if (line->style) + { free (line->style); } + if (line->ctx) + { ctx_free (line->ctx); } + if (line->ctx_copy) + { ctx_free (line->ctx_copy); } + } +#endif + free (string); +} + +void +ctx_string_set (CtxString *string, const char *new_string) +{ + ctx_string_clear (string); + _ctx_string_append_str (string, new_string); +} + +static char *ctx_strdup (const char *str) +{ + int len = strlen (str); + char *ret = (char*)malloc (len + 1); + memcpy (ret, str, len); + ret[len]=0; + return ret; +} + +void ctx_string_replace_utf8 (CtxString *string, int pos, const char *new_glyph) +{ +#if 1 + int old_len = string->utf8_length; +#else + int old_len = ctx_utf8_strlen (string->str);// string->utf8_length; +#endif + if (CTX_LIKELY(pos == old_len)) + { + _ctx_string_append_str (string, new_glyph); + return; + } + + char tmpg[3]=" "; + int new_len = ctx_utf8_len (*new_glyph); + if (new_len <= 1 && new_glyph[0] < 32) + { + new_len = 1; + tmpg[0]=new_glyph[0]+64; + new_glyph = tmpg; + } + { + for (int i = old_len; i <= pos + 2; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + if (string->length + new_len >= string->allocated_length - 2) + { + char *tmp; + char *defer; + string->allocated_length = string->length + new_len + 2; + tmp = (char*) ctx_calloc (string->allocated_length + 1 + 8, 1); + strcpy (tmp, string->str); + defer = string->str; + string->str = tmp; + free (defer); + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if (*p == 0 || * (p+prev_len) == 0) + { + rest = ctx_strdup (""); + } + else + { + if (p + prev_len >= string->length + string->str) + { rest = ctx_strdup (""); } + else + { rest = ctx_strdup (p + prev_len); } + } + memcpy (p, new_glyph, new_len); + memcpy (p + new_len, rest, strlen (rest) + 1); + string->length += new_len; + string->length -= prev_len; + free (rest); + //string->length = strlen (string->str); + //string->utf8_length = ctx_utf8_strlen (string->str); +} + +void ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar) +{ + uint8_t utf8[8]; + ctx_unichar_to_utf8 (unichar, utf8); + ctx_string_replace_utf8 (string, pos, (char *) utf8); +} + +uint32_t ctx_string_get_unichar (CtxString *string, int pos) +{ + char *p = (char *) ctx_utf8_skip (string->str, pos); + if (!p) + { return 0; } + return ctx_utf8_to_unichar (p); +} + + +void ctx_string_insert_utf8 (CtxString *string, int pos, const char *new_glyph) +{ + int new_len = ctx_utf8_len (*new_glyph); + int old_len = string->utf8_length; + char tmpg[3]=" "; + if (old_len == pos && 0) + { + ctx_string_append_str (string, new_glyph); + return; + } + if (new_len <= 1 && new_glyph[0] < 32) + { + tmpg[0]=new_glyph[0]+64; + new_glyph = tmpg; + } + { + for (int i = old_len; i <= pos; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + if (string->length + new_len + 1 > string->allocated_length) + { + char *tmp; + char *defer; + string->allocated_length = string->length + new_len + 1; + tmp = (char*) ctx_calloc (string->allocated_length + 1, 1); + strcpy (tmp, string->str); + defer = string->str; + string->str = tmp; + free (defer); + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if ( (*p == 0 || * (p+prev_len) == 0) && pos != 0) + { + rest = ctx_strdup (""); + } + else + { + rest = ctx_strdup (p); + } + memcpy (p, new_glyph, new_len); + memcpy (p + new_len, rest, strlen (rest) + 1); + free (rest); + string->length = strlen (string->str); + string->utf8_length = ctx_utf8_strlen (string->str); +} + +void ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar) +{ + uint8_t utf8[5]=""; + utf8[ctx_unichar_to_utf8(unichar, utf8)]=0; + ctx_string_insert_utf8 (string, pos, (char*)utf8); +} + +void ctx_string_remove (CtxString *string, int pos) +{ + int old_len = string->utf8_length; + { + for (int i = old_len; i <= pos; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if (!p || *p == 0) + { + return; + rest = ctx_strdup (""); + prev_len = 0; + } + else if (* (p+prev_len) == 0) + { + rest = ctx_strdup (""); + } + else + { + rest = ctx_strdup (p + prev_len); + } + strcpy (p, rest); + string->str[string->length - prev_len] = 0; + free (rest); + string->length = strlen (string->str); + string->utf8_length = ctx_utf8_strlen (string->str); +} + +char *ctx_strdup_printf (const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*)malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + return buffer; +} + +void ctx_string_append_printf (CtxString *string, const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*)malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + ctx_string_append_str (string, buffer); + free (buffer); +} + +#if CTX_CAIRO + +typedef struct _CtxCairo CtxCairo; +struct + _CtxCairo +{ + CtxImplementation vfuncs; + Ctx *ctx; + cairo_t *cr; + cairo_pattern_t *pat; + cairo_surface_t *image; + int preserve; +}; + +static void +ctx_cairo_process (CtxCairo *ctx_cairo, CtxCommand *c) +{ + CtxEntry *entry = (CtxEntry *) &c->entry; + cairo_t *cr = ctx_cairo->cr; + switch (entry->code) + { + case CTX_LINE_TO: + cairo_line_to (cr, c->line_to.x, c->line_to.y); + break; + case CTX_REL_LINE_TO: + cairo_rel_line_to (cr, c->rel_line_to.x, c->rel_line_to.y); + break; + case CTX_MOVE_TO: + cairo_move_to (cr, c->move_to.x, c->move_to.y); + break; + case CTX_REL_MOVE_TO: + cairo_rel_move_to (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_CURVE_TO: + cairo_curve_to (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + break; + case CTX_REL_CURVE_TO: + cairo_rel_curve_to (cr,ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + break; + case CTX_PRESERVE: + ctx_cairo->preserve = 1; + break; + case CTX_QUAD_TO: + { + double x0, y0; + cairo_get_current_point (cr, &x0, &y0); + float cx = ctx_arg_float (0); + float cy = ctx_arg_float (1); + float x = ctx_arg_float (2); + float y = ctx_arg_float (3); + cairo_curve_to (cr, + (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); + } + break; + case CTX_REL_QUAD_TO: + { + double x0, y0; + cairo_get_current_point (cr, &x0, &y0); + float cx = ctx_arg_float (0) + x0; + float cy = ctx_arg_float (1) + y0; + float x = ctx_arg_float (2) + x0; + float y = ctx_arg_float (3) + y0; + cairo_curve_to (cr, + (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); + } + break; + /* rotate/scale/translate does not occur in fully minified data stream */ + case CTX_ROTATE: + cairo_rotate (cr, ctx_arg_float (0) ); + break; + case CTX_SCALE: + cairo_scale (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_TRANSLATE: + cairo_translate (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_LINE_WIDTH: + cairo_set_line_width (cr, ctx_arg_float (0) ); + break; + case CTX_ARC: +#if 0 + fprintf (stderr, "F %2.1f %2.1f %2.1f %2.1f %2.1f %2.1f\n", + ctx_arg_float(0), + ctx_arg_float(1), + ctx_arg_float(2), + ctx_arg_float(3), + ctx_arg_float(4), + ctx_arg_float(5), + ctx_arg_float(6)); +#endif + if (ctx_arg_float (5) == 1) + cairo_arc (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4) ); + else + cairo_arc_negative (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4) ); + break; + case CTX_SET_RGBA_U8: + cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ), + ctx_u8_to_float (ctx_arg_u8 (1) ), + ctx_u8_to_float (ctx_arg_u8 (2) ), + ctx_u8_to_float (ctx_arg_u8 (3) ) ); + break; +#if 0 + case CTX_SET_RGBA_STROKE: // XXX : we need to maintain + // state for the two kinds + cairo_set_source_rgba (cr, ctx_arg_u8 (0) /255.0, + ctx_arg_u8 (1) /255.0, + ctx_arg_u8 (2) /255.0, + ctx_arg_u8 (3) /255.0); + break; +#endif + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: // XXX - arcs + cairo_rectangle (cr, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_SET_PIXEL: + cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ), + ctx_u8_to_float (ctx_arg_u8 (1) ), + ctx_u8_to_float (ctx_arg_u8 (2) ), + ctx_u8_to_float (ctx_arg_u8 (3) ) ); + cairo_rectangle (cr, ctx_arg_u16 (2), ctx_arg_u16 (3), 1, 1); + cairo_fill (cr); + break; + case CTX_FILL: + if (ctx_cairo->preserve) + { + cairo_fill_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_fill (cr); + } + break; + case CTX_STROKE: + if (ctx_cairo->preserve) + { + cairo_stroke_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_stroke (cr); + } + break; + case CTX_IDENTITY: + cairo_identity_matrix (cr); + break; + case CTX_CLIP: + if (ctx_cairo->preserve) + { + cairo_clip_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_clip (cr); + } + break; + break; + case CTX_BEGIN_PATH: + cairo_new_path (cr); + break; + case CTX_CLOSE_PATH: + cairo_close_path (cr); + break; + case CTX_SAVE: + cairo_save (cr); + break; + case CTX_RESTORE: + cairo_restore (cr); + break; + case CTX_FONT_SIZE: + cairo_set_font_size (cr, ctx_arg_float (0) ); + break; + case CTX_MITER_LIMIT: + cairo_set_miter_limit (cr, ctx_arg_float (0) ); + break; + case CTX_LINE_CAP: + { + int cairo_val = CAIRO_LINE_CAP_SQUARE; + switch (ctx_arg_u8 (0) ) + { + case CTX_CAP_ROUND: + cairo_val = CAIRO_LINE_CAP_ROUND; + break; + case CTX_CAP_SQUARE: + cairo_val = CAIRO_LINE_CAP_SQUARE; + break; + case CTX_CAP_NONE: + cairo_val = CAIRO_LINE_CAP_BUTT; + break; + } + cairo_set_line_cap (cr, cairo_val); + } + break; + case CTX_BLEND_MODE: + { + // does not map to cairo + } + break; + case CTX_COMPOSITING_MODE: + { + int cairo_val = CAIRO_OPERATOR_OVER; + switch (ctx_arg_u8 (0) ) + { + case CTX_COMPOSITE_SOURCE_OVER: + cairo_val = CAIRO_OPERATOR_OVER; + break; + case CTX_COMPOSITE_COPY: + cairo_val = CAIRO_OPERATOR_SOURCE; + break; + } + cairo_set_operator (cr, cairo_val); + } + case CTX_LINE_JOIN: + { + int cairo_val = CAIRO_LINE_JOIN_ROUND; + switch (ctx_arg_u8 (0) ) + { + case CTX_JOIN_ROUND: + cairo_val = CAIRO_LINE_JOIN_ROUND; + break; + case CTX_JOIN_BEVEL: + cairo_val = CAIRO_LINE_JOIN_BEVEL; + break; + case CTX_JOIN_MITER: + cairo_val = CAIRO_LINE_JOIN_MITER; + break; + } + cairo_set_line_join (cr, cairo_val); + } + break; + case CTX_LINEAR_GRADIENT: + { + if (ctx_cairo->pat) + { + cairo_pattern_destroy (ctx_cairo->pat); + ctx_cairo->pat = NULL; + } + ctx_cairo->pat = cairo_pattern_create_linear (ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3) ); + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 0, 0, 0, 0, 1); + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 1, 1, 1, 1, 1); + cairo_set_source (cr, ctx_cairo->pat); + } + break; + case CTX_RADIAL_GRADIENT: + { + if (ctx_cairo->pat) + { + cairo_pattern_destroy (ctx_cairo->pat); + ctx_cairo->pat = NULL; + } + ctx_cairo->pat = cairo_pattern_create_radial (ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + cairo_set_source (cr, ctx_cairo->pat); + } + break; + case CTX_GRADIENT_STOP: + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, + ctx_arg_float (0), + ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (5) ), + ctx_u8_to_float (ctx_arg_u8 (6) ), + ctx_u8_to_float (ctx_arg_u8 (7) ) ); + break; + // XXX implement TEXTURE +#if 0 + case CTX_LOAD_IMAGE: + { + if (image) + { + cairo_surface_destroy (image); + image = NULL; + } + if (pat) + { + cairo_pattern_destroy (pat); + pat = NULL; + } + image = cairo_image_surface_create_from_png (ctx_arg_string() ); + cairo_set_source_surface (cr, image, ctx_arg_float (0), ctx_arg_float (1) ); + } + break; +#endif + case CTX_TEXT: + /* XXX: implement some linebreaking/wrap, positioning + * behavior here + */ + cairo_show_text (cr, ctx_arg_string () ); + break; + case CTX_CONT: + case CTX_EDGE: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_FLUSH: + break; + } + ctx_process (ctx_cairo->ctx, entry); +} + +void ctx_cairo_free (CtxCairo *ctx_cairo) +{ + if (ctx_cairo->pat) + { cairo_pattern_destroy (ctx_cairo->pat); } + if (ctx_cairo->image) + { cairo_surface_destroy (ctx_cairo->image); } + free (ctx_cairo); +} + +void +ctx_render_cairo (Ctx *ctx, cairo_t *cr) +{ + CtxIterator iterator; + CtxCommand *command; + CtxCairo ctx_cairo = {{(void*)ctx_cairo_process, NULL, NULL}, ctx, cr, NULL, NULL}; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_cairo_process (&ctx_cairo, command); } +} + +Ctx * +ctx_new_for_cairo (cairo_t *cr) +{ + Ctx *ctx = ctx_new (); + CtxCairo *ctx_cairo = calloc(sizeof(CtxCairo),1); + ctx_cairo->vfuncs.free = (void*)ctx_cairo_free; + ctx_cairo->vfuncs.process = (void*)ctx_cairo_process; + ctx_cairo->ctx = ctx; + ctx_cairo->cr = cr; + + ctx_set_renderer (ctx, (void*)ctx_cairo); + return ctx; +} + +#endif + +#if CTX_EVENTS + +static int ctx_find_largest_matching_substring + (const char *X, const char *Y, int m, int n, int *offsetY, int *offsetX) +{ + int longest_common_suffix[2][n+1]; + int best_length = 0; + for (int i=0; i<=m; i++) + { + for (int j=0; j<=n; j++) + { + if (i == 0 || j == 0 || !(X[i-1] == Y[j-1])) + { + longest_common_suffix[i%2][j] = 0; + } + else + { + longest_common_suffix[i%2][j] = longest_common_suffix[(i-1)%2][j-1] + 1; + if (best_length < longest_common_suffix[i%2][j]) + { + best_length = longest_common_suffix[i%2][j]; + if (offsetY) *offsetY = j - best_length; + if (offsetX) *offsetX = i - best_length; + } + } + } + } + return best_length; +} + +typedef struct CtxSpan { + int from_prev; + int start; + int length; +} CtxSpan; + +#define CHUNK_SIZE 32 +#define MIN_MATCH 7 // minimum match length to be encoded +#define WINDOW_PADDING 16 // look-aside amount + +#if 0 +static void _dassert(int line, int condition, const char *str, int foo, int bar, int baz) +{ + if (!condition) + { + FILE *f = fopen ("/tmp/cdebug", "a"); + fprintf (f, "%i: %s %i %i %i\n", line, str, foo, bar, baz); + fclose (f); + } +} +#define dassert(cond, foo, bar, baz) _dassert(__LINE__, cond, #cond, foo, bar ,baz) +#endif +#define dassert(cond, foo, bar, baz) + +/* XXX repeated substring matching is slow, we'll be + * better off with a hash-table with linked lists of + * matching 3-4 characters in previous.. or even + * a naive approach that expects rough alignment.. + */ +static char *encode_in_terms_of_previous ( + const char *src, int src_len, + const char *prev, int prev_len, + int *out_len, + int max_ticks) +{ + CtxString *string = ctx_string_new (""); + CtxList *encoded_list = NULL; + + /* TODO : make expected position offset in prev slide based on + * matches and not be constant */ + + long ticks_start = ctx_ticks (); + int start = 0; + int length = CHUNK_SIZE; + for (start = 0; start < src_len; start += length) + { + CtxSpan *span = calloc (sizeof (CtxSpan), 1); + span->start = start; + if (start + length > src_len) + span->length = src_len - start; + else + span->length = length; + span->from_prev = 0; + ctx_list_append (&encoded_list, span); + } + + for (CtxList *l = encoded_list; l; l = l->next) + { + CtxSpan *span = l->data; + if (!span->from_prev) + { + if (span->length >= MIN_MATCH) + { + int prev_pos = 0; + int curr_pos = 0; + assert(1); +#if 0 + int prev_start = 0; + int prev_window_length = prev_len; +#else + int window_padding = WINDOW_PADDING; + int prev_start = span->start - window_padding; + if (prev_start < 0) + prev_start = 0; + + dassert(span->start>=0 , 0,0,0); + + int prev_window_length = prev_len - prev_start; + if (prev_window_length > span->length + window_padding * 2 + span->start) + prev_window_length = span->length + window_padding * 2 + span->start; +#endif + int match_len = 0; + if (prev_window_length > 0) + match_len = ctx_find_largest_matching_substring(prev + prev_start, src + span->start, prev_window_length, span->length, &curr_pos, &prev_pos); +#if 1 + prev_pos += prev_start; +#endif + + if (match_len >= MIN_MATCH) + { + int start = span->start; + int length = span->length; + + span->from_prev = 1; + span->start = prev_pos; + span->length = match_len; + dassert (span->start >= 0, prev_pos, prev_start, span->start); + dassert (span->length > 0, prev_pos, prev_start, span->length); + + if (curr_pos) + { + CtxSpan *prev = calloc (sizeof (CtxSpan), 1); + prev->start = start; + prev->length = curr_pos; + dassert (prev->start >= 0, prev_pos, prev_start, prev->start); + dassert (prev->length > 0, prev_pos, prev_start, prev->length); + prev->from_prev = 0; + ctx_list_insert_before (&encoded_list, l, prev); + } + + + if (match_len + curr_pos < start + length) + { + CtxSpan *next = calloc (sizeof (CtxSpan), 1); + next->start = start + curr_pos + match_len; + next->length = (start + length) - next->start; + dassert (next->start >= 0, prev_pos, prev_start, next->start); + // dassert (next->length > 0, prev_pos, prev_start, next->length); + next->from_prev = 0; + if (next->length) + { + if (l->next) + ctx_list_insert_before (&encoded_list, l->next, next); + else + ctx_list_append (&encoded_list, next); + } + else + free (next); + } + + if (curr_pos) // step one item back for forloop + { + CtxList *tmp = encoded_list; + int found = 0; + while (!found && tmp && tmp->next) + { + if (tmp->next == l) + { + l = tmp; + break; + } + tmp = tmp->next; + } + } + } + } + } + + if (ctx_ticks ()-ticks_start > (unsigned long)max_ticks) + break; + } + + /* merge adjecant prev span references */ + { + for (CtxList *l = encoded_list; l; l = l->next) + { + CtxSpan *span = l->data; +again: + if (l->next) + { + CtxSpan *next_span = l->next->data; + if (span->from_prev && next_span->from_prev && + span->start + span->length == + next_span->start) + { + span->length += next_span->length; + ctx_list_remove (&encoded_list, next_span); + goto again; + } + } + } + } + + while (encoded_list) + { + CtxSpan *span = encoded_list->data; + if (span->from_prev) + { + char ref[128]; + sprintf (ref, "%c%i %i%c", CTX_CODEC_CHAR, span->start, span->length, CTX_CODEC_CHAR); + ctx_string_append_data (string, ref, strlen(ref)); + } + else + { + for (int i = span->start; i< span->start+span->length; i++) + { + if (src[i] == CTX_CODEC_CHAR) + { + char bytes[2]={CTX_CODEC_CHAR, CTX_CODEC_CHAR}; + ctx_string_append_data (string, bytes, 2); + } + else + { + ctx_string_append_data (string, &src[i], 1); + } + } + } + free (span); + ctx_list_remove (&encoded_list, span); + } + + char *ret = string->str; + if (out_len) *out_len = string->length; + ctx_string_free (string, 0); + return ret; +} + +#if 0 // for documentation/reference purposes +static char *decode_ctx (const char *encoded, int enc_len, const char *prev, int prev_len, int *out_len) +{ + CtxString *string = ctx_string_new (""); + char reference[32]=""; + int ref_len = 0; + int in_ref = 0; + for (int i = 0; i < enc_len; i++) + { + if (encoded[i] == CTX_CODEC_CHAR) + { + if (!in_ref) + { + in_ref = 1; + } + else + { + int start = atoi (reference); + int len = 0; + if (strchr (reference, ' ')) + len = atoi (strchr (reference, ' ')+1); + + if (start < 0)start = 0; + if (start >= prev_len)start = prev_len-1; + if (len + start > prev_len) + len = prev_len - start; + + if (start == 0 && len == 0) + ctx_string_append_byte (string, CTX_CODEC_CHAR); + else + ctx_string_append_data (string, prev + start, len); + ref_len = 0; + in_ref = 0; + } + } + else + { + if (in_ref) + { + if (ref_len < 16) + { + reference[ref_len++] = encoded[i]; + reference[ref_len] = 0; + } + } + else + ctx_string_append_data (string, &encoded[i], 1); + } + } + char *ret = string->str; + if (out_len) *out_len = string->length; + ctx_string_free (string, 0); + return ret; +} +#endif + +#define CTX_START_STRING "U\n" // or " reset " +#define CTX_END_STRING "\nX" // or "\ndone" +#define CTX_END_STRING2 "\n\e" + +int ctx_frame_ack = -1; +static char *prev_frame_contents = NULL; +static int prev_frame_len = 0; + +static void ctx_ctx_flush (CtxCtx *ctxctx) +{ +#if 0 + FILE *debug = fopen ("/tmp/ctx-debug", "a"); + fprintf (debug, "------\n"); +#endif + + if (ctx_native_events) + fprintf (stdout, "\e[?201h"); + fprintf (stdout, "\e[H\e[?25l\e[?200h"); +#if 1 + fprintf (stdout, CTX_START_STRING); + ctx_render_stream (ctxctx->ctx, stdout, 0); + fprintf (stdout, CTX_END_STRING); +#else + { + int cur_frame_len = 0; + char *rest = ctx_render_string (ctxctx->ctx, 0, &cur_frame_len); + char *cur_frame_contents = malloc (cur_frame_len + strlen(CTX_START_STRING) + strlen (CTX_END_STRING) + 1); + + cur_frame_contents[0]=0; + strcat (cur_frame_contents, CTX_START_STRING); + strcat (cur_frame_contents, rest); + strcat (cur_frame_contents, CTX_END_STRING); + free (rest); + cur_frame_len += strlen (CTX_START_STRING) + strlen (CTX_END_STRING); + + if (prev_frame_contents && 0) // XXX : + { + char *encoded; + int encoded_len = 0; + //uint64_t ticks_start = ctx_ticks (); + + encoded = encode_in_terms_of_previous (cur_frame_contents, cur_frame_len, prev_frame_contents, prev_frame_len, &encoded_len, 1000 * 10); +// encoded = strdup (cur_frame_contents); +// encoded_len = strlen (encoded); + //uint64_t ticks_end = ctx_ticks (); + + fwrite (encoded, encoded_len, 1, stdout); +// fwrite (encoded, cur_frame_len, 1, stdout); +#if 0 + fprintf (debug, "---prev-frame(%i)\n%s", (int)strlen(prev_frame_contents), prev_frame_contents); + fprintf (debug, "---cur-frame(%i)\n%s", (int)strlen(cur_frame_contents), cur_frame_contents); + fprintf (debug, "---encoded(%.4f %i)---\n%s--------\n", + (ticks_end-ticks_start)/1000.0, + (int)strlen(encoded), encoded); +#endif + free (encoded); + } + else + { + fwrite (cur_frame_contents, cur_frame_len, 1, stdout); + } + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = cur_frame_contents; + prev_frame_len = cur_frame_len; + } +#endif +#if 0 + fclose (debug); +#endif + fprintf (stdout, CTX_END_STRING2); + + fprintf (stdout, "\e[5n"); + fflush (stdout); + + ctx_frame_ack = 0; + do { + ctx_consume_events (ctxctx->ctx); + } while (ctx_frame_ack != 1); +} + +void ctx_ctx_free (CtxCtx *ctx) +{ + nc_at_exit (); + free (ctx); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +Ctx *ctx_new_ctx (int width, int height) +{ + float font_size = 12.0; + Ctx *ctx = ctx_new (); + CtxCtx *ctxctx = (CtxCtx*)calloc (sizeof (CtxCtx), 1); + fprintf (stdout, "\e[?1049h"); + fflush (stdout); + //fprintf (stderr, "\e[H"); + //fprintf (stderr, "\e[2J"); + ctx_native_events = 1; + if (width <= 0 || height <= 0) + { + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + width = ctxctx->width = ctx_terminal_width (); + height = ctxctx->height = ctx_terminal_height (); + font_size = height / ctxctx->rows; + ctx_font_size (ctx, font_size); + } + else + { + ctxctx->width = width; + ctxctx->height = height; + ctxctx->cols = width / 80; + ctxctx->rows = height / 24; + } + ctxctx->ctx = ctx; + if (!ctx_native_events) + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, ctxctx); + ctx_set_size (ctx, width, height); + ctxctx->flush = (void(*)(void *))ctx_ctx_flush; + ctxctx->free = (void(*)(void *))ctx_ctx_free; + return ctx; +} + +void ctx_ctx_pcm (Ctx *ctx); + +int ctx_ctx_consume_events (Ctx *ctx) +{ + int ix, iy; + CtxCtx *ctxctx = (CtxCtx*)ctx->renderer; + const char *event = NULL; +#if CTX_AUDIO + ctx_ctx_pcm (ctx); +#endif + if (ctx_native_events) + { + float x = 0, y = 0; + int b = 0; + char event_type[128]=""; + event = ctx_native_get_event (ctx, 1000/120); +#if 0 + if(event){ + FILE *file = fopen ("/tmp/log", "a"); + fprintf (file, "[%s]\n", event); + fclose (file); + } +#endif + if (event) + { + sscanf (event, "%s %f %f %i", event_type, &x, &y, &b); + if (!strcmp (event_type, "idle")) + { + } + else if (!strcmp (event_type, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "mouse-drag")|| + !strcmp (event_type, "mouse-motion")) + { + ctx_pointer_motion (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "message")) + { + ctx_incoming_message (ctx, event + strlen ("message"), 0); + } else if (!strcmp (event, "size-changed")) + { + fprintf (stdout, "\e[H\e[2J\e[?25l"); + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + ctxctx->width = ctx_terminal_width (); + ctxctx->height = ctx_terminal_height (); + ctx_set_size (ctx, ctxctx->width, ctxctx->height); + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = NULL; + prev_frame_len = 0; + ctx_set_dirty (ctx, 1); + //ctx_key_press (ctx, 0, "size-changed", 0); + } + else if (!strcmp (event_type, "keyup")) + { + char buf[4]={ x, 0 }; + ctx_key_up (ctx, (int)x, buf, 0); + } + else if (!strcmp (event_type, "keydown")) + { + char buf[4]={ x, 0 }; + ctx_key_down (ctx, (int)x, buf, 0); + } + else + { + ctx_key_press (ctx, 0, event, 0); + } + } + } + else + { + float x, y; + event = ctx_nct_get_event (ctx, 20, &ix, &iy); + + x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width; + y = (iy - 1.0) / ctxctx->rows * ctx->events.height; + + if (!strcmp (event, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, 0, 0); + ctxctx->was_down = 1; + } else if (!strcmp (event, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-motion")) + { + //nct_set_cursor_pos (backend->term, ix, iy); + //nct_flush (backend->term); + if (ctxctx->was_down) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-drag")) + { + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "size-changed")) + { + fprintf (stdout, "\e[H\e[2J\e[?25l"); + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + ctxctx->width = ctx_terminal_width (); + ctxctx->height = ctx_terminal_height (); + ctx_set_size (ctx, ctxctx->width, ctxctx->height); + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = NULL; + prev_frame_len = 0; + ctx_set_dirty (ctx, 1); + //ctx_key_press (ctx, 0, "size-changed", 0); + } + else + { + if (!strcmp (event, "esc")) + ctx_key_press (ctx, 0, "escape", 0); + else if (!strcmp (event, "space")) + ctx_key_press (ctx, 0, "space", 0); + else if (!strcmp (event, "enter")|| + !strcmp (event, "return")) + ctx_key_press (ctx, 0, "\n", 0); + else + ctx_key_press (ctx, 0, event, 0); + } + } + + return 1; +} + +int ctx_renderer_is_ctx (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_ctx_free) + return 1; + return 0; +} + +#endif + +#if CTX_TILED +static inline int +ctx_tiled_threads_done (CtxTiled *tiled) +{ + int sum = 0; + for (int i = 0; i < _ctx_max_threads; i++) + { + if (tiled->rendered_frame[i] == tiled->render_frame) + sum ++; + } + return sum; +} + +int _ctx_damage_control = 0; + +void ctx_tiled_free (CtxTiled *tiled) +{ + tiled->quit = 1; + mtx_lock (&tiled->mtx); + cnd_broadcast (&tiled->cond); + mtx_unlock (&tiled->mtx); + + while (tiled->thread_quit < _ctx_max_threads) + usleep (1000); + + if (tiled->pixels) + { + free (tiled->pixels); + tiled->pixels = NULL; + for (int i = 0 ; i < _ctx_max_threads; i++) + { + ctx_free (tiled->host[i]); + tiled->host[i]=NULL; + } + + ctx_free (tiled->ctx_copy); + } + // leak? +} +static unsigned char *sdl_icc = NULL; +static long sdl_icc_length = 0; + +inline static void ctx_tiled_flush (CtxTiled *tiled) +{ + if (tiled->shown_frame == tiled->render_frame) + { + int dirty_tiles = 0; + ctx_set_drawlist (tiled->ctx_copy, &tiled->ctx->drawlist.entries[0], + tiled->ctx->drawlist.count * 9); + if (_ctx_enable_hash_cache) + { + Ctx *hasher = ctx_hasher_new (tiled->width, tiled->height, + CTX_HASH_COLS, CTX_HASH_ROWS); + ctx_render_ctx (tiled->ctx_copy, hasher); + + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + uint8_t *new_hash = ctx_hasher_get_hash (hasher, col, row); + if (new_hash && memcmp (new_hash, &tiled->hashes[(row * CTX_HASH_COLS + col) * 20], 20)) + { + memcpy (&tiled->hashes[(row * CTX_HASH_COLS + col)*20], new_hash, 20); + tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1; + dirty_tiles++; + } + else + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = -1; + } + } + free (((CtxHasher*)(hasher->renderer))->hashes); + ctx_free (hasher); + } + else + { + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1; + dirty_tiles++; + } + } + int dirty_no = 0; + if (dirty_tiles) + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + if (tiled->tile_affinity[row * CTX_HASH_COLS + col] != -1) + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = dirty_no * (_ctx_max_threads) / dirty_tiles; + dirty_no++; + if (col > tiled->max_col) tiled->max_col = col; + if (col < tiled->min_col) tiled->min_col = col; + if (row > tiled->max_row) tiled->max_row = row; + if (row < tiled->min_row) tiled->min_row = row; + } + } + + if (_ctx_damage_control) + { + for (int i = 0; i < tiled->width * tiled->height; i++) + { + tiled->pixels[i*4+2] = (tiled->pixels[i*4+2] + 255)/2; + } + } + + tiled->render_frame = ++tiled->frame; + +#if 0 + + //if (tiled->tile_affinity[hno]==no) + { + int x0 = ((tiled->width)/CTX_HASH_COLS) * 0; + int y0 = ((tiled->height)/CTX_HASH_ROWS) * 0; + int width = tiled->width / CTX_HASH_COLS; + int height = tiled->height / CTX_HASH_ROWS; + Ctx *host = tiled->host[0]; + + CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer; + int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green; + ctx_rasterizer_init (rasterizer, + host, tiled->ctx, &host->state, + &tiled->pixels[tiled->width * 4 * y0 + x0 * 4], + 0, 0, 1, 1, + tiled->width*4, CTX_FORMAT_BGRA8, + tiled->antialias); + ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green; + if (sdl_icc_length) + ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length); + + ctx_translate (host, -x0, -y0); + ctx_render_ctx (tiled->ctx_copy, host); + } +#endif + + + mtx_lock (&tiled->mtx); + cnd_broadcast (&tiled->cond); + mtx_unlock (&tiled->mtx); + } +} + +static +void ctx_tiled_render_fun (void **data) +{ + int no = (size_t)data[0]; + CtxTiled *tiled = data[1]; + + while (!tiled->quit) + { + Ctx *host = tiled->host[no]; + + mtx_lock (&tiled->mtx); + cnd_wait(&tiled->cond, &tiled->mtx); + mtx_unlock (&tiled->mtx); + + if (tiled->render_frame != tiled->rendered_frame[no]) + { + int hno = 0; + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++, hno++) + { + if (tiled->tile_affinity[hno]==no) + { + int x0 = ((tiled->width)/CTX_HASH_COLS) * col; + int y0 = ((tiled->height)/CTX_HASH_ROWS) * row; + int width = tiled->width / CTX_HASH_COLS; + int height = tiled->height / CTX_HASH_ROWS; + + CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer; +#if 1 // merge horizontally adjecant tiles of same affinity into one job + while (col + 1 < CTX_HASH_COLS && + tiled->tile_affinity[hno+1] == no) + { + width += tiled->width / CTX_HASH_COLS; + col++; + hno++; + } +#endif + int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green; + ctx_rasterizer_init (rasterizer, + host, tiled->ctx, &host->state, + &tiled->pixels[tiled->width * 4 * y0 + x0 * 4], + 0, 0, width, height, + tiled->width*4, CTX_FORMAT_BGRA8, + tiled->antialias); + ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green; + if (sdl_icc_length) + ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length); + + ctx_translate (host, -x0, -y0); + ctx_render_ctx (tiled->ctx_copy, host); + } + } + tiled->rendered_frame[no] = tiled->render_frame; + } + } + tiled->thread_quit++; // need atomic? +} + +#endif + + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> +#endif + + +#if CTX_FB + #include <linux/fb.h> + #include <linux/vt.h> + #include <linux/kd.h> + #include <sys/mman.h> + #include <threads.h> + #include <libdrm/drm.h> + #include <libdrm/drm_mode.h> + +typedef struct _EvSource EvSource; + + +struct _EvSource +{ + void *priv; /* private storage */ + + /* returns non 0 if there is events waiting */ + int (*has_event) (EvSource *ev_source); + + /* get an event, the returned event should be freed by the caller */ + char *(*get_event) (EvSource *ev_source); + + /* destroy/unref this instance */ + void (*destroy) (EvSource *ev_source); + + /* get the underlying fd, useful for using select on */ + int (*get_fd) (EvSource *ev_source); + + + void (*set_coord) (EvSource *ev_source, double x, double y); + /* set_coord is needed to warp relative cursors into normalized range, + * like normal mice/trackpads/nipples - to obey edges and more. + */ + + /* if this returns non-0 select can be used for non-blocking.. */ +}; + + +typedef struct _CtxFb CtxFb; +struct _CtxFb +{ + CtxTiled tiled; +#if 0 + void (*render) (void *fb, CtxCommand *command); + void (*reset) (void *fb); + void (*flush) (void *fb); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *fb); + Ctx *ctx; + int width; + int height; + int cols; // unused + int rows; // unused + int was_down; + uint8_t *pixels; + Ctx *ctx_copy; + Ctx *host[CTX_MAX_THREADS]; + CtxAntialias antialias; + int quit; + _Atomic int thread_quit; + int shown_frame; + int render_frame; + int rendered_frame[CTX_MAX_THREADS]; + int frame; + int min_col; // hasher cols and rows + int min_row; + int max_col; + int max_row; + uint8_t hashes[CTX_HASH_ROWS * CTX_HASH_COLS * 20]; + int8_t tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is + // responsible for a tile + // + + + int pointer_down[3]; +#endif + int key_balance; + int key_repeat; + int lctrl; + int lalt; + int rctrl; + + uint8_t *fb; + + int fb_fd; + char *fb_path; + int fb_bits; + int fb_bpp; + int fb_mapped_size; + struct fb_var_screeninfo vinfo; + struct fb_fix_screeninfo finfo; + int vt; + int tty; + int vt_active; + EvSource *evsource[4]; + int evsource_count; + int is_drm; + cnd_t cond; + mtx_t mtx; + struct drm_mode_crtc crtc; +}; + +static char *ctx_fb_clipboard = NULL; +static void ctx_fb_set_clipboard (CtxFb *fb, const char *text) +{ + if (ctx_fb_clipboard) + free (ctx_fb_clipboard); + ctx_fb_clipboard = NULL; + if (text) + { + ctx_fb_clipboard = strdup (text); + } +} + +static char *ctx_fb_get_clipboard (CtxFb *sdl) +{ + if (ctx_fb_clipboard) return strdup (ctx_fb_clipboard); + return strdup (""); +} + +#if UINTPTR_MAX == 0xffFFffFF + #define fbdrmuint_t uint32_t +#elif UINTPTR_MAX == 0xffFFffFFffFFffFF + #define fbdrmuint_t uint64_t +#endif + +void *ctx_fbdrm_new (CtxFb *fb, int *width, int *height) +{ + int got_master = 0; + fb->fb_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); + if (!fb->fb_fd) + return NULL; + static fbdrmuint_t res_conn_buf[20]={0}; // this is static since its contents + // are used by the flip callback + fbdrmuint_t res_fb_buf[20]={0}; + fbdrmuint_t res_crtc_buf[20]={0}; + fbdrmuint_t res_enc_buf[20]={0}; + struct drm_mode_card_res res={0}; + + if (ioctl(fb->fb_fd, DRM_IOCTL_SET_MASTER, 0)) + goto cleanup; + got_master = 1; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) + goto cleanup; + res.fb_id_ptr=(fbdrmuint_t)res_fb_buf; + res.crtc_id_ptr=(fbdrmuint_t)res_crtc_buf; + res.connector_id_ptr=(fbdrmuint_t)res_conn_buf; + res.encoder_id_ptr=(fbdrmuint_t)res_enc_buf; + if(ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) + goto cleanup; + + + unsigned int i; + for (i=0;i<res.count_connectors;i++) + { + struct drm_mode_modeinfo conn_mode_buf[20]={0}; + fbdrmuint_t conn_prop_buf[20]={0}, + conn_propval_buf[20]={0}, + conn_enc_buf[20]={0}; + + struct drm_mode_get_connector conn={0}; + + conn.connector_id=res_conn_buf[i]; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) + goto cleanup; + + conn.modes_ptr=(fbdrmuint_t)conn_mode_buf; + conn.props_ptr=(fbdrmuint_t)conn_prop_buf; + conn.prop_values_ptr=(fbdrmuint_t)conn_propval_buf; + conn.encoders_ptr=(fbdrmuint_t)conn_enc_buf; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) + goto cleanup; + + //Check if the connector is OK to use (connected to something) + if (conn.count_encoders<1 || conn.count_modes<1 || !conn.encoder_id || !conn.connection) + continue; + +//------------------------------------------------------------------------------ +//Creating a dumb buffer +//------------------------------------------------------------------------------ + struct drm_mode_create_dumb create_dumb={0}; + struct drm_mode_map_dumb map_dumb={0}; + struct drm_mode_fb_cmd cmd_dumb={0}; + create_dumb.width = conn_mode_buf[0].hdisplay; + create_dumb.height = conn_mode_buf[0].vdisplay; + create_dumb.bpp = 32; + create_dumb.flags = 0; + create_dumb.pitch = 0; + create_dumb.size = 0; + create_dumb.handle = 0; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) || + !create_dumb.handle) + goto cleanup; + + cmd_dumb.width =create_dumb.width; + cmd_dumb.height=create_dumb.height; + cmd_dumb.bpp =create_dumb.bpp; + cmd_dumb.pitch =create_dumb.pitch; + cmd_dumb.depth =24; + cmd_dumb.handle=create_dumb.handle; + if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb)) + goto cleanup; + + map_dumb.handle=create_dumb.handle; + if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb)) + goto cleanup; + + void *base = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, + fb->fb_fd, map_dumb.offset); + if (!base) + { + goto cleanup; + } + *width = create_dumb.width; + *height = create_dumb.height; + + struct drm_mode_get_encoder enc={0}; + enc.encoder_id=conn.encoder_id; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETENCODER, &enc)) + goto cleanup; + + fb->crtc.crtc_id=enc.crtc_id; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCRTC, &fb->crtc)) + goto cleanup; + + fb->crtc.fb_id=cmd_dumb.fb_id; + fb->crtc.set_connectors_ptr=(fbdrmuint_t)&res_conn_buf[i]; + fb->crtc.count_connectors=1; + fb->crtc.mode=conn_mode_buf[0]; + fb->crtc.mode_valid=1; + return base; + } +cleanup: + if (got_master) + ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + fb->fb_fd = 0; + return NULL; +} + +void ctx_fbdrm_flip (CtxFb *fb) +{ + if (!fb->fb_fd) + return; + ioctl(fb->fb_fd, DRM_IOCTL_MODE_SETCRTC, &fb->crtc); +} + +void ctx_fbdrm_close (CtxFb *fb) +{ + if (!fb->fb_fd) + return; + ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + close (fb->fb_fd); + fb->fb_fd = 0; +} + +static void ctx_fb_flip (CtxFb *fb) +{ + if (fb->is_drm) + ctx_fbdrm_flip (fb); + else + ioctl (fb->fb_fd, FBIOPAN_DISPLAY, &fb->vinfo); +} + +inline static uint32_t +ctx_swap_red_green2 (uint32_t orig) +{ + uint32_t green_alpha = (orig & 0xff00ff00); + uint32_t red_blue = (orig & 0x00ff00ff); + uint32_t red = red_blue << 16; + uint32_t blue = red_blue >> 16; + return green_alpha | red | blue; +} + +static int ctx_fb_cursor_drawn = 0; +static int ctx_fb_cursor_drawn_x = 0; +static int ctx_fb_cursor_drawn_y = 0; +static CtxCursor ctx_fb_cursor_drawn_shape = 0; + + +#define CTX_FB_HIDE_CURSOR_FRAMES 200 + +static int ctx_fb_cursor_same_pos = CTX_FB_HIDE_CURSOR_FRAMES; + +static inline int ctx_is_in_cursor (int x, int y, int size, CtxCursor shape) +{ + switch (shape) + { + case CTX_CURSOR_ARROW: + if (x > ((size * 4)-y*4)) return 0; + if (x < y && x > y / 16) + return 1; + return 0; + + case CTX_CURSOR_RESIZE_SE: + case CTX_CURSOR_RESIZE_NW: + case CTX_CURSOR_RESIZE_SW: + case CTX_CURSOR_RESIZE_NE: + { + float theta = -45.0/180 * M_PI; + float cos_theta; + float sin_theta; + + if ((shape == CTX_CURSOR_RESIZE_SW) || + (shape == CTX_CURSOR_RESIZE_NE)) + { + theta = -theta; + cos_theta = cos (theta); + sin_theta = sin (theta); + } + else + { + cos_theta = cos (theta); + sin_theta = sin (theta); + } + int rot_x = x * cos_theta - y * sin_theta; + int rot_y = y * cos_theta + x * sin_theta; + x = rot_x; + y = rot_y; + } + /*FALLTHROUGH*/ + case CTX_CURSOR_RESIZE_W: + case CTX_CURSOR_RESIZE_E: + case CTX_CURSOR_RESIZE_ALL: + if (abs (x) < size/2 && abs (y) < size/2) + { + if (abs(y) < size/10) + { + return 1; + } + } + if ((abs (x) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0) + { + if (abs(y) < (size/2.8)-(abs(x) - (size/2))) + return 1; + } + if (shape != CTX_CURSOR_RESIZE_ALL) + break; + /* FALLTHROUGH */ + case CTX_CURSOR_RESIZE_S: + case CTX_CURSOR_RESIZE_N: + if (abs (y) < size/2 && abs (x) < size/2) + { + if (abs(x) < size/10) + { + return 1; + } + } + if ((abs (y) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0) + { + if (abs(x) < (size/2.8)-(abs(y) - (size/2))) + return 1; + } + break; +#if 0 + case CTX_CURSOR_RESIZE_ALL: + if (abs (x) < size/2 && abs (y) < size/2) + { + if (abs (x) < size/10 || abs(y) < size/10) + return 1; + } + break; +#endif + default: + return (x ^ y) & 1; + } + return 0; +} + +static void ctx_fb_undraw_cursor (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int cursor_size = ctx_height (tiled->ctx) / 28; + + if (ctx_fb_cursor_drawn) + { + int no = 0; + int startx = -cursor_size; + int starty = -cursor_size; + if (ctx_fb_cursor_drawn_shape == CTX_CURSOR_ARROW) + startx = starty = 0; + + for (int y = starty; y < cursor_size; y++) + for (int x = startx; x < cursor_size; x++, no+=4) + { + if (x + ctx_fb_cursor_drawn_x < tiled->width && y + ctx_fb_cursor_drawn_y < tiled->height) + { + if (ctx_is_in_cursor (x, y, cursor_size, ctx_fb_cursor_drawn_shape)) + { + int o = ((ctx_fb_cursor_drawn_y + y) * tiled->width + (ctx_fb_cursor_drawn_x + x)) * 4; + fb->fb[o+0]^=0x88; + fb->fb[o+1]^=0x88; + fb->fb[o+2]^=0x88; + } + } + } + + ctx_fb_cursor_drawn = 0; + } +} + +static void ctx_fb_draw_cursor (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int cursor_x = ctx_pointer_x (tiled->ctx); + int cursor_y = ctx_pointer_y (tiled->ctx); + int cursor_size = ctx_height (tiled->ctx) / 28; + CtxCursor cursor_shape = tiled->ctx->cursor; + int no = 0; + + if (cursor_x == ctx_fb_cursor_drawn_x && + cursor_y == ctx_fb_cursor_drawn_y && + cursor_shape == ctx_fb_cursor_drawn_shape) + ctx_fb_cursor_same_pos ++; + else + ctx_fb_cursor_same_pos = 0; + + if (ctx_fb_cursor_same_pos >= CTX_FB_HIDE_CURSOR_FRAMES) + { + if (ctx_fb_cursor_drawn) + ctx_fb_undraw_cursor (fb); + return; + } + + /* no need to flicker when stationary, motion flicker can also be removed + * by combining the previous and next position masks when a motion has + * occured.. + */ + if (ctx_fb_cursor_same_pos && ctx_fb_cursor_drawn) + return; + + ctx_fb_undraw_cursor (fb); + + no = 0; + + int startx = -cursor_size; + int starty = -cursor_size; + + if (cursor_shape == CTX_CURSOR_ARROW) + startx = starty = 0; + + for (int y = starty; y < cursor_size; y++) + for (int x = startx; x < cursor_size; x++, no+=4) + { + if (x + cursor_x < tiled->width && y + cursor_y < tiled->height) + { + if (ctx_is_in_cursor (x, y, cursor_size, cursor_shape)) + { + int o = ((cursor_y + y) * tiled->width + (cursor_x + x)) * 4; + fb->fb[o+0]^=0x88; + fb->fb[o+1]^=0x88; + fb->fb[o+2]^=0x88; + } + } + } + ctx_fb_cursor_drawn = 1; + ctx_fb_cursor_drawn_x = cursor_x; + ctx_fb_cursor_drawn_y = cursor_y; + ctx_fb_cursor_drawn_shape = cursor_shape; +} + +static void ctx_fb_show_frame (CtxFb *fb, int block) +{ + CtxTiled *tiled = (void*)fb; + if (tiled->shown_frame == tiled->render_frame) + { + if (block == 0) // consume event call + { + ctx_fb_draw_cursor (fb); + ctx_fb_flip (fb); + } + return; + } + + if (block) + { + int count = 0; + while (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + { + usleep (500); + count ++; + if (count > 2000) + { + tiled->shown_frame = tiled->render_frame; + return; + } + } + } + else + { + if (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + return; + } + + if (fb->vt_active) + { + int pre_skip = tiled->min_row * tiled->height/CTX_HASH_ROWS * tiled->width; + int post_skip = (CTX_HASH_ROWS-tiled->max_row-1) * tiled->height/CTX_HASH_ROWS * tiled->width; + + int rows = ((tiled->width * tiled->height) - pre_skip - post_skip)/tiled->width; + + int col_pre_skip = tiled->min_col * tiled->width/CTX_HASH_COLS; + int col_post_skip = (CTX_HASH_COLS-tiled->max_col-1) * tiled->width/CTX_HASH_COLS; + if (_ctx_damage_control) + { + pre_skip = post_skip = col_pre_skip = col_post_skip = 0; + } + + if (pre_skip < 0) pre_skip = 0; + if (post_skip < 0) post_skip = 0; + + __u32 dummy = 0; + + if (tiled->min_row == 100){ + pre_skip = 0; + post_skip = 0; + // not when drm ? + ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy); + ctx_fb_undraw_cursor (fb); + } + else + { + + tiled->min_row = 100; + tiled->max_row = 0; + tiled->min_col = 100; + tiled->max_col = 0; + + // not when drm ? + ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy); + ctx_fb_undraw_cursor (fb); + switch (fb->fb_bits) + { + case 32: +#if 1 + { + uint8_t *dst = fb->fb + pre_skip * 4; + uint8_t *src = tiled->pixels + pre_skip * 4; + int pre = col_pre_skip * 4; + int post = col_post_skip * 4; + int core = tiled->width * 4 - pre - post; + for (int i = 0; i < rows; i++) + { + dst += pre; + src += pre; + memcpy (dst, src, core); + src += core; + dst += core; + dst += post; + src += post; + } + } +#else + { int count = tiled->width * tiled->height; + const uint32_t *src = (void*)tiled->pixels; + uint32_t *dst = (void*)fb->fb; + count-= pre_skip; + src+= pre_skip; + dst+= pre_skip; + count-= post_skip; + while (count -- > 0) + { + dst[0] = ctx_swap_red_green2 (src[0]); + src++; + dst++; + } + } +#endif + break; + /* XXX : note: converting a scanline (or all) to target and + * then doing a bulk memcpy be faster (at least with som /dev/fbs) */ + case 24: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 3; + count-= post_skip; + while (count -- > 0) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst+=3; + src+=4; + } + } + break; + case 16: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 2; + while (count -- > 0) + { + int big = ((src[0] >> 3)) + + ((src[1] >> 2)<<5) + + ((src[2] >> 3)<<11); + dst[0] = big & 255; + dst[1] = big >> 8; + dst+=2; + src+=4; + } + } + break; + case 15: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 2; + while (count -- > 0) + { + int big = ((src[2] >> 3)) + + ((src[1] >> 2)<<5) + + ((src[0] >> 3)<<10); + dst[0] = big & 255; + dst[1] = big >> 8; + dst+=2; + src+=4; + } + } + break; + case 8: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip; + while (count -- > 0) + { + dst[0] = ((src[0] >> 5)) + + ((src[1] >> 5)<<3) + + ((src[2] >> 6)<<6); + dst+=1; + src+=4; + } + } + break; + } + } + ctx_fb_cursor_drawn = 0; + ctx_fb_draw_cursor (fb); + ctx_fb_flip (fb); + tiled->shown_frame = tiled->render_frame; + } +} + + +#define evsource_has_event(es) (es)->has_event((es)) +#define evsource_get_event(es) (es)->get_event((es)) +#define evsource_destroy(es) do{if((es)->destroy)(es)->destroy((es));}while(0) +#define evsource_set_coord(es,x,y) do{if((es)->set_coord)(es)->set_coord((es),(x),(y));}while(0) +#define evsource_get_fd(es) ((es)->get_fd?(es)->get_fd((es)):0) + + + +static int mice_has_event (); +static char *mice_get_event (); +static void mice_destroy (); +static int mice_get_fd (EvSource *ev_source); +static void mice_set_coord (EvSource *ev_source, double x, double y); + +static EvSource ctx_ev_src_mice = { + NULL, + (void*)mice_has_event, + (void*)mice_get_event, + (void*)mice_destroy, + mice_get_fd, + mice_set_coord +}; + +typedef struct Mice +{ + int fd; + double x; + double y; + int button; + int prev_state; +} Mice; + +Mice *_mrg_evsrc_coord = NULL; +static int _ctx_mice_fd = 0; + +void _mmm_get_coords (Ctx *ctx, double *x, double *y) +{ + if (!_mrg_evsrc_coord) + return; + if (x) + *x = _mrg_evsrc_coord->x; + if (y) + *y = _mrg_evsrc_coord->y; +} + +static Mice mice; +static Mice* mrg_mice_this = &mice; + +static int mmm_evsource_mice_init () +{ + unsigned char reset[]={0xff}; + /* need to detect which event */ + + mrg_mice_this->prev_state = 0; + mrg_mice_this->fd = open ("/dev/input/mice", O_RDONLY | O_NONBLOCK); + if (mrg_mice_this->fd == -1) + { + fprintf (stderr, "error opening /dev/input/mice device, maybe add user to input group if such group exist, or otherwise make the rights be satisfied.\n"); + return -1; + } + if (write (mrg_mice_this->fd, reset, 1) == -1) + { + // might happen if we're a regular user with only read permission + } + _ctx_mice_fd = mrg_mice_this->fd; + _mrg_evsrc_coord = mrg_mice_this; + return 0; +} + +static void mice_destroy () +{ + if (mrg_mice_this->fd != -1) + close (mrg_mice_this->fd); +} + +static int mice_has_event () +{ + struct timeval tv; + int retval; + + if (mrg_mice_this->fd == -1) + return 0; + + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(mrg_mice_this->fd, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (mrg_mice_this->fd+1, &rfds, NULL, NULL, &tv); + if (retval == 1) + return FD_ISSET (mrg_mice_this->fd, &rfds); + return 0; +} + +static char *mice_get_event () +{ + const char *ret = "mouse-motion"; + double relx, rely; + signed char buf[3]; + int n_read = 0; + CtxFb *fb = ctx_ev_src_mice.priv; + CtxTiled *tiled = (void*)fb; + n_read = read (mrg_mice_this->fd, buf, 3); + if (n_read == 0) + return strdup (""); + relx = buf[1]; + rely = -buf[2]; + + if (relx < 0) + { + if (relx > -6) + relx = - relx*relx; + else + relx = -36; + } + else + { + if (relx < 6) + relx = relx*relx; + else + relx = 36; + } + + if (rely < 0) + { + if (rely > -6) + rely = - rely*rely; + else + rely = -36; + } + else + { + if (rely < 6) + rely = rely*rely; + else + rely = 36; + } + + mrg_mice_this->x += relx; + mrg_mice_this->y += rely; + + if (mrg_mice_this->x < 0) + mrg_mice_this->x = 0; + if (mrg_mice_this->y < 0) + mrg_mice_this->y = 0; + if (mrg_mice_this->x >= tiled->width) + mrg_mice_this->x = tiled->width -1; + if (mrg_mice_this->y >= tiled->height) + mrg_mice_this->y = tiled->height -1; + int button = 0; + + if ((mrg_mice_this->prev_state & 1) != (buf[0] & 1)) + { + if (buf[0] & 1) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 1; + } + else if (buf[0] & 1) + { + ret = "mouse-drag"; + button = 1; + } + + if (!button) + { + if ((mrg_mice_this->prev_state & 2) != (buf[0] & 2)) + { + if (buf[0] & 2) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 3; + } + else if (buf[0] & 2) + { + ret = "mouse-drag"; + button = 3; + } + } + + if (!button) + { + if ((mrg_mice_this->prev_state & 4) != (buf[0] & 4)) + { + if (buf[0] & 4) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 2; + } + else if (buf[0] & 4) + { + ret = "mouse-drag"; + button = 2; + } + } + + mrg_mice_this->prev_state = buf[0]; + + { + char *r = malloc (64); + sprintf (r, "%s %.0f %.0f %i", ret, mrg_mice_this->x, mrg_mice_this->y, button); + return r; + } + + return NULL; +} + +static int mice_get_fd (EvSource *ev_source) +{ + return mrg_mice_this->fd; +} + +static void mice_set_coord (EvSource *ev_source, double x, double y) +{ + mrg_mice_this->x = x; + mrg_mice_this->y = y; +} + +static EvSource *evsource_mice_new (void) +{ + if (mmm_evsource_mice_init () == 0) + { + mrg_mice_this->x = 0; + mrg_mice_this->y = 0; + return &ctx_ev_src_mice; + } + return NULL; +} + +static int evsource_kb_has_event (void); +static char *evsource_kb_get_event (void); +static void evsource_kb_destroy (int sign); +static int evsource_kb_get_fd (void); + +/* kept out of struct to be reachable by atexit */ +static EvSource ctx_ev_src_kb = { + NULL, + (void*)evsource_kb_has_event, + (void*)evsource_kb_get_event, + (void*)evsource_kb_destroy, + (void*)evsource_kb_get_fd, + NULL +}; + +static struct termios orig_attr; + +static void real_evsource_kb_destroy (int sign) +{ + static int done = 0; + + if (sign == 0) + return; + + if (done) + return; + done = 1; + + switch (sign) + { + case -11:break; /* will be called from atexit with sign==-11 */ + case SIGSEGV: break;//fprintf (stderr, " SIGSEGV\n");break; + case SIGABRT: fprintf (stderr, " SIGABRT\n");break; + case SIGBUS: fprintf (stderr, " SIGBUS\n");break; + case SIGKILL: fprintf (stderr, " SIGKILL\n");break; + case SIGINT: fprintf (stderr, " SIGINT\n");break; + case SIGTERM: fprintf (stderr, " SIGTERM\n");break; + case SIGQUIT: fprintf (stderr, " SIGQUIT\n");break; + default: fprintf (stderr, "sign: %i\n", sign); + fprintf (stderr, "%i %i %i %i %i %i %i\n", SIGSEGV, SIGABRT, SIGBUS, SIGKILL, SIGINT, SIGTERM, SIGQUIT); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + //fprintf (stderr, "evsource kb destroy\n"); +} + +static void evsource_kb_destroy (int sign) +{ + real_evsource_kb_destroy (-11); +} + +static int evsource_kb_init () +{ +// ioctl(STDIN_FILENO, KDSKBMODE, K_RAW); + atexit ((void*) real_evsource_kb_destroy); + signal (SIGSEGV, (void*) real_evsource_kb_destroy); + signal (SIGABRT, (void*) real_evsource_kb_destroy); + signal (SIGBUS, (void*) real_evsource_kb_destroy); + signal (SIGKILL, (void*) real_evsource_kb_destroy); + signal (SIGINT, (void*) real_evsource_kb_destroy); + signal (SIGTERM, (void*) real_evsource_kb_destroy); + signal (SIGQUIT, (void*) real_evsource_kb_destroy); + + struct termios raw; + if (tcgetattr (STDIN_FILENO, &orig_attr) == -1) + { + fprintf (stderr, "error initializing keyboard\n"); + return -1; + } + raw = orig_attr; + + cfmakeraw (&raw); + + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; // XXX? return other value? + + return 0; +} +static int evsource_kb_has_event (void) +{ + struct timeval tv; + int retval; + + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(STDIN_FILENO, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv); + return retval == 1; +} + +/* note that a nick can have multiple occurences, the labels + * should be kept the same for all occurences of a combination. + * + * this table is taken from nchanterm. + */ +typedef struct MmmKeyCode { + char *nick; /* programmers name for key */ + char sequence[10]; /* terminal sequence */ +} MmmKeyCode; +static const MmmKeyCode ufb_keycodes[]={ + {"up", "\e[A"}, + {"down", "\e[B"}, + {"right", "\e[C"}, + {"left", "\e[D"}, + + {"shift-up", "\e[1;2A"}, + {"shift-down", "\e[1;2B"}, + {"shift-right", "\e[1;2C"}, + {"shift-left", "\e[1;2D"}, + + {"alt-up", "\e[1;3A"}, + {"alt-down", "\e[1;3B"}, + {"alt-right", "\e[1;3C"}, + {"alt-left", "\e[1;3D"}, + {"alt-shift-up", "\e[1;4A"}, + {"alt-shift-down", "\e[1;4B"}, + {"alt-shift-right", "\e[1;4C"}, + {"alt-shift-left", "\e[1;4D"}, + + {"control-up", "\e[1;5A"}, + {"control-down", "\e[1;5B"}, + {"control-right", "\e[1;5C"}, + {"control-left", "\e[1;5D"}, + + /* putty */ + {"control-up", "\eOA"}, + {"control-down", "\eOB"}, + {"control-right", "\eOC"}, + {"control-left", "\eOD"}, + + {"control-shift-up", "\e[1;6A"}, + {"control-shift-down", "\e[1;6B"}, + {"control-shift-right", "\e[1;6C"}, + {"control-shift-left", "\e[1;6D"}, + + {"control-up", "\eOa"}, + {"control-down", "\eOb"}, + {"control-right", "\eOc"}, + {"control-left", "\eOd"}, + + {"shift-up", "\e[a"}, + {"shift-down", "\e[b"}, + {"shift-right", "\e[c"}, + {"shift-left", "\e[d"}, + + {"insert", "\e[2~"}, + {"delete", "\e[3~"}, + {"page-up", "\e[5~"}, + {"page-down", "\e[6~"}, + {"home", "\eOH"}, + {"end", "\eOF"}, + {"home", "\e[H"}, + {"end", "\e[F"}, + {"control-delete", "\e[3;5~"}, + {"shift-delete", "\e[3;2~"}, + {"control-shift-delete","\e[3;6~"}, + + {"F1", "\e[25~"}, + {"F2", "\e[26~"}, + {"F3", "\e[27~"}, + {"F4", "\e[26~"}, + + + {"F1", "\e[11~"}, + {"F2", "\e[12~"}, + {"F3", "\e[13~"}, + {"F4", "\e[14~"}, + {"F1", "\eOP"}, + {"F2", "\eOQ"}, + {"F3", "\eOR"}, + {"F4", "\eOS"}, + {"F5", "\e[15~"}, + {"F6", "\e[16~"}, + {"F7", "\e[17~"}, + {"F8", "\e[18~"}, + {"F9", "\e[19~"}, + {"F9", "\e[20~"}, + {"F10", "\e[21~"}, + {"F11", "\e[22~"}, + {"F12", "\e[23~"}, + {"tab", {9, '\0'}}, + {"shift-tab", {27, 9, '\0'}}, // also generated by alt-tab in linux console + {"alt-space", {27, ' ', '\0'}}, + {"shift-tab", "\e[Z"}, + {"backspace", {127, '\0'}}, + {"space", " "}, + {"\e", "\e"}, + {"return", {10,0}}, + {"return", {13,0}}, + /* this section could be autogenerated by code */ + {"control-a", {1,0}}, + {"control-b", {2,0}}, + {"control-c", {3,0}}, + {"control-d", {4,0}}, + {"control-e", {5,0}}, + {"control-f", {6,0}}, + {"control-g", {7,0}}, + {"control-h", {8,0}}, /* backspace? */ + {"control-i", {9,0}}, + {"control-j", {10,0}}, + {"control-k", {11,0}}, + {"control-l", {12,0}}, + {"control-n", {14,0}}, + {"control-o", {15,0}}, + {"control-p", {16,0}}, + {"control-q", {17,0}}, + {"control-r", {18,0}}, + {"control-s", {19,0}}, + {"control-t", {20,0}}, + {"control-u", {21,0}}, + {"control-v", {22,0}}, + {"control-w", {23,0}}, + {"control-x", {24,0}}, + {"control-y", {25,0}}, + {"control-z", {26,0}}, + {"alt-`", "\e`"}, + {"alt-0", "\e0"}, + {"alt-1", "\e1"}, + {"alt-2", "\e2"}, + {"alt-3", "\e3"}, + {"alt-4", "\e4"}, + {"alt-5", "\e5"}, + {"alt-6", "\e6"}, + {"alt-7", "\e7"}, /* backspace? */ + {"alt-8", "\e8"}, + {"alt-9", "\e9"}, + {"alt-+", "\e+"}, + {"alt--", "\e-"}, + {"alt-/", "\e/"}, + {"alt-a", "\ea"}, + {"alt-b", "\eb"}, + {"alt-c", "\ec"}, + {"alt-d", "\ed"}, + {"alt-e", "\ee"}, + {"alt-f", "\ef"}, + {"alt-g", "\eg"}, + {"alt-h", "\eh"}, /* backspace? */ + {"alt-i", "\ei"}, + {"alt-j", "\ej"}, + {"alt-k", "\ek"}, + {"alt-l", "\el"}, + {"alt-n", "\em"}, + {"alt-n", "\en"}, + {"alt-o", "\eo"}, + {"alt-p", "\ep"}, + {"alt-q", "\eq"}, + {"alt-r", "\er"}, + {"alt-s", "\es"}, + {"alt-t", "\et"}, + {"alt-u", "\eu"}, + {"alt-v", "\ev"}, + {"alt-w", "\ew"}, + {"alt-x", "\ex"}, + {"alt-y", "\ey"}, + {"alt-z", "\ez"}, + /* Linux Console */ + {"home", "\e[1~"}, + {"end", "\e[4~"}, + {"F1", "\e[[A"}, + {"F2", "\e[[B"}, + {"F3", "\e[[C"}, + {"F4", "\e[[D"}, + {"F5", "\e[[E"}, + {"F6", "\e[[F"}, + {"F7", "\e[[G"}, + {"F8", "\e[[H"}, + {"F9", "\e[[I"}, + {"F10", "\e[[J"}, + {"F11", "\e[[K"}, + {"F12", "\e[[L"}, + {NULL, } +}; +static int fb_keyboard_match_keycode (const char *buf, int length, const MmmKeyCode **ret) +{ + int i; + int matches = 0; + + if (!strncmp (buf, "\e[M", MIN(length,3))) + { + if (length >= 6) + return 9001; + return 2342; + } + for (i = 0; ufb_keycodes[i].nick; i++) + if (!strncmp (buf, ufb_keycodes[i].sequence, length)) + { + matches ++; + if ((int)strlen (ufb_keycodes[i].sequence) == length && ret) + { + *ret = &ufb_keycodes[i]; + return 1; + } + } + if (matches != 1 && ret) + *ret = NULL; + return matches==1?2:matches; +} + +//int is_active (void *host) +//{ +// return 1; +//} + +static char *evsource_kb_get_event (void) +{ + unsigned char buf[20]; + int length; + + + for (length = 0; length < 10; length ++) + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + const MmmKeyCode *match = NULL; + + //if (!is_active (ctx_ev_src_kb.priv)) + // return NULL; + + /* special case ESC, so that we can use it alone in keybindings */ + if (length == 0 && buf[0] == 27) + { + struct timeval tv; + fd_set rfds; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1000 * 120; + if (select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv) == 0) + return strdup ("escape"); + } + + switch (fb_keyboard_match_keycode ((void*)buf, length + 1, &match)) + { + case 1: /* unique match */ + if (!match) + return NULL; + return strdup (match->nick); + break; + case 0: /* no matches, bail*/ + { + static char ret[256]=""; + if (length == 0 && ctx_utf8_len (buf[0])>1) /* read a + * single unicode + * utf8 character + */ + { + int bytes = read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1); + if (bytes) + { + buf[ctx_utf8_len(buf[0])]=0; + strcpy (ret, (void*)buf); + } + return strdup(ret); //XXX: simplify + } + if (length == 0) /* ascii */ + { + buf[1]=0; + strcpy (ret, (void*)buf); + return strdup(ret); + } + sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'", + length >=0 ? buf[0] : 0, + length >=0 ? buf[0]>31?buf[0]:'?' : ' ', + length >=1 ? buf[1] : 0, + length >=1 ? buf[1]>31?buf[1]:'?' : ' ', + length >=2 ? buf[2] : 0, + length >=2 ? buf[2]>31?buf[2]:'?' : ' ', + length >=3 ? buf[3] : 0, + length >=3 ? buf[3]>31?buf[3]:'?' : ' ', + length >=4 ? buf[4] : 0, + length >=4 ? buf[4]>31?buf[4]:'?' : ' ', + length >=5 ? buf[5] : 0, + length >=5 ? buf[5]>31?buf[5]:'?' : ' ', + length >=6 ? buf[6] : 0, + length >=6 ? buf[6]>31?buf[6]:'?' : ' ' + ); + return strdup(ret); + } + return NULL; + default: /* continue */ + break; + } + } + else + return strdup("key read eek"); + return strdup("fail"); +} + +static int evsource_kb_get_fd (void) +{ + return STDIN_FILENO; +} + + +static EvSource *evsource_kb_new (void) +{ + if (evsource_kb_init() == 0) + { + return &ctx_ev_src_kb; + } + return NULL; +} + +static int event_check_pending (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int events = 0; + for (int i = 0; i < fb->evsource_count; i++) + { + while (evsource_has_event (fb->evsource[i])) + { + char *event = evsource_get_event (fb->evsource[i]); + if (event) + { + if (fb->vt_active) + { + ctx_key_press (tiled->ctx, 0, event, 0); // we deliver all events as key-press, the key_press handler disambiguates + events++; + } + free (event); + } + } + } + return events; +} + +int ctx_fb_consume_events (Ctx *ctx) +{ + CtxFb *fb = (void*)ctx->renderer; + ctx_fb_show_frame (fb, 0); + event_check_pending (fb); + return 0; +} + +inline static void ctx_fb_reset (CtxFb *fb) +{ + ctx_fb_show_frame (fb, 1); +} + +inline static void ctx_fb_flush (CtxFb *fb) +{ + ctx_tiled_flush ((CtxTiled*)fb); +} + +void ctx_fb_free (CtxFb *fb) +{ + if (fb->is_drm) + { + ctx_fbdrm_close (fb); + } + + ioctl (0, KDSETMODE, KD_TEXT); + if (system("stty sane")){}; + ctx_tiled_free ((CtxTiled*)fb); + //free (fb); +#if CTX_BABL + babl_exit (); +#endif +} + +//static unsigned char *fb_icc = NULL; +//static long fb_icc_length = 0; + +int ctx_renderer_is_fb (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_fb_free) + return 1; + return 0; +} + +static CtxFb *ctx_fb = NULL; +static void vt_switch_cb (int sig) +{ + CtxTiled *tiled = (void*)ctx_fb; + if (sig == SIGUSR1) + { + if (ctx_fb->is_drm) + ioctl(ctx_fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + ioctl (0, VT_RELDISP, 1); + ctx_fb->vt_active = 0; + ioctl (0, KDSETMODE, KD_TEXT); + } + else + { + ioctl (0, VT_RELDISP, VT_ACKACQ); + ctx_fb->vt_active = 1; + // queue draw + tiled->render_frame = ++tiled->frame; + ioctl (0, KDSETMODE, KD_GRAPHICS); + if (ctx_fb->is_drm) + { + ioctl(ctx_fb->fb_fd, DRM_IOCTL_SET_MASTER, 0); + ctx_fb_flip (ctx_fb); + } + else + { + tiled->ctx->dirty=1; + + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + tiled->hashes[(row * CTX_HASH_COLS + col) * 20] += 1; + } + } + } +} + +static int ctx_fb_get_mice_fd (Ctx *ctx) +{ + //CtxFb *fb = (void*)ctx->renderer; + return _ctx_mice_fd; +} + +Ctx *ctx_new_fb (int width, int height, int drm) +{ +#if CTX_RASTERIZER + CtxFb *fb = calloc (sizeof (CtxFb), 1); + + CtxTiled *tiled = (void*)fb; + ctx_fb = fb; + if (drm) + fb->fb = ctx_fbdrm_new (fb, &tiled->width, &tiled->height); + if (fb->fb) + { + fb->is_drm = 1; + width = tiled->width; + height = tiled->height; + /* + we're ignoring the input width and height , + maybe turn them into properties - for + more generic handling. + */ + fb->fb_mapped_size = tiled->width * tiled->height * 4; + fb->fb_bits = 32; + fb->fb_bpp = 4; + } + else + { + fb->fb_fd = open ("/dev/fb0", O_RDWR); + if (fb->fb_fd > 0) + fb->fb_path = strdup ("/dev/fb0"); + else + { + fb->fb_fd = open ("/dev/graphics/fb0", O_RDWR); + if (fb->fb_fd > 0) + { + fb->fb_path = strdup ("/dev/graphics/fb0"); + } + else + { + free (fb); + return NULL; + } + } + + if (ioctl(fb->fb_fd, FBIOGET_FSCREENINFO, &fb->finfo)) + { + fprintf (stderr, "error getting fbinfo\n"); + close (fb->fb_fd); + free (fb->fb_path); + free (fb); + return NULL; + } + + if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &fb->vinfo)) + { + fprintf (stderr, "error getting fbinfo\n"); + close (fb->fb_fd); + free (fb->fb_path); + free (fb); + return NULL; + } + +//fprintf (stderr, "%s\n", fb->fb_path); + width = tiled->width = fb->vinfo.xres; + height = tiled->height = fb->vinfo.yres; + + fb->fb_bits = fb->vinfo.bits_per_pixel; +//fprintf (stderr, "fb bits: %i\n", fb->fb_bits); + + if (fb->fb_bits == 16) + fb->fb_bits = + fb->vinfo.red.length + + fb->vinfo.green.length + + fb->vinfo.blue.length; + + else if (fb->fb_bits == 8) + { + unsigned short red[256], green[256], blue[256]; + unsigned short original_red[256]; + unsigned short original_green[256]; + unsigned short original_blue[256]; + struct fb_cmap cmap = {0, 256, red, green, blue, NULL}; + struct fb_cmap original_cmap = {0, 256, original_red, original_green, original_blue, NULL}; + int i; + + /* do we really need to restore it ? */ + if (ioctl (fb->fb_fd, FBIOPUTCMAP, &original_cmap) == -1) + { + fprintf (stderr, "palette initialization problem %i\n", __LINE__); + } + + for (i = 0; i < 256; i++) + { + red[i] = ((( i >> 5) & 0x7) << 5) << 8; + green[i] = ((( i >> 2) & 0x7) << 5) << 8; + blue[i] = ((( i >> 0) & 0x3) << 6) << 8; + } + + if (ioctl (fb->fb_fd, FBIOPUTCMAP, &cmap) == -1) + { + fprintf (stderr, "palette initialization problem %i\n", __LINE__); + } + } + + fb->fb_bpp = fb->vinfo.bits_per_pixel / 8; + fb->fb_mapped_size = fb->finfo.smem_len; + + fb->fb = mmap (NULL, fb->fb_mapped_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb->fb_fd, 0); + } + if (!fb->fb) + return NULL; + tiled->pixels = calloc (fb->fb_mapped_size, 1); + ctx_fb_events = 1; + +#if CTX_BABL + babl_init (); +#endif + + ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length); + + tiled->ctx = ctx_new (); + tiled->ctx_copy = ctx_new (); + tiled->width = width; + tiled->height = height; + + ctx_set_renderer (tiled->ctx, fb); + ctx_set_renderer (tiled->ctx_copy, fb); + ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx); + + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + + tiled->flush = (void*)ctx_fb_flush; + tiled->reset = (void*)ctx_fb_reset; + tiled->free = (void*)ctx_fb_free; + tiled->set_clipboard = (void*)ctx_fb_set_clipboard; + tiled->get_clipboard = (void*)ctx_fb_get_clipboard; + + for (int i = 0; i < _ctx_max_threads; i++) + { + tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels, + tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS, + tiled->width * 4, CTX_FORMAT_BGRA8); // this format + // is overriden in thread + ((CtxRasterizer*)(tiled->host[i]->renderer))->swap_red_green = 1; + ctx_set_texture_source (tiled->host[i], tiled->ctx); + } + + mtx_init (&tiled->mtx, mtx_plain); + cnd_init (&tiled->cond); + +#define start_thread(no)\ + if(_ctx_max_threads>no){ \ + static void *args[2]={(void*)no, };\ + thrd_t tid;\ + args[1]=fb;\ + thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\ + } + start_thread(0); + start_thread(1); + start_thread(2); + start_thread(3); + start_thread(4); + start_thread(5); + start_thread(6); + start_thread(7); + start_thread(8); + start_thread(9); + start_thread(10); + start_thread(11); + start_thread(12); + start_thread(13); + start_thread(14); + start_thread(15); +#undef start_thread + + ctx_flush (tiled->ctx); + + EvSource *kb = evsource_kb_new (); + if (kb) + { + fb->evsource[fb->evsource_count++] = kb; + kb->priv = fb; + } + EvSource *mice = evsource_mice_new (); + if (mice) + { + fb->evsource[fb->evsource_count++] = mice; + mice->priv = fb; + } + + fb->vt_active = 1; + ioctl(0, KDSETMODE, KD_GRAPHICS); + signal (SIGUSR1, vt_switch_cb); + signal (SIGUSR2, vt_switch_cb); + struct vt_stat st; + if (ioctl (0, VT_GETSTATE, &st) == -1) + { + ctx_log ("VT_GET_MODE on vt %i failed\n", fb->vt); + return NULL; + } + + fb->vt = st.v_active; + + struct vt_mode mode; + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR2; + if (ioctl (0, VT_SETMODE, &mode) < 0) + { + ctx_log ("VT_SET_MODE on vt %i failed\n", fb->vt); + return NULL; + } + + return tiled->ctx; +#else + return NULL; +#endif +} +#else + +int ctx_renderer_is_fb (Ctx *ctx) +{ + return 0; +} +#endif +#endif + +#if CTX_SDL + +/**/ + +typedef struct _CtxSDL CtxSDL; +struct _CtxSDL +{ + CtxTiled tiled; + /* where we diverge from fb*/ + int key_balance; + int key_repeat; + int lctrl; + int lalt; + int rctrl; + int lshift; + int rshift; + + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + +// cnd_t cond; +// mtx_t mtx; + int fullscreen; +}; + +#include "stb_image_write.h" + +void ctx_screenshot (Ctx *ctx, const char *output_path) +{ +#if CTX_SCREENSHOT + int valid = 0; + CtxSDL *sdl = (void*)ctx->renderer; + + if (ctx_renderer_is_sdl (ctx)) valid = 1; +#if CTX_FB + if (ctx_renderer_is_fb (ctx)) valid = 1; +#endif + + if (!valid) + return; + +#if CTX_FB + // we rely on the same layout + for (int i = 0; i < sdl->width * sdl->height; i++) + { + int tmp = sdl->pixels[i*4]; + sdl->pixels[i*4] = sdl->pixels[i*4 + 2]; + sdl->pixels[i*4 + 2] = tmp; + } +#endif + + stbi_write_png (output_path, sdl->width, sdl->height, 4, sdl->pixels, sdl->width*4); + +#if CTX_FB + for (int i = 0; i < sdl->width * sdl->height; i++) + { + int tmp = sdl->pixels[i*4]; + sdl->pixels[i*4] = sdl->pixels[i*4 + 2]; + sdl->pixels[i*4 + 2] = tmp; + } +#endif +#endif +} + +int ctx_show_fps = 1; +void ctx_sdl_set_title (void *self, const char *new_title) +{ + CtxSDL *sdl = self; + if (!ctx_show_fps) + SDL_SetWindowTitle (sdl->window, new_title); +} + +static void ctx_sdl_show_frame (CtxSDL *sdl, int block) +{ + CtxTiled *tiled = &sdl->tiled; + if (tiled->shown_cursor != tiled->ctx->cursor) + { + tiled->shown_cursor = tiled->ctx->cursor; + SDL_Cursor *new_cursor = NULL; + switch (tiled->shown_cursor) + { + case CTX_CURSOR_UNSET: // XXX: document how this differs from none + // perhaps falling back to arrow? + break; + case CTX_CURSOR_NONE: + new_cursor = NULL; + break; + case CTX_CURSOR_ARROW: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + break; + case CTX_CURSOR_CROSSHAIR: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + break; + case CTX_CURSOR_WAIT: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + break; + case CTX_CURSOR_HAND: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + break; + case CTX_CURSOR_IBEAM: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + break; + case CTX_CURSOR_MOVE: + case CTX_CURSOR_RESIZE_ALL: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + break; + case CTX_CURSOR_RESIZE_N: + case CTX_CURSOR_RESIZE_S: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + break; + case CTX_CURSOR_RESIZE_E: + case CTX_CURSOR_RESIZE_W: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + break; + case CTX_CURSOR_RESIZE_NE: + case CTX_CURSOR_RESIZE_SW: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + break; + case CTX_CURSOR_RESIZE_NW: + case CTX_CURSOR_RESIZE_SE: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + break; + } + if (new_cursor) + { + SDL_Cursor *old_cursor = SDL_GetCursor(); + SDL_SetCursor (new_cursor); + SDL_ShowCursor (1); + if (old_cursor) + SDL_FreeCursor (old_cursor); + } + else + { + SDL_ShowCursor (0); + } + } + + if (tiled->shown_frame == tiled->render_frame) + { + return; + } + + if (block) + { + int count = 0; + while (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + { + usleep (50); + count ++; + if (count > 2000) + { + tiled->shown_frame = tiled->render_frame; + return; + } + } + } + else + { + if (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + return; + } + + + if (tiled->min_row == 100) + { + } + else + { +#if 1 + int x = tiled->min_col * tiled->width/CTX_HASH_COLS; + int y = tiled->min_row * tiled->height/CTX_HASH_ROWS; + int x1 = (tiled->max_col+1) * tiled->width/CTX_HASH_COLS; + int y1 = (tiled->max_row+1) * tiled->height/CTX_HASH_ROWS; + int width = x1 - x; + int height = y1 - y; +#endif + tiled->min_row = 100; + tiled->max_row = 0; + tiled->min_col = 100; + tiled->max_col = 0; + + SDL_Rect r = {x, y, width, height}; + SDL_UpdateTexture (sdl->texture, &r, + //(void*)sdl->pixels, + (void*)(tiled->pixels + y * tiled->width * 4 + x * 4), + + tiled->width * 4); + SDL_RenderClear (sdl->renderer); + SDL_RenderCopy (sdl->renderer, sdl->texture, NULL, NULL); + SDL_RenderPresent (sdl->renderer); + + + if (ctx_show_fps) + { + static uint64_t prev_time = 0; + static char tmp_title[1024]; + uint64_t time = ctx_ticks (); + sprintf (tmp_title, "FPS: %.1f", 1000000.0/ (time - prev_time)); + prev_time = time; + SDL_SetWindowTitle (sdl->window, tmp_title); + } + } + tiled->shown_frame = tiled->render_frame; +} + +static const char *ctx_sdl_keysym_to_name (unsigned int sym, int *r_keycode) +{ + static char buf[16]=""; + buf[ctx_unichar_to_utf8 (sym, (void*)buf)]=0; + int code = sym; + const char *name = &buf[0]; + switch (sym) + { + case SDLK_RSHIFT: code = 16 ; break; + case SDLK_LSHIFT: code = 16 ; break; + case SDLK_LCTRL: code = 17 ; break; + case SDLK_RCTRL: code = 17 ; break; + case SDLK_LALT: code = 18 ; break; + case SDLK_RALT: code = 18 ; break; + case SDLK_CAPSLOCK: name = "capslock"; code = 20 ; break; + //case SDLK_NUMLOCK: name = "numlock"; code = 144 ; break; + //case SDLK_SCROLLLOCK: name = "scrollock"; code = 145 ; break; + + case SDLK_F1: name = "F1"; code = 112; break; + case SDLK_F2: name = "F2"; code = 113; break; + case SDLK_F3: name = "F3"; code = 114; break; + case SDLK_F4: name = "F4"; code = 115; break; + case SDLK_F5: name = "F5"; code = 116; break; + case SDLK_F6: name = "F6"; code = 117; break; + case SDLK_F7: name = "F7"; code = 118; break; + case SDLK_F8: name = "F8"; code = 119; break; + case SDLK_F9: name = "F9"; code = 120; break; + case SDLK_F10: name = "F10"; code = 121; break; + case SDLK_F11: name = "F11"; code = 122; break; + case SDLK_F12: name = "F12"; code = 123; break; + case SDLK_ESCAPE: name = "escape"; break; + case SDLK_DOWN: name = "down"; code = 40; break; + case SDLK_LEFT: name = "left"; code = 37; break; + case SDLK_UP: name = "up"; code = 38; break; + case SDLK_RIGHT: name = "right"; code = 39; break; + case SDLK_BACKSPACE: name = "backspace"; break; + case SDLK_SPACE: name = "space"; break; + case SDLK_TAB: name = "tab"; break; + case SDLK_DELETE: name = "delete"; code = 46; break; + case SDLK_INSERT: name = "insert"; code = 45; break; + case SDLK_RETURN: + //if (key_repeat == 0) // return never should repeat + name = "return"; // on a DEC like terminal + break; + case SDLK_HOME: name = "home"; code = 36; break; + case SDLK_END: name = "end"; code = 35; break; + case SDLK_PAGEDOWN: name = "page-down"; code = 34; break; + case SDLK_PAGEUP: name = "page-up"; code = 33; break; + case ',': code = 188; break; + case '.': code = 190; break; + case '/': code = 191; break; + case '`': code = 192; break; + case '[': code = 219; break; + case '\\': code = 220; break; + case ']': code = 221; break; + case '\'': code = 222; break; + default: + ; + } + if (sym >= 'a' && sym <='z') code -= 32; + if (r_keycode) + { + *r_keycode = code; + } + return name; +} + +int ctx_sdl_consume_events (Ctx *ctx) +{ + CtxTiled *tiled = (void*)ctx->renderer; + CtxSDL *sdl = (void*)ctx->renderer; + SDL_Event event; + int got_events = 0; + + ctx_sdl_show_frame (sdl, 0); + + while (SDL_PollEvent (&event)) + { + got_events ++; + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + SDL_CaptureMouse (1); + ctx_pointer_press (ctx, event.button.x, event.button.y, event.button.button, 0); + break; + case SDL_MOUSEBUTTONUP: + SDL_CaptureMouse (0); + ctx_pointer_release (ctx, event.button.x, event.button.y, event.button.button, 0); + break; + case SDL_MOUSEMOTION: + // XXX : look at mask and generate motion for each pressed + // button + ctx_pointer_motion (ctx, event.motion.x, event.motion.y, 1, 0); + break; + case SDL_FINGERMOTION: + ctx_pointer_motion (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + break; + case SDL_FINGERDOWN: + { + static int fdowns = 0; + fdowns ++; + if (fdowns > 1) // the very first finger down from SDL seems to be + // mirrored as mouse events, later ones not - at + // least under wayland + { + ctx_pointer_press (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + } + } + break; + case SDL_FINGERUP: + ctx_pointer_release (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + break; +#if 1 + case SDL_TEXTINPUT: + // if (!active) + // break; + if (!sdl->lctrl && !sdl->rctrl && !sdl->lalt + //&& ( (vt && vt_keyrepeat (vt) ) || (key_repeat==0) ) + ) + { + const char *name = event.text.text; + int keycode = 0; + if (!strcmp (name, " ") ) { name = "space"; } + if (name[0] && name[1] == 0) + { + keycode = name[0]; + keycode = toupper (keycode); + switch (keycode) + { + case '.': keycode = 190; break; + case ';': keycode = 59; break; + case ',': keycode = 188; break; + case '/': keycode = 191; break; + case '\'': keycode = 222; break; + case '`': keycode = 192; break; + case '[': keycode = 219; break; + case ']': keycode = 221; break; + case '\\': keycode = 220; break; + } + } + ctx_key_press (ctx, keycode, name, 0); + //got_event = 1; + } + break; +#endif + case SDL_KEYDOWN: + { + char buf[32] = ""; + const char *name = buf; + if (!event.key.repeat) + { + sdl->key_balance ++; + sdl->key_repeat = 0; + } + else + { + sdl->key_repeat ++; + } + switch (event.key.keysym.sym) + { + case SDLK_LSHIFT: sdl->lshift = 1; break; + case SDLK_RSHIFT: sdl->rshift = 1; break; + case SDLK_LCTRL: sdl->lctrl = 1; break; + case SDLK_LALT: sdl->lalt = 1; break; + case SDLK_RCTRL: sdl->rctrl = 1; break; + } + if (sdl->lshift | sdl->rshift | sdl->lctrl | sdl->lalt | sdl->rctrl) + { + ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL| + CTX_MODIFIER_STATE_ALT| + CTX_MODIFIER_STATE_SHIFT); + if (sdl->lshift | sdl->rshift) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT; + if (sdl->lctrl | sdl->rctrl) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL; + if (sdl->lalt) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT; + } + int keycode; + name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode); + ctx_key_down (ctx, keycode, name, 0); + + if (strlen (name) + &&(event.key.keysym.mod & (KMOD_CTRL) || + event.key.keysym.mod & (KMOD_ALT) || + strlen (name) >= 2)) + { + if (event.key.keysym.mod & (KMOD_CTRL) ) + { + static char buf[64] = ""; + sprintf (buf, "control-%s", name); + name = buf; + } + if (event.key.keysym.mod & (KMOD_ALT) ) + { + static char buf[128] = ""; + sprintf (buf, "alt-%s", name); + name = buf; + } + if (event.key.keysym.mod & (KMOD_SHIFT) ) + { + static char buf[196] = ""; + sprintf (buf, "shift-%s", name); + name = buf; + } + if (strcmp (name, "space")) + { + ctx_key_press (ctx, keycode, name, 0); + } + } + else + { +#if 0 + ctx_key_press (ctx, 0, buf, 0); +#endif + } + } + break; + case SDL_KEYUP: + { + sdl->key_balance --; + switch (event.key.keysym.sym) + { + case SDLK_LSHIFT: sdl->lshift = 0; break; + case SDLK_RSHIFT: sdl->rshift = 0; break; + case SDLK_LCTRL: sdl->lctrl = 0; break; + case SDLK_RCTRL: sdl->rctrl = 0; break; + case SDLK_LALT: sdl->lalt = 0; break; + } + + { + ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL| + CTX_MODIFIER_STATE_ALT| + CTX_MODIFIER_STATE_SHIFT); + if (sdl->lshift | sdl->rshift) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT; + if (sdl->lctrl | sdl->rctrl) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL; + if (sdl->lalt) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT; + } + + int keycode; + const char *name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode); + ctx_key_up (ctx, keycode, name, 0); + } + break; + case SDL_QUIT: + ctx_quit (ctx); + break; + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_RESIZED) + { + ctx_sdl_show_frame (sdl, 1); + int width = event.window.data1; + int height = event.window.data2; + SDL_DestroyTexture (sdl->texture); + sdl->texture = SDL_CreateTexture (sdl->renderer, SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STREAMING, width, height); + free (tiled->pixels); + tiled->pixels = calloc (4, width * height); + + tiled->width = width; + tiled->height = height; + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + } + break; + } + } + return 1; +} +#else +void ctx_screenshot (Ctx *ctx, const char *path) +{ +} +#endif + +#if CTX_SDL + +static void ctx_sdl_set_clipboard (CtxSDL *sdl, const char *text) +{ + if (text) + SDL_SetClipboardText (text); +} + +static char *ctx_sdl_get_clipboard (CtxSDL *sdl) +{ + return SDL_GetClipboardText (); +} + +inline static void ctx_sdl_reset (CtxSDL *sdl) +{ + ctx_sdl_show_frame (sdl, 1); +} + +inline static void ctx_sdl_flush (CtxSDL *sdl) +{ + ctx_tiled_flush ((void*)sdl); + //CtxTiled *tiled = (void*)sdl; +} + +void ctx_sdl_free (CtxSDL *sdl) +{ + + if (sdl->texture) + SDL_DestroyTexture (sdl->texture); + if (sdl->renderer) + SDL_DestroyRenderer (sdl->renderer); + if (sdl->window) + SDL_DestroyWindow (sdl->window); + + ctx_tiled_free ((CtxTiled*)sdl); +#if CTX_BABL + babl_exit (); +#endif +} + + +int ctx_renderer_is_sdl (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_sdl_free) + return 1; + return 0; +} + +void ctx_sdl_set_fullscreen (Ctx *ctx, int val) +{ + CtxSDL *sdl = (void*)ctx->renderer; + + if (val) + { + SDL_SetWindowFullscreen (sdl->window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else + { + SDL_SetWindowFullscreen (sdl->window, 0); + } + // XXX we're presuming success + sdl->fullscreen = val; +} +int ctx_sdl_get_fullscreen (Ctx *ctx) +{ + CtxSDL *sdl = (void*)ctx->renderer; + return sdl->fullscreen; +} + + +Ctx *ctx_new_sdl (int width, int height) +{ +#if CTX_RASTERIZER + + CtxSDL *sdl = (CtxSDL*)calloc (sizeof (CtxSDL), 1); + CtxTiled *tiled = (void*)sdl; + + ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length); + if (width <= 0 || height <= 0) + { + width = 1920; + height = 1080; + } + sdl->window = SDL_CreateWindow("ctx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE); + //sdl->renderer = SDL_CreateRenderer (sdl->window, -1, SDL_RENDERER_SOFTWARE); + sdl->renderer = SDL_CreateRenderer (sdl->window, -1, 0); + if (!sdl->renderer) + { + ctx_free (tiled->ctx); + free (sdl); + return NULL; + } +#if CTX_BABL + babl_init (); +#endif + sdl->fullscreen = 0; + + + ctx_show_fps = getenv ("CTX_SHOW_FPS")!=NULL; + + ctx_sdl_events = 1; + sdl->texture = SDL_CreateTexture (sdl->renderer, + SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STREAMING, + width, height); + + SDL_StartTextInput (); + SDL_EnableScreenSaver (); + + tiled->ctx = ctx_new (); + tiled->ctx_copy = ctx_new (); + tiled->width = width; + tiled->height = height; + tiled->cols = 80; + tiled->rows = 20; + ctx_set_renderer (tiled->ctx, sdl); + ctx_set_renderer (tiled->ctx_copy, sdl); + ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx); + + tiled->pixels = (uint8_t*)malloc (width * height * 4); + + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + + tiled->flush = (void*)ctx_sdl_flush; + tiled->reset = (void*)ctx_sdl_reset; + tiled->free = (void*)ctx_sdl_free; + tiled->set_clipboard = (void*)ctx_sdl_set_clipboard; + tiled->get_clipboard = (void*)ctx_sdl_get_clipboard; + + for (int i = 0; i < _ctx_max_threads; i++) + { + tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels, + tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS, + tiled->width * 4, CTX_FORMAT_RGBA8); + ctx_set_texture_source (tiled->host[i], tiled->ctx); + } + + mtx_init (&tiled->mtx, mtx_plain); + cnd_init (&tiled->cond); + +#define start_thread(no)\ + if(_ctx_max_threads>no){ \ + static void *args[2]={(void*)no, };\ + thrd_t tid;\ + args[1]=sdl;\ + thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\ + } + start_thread(0); + start_thread(1); + start_thread(2); + start_thread(3); + start_thread(4); + start_thread(5); + start_thread(6); + start_thread(7); + start_thread(8); + start_thread(9); + start_thread(10); + start_thread(11); + start_thread(12); + start_thread(13); + start_thread(14); + start_thread(15); +#undef start_thread + + ctx_flush (tiled->ctx); + return tiled->ctx; +#else + return NULL; +#endif +} +#else + +int ctx_renderer_is_sdl (Ctx *ctx) +{ + return 0; +} +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +typedef struct CtxTermCell +{ + char utf8[5]; + uint8_t fg[4]; + uint8_t bg[4]; + + char prev_utf8[5]; + uint8_t prev_fg[4]; + uint8_t prev_bg[4]; +} CtxTermCell; + +typedef struct CtxTermLine +{ + CtxTermCell *cells; + int maxcol; + int size; +} CtxTermLine; + +typedef enum +{ + CTX_TERM_ASCII, + CTX_TERM_ASCII_MONO, + CTX_TERM_SEXTANT, + CTX_TERM_BRAILLE_MONO, + CTX_TERM_BRAILLE, + CTX_TERM_QUARTER, +} CtxTermMode; + +typedef struct _CtxTerm CtxTerm; +struct _CtxTerm +{ + void (*render) (void *term, CtxCommand *command); + void (*reset) (void *term); + void (*flush) (void *term); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *term); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + + uint8_t *pixels; + + Ctx *host; + CtxList *lines; + CtxTermMode mode; +}; + +static int ctx_term_ch = 8; +static int ctx_term_cw = 8; + +void ctx_term_set (CtxTerm *term, + int col, int row, const char *utf8, + uint8_t *fg, uint8_t *bg) +{ + if (col < 1 || row < 1 || col > term->cols || row > term->rows) return; + while (ctx_list_length (term->lines) < row) + { + ctx_list_append (&term->lines, calloc (sizeof (CtxTermLine), 1)); + } + CtxTermLine *line = ctx_list_nth_data (term->lines, row-1); + assert (line); + if (line->size < col) + { + int new_size = ((col + 128)/128)*128; + line->cells = realloc (line->cells, sizeof (CtxTermCell) * new_size); + memset (&line->cells[line->size], 0, sizeof (CtxTermCell) * (new_size - line->size) ); + line->size = new_size; + } + if (col > line->maxcol) line->maxcol = col; + strncpy (line->cells[col-1].utf8, (char*)utf8, 4); + memcpy (line->cells[col-1].fg, fg, 4); + memcpy (line->cells[col-1].bg, bg, 4); +} + +static int _ctx_term256 = 0; // XXX TODO implement autodetect for this +static long _ctx_curfg = -1; +static long _ctx_curbg = -1; + +static long ctx_rgb_to_long (int r,int g, int b) +{ + return r * 256 * 256 + g * 256 + b; +} + + +static void ctx_term_set_fg (int red, int green, int blue) +{ + long lc = ctx_rgb_to_long (red, green, blue); + if (lc == _ctx_curfg) + return; + _ctx_curfg=lc; + if (_ctx_term256 == 0) + { + printf("\e[38;2;%i;%i;%im", red,green,blue); + } + else + { + int gray = (green /255.0) * 24 + 0.5; + int r = (red/255.0) * 6 + 0.5; + int g = (green/255.0) * 6 + 0.5; + int b = (blue/255.0) * 6 + 0.5; + if (gray > 23) gray = 23; + + if (r > 5) r = 5; + if (g > 5) g = 5; + if (b > 5) b = 5; + + if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66)))) + { + printf("\e[38;5;%im", 16 + 216 + gray); + } + else + printf("\e[38;5;%im", 16 + r * 6 * 6 + g * 6 + b); + } +} + +static void ctx_term_set_bg(int red, int green, int blue) +{ + long lc = ctx_rgb_to_long (red, green, blue); +//if (lc == _ctx_curbg) +// return; + _ctx_curbg=lc; + if (_ctx_term256 == 0) + { + printf("\e[48;2;%i;%i;%im", red,green,blue); + } + else + { + int gray = (green /255.0) * 24 + 0.5; + int r = (red/255.0) * 6 + 0.5; + int g = (green/255.0) * 6 + 0.5; + int b = (blue/255.0) * 6 + 0.5; + if (gray > 23) gray = 23; + + if (r > 5) r = 5; + if (g > 5) g = 5; + if (b > 5) b = 5; + + if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66)))) + { + printf("\e[48;5;%im", 16 + 216 + gray); + } + else + printf("\e[48;5;%im", 16 + r * 6 * 6 + g * 6 + b); + } +} + +static int _ctx_term_force_full = 0; + +void ctx_term_scanout (CtxTerm *term) +{ + int row = 1; + printf ("\e[H"); +// printf ("\e[?25l"); + printf ("\e[0m"); + for (CtxList *l = term->lines; l; l = l->next) + { + CtxTermLine *line = l->data; + for (int col = 1; col <= line->maxcol; col++) + { + CtxTermCell *cell = &line->cells[col-1]; + + if (strcmp(cell->utf8, cell->prev_utf8) || + memcmp(cell->fg, cell->prev_fg, 3) || + memcmp(cell->bg, cell->prev_bg, 3) || _ctx_term_force_full) + { + ctx_term_set_fg (cell->fg[0], cell->fg[1], cell->fg[2]); + ctx_term_set_bg (cell->bg[0], cell->bg[1], cell->bg[2]); + printf ("%s", cell->utf8); + } + else + { + // TODO: accumulate succesive such, and compress them + // into one + printf ("\e[C"); + } + strcpy (cell->prev_utf8, cell->utf8); + memcpy (cell->prev_fg, cell->fg, 3); + memcpy (cell->prev_bg, cell->bg, 3); + } + if (row != term->rows) + printf ("\n\r"); + row ++; + } + printf ("\e[0m"); + //printf ("\e[?25h"); + // +} + +// xx +// xx +// xx +// + +static inline int _ctx_rgba8_manhattan_diff (const uint8_t *a, const uint8_t *b) +{ + int c; + int diff = 0; + for (c = 0; c<3;c++) + diff += ctx_pow2(a[c]-b[c]); + return sqrtf(diff); + return diff; +} + +static void ctx_term_output_buf_half (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + const char *sextants[]={ + " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█", + + }; + for (int row = 0; row < height/2; row++) + { + for (int col = 0; col < width-3; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + int i = 0; + + int rgbasum[2][4] = {0,}; + int sumcount[2]; + + int curdiff = 0; + /* first find starting point colors */ + for (int yi = 0; yi < ctx_term_ch; yi++) + for (int xi = 0; xi < ctx_term_cw; xi++, i++) + { + int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4; + + if (rgba[0][3] == 0) + { + for (int c = 0; c < 3; c++) + rgba[0][c] = pixels[noi + c]; + rgba[0][3] = 255; // used only as mark of in-use + } + else + { + int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + if (diff > curdiff) + { + curdiff = diff; + for (int c = 0; c < 3; c++) + rgba[1][c] = pixels[noi + c]; + } + } + + } + + for (int iters = 0; iters < 1; iters++) + { + i= 0; + for (int i = 0; i < 4; i ++) + rgbasum[0][i] = rgbasum[1][i]=0; + sumcount[0] = sumcount[1] = 0; + + for (int yi = 0; yi < ctx_term_ch; yi++) + for (int xi = 0; xi < ctx_term_cw; xi++, i++) + { + int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4; + + int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]); + int cluster = 0; + if (diff1 <= diff2) + cluster = 0; + else + cluster = 1; + sumcount[cluster]++; + for (int c = 0; c < 3; c++) + rgbasum[cluster][c] += pixels[noi+c]; + } + + + if (sumcount[0]) + for (int c = 0; c < 3; c++) + { + rgba[0][c] = rgbasum[0][c] / sumcount[0]; + } + if (sumcount[1]) + for (int c = 0; c < 3; c++) + { + rgba[1][c] = rgbasum[1][c] / sumcount[1]; + } + } + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + if (pixels_set == 4) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + +void ctx_term_find_color_pair (CtxTerm *term, int x0, int y0, int w, int h, + uint8_t rgba[2][4]) + //uint8_t *rgba0, uint8_t *rgba1) +{ +int curdiff = 0; +int stride = term->width * 4; +uint8_t *pixels = term->pixels; +/* first find starting point colors */ +for (int y = y0; y < y0 + h; y++) + for (int x = x0; x < x0 + w; x++) + { + int noi = (y) * stride + (x) * 4; + + if (rgba[0][3] == 0) + { + for (int c = 0; c < 3; c++) + rgba[0][c] = pixels[noi + c]; + rgba[0][3] = 255; // used only as mark of in-use + } + else + { + int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], &rgba[0][0]); + if (diff > curdiff) + { + curdiff = diff; + for (int c = 0; c < 3; c++) + rgba[1][c] = pixels[noi + c]; + } + } + } + int rgbasum[2][4] = {0,}; + int sumcount[2]; + + for (int iters = 0; iters < 1; iters++) + { + for (int i = 0; i < 4; i ++) + rgbasum[0][i] = rgbasum[1][i]=0; + sumcount[0] = sumcount[1] = 0; + + for (int y = y0; y < y0 + h; y++) + for (int x = x0; x < x0 + w; x++) + { + int noi = (y) * stride + (x) * 4; + + int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]); + int cluster = 0; + if (diff1 <= diff2) + cluster = 0; + else + cluster = 1; + sumcount[cluster]++; + for (int c = 0; c < 3; c++) + rgbasum[cluster][c] += pixels[noi+c]; + } + + + if (sumcount[0]) + for (int c = 0; c < 3; c++) + { + rgba[0][c] = rgbasum[0][c] / sumcount[0]; + } + if (sumcount[1]) + for (int c = 0; c < 3; c++) + { + rgba[1][c] = rgbasum[1][c] / sumcount[1]; + } + } + +} + + + +static void ctx_term_output_buf_quarter (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + const char *sextants[]={ + " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█" + + }; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + int pixels_set = 0; + for (int y = 0; y < 2; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + if (pixels_set == 4) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + + +static void ctx_term_output_buf_sextant (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + + const char *sextants[]={ + " ","🬀","🬁","🬂","🬃","🬄","🬅","🬆","🬇","🬈","🬉","🬊","🬋","🬌","🬍","🬎","🬏","🬐","🬑","🬒","🬓","▌","🬔","🬕","🬖","🬗","🬘","🬙","🬚","🬛","🬜","🬝","🬞","🬟","🬠","🬡","🬢","🬣","🬤","🬥","🬦","🬧","▐","🬨","🬩","🬪","🬫","🬬","🬭","🬮","🬯","🬰","🬱","🬲","🬳","🬴","🬵","🬶","🬷","🬸","🬹","🬺","🬻","█" + }; + + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + + if (pixels_set == 6) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], rgba[0], rgba[1]); + } + } +} + +static void ctx_term_output_buf_ascii (uint8_t *pixels, + int width, + int height, + CtxTerm *term, + int mono) +{ + /* this is a crude ascii-mode built on a quick mapping of sexels to ascii */ + int stride = width * 4; + const char *sextants[]={ + " ","`","'","^","🬃","`","~","\"","-","\"","'","\"","-","\"","~","^",",",";", + "=","/","i","[","p","P","z",")","/","7","f",">","/","F",",","\\",":",":", + "\\","\\","(","T","j","T","]","?","s","\\","<","q","_","=","=","=","c","L", + "Q","C","a","b","J","]","m","b","d","@" + }; + uint8_t black[4] = {0,0,0,255}; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + + if (_ctx_rgba8_manhattan_diff (black, rgba[1]) > + _ctx_rgba8_manhattan_diff (black, rgba[0])) + { + for (int c = 0; c < 4; c ++) + { + int tmp = rgba[0][c]; + rgba[0][c] = rgba[1][c]; + rgba[1][c] = tmp; + } + } + if (mono) + { + rgba[1][0] = 0; + rgba[1][1] = 0; + rgba[1][2] = 0; + } + + + int brightest_dark_diff = _ctx_rgba8_manhattan_diff (black, rgba[0]); + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + + + if (pixels_set == 6 && brightest_dark_diff < 40) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + +static void ctx_term_output_buf_braille (uint8_t *pixels, + int width, + int height, + CtxTerm *term, + int mono) +{ + int reverse = 0; + int stride = width * 4; + uint8_t black[4] = {0,0,0,255}; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + + /* make darkest consistently be background */ + if (_ctx_rgba8_manhattan_diff (black, rgba[1]) > + _ctx_rgba8_manhattan_diff (black, rgba[0])) + { + for (int c = 0; c < 4; c ++) + { + int tmp = rgba[0][c]; + rgba[0][c] = rgba[1][c]; + rgba[1][c] = tmp; + } + } + if (mono) + { + rgba[1][0] = 0; + rgba[1][1] = 0; + rgba[1][2] = 0; + } + + int pixels_set = 0; + for (int x = 0; x < 2; x++) + for (int y = 0; y < 3; y++) + { + int no = (row * 4 + y) * stride + (col*2+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; + if (reverse) { set = !set; } + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + { + int x = 0; + int y = 3; + int no = (row * 4 + y) * stride + (col*2+x) * 4; + int setA = CHECK_IS_SET; + no = (row * 4 + y) * stride + (col*2+x+1) * 4; + int setB = CHECK_IS_SET; + + pixels_set += setA; + pixels_set += setB; +#undef CHECK_IS_SET + if (reverse) { setA = !setA; } + if (reverse) { setB = !setB; } + if (setA != 0 && setB==0) + { unicode += 0x2840; } + else if (setA == 0 && setB) + { unicode += 0x2880; } + else if ( (setA != 0) && (setB != 0) ) + { unicode += 0x28C0; } + else + { unicode += 0x2800; } + char utf8[5]; + utf8[ctx_unichar_to_utf8 (unicode, (uint8_t*)utf8)]=0; + +#if 0 + if (pixels_set == 8) + { + if (rgba[0][0] < 32 && rgba[0][1] < 32 && rgba[0][2] < 32) + { + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + continue; + } + } +#endif + { + ctx_term_set (term, col +1, row + 1, utf8, + rgba[0], rgba[1]); + } + } + } + } +} + + +inline static void ctx_term_render (void *ctx, + CtxCommand *command) +{ + CtxTerm *term = (void*)ctx; + /* directly forward */ + ctx_process (term->host, &command->entry); +} + +inline static void ctx_term_flush (CtxTerm *term) +{ + int width = term->width; + int height = term->height; + switch (term->mode) + { + case CTX_TERM_QUARTER: + ctx_term_output_buf_quarter (term->pixels, + width, height, term); + break; + case CTX_TERM_ASCII: + ctx_term_output_buf_ascii (term->pixels, + width, height, term, 0); + break; + case CTX_TERM_ASCII_MONO: + ctx_term_output_buf_ascii (term->pixels, + width, height, term, 1); + break; + case CTX_TERM_SEXTANT: + ctx_term_output_buf_sextant (term->pixels, + width, height, term); + break; + case CTX_TERM_BRAILLE: + ctx_term_output_buf_braille (term->pixels, + width, height, term, 0); + break; + case CTX_TERM_BRAILLE_MONO: + ctx_term_output_buf_braille (term->pixels, + width, height, term, 1); + break; + } +#if CTX_BRAILLE_TEXT + CtxRasterizer *rasterizer = (CtxRasterizer*)(term->host->renderer); + // XXX instead sort and inject along with braille + // + + //uint8_t rgba_bg[4]={0,0,0,0}; + //uint8_t rgba_fg[4]={255,0,255,255}; + + for (CtxList *l = rasterizer->glyphs; l; l = l->next) + { + CtxTermGlyph *glyph = l->data; + + uint8_t *pixels = term->pixels; + long rgb_sum[4]={0,0,0}; + for (int v = 0; v < ctx_term_ch; v ++) + for (int u = 0; u < ctx_term_cw; u ++) + { + int i = ((glyph->row-1) * ctx_term_ch + v) * rasterizer->blit_width + + ((glyph->col-1) * ctx_term_cw + u); + for (int c = 0; c < 3; c ++) + rgb_sum[c] += pixels[i*4+c]; + } + for (int c = 0; c < 3; c ++) + glyph->rgba_bg[c] = rgb_sum[c] / (ctx_term_ch * ctx_term_cw); + char utf8[8]; + utf8[ctx_unichar_to_utf8(glyph->unichar, (uint8_t*)utf8)]=0; + ctx_term_set (term, glyph->col, glyph->row, + utf8, glyph->rgba_fg, glyph->rgba_bg); + free (glyph); + } + + printf ("\e[H"); + printf ("\e[0m"); + ctx_term_scanout (term); + printf ("\e[0m"); + fflush(NULL); + while (rasterizer->glyphs) + ctx_list_remove (&rasterizer->glyphs, rasterizer->glyphs->data); +#endif +} + +void ctx_term_free (CtxTerm *term) +{ + while (term->lines) + { + free (term->lines->data); + ctx_list_remove (&term->lines, term->lines->data); + } + printf ("\e[?25h"); // cursor on + nc_at_exit (); + free (term->pixels); + ctx_free (term->host); + free (term); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +int ctx_renderer_is_term (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_term_free) + return 1; + return 0; +} + +float ctx_term_get_cell_width (Ctx *ctx) +{ + return ctx_term_cw; +} + +float ctx_term_get_cell_height (Ctx *ctx) +{ + return ctx_term_ch; +} + +Ctx *ctx_new_term (int width, int height) +{ + Ctx *ctx = ctx_new (); +#if CTX_RASTERIZER + CtxTerm *term = (CtxTerm*)calloc (sizeof (CtxTerm), 1); + + const char *mode = getenv ("CTX_TERM_MODE"); + ctx_term_cw = 2; + ctx_term_ch = 3; + + if (!mode) term->mode = CTX_TERM_SEXTANT; + else if (!strcmp (mode, "sextant")) term->mode = CTX_TERM_SEXTANT; + else if (!strcmp (mode, "ascii")) term->mode = CTX_TERM_ASCII_MONO; + //else if (!strcmp (mode, "ascii-mono")) term->mode = CTX_TERM_ASCII_MONO; + else if (!strcmp (mode, "quarter")) term->mode = CTX_TERM_QUARTER; + //else if (!strcmp (mode, "braille")){ + // term->mode = CTX_TERM_BRAILLE; + // ctx_term_ch = 4; + //} + else if (!strcmp (mode, "braille")){ + term->mode = CTX_TERM_BRAILLE_MONO; + ctx_term_ch = 4; + } + else { + fprintf (stderr, "recognized values for CTX_TERM_MODE:\n" + " sextant ascii quarter braille\n"); + exit (1); + } + + mode = getenv ("CTX_TERM_FORCE_FULL"); + if (mode && strcmp (mode, "0") && strcmp (mode, "no")) + _ctx_term_force_full = 1; + + fprintf (stdout, "\e[?1049h"); + fprintf (stdout, "\e[?25l"); // cursor off + + int maxwidth = ctx_terminal_cols () * ctx_term_cw; + int maxheight = (ctx_terminal_rows ()) * ctx_term_ch; + if (width <= 0 || height <= 0) + { + width = maxwidth; + height = maxheight; + } + if (width > maxwidth) width = maxwidth; + if (height > maxheight) height = maxheight; + term->ctx = ctx; + term->width = width; + term->height = height; + + term->cols = (width + 1) / ctx_term_cw; + term->rows = (height + 2) / ctx_term_ch; + term->lines = 0; + term->pixels = (uint8_t*)malloc (width * height * 4); + term->host = ctx_new_for_framebuffer (term->pixels, + width, height, + width * 4, CTX_FORMAT_RGBA8); +#if CTX_BRAILLE_TEXT + ((CtxRasterizer*)term->host->renderer)->term_glyphs=1; +#endif + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, term); + ctx_set_size (ctx, width, height); + ctx_font_size (ctx, ctx_term_ch); + term->render = ctx_term_render; + term->flush = (void(*)(void*))ctx_term_flush; + term->free = (void(*)(void*))ctx_term_free; +#endif + + + return ctx; +} + +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +typedef struct _CtxTermImg CtxTermImg; +struct _CtxTermImg +{ + void (*render) (void *termimg, CtxCommand *command); + void (*reset) (void *termimg); + void (*flush) (void *termimg); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *termimg); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + // we need to have the above members in that order up to here + uint8_t *pixels; + Ctx *host; + CtxList *lines; +}; + +inline static void ctx_termimg_render (void *ctx, + CtxCommand *command) +{ + CtxTermImg *termimg = (void*)ctx; + /* directly forward */ + ctx_process (termimg->host, &command->entry); +} + +inline static void ctx_termimg_flush (CtxTermImg *termimg) +{ + int width = termimg->width; + int height = termimg->height; + if (!termimg->pixels) return; + char *encoded = malloc (width * height * 3 * 3); + ctx_bin2base64 (termimg->pixels, width * height * 3, + encoded); + int encoded_len = strlen (encoded); + + int i = 0; + + printf ("\e[H"); + printf ("\e_Gf=24,s=%i,v=%i,t=d,a=T,m=1;\e\\", width, height); + while (i < encoded_len) + { + if (i + 4096 < encoded_len) + { + printf ("\e_Gm=1;"); + } + else + { + printf ("\e_Gm=0;"); + } + for (int n = 0; n < 4000 && i < encoded_len; n++) + { + printf ("%c", encoded[i]); + i++; + } + printf ("\e\\"); + } + free (encoded); + + fflush (NULL); +} + +void ctx_termimg_free (CtxTermImg *termimg) +{ + while (termimg->lines) + { + free (termimg->lines->data); + ctx_list_remove (&termimg->lines, termimg->lines->data); + } + printf ("\e[?25h"); // cursor on + nc_at_exit (); + free (termimg->pixels); + ctx_free (termimg->host); + free (termimg); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +int ctx_renderer_is_termimg (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_termimg_free) + return 1; + return 0; +} + +Ctx *ctx_new_termimg (int width, int height) +{ + Ctx *ctx = ctx_new (); +#if CTX_RASTERIZER + fprintf (stdout, "\e[?1049h"); + fprintf (stdout, "\e[?25l"); // cursor off + CtxTermImg *termimg = (CtxTermImg*)calloc (sizeof (CtxTermImg), 1); + + + int maxwidth = ctx_terminal_width (); + + int colwidth = maxwidth/ctx_terminal_cols (); + maxwidth-=colwidth; + + int maxheight = ctx_terminal_height (); + if (width <= 0 || height <= 0) + { + width = maxwidth; + height = maxheight; + } + if (width > maxwidth) width = maxwidth; + if (height > maxheight) height = maxheight; + termimg->ctx = ctx; + termimg->width = width; + termimg->height = height; + termimg->lines = 0; + termimg->pixels = (uint8_t*)malloc (width * height * 3); + termimg->host = ctx_new_for_framebuffer (termimg->pixels, + width, height, + width * 3, CTX_FORMAT_RGB8); + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, termimg); + ctx_set_size (ctx, width, height); + ctx_font_size (ctx, 14.0f); + termimg->render = ctx_termimg_render; + termimg->flush = (void(*)(void*))ctx_termimg_flush; + termimg->free = (void(*)(void*))ctx_termimg_free; +#endif + + return ctx; +} + +#endif + +#if CTX_FORMATTER + +typedef struct _CtxFormatter CtxFormatter; +struct _CtxFormatter +{ + void *target; // FILE + int longform; + int indent; + + void (*add_str)(CtxFormatter *formatter, const char *str, int len); +}; + +static inline void ctx_formatter_addstr (CtxFormatter *formatter, const char *str, int len) +{ + formatter->add_str (formatter, str, len); +} + +static inline void ctx_formatter_addstrf (CtxFormatter *formatter, const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*) malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + ctx_formatter_addstr (formatter, buffer, -1); + free (buffer); +} + +static void _ctx_stream_addstr (CtxFormatter *formatter, const char *str, int len) +{ + if (!str || len == 0) + { + return; + } + if (len < 0) len = strlen (str); + fwrite (str, len, 1, (FILE*)formatter->target); +} + +void _ctx_string_addstr (CtxFormatter *formatter, const char *str, int len) +{ + if (!str || len == 0) + { + return; + } + if (len < 0) len = strlen (str); + ctx_string_append_data ((CtxString*)(formatter->target), str, len); +} + + +static void _ctx_print_endcmd (CtxFormatter *formatter) +{ + if (formatter->longform) + { + ctx_formatter_addstr (formatter, ");\n", 3); + } +} + +static void _ctx_indent (CtxFormatter *formatter) +{ + for (int i = 0; i < formatter->indent; i++) + { ctx_formatter_addstr (formatter, " ", 2); + } +} + +const char *_ctx_code_to_name (int code) +{ + switch (code) + { + case CTX_REL_LINE_TO_X4: return "relLinetoX4"; break; + case CTX_REL_LINE_TO_REL_CURVE_TO: return "relLineToRelCurveTo"; break; + case CTX_REL_CURVE_TO_REL_LINE_TO: return "relCurveToRelLineTo"; break; + case CTX_REL_CURVE_TO_REL_MOVE_TO: return "relCurveToRelMoveTo"; break; + case CTX_REL_LINE_TO_X2: return "relLineToX2"; break; + case CTX_MOVE_TO_REL_LINE_TO: return "moveToRelLineTo"; break; + case CTX_REL_LINE_TO_REL_MOVE_TO: return "relLineToRelMoveTo"; break; + case CTX_FILL_MOVE_TO: return "fillMoveTo"; break; + case CTX_REL_QUAD_TO_REL_QUAD_TO: return "relQuadToRelQuadTo"; break; + case CTX_REL_QUAD_TO_S16: return "relQuadToS16"; break; + + case CTX_SET_KEY: return "setParam"; break; + case CTX_COLOR: return "setColor"; break; + case CTX_DEFINE_GLYPH: return "defineGlyph"; break; + case CTX_KERNING_PAIR: return "kerningPair"; break; + case CTX_SET_PIXEL: return "setPixel"; break; + case CTX_GLOBAL_ALPHA: return "globalAlpha"; break; + case CTX_TEXT: return "text"; break; + case CTX_STROKE_TEXT: return "strokeText"; break; + case CTX_SAVE: return "save"; break; + case CTX_RESTORE: return "restore"; break; + case CTX_STROKE_SOURCE: return "strokeSource"; break; + case CTX_NEW_PAGE: return "newPage"; break; + case CTX_START_GROUP: return "startGroup"; break; + case CTX_END_GROUP: return "endGroup"; break; + case CTX_RECTANGLE: return "rectangle"; break; + case CTX_ROUND_RECTANGLE: return "roundRectangle"; break; + case CTX_LINEAR_GRADIENT: return "linearGradient"; break; + case CTX_RADIAL_GRADIENT: return "radialGradient"; break; + case CTX_GRADIENT_STOP: return "gradientAddStop"; break; + case CTX_VIEW_BOX: return "viewBox"; break; + case CTX_MOVE_TO: return "moveTo"; break; + case CTX_LINE_TO: return "lineTo"; break; + case CTX_BEGIN_PATH: return "beginPath"; break; + case CTX_REL_MOVE_TO: return "relMoveTo"; break; + case CTX_REL_LINE_TO: return "relLineTo"; break; + case CTX_FILL: return "fill"; break; + case CTX_EXIT: return "exit"; break; + case CTX_APPLY_TRANSFORM: return "transform"; break; + case CTX_SOURCE_TRANSFORM: return "sourceTransform"; break; + case CTX_REL_ARC_TO: return "relArcTo"; break; + case CTX_GLYPH: return "glyph"; break; + case CTX_TEXTURE: return "texture"; break; + case CTX_DEFINE_TEXTURE: return "defineTexture"; break; + case CTX_IDENTITY: return "identity"; break; + case CTX_CLOSE_PATH: return "closePath"; break; + case CTX_PRESERVE: return "preserve"; break; + case CTX_FLUSH: return "flush"; break; + case CTX_RESET: return "reset"; break; + case CTX_FONT: return "font"; break; + case CTX_STROKE: return "stroke"; break; + case CTX_CLIP: return "clip"; break; + case CTX_ARC: return "arc"; break; + case CTX_SCALE: return "scale"; break; + case CTX_TRANSLATE: return "translate"; break; + case CTX_ROTATE: return "rotate"; break; + case CTX_ARC_TO: return "arcTo"; break; + case CTX_CURVE_TO: return "curveTo"; break; + case CTX_REL_CURVE_TO: return "relCurveTo"; break; + case CTX_REL_QUAD_TO: return "relQuadTo"; break; + case CTX_QUAD_TO: return "quadTo"; break; + case CTX_SMOOTH_TO: return "smoothTo"; break; + case CTX_REL_SMOOTH_TO: return "relSmoothTo"; break; + case CTX_SMOOTHQ_TO: return "smoothqTo"; break; + case CTX_REL_SMOOTHQ_TO: return "relSmoothqTo"; break; + case CTX_HOR_LINE_TO: return "horLineTo"; break; + case CTX_VER_LINE_TO: return "verLineTo"; break; + case CTX_REL_HOR_LINE_TO: return "relHorLineTo"; break; + case CTX_REL_VER_LINE_TO: return "relVerLineTo"; break; + case CTX_COMPOSITING_MODE: return "compositingMode"; break; + case CTX_BLEND_MODE: return "blendMode"; break; + case CTX_TEXT_ALIGN: return "textAlign"; break; + case CTX_TEXT_BASELINE: return "textBaseline"; break; + case CTX_TEXT_DIRECTION: return "textDirection"; break; + case CTX_FONT_SIZE: return "fontSize"; break; + case CTX_MITER_LIMIT: return "miterLimit"; break; + case CTX_LINE_JOIN: return "lineJoin"; break; + case CTX_LINE_CAP: return "lineCap"; break; + case CTX_LINE_WIDTH: return "lineWidth"; break; + case CTX_LINE_DASH_OFFSET: return "lineDashOffset"; break; + case CTX_IMAGE_SMOOTHING: return "imageSmoothing"; break; + case CTX_SHADOW_BLUR: return "shadowBlur"; break; + case CTX_FILL_RULE: return "fillRule"; break; + } + return NULL; +} + +static void _ctx_print_name (CtxFormatter *formatter, int code) +{ +#define CTX_VERBOSE_NAMES 1 +#if CTX_VERBOSE_NAMES + if (formatter->longform) + { + const char *name = NULL; + _ctx_indent (formatter); + //switch ((CtxCode)code) + name = _ctx_code_to_name (code); + if (name) + { + ctx_formatter_addstr (formatter, name, -1); + ctx_formatter_addstr (formatter, " (", 2); + if (code == CTX_SAVE) + { formatter->indent ++; } + else if (code == CTX_RESTORE) + { formatter->indent --; } + return; + } + } +#endif + { + char name[3]; + name[0]=CTX_SET_KEY; + name[2]='\0'; + switch (code) + { + case CTX_GLOBAL_ALPHA: name[1]='a'; break; + case CTX_COMPOSITING_MODE: name[1]='m'; break; + case CTX_BLEND_MODE: name[1]='B'; break; + case CTX_TEXT_ALIGN: name[1]='t'; break; + case CTX_TEXT_BASELINE: name[1]='b'; break; + case CTX_TEXT_DIRECTION: name[1]='d'; break; + case CTX_FONT_SIZE: name[1]='f'; break; + case CTX_MITER_LIMIT: name[1]='l'; break; + case CTX_LINE_JOIN: name[1]='j'; break; + case CTX_LINE_CAP: name[1]='c'; break; + case CTX_LINE_WIDTH: name[1]='w'; break; + case CTX_LINE_DASH_OFFSET: name[1]='D'; break; + case CTX_IMAGE_SMOOTHING: name[1]='S'; break; + case CTX_SHADOW_BLUR: name[1]='s'; break; + case CTX_SHADOW_COLOR: name[1]='C'; break; + case CTX_SHADOW_OFFSET_X: name[1]='x'; break; + case CTX_SHADOW_OFFSET_Y: name[1]='y'; break; + case CTX_FILL_RULE: name[1]='r'; break; + default: + name[0] = code; + name[1] = 0; + break; + } + ctx_formatter_addstr (formatter, name, -1); + if (formatter->longform) + ctx_formatter_addstr (formatter, " (", 2); + else + ctx_formatter_addstr (formatter, " ", 1); + } +} + +static void +ctx_print_entry_enum (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + for (int i = 0; i < args; i ++) + { + int val = ctx_arg_u8 (i); + if (i>0) + { + ctx_formatter_addstr (formatter, " ", 1); + } +#if CTX_VERBOSE_NAMES + if (formatter->longform) + { + const char *str = NULL; + switch (entry->code) + { + case CTX_TEXT_BASELINE: + switch (val) + { + case CTX_TEXT_BASELINE_ALPHABETIC: str = "alphabetic"; break; + case CTX_TEXT_BASELINE_TOP: str = "top"; break; + case CTX_TEXT_BASELINE_BOTTOM: str = "bottom"; break; + case CTX_TEXT_BASELINE_HANGING: str = "hanging"; break; + case CTX_TEXT_BASELINE_MIDDLE: str = "middle"; break; + case CTX_TEXT_BASELINE_IDEOGRAPHIC:str = "ideographic";break; + } + break; + case CTX_TEXT_ALIGN: + switch (val) + { + case CTX_TEXT_ALIGN_LEFT: str = "left"; break; + case CTX_TEXT_ALIGN_RIGHT: str = "right"; break; + case CTX_TEXT_ALIGN_START: str = "start"; break; + case CTX_TEXT_ALIGN_END: str = "end"; break; + case CTX_TEXT_ALIGN_CENTER: str = "center"; break; + } + break; + case CTX_LINE_CAP: + switch (val) + { + case CTX_CAP_NONE: str = "none"; break; + case CTX_CAP_ROUND: str = "round"; break; + case CTX_CAP_SQUARE: str = "square"; break; + } + break; + case CTX_LINE_JOIN: + switch (val) + { + case CTX_JOIN_MITER: str = "miter"; break; + case CTX_JOIN_ROUND: str = "round"; break; + case CTX_JOIN_BEVEL: str = "bevel"; break; + } + break; + case CTX_FILL_RULE: + switch (val) + { + case CTX_FILL_RULE_WINDING: str = "winding"; break; + case CTX_FILL_RULE_EVEN_ODD: str = "evenodd"; break; + } + break; + case CTX_BLEND_MODE: + val = ctx_arg_u32 (i); + switch (val) + { + case CTX_BLEND_NORMAL: str = "normal"; break; + case CTX_BLEND_MULTIPLY: str = "multiply"; break; + case CTX_BLEND_SCREEN: str = "screen"; break; + case CTX_BLEND_OVERLAY: str = "overlay"; break; + case CTX_BLEND_DARKEN: str = "darken"; break; + case CTX_BLEND_LIGHTEN: str = "lighten"; break; + case CTX_BLEND_COLOR_DODGE: str = "colorDodge"; break; + case CTX_BLEND_COLOR_BURN: str = "colorBurn"; break; + case CTX_BLEND_HARD_LIGHT: str = "hardLight"; break; + case CTX_BLEND_SOFT_LIGHT: str = "softLight"; break; + case CTX_BLEND_DIFFERENCE: str = "difference"; break; + case CTX_BLEND_EXCLUSION: str = "exclusion"; break; + case CTX_BLEND_HUE: str = "hue"; break; + case CTX_BLEND_SATURATION: str = "saturation"; break; + case CTX_BLEND_COLOR: str = "color"; break; + case CTX_BLEND_LUMINOSITY: str = "luminosity"; break; + } + break; + case CTX_COMPOSITING_MODE: + val = ctx_arg_u32 (i); + switch (val) + { + case CTX_COMPOSITE_SOURCE_OVER: str = "sourceOver"; break; + case CTX_COMPOSITE_COPY: str = "copy"; break; + case CTX_COMPOSITE_CLEAR: str = "clear"; break; + case CTX_COMPOSITE_SOURCE_IN: str = "sourceIn"; break; + case CTX_COMPOSITE_SOURCE_OUT: str = "sourceOut"; break; + case CTX_COMPOSITE_SOURCE_ATOP: str = "sourceAtop"; break; + case CTX_COMPOSITE_DESTINATION: str = "destination"; break; + case CTX_COMPOSITE_DESTINATION_OVER: str = "destinationOver"; break; + case CTX_COMPOSITE_DESTINATION_IN: str = "destinationIn"; break; + case CTX_COMPOSITE_DESTINATION_OUT: str = "destinationOut"; break; + case CTX_COMPOSITE_DESTINATION_ATOP: str = "destinationAtop"; break; + case CTX_COMPOSITE_XOR: str = "xor"; break; + } + + break; + } + if (str) + { + ctx_formatter_addstr (formatter, str, -1); + } + else + { + ctx_formatter_addstrf (formatter, "%i", val); + } + } + else +#endif + { + ctx_formatter_addstrf (formatter, "%i", val); + } + } + _ctx_print_endcmd (formatter); +} + + +static void +ctx_print_a85 (CtxFormatter *formatter, uint8_t *data, int length) +{ + char *tmp = (char*)malloc (ctx_a85enc_len (length)); + ctx_a85enc (data, tmp, length); + ctx_formatter_addstr (formatter, " ~", 2); + ctx_formatter_addstr (formatter, tmp, -1); + ctx_formatter_addstr (formatter, "~ ", 2); + free (tmp); +} + +static void +ctx_print_yenc (CtxFormatter *formatter, uint8_t *data, int length) +{ + char *tmp = (char*)malloc (length * 2 + 2);// worst case scenario + int enclength = ctx_yenc ((char*)data, tmp, length); + data[enclength]=0; + ctx_formatter_addstr (formatter, " =", 2); + ctx_formatter_addstr (formatter, tmp, enclength); + ctx_formatter_addstr (formatter, "=y ", 2); + free (tmp); +} + +static void +ctx_print_escaped_string (CtxFormatter *formatter, const char *string) +{ + if (!string) { return; } + for (int i = 0; string[i]; i++) + { + switch (string[i]) + { + case '"': + ctx_formatter_addstr (formatter, "\\\"", 2); + break; + case '\\': + ctx_formatter_addstr (formatter, "\\\\", 2); + break; + case '\n': + ctx_formatter_addstr (formatter, "\\n", 2); + break; + default: + ctx_formatter_addstr (formatter, &string[i], 1); + } + } +} + +static void +ctx_print_float (CtxFormatter *formatter, float val) +{ + char temp[128]; + sprintf (temp, "%0.3f", val); + int j; + for (j = 0; temp[j]; j++) + if (j == ',') { temp[j] = '.'; } + j--; + if (j>0) + while (temp[j] == '0') + { + temp[j]=0; + j--; + } + if (temp[j]=='.') + { temp[j]='\0'; } + ctx_formatter_addstr (formatter, temp, -1); +} + +static void +ctx_print_int (CtxFormatter *formatter, int val) +{ + ctx_formatter_addstrf (formatter, "%i", val); +} + +static void +ctx_print_entry (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + for (int i = 0; i < args; i ++) + { + float val = ctx_arg_float (i); + if (i>0 && val >= 0.0f) + { + if (formatter->longform) + { + ctx_formatter_addstr (formatter, ", ", 2); + } + else + { + if (val >= 0.0f) + ctx_formatter_addstr (formatter, " ", 1); + } + } + ctx_print_float (formatter, val); + } + _ctx_print_endcmd (formatter); +} + +static void +ctx_print_glyph (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "%i", entry->data.u32[0]); + _ctx_print_endcmd (formatter); +} + +static void +ctx_formatter_process (void *user_data, CtxCommand *c); + + +static void +ctx_formatter_process (void *user_data, CtxCommand *c) +{ + CtxEntry *entry = &c->entry; + CtxFormatter *formatter = (CtxFormatter*)user_data; + + switch (entry->code) + //switch ((CtxCode)(entry->code)) + { + case CTX_GLYPH: + ctx_print_glyph (formatter, entry, 1); + break; + case CTX_LINE_TO: + case CTX_REL_LINE_TO: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_MOVE_TO: + case CTX_REL_MOVE_TO: + case CTX_SMOOTHQ_TO: + case CTX_REL_SMOOTHQ_TO: + ctx_print_entry (formatter, entry, 2); + break; + case CTX_TEXTURE: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, c->texture.eid); + ctx_formatter_addstrf (formatter, "\", "); + ctx_print_float (formatter, c->texture.x); + ctx_formatter_addstrf (formatter, ", "); + ctx_print_float (formatter, c->texture.y); + ctx_formatter_addstrf (formatter, " "); + _ctx_print_endcmd (formatter); + break; + + case CTX_DEFINE_TEXTURE: + { + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, c->define_texture.eid); + ctx_formatter_addstrf (formatter, "\", "); + ctx_print_int (formatter, c->define_texture.width); + ctx_formatter_addstrf (formatter, ", "); + ctx_print_int (formatter, c->define_texture.height); + ctx_formatter_addstrf (formatter, ",%i, ", c->define_texture.format); + + uint8_t *pixel_data = ctx_define_texture_pixel_data (entry); +#if 1 + + int stride = ctx_pixel_format_get_stride ((CtxPixelFormat)c->define_texture.format, c->define_texture.width); + int data_len = stride * c->define_texture.height; + if (c->define_texture.format == CTX_FORMAT_YUV420) + data_len = c->define_texture.height * c->define_texture.width + + 2*(c->define_texture.height/2) * (c->define_texture.width/2); + //fprintf (stderr, "encoding %i bytes\n", c->define_texture.height *stride); + //ctx_print_a85 (formatter, pixel_data, c->define_texture.height * stride); + ctx_print_yenc (formatter, pixel_data, data_len); +#else + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, pixel_data); + ctx_formatter_addstrf (formatter, "\" "); + +#endif + + _ctx_print_endcmd (formatter); + } + break; + + case CTX_REL_ARC_TO: + case CTX_ARC_TO: + case CTX_ROUND_RECTANGLE: + ctx_print_entry (formatter, entry, 5); + break; + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_ARC: + case CTX_RADIAL_GRADIENT: + case CTX_APPLY_TRANSFORM: + ctx_print_entry (formatter, entry, 6); + break; + case CTX_SOURCE_TRANSFORM: + ctx_print_entry (formatter, entry, 6); + break; + case CTX_QUAD_TO: + case CTX_RECTANGLE: + case CTX_REL_QUAD_TO: + case CTX_LINEAR_GRADIENT: + case CTX_VIEW_BOX: + case CTX_SMOOTH_TO: + case CTX_REL_SMOOTH_TO: + ctx_print_entry (formatter, entry, 4); + break; + case CTX_FONT_SIZE: + case CTX_MITER_LIMIT: + case CTX_ROTATE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_GLOBAL_ALPHA: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_VER_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_REL_VER_LINE_TO: + case CTX_REL_HOR_LINE_TO: + ctx_print_entry (formatter, entry, 1); + break; +#if 0 + case CTX_SET: + _ctx_print_name (formatter, entry->code); + switch (c->set.key_hash) + { + case CTX_x: ctx_formatter_addstrf (formatter, " 'x' "); break; + case CTX_y: ctx_formatter_addstrf (formatter, " 'y' "); break; + case CTX_width: ctx_formatter_addstrf (formatter, " width "); break; + case CTX_height: ctx_formatter_addstrf (formatter, " height "); break; + default: + ctx_formatter_addstrf (formatter, " %d ", c->set.key_hash); + } + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, (char*)c->set.utf8); + ctx_formatter_addstrf (formatter, "\""); + _ctx_print_endcmd (formatter); + break; +#endif + case CTX_COLOR: + if (formatter->longform || 1) + { + _ctx_indent (formatter); + int model = (int) c->set_color.model; + const char *suffix=""; + if (model & 512) + { + model = model & 511; + suffix = "S"; + } + switch (model) + { + case CTX_GRAY: + ctx_formatter_addstrf (formatter, "gray%s ", suffix); + ctx_print_float (formatter, c->graya.g); + break; + case CTX_GRAYA: + ctx_formatter_addstrf (formatter, "graya%s ", suffix); + ctx_print_float (formatter, c->graya.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->graya.a); + break; + case CTX_RGBA: + if (c->rgba.a != 1.0) + { + ctx_formatter_addstrf (formatter, "rgba%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.a); + break; + } + /* FALLTHROUGH */ + case CTX_RGB: + if (c->rgba.r == c->rgba.g && c->rgba.g == c->rgba.b) + { + ctx_formatter_addstrf (formatter, "gray%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + break; + } + ctx_formatter_addstrf (formatter, "rgb%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + break; + case CTX_DRGB: + ctx_formatter_addstrf (formatter, "drgb%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + break; + case CTX_DRGBA: + ctx_formatter_addstrf (formatter, "drgba%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.a); + break; + case CTX_CMYK: + ctx_formatter_addstrf (formatter, "cmyk%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + break; + case CTX_CMYKA: + ctx_formatter_addstrf (formatter, "cmyka%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_formatter_addstrf (formatter, "dcmyk%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + break; + case CTX_DCMYKA: + ctx_formatter_addstrf (formatter, "dcmyka%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.a); + break; + } + } + else + { + ctx_print_entry (formatter, entry, 1); + } + break; + case CTX_SET_RGBA_U8: + if (formatter->longform) + { + _ctx_indent (formatter); + ctx_formatter_addstrf (formatter, "rgba ("); + } + else + { + ctx_formatter_addstrf (formatter, "rgba ("); + } + for (int c = 0; c < 4; c++) + { + if (c) + { + if (formatter->longform) + ctx_formatter_addstrf (formatter, ", "); + else + ctx_formatter_addstrf (formatter, " "); + } + ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (c) ) ); + } + _ctx_print_endcmd (formatter); + break; + case CTX_SET_PIXEL: +#if 0 + ctx_set_pixel_u8 (d_ctx, + ctx_arg_u16 (2), ctx_arg_u16 (3), + ctx_arg_u8 (0), + ctx_arg_u8 (1), + ctx_arg_u8 (2), + ctx_arg_u8 (3) ); +#endif + break; + case CTX_FILL: + case CTX_RESET: + case CTX_STROKE: + case CTX_IDENTITY: + case CTX_CLIP: + case CTX_BEGIN_PATH: + case CTX_CLOSE_PATH: + case CTX_SAVE: + case CTX_PRESERVE: + case CTX_START_GROUP: + case CTX_NEW_PAGE: + case CTX_END_GROUP: + case CTX_RESTORE: + case CTX_STROKE_SOURCE: + ctx_print_entry (formatter, entry, 0); + break; + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_FILL_RULE: + case CTX_LINE_CAP: + case CTX_LINE_JOIN: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_IMAGE_SMOOTHING: + ctx_print_entry_enum (formatter, entry, 1); + break; + case CTX_GRADIENT_STOP: + _ctx_print_name (formatter, entry->code); + for (int c = 0; c < 4; c++) + { + if (c) + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (4+c) ) ); + } + _ctx_print_endcmd (formatter); + break; + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_FONT: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, ctx_arg_string() ); + ctx_formatter_addstrf (formatter, "\""); + _ctx_print_endcmd (formatter); + break; + case CTX_CONT: + case CTX_EDGE: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_FLUSH: + break; + case CTX_KERNING_PAIR: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + { + uint8_t utf8[16]; + utf8[ctx_unichar_to_utf8 (c->kern.glyph_before, utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\", \""); + utf8[ctx_unichar_to_utf8 (c->kern.glyph_after, utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\""); + sprintf ((char*)utf8, ", %f", c->kern.amount / 256.0); + ctx_print_escaped_string (formatter, (char*)utf8); + } + _ctx_print_endcmd (formatter); + break; + + case CTX_DEFINE_GLYPH: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + { + uint8_t utf8[16]; + utf8[ctx_unichar_to_utf8 (entry->data.u32[0], utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\""); + sprintf ((char*)utf8, ", %f", entry->data.u32[1]/256.0); + ctx_print_escaped_string (formatter, (char*)utf8); + } + _ctx_print_endcmd (formatter); + break; + } +} + +void +ctx_render_stream (Ctx *ctx, FILE *stream, int longform) +{ + CtxIterator iterator; + CtxFormatter formatter; + formatter.target= stream; + formatter.longform = longform; + formatter.indent = 0; + formatter.add_str = _ctx_stream_addstr; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_formatter_process (&formatter, command); } + fprintf (stream, "\n"); +} + +char * +ctx_render_string (Ctx *ctx, int longform, int *retlen) +{ + CtxString *string = ctx_string_new (""); + CtxIterator iterator; + CtxFormatter formatter; + formatter.target= string; + formatter.longform = longform; + formatter.indent = 0; + formatter.add_str = _ctx_string_addstr; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_formatter_process (&formatter, command); } + char *ret = string->str; + if (retlen) + *retlen = string->length; + ctx_string_free (string, 0); + return ret; +} + + +#endif + +#if CTX_EVENTS +int ctx_width (Ctx *ctx) +{ + return ctx->events.width; +} +int ctx_height (Ctx *ctx) +{ + return ctx->events.height; +} +#else +int ctx_width (Ctx *ctx) +{ + return 512; +} +int ctx_height (Ctx *ctx) +{ + return 384; +} +#endif + +int ctx_rev (Ctx *ctx) +{ + return ctx->rev; +} + +CtxState *ctx_get_state (Ctx *ctx) +{ + return &ctx->state; +} + +void ctx_dirty_rect (Ctx *ctx, int *x, int *y, int *width, int *height) +{ + if ( (ctx->state.min_x > ctx->state.max_x) || + (ctx->state.min_y > ctx->state.max_y) ) + { + if (x) { *x = 0; } + if (y) { *y = 0; } + if (width) { *width = 0; } + if (height) { *height = 0; } + return; + } + if (ctx->state.min_x < 0) + { ctx->state.min_x = 0; } + if (ctx->state.min_y < 0) + { ctx->state.min_y = 0; } + if (x) { *x = ctx->state.min_x; } + if (y) { *y = ctx->state.min_y; } + if (width) { *width = ctx->state.max_x - ctx->state.min_x; } + if (height) { *height = ctx->state.max_y - ctx->state.min_y; } +} + +#if CTX_CURRENT_PATH +CtxIterator * +ctx_current_path (Ctx *ctx) +{ + CtxIterator *iterator = &ctx->current_path_iterator; + ctx_iterator_init (iterator, &ctx->current_path, 0, CTX_ITERATOR_EXPAND_BITPACK); + return iterator; +} + +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2) +{ + float minx = 50000.0; + float miny = 50000.0; + float maxx = -50000.0; + float maxy = -50000.0; + float x = 0; + float y = 0; + + CtxIterator *iterator = ctx_current_path (ctx); + CtxCommand *command; + + while ((command = ctx_iterator_next (iterator))) + { + int got_coord = 0; + switch (command->code) + { + // XXX missing many curve types + case CTX_LINE_TO: + case CTX_MOVE_TO: + x = command->move_to.x; + y = command->move_to.y; + got_coord++; + break; + case CTX_REL_LINE_TO: + case CTX_REL_MOVE_TO: + x += command->move_to.x; + y += command->move_to.y; + got_coord++; + break; + case CTX_CURVE_TO: + x = command->curve_to.x; + y = command->curve_to.y; + got_coord++; + break; + case CTX_REL_CURVE_TO: + x += command->curve_to.x; + y += command->curve_to.y; + got_coord++; + break; + case CTX_ARC: + minx = ctx_minf (minx, command->arc.x - command->arc.radius); + miny = ctx_minf (miny, command->arc.y - command->arc.radius); + maxx = ctx_maxf (maxx, command->arc.x + command->arc.radius); + maxy = ctx_maxf (maxy, command->arc.y + command->arc.radius); + + break; + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: + x = command->rectangle.x; + y = command->rectangle.y; + minx = ctx_minf (minx, x); + miny = ctx_minf (miny, y); + maxx = ctx_maxf (maxx, x); + maxy = ctx_maxf (maxy, y); + + x += command->rectangle.width; + y += command->rectangle.height; + got_coord++; + break; + } + if (got_coord) + { + minx = ctx_minf (minx, x); + miny = ctx_minf (miny, y); + maxx = ctx_maxf (maxx, x); + maxy = ctx_maxf (maxy, y); + } + } + + if (ex1) *ex1 = minx; + if (ey1) *ey1 = miny; + if (ex2) *ex2 = maxx; + if (ey2) *ey2 = maxy; +} + +#else +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2) +{ +} +#endif + + +static inline void +ctx_gstate_push (CtxState *state) +{ + if (state->gstate_no + 1 >= CTX_MAX_STATES) + { return; } + state->gstate_stack[state->gstate_no] = state->gstate; + state->gstate_no++; + ctx_state_set (state, CTX_new_state, 0.0); + state->has_clipped=0; +} + +static inline void +ctx_gstate_pop (CtxState *state) +{ + if (state->gstate_no <= 0) + { return; } + state->gstate = state->gstate_stack[state->gstate_no-1]; + state->gstate_no--; +} + +void +ctx_close_path (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_CLOSE_PATH); +} + +int _ctx_is_rasterizer (Ctx *ctx); + +void +ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh, + CtxPixelFormat format, int dst_stride, + uint8_t *dst_data) +{ + if (0) + { + } +#if CTX_RASTERIZER + else if (_ctx_is_rasterizer (ctx)) + { + CtxRasterizer *rasterizer = (CtxRasterizer*)ctx->renderer; + if (rasterizer->format->pixel_format == format) + { + if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw); + int bytes_per_pix = rasterizer->format->bpp/8; + int y = 0; + for (int v = sy; v < sy + sh; v++, y++) + { + int x = 0; + for (int u = sx; u < sx + sw; u++, x++) + { + uint8_t* src_buf = (uint8_t*)rasterizer->buf; + memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * rasterizer->blit_stride + u * bytes_per_pix], bytes_per_pix); + } + } + return; + } + } +#endif +#if CTX_FB + else if (format == CTX_FORMAT_RGBA8 && + ( + ctx_renderer_is_fb (ctx) +#if CTX_SDL + || ctx_renderer_is_sdl (ctx) +#endif + )) + { + CtxTiled *tiled = (CtxTiled*)ctx->renderer; + { + if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw); + int bytes_per_pix = 4; + int y = 0; + for (int v = sy; v < sy + sh; v++, y++) + { + int x = 0; + for (int u = sx; u < sx + sw; u++, x++) + { + uint8_t* src_buf = (uint8_t*)tiled->pixels; + memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * tiled->width * bytes_per_pix + u * bytes_per_pix], bytes_per_pix); + } + } + return; + } + } +#endif +} + +void +ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format, + uint8_t *data, + int ox, int oy, + int dirtyX, int dirtyY, + int dirtyWidth, int dirtyHeight) +{ + char eid[65]=""; + ctx_save (ctx); + ctx_identity (ctx); + ctx_define_texture (ctx, NULL, w, h, stride, format, data, eid); + if (eid[0]) + { + // XXX set compositor to source + ctx_compositing_mode (ctx, CTX_COMPOSITE_COPY); + ctx_draw_texture_clipped (ctx, eid, ox, oy, w, h, dirtyX, dirtyY, dirtyWidth, dirtyHeight); + } + ctx_restore (ctx); +} + +/* checking if an eid is valid also sets the frame for it + */ +static int ctx_eid_valid (Ctx *ctx, const char *eid, int *w, int *h) +{ + ctx = ctx->texture_cache; + CtxList *to_remove = NULL; + int ret = 0; + //fprintf (stderr, "{%i}\n", ctx->frame); + for (CtxList *l = ctx->eid_db; l; l = l->next) + { + CtxEidInfo *eid_info = (CtxEidInfo*)l->data; + if (ctx->frame - eid_info->frame >= 2) + /* XXX XXX XXX this breaks texture caching since + * it is wrong in some cases where more frames + * have passed? + */ + { + ctx_list_prepend (&to_remove, eid_info); + } + else if (!strcmp (eid_info->eid, eid) && + ctx->frame - eid_info->frame < 2) + { + //fclose (f); + eid_info->frame = ctx->frame; + if (w) *w = eid_info->width; + if (h) *h = eid_info->height; + ret = 1; + } + } + while (to_remove) + { + CtxEidInfo *eid_info = (CtxEidInfo*)to_remove->data; + //FILE *f = fopen ("/tmp/l", "a"); + //fprintf (f, "%i client removing %s\n", getpid(), eid_info->eid); + //fclose (f); + free (eid_info->eid); + free (eid_info); + ctx_list_remove (&ctx->eid_db, eid_info); + ctx_list_remove (&to_remove, eid_info); + } + return ret; +} + +void ctx_texture (Ctx *ctx, const char *eid, float x, float y) +{ + int eid_len = strlen (eid); + char ascii[41]=""; + //fprintf (stderr, "tx %s\n", eid); + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid=ascii; + } + + //FILE *f = fopen ("/tmp/l", "a"); + if (ctx_eid_valid (ctx, eid, 0, 0)) + { + ctx_process_cmd_str_float (ctx, CTX_TEXTURE, eid, x, y); + //fprintf (stderr, "setting texture eid %s\n", eid); + } + else + { + //fprintf (stderr, "tried setting invalid texture eid %s\n", eid); + } + //fclose (f); +} +int +_ctx_frame (Ctx *ctx) +{ + return ctx->frame; +} +int +_ctx_set_frame (Ctx *ctx, int frame) +{ + return ctx->frame = frame; +} + +void ctx_define_texture (Ctx *ctx, + const char *eid, + int width, int height, int stride, int format, void *data, + char *ret_eid) +{ + uint8_t hash[20]=""; + char ascii[41]=""; + int dst_stride = width; + //fprintf (stderr, "df %s\n", eid); + + dst_stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width); + if (stride <= 0) + stride = dst_stride; + + int data_len; + + if (format == CTX_FORMAT_YUV420) + data_len = width * height + ((width/2) * (height/2)) * 2; + else + data_len = height * dst_stride; + + if (eid == NULL) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t *src = (uint8_t*)data; + for (int y = 0; y < height; y++) + { + ctx_sha1_process (sha1, src, dst_stride); + src += stride; + } + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2] =hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + } + + int eid_len = strlen (eid); + + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2] =hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + eid_len = 40; + } + + // we now have eid + + if (ctx_eid_valid (ctx, eid, 0, 0)) + { + ctx_texture (ctx, eid, 0.0, 0.0); + } + else + + { + CtxEntry *commands; + int command_size = 1 + (data_len+1+1)/9 + 1 + (eid_len+1+1)/9 + 1 + 8; + if (ctx->renderer && ctx->renderer->process) + { + commands = (CtxEntry*)calloc (sizeof (CtxEntry), command_size); + } + else + { + commands = NULL; + ctx_drawlist_resize (&ctx->drawlist, ctx->drawlist.count + command_size); + commands = &(ctx->drawlist.entries[ctx->drawlist.count]); + memset (commands, 0, sizeof (CtxEntry) * command_size); + } + /* bottleneck, we can avoid copying sometimes - and even when copying + * we should cut this down to one copy, direct to the drawlist. + * + */ + commands[0] = ctx_u32 (CTX_DEFINE_TEXTURE, width, height); + commands[1].data.u16[0] = format; + + int pos = 2; + + commands[pos].code = CTX_DATA; + commands[pos].data.u32[0] = eid_len; + commands[pos].data.u32[1] = (eid_len+1+1)/9 + 1; + memcpy ((char *) &commands[pos+1].data.u8[0], eid, eid_len); + ((char *) &commands[pos+1].data.u8[0])[eid_len]=0; + + pos = 2 + 1 + ctx_conts_for_entry (&commands[2]); + commands[pos].code = CTX_DATA; + commands[pos].data.u32[0] = data_len; + commands[pos].data.u32[1] = (data_len+1+1)/9 + 1; + { + uint8_t *src = (uint8_t*)data; + uint8_t *dst = &commands[pos+1].data.u8[0]; +#if 1 + memcpy (dst, src, data_len); +#else + for (int y = 0; y < height; y++) + { + memcpy (dst, src, dst_stride); + src += stride; + dst += dst_stride; + } +#endif + } + ((char *) &commands[pos+1].data.u8[0])[data_len]=0; + + if (ctx->renderer && ctx->renderer->process) + { + ctx_process (ctx, commands); + free (commands); + } + else + { + ctx->drawlist.count += ctx_conts_for_entry (commands) + 1; + } + + CtxEidInfo *eid_info = (CtxEidInfo*)calloc (sizeof (CtxEidInfo), 1); + eid_info->width = width; + eid_info->height = height; + eid_info->frame = ctx->texture_cache->frame; + //fprintf (stderr, "%i\n", eid_info->frame); + eid_info->eid = strdup (eid); + ctx_list_prepend (&ctx->texture_cache->eid_db, eid_info); + } + + if (ret_eid) + { + strcpy (ret_eid, eid); + ret_eid[64]=0; + } +} + +void +ctx_texture_load (Ctx *ctx, const char *path, int *tw, int *th, char *reid) +{ + const char *eid = path; + char ascii[41]=""; + int eid_len = strlen (eid); + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + } + + if (ctx_eid_valid (ctx, eid , tw, th)) + { + if (reid) + { + strcpy (reid, eid); + } + return; + } + +#ifdef STBI_INCLUDE_STB_IMAGE_H + CtxPixelFormat pixel_format = CTX_FORMAT_RGBA8; + int w, h, components; + unsigned char *pixels = NULL; + + if (!strncmp (path, "file://", 7)) + { + pixels = stbi_load (path + 7, &w, &h, &components, 0); + } + else + { + unsigned char *data = NULL; + long length = 0; + ctx_get_contents (path, &data, &length); + if (data) + { + pixels = stbi_load_from_memory (data, length, &w, &h, &components, 0); + free (data); + } + } + + if (pixels) + { + switch (components) + { + case 1: pixel_format = CTX_FORMAT_GRAY8; break; + case 2: pixel_format = CTX_FORMAT_GRAYA8; break; + case 3: pixel_format = CTX_FORMAT_RGB8; break; + case 4: pixel_format = CTX_FORMAT_RGBA8; break; + } + if (tw) *tw = w; + if (th) *th = h; + ctx_define_texture (ctx, eid, w, h, w * components, pixel_format, pixels, reid); + free (pixels); + } + else + { + fprintf (stderr, "texture loading problem for %s\n", path); + } +#endif +} + +void +ctx_draw_texture_clipped (Ctx *ctx, const char *eid, + float x, float y, + float width, float height, + float clip_x, float clip_y, + float clip_width, float clip_height) +{ + int tex_width = 0; + int tex_height = 0; + if (ctx_eid_valid (ctx, eid , &tex_width, &tex_height)) + { + if (width > 0.0 && height > 0.0) + { + ctx_save (ctx); +#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); + if (clip_width > 0.0f) + { + ctx_translate (ctx, -clip_x, -clip_y); + ctx_scale (ctx, width/clip_width, height/clip_height); + } + else + { + ctx_scale (ctx, width/tex_width, height/tex_height); + } + //ctx_texture (ctx, eid, x / (width/tex_width), y / (height/tex_height)); + ctx_texture (ctx, eid, x, y);// / (width/tex_width), y / (height/tex_height)); + ctx_fill (ctx); + ctx_restore (ctx); + } + } +} + +void ctx_draw_texture (Ctx *ctx, const char *eid, float x, float y, float w, float h) +{ + ctx_draw_texture_clipped (ctx, eid, x, y, w, h, 0,0,0,0); +} + +void ctx_draw_image_clipped (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight) +{ + char reteid[65]; + int width, height; + ctx_texture_load (ctx, path, &width, &height, reteid); + if (reteid[0]) + { + ctx_draw_texture_clipped (ctx, reteid, x, y, w, h, sx, sy, swidth, sheight); + } +} + +void +ctx_draw_image (Ctx *ctx, const char *path, float x, float y, float w, float h) +{ + ctx_draw_image_clipped (ctx, path, x, y, w, h, 0,0,0,0); +} + +void +ctx_set_pixel_u8 (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + CtxEntry command = ctx_u8 (CTX_SET_PIXEL, r, g, b, a, 0, 0, 0, 0); + command.data.u16[2]=x; + command.data.u16[3]=y; + ctx_process (ctx, &command); +} + +void +ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1) +{ + CtxEntry command[2]= + { + ctx_f (CTX_LINEAR_GRADIENT, x0, y0), + ctx_f (CTX_CONT, x1, y1) + }; + ctx_process (ctx, command); +} + +void +ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0, float x1, float y1, float r1) +{ + CtxEntry command[3]= + { + ctx_f (CTX_RADIAL_GRADIENT, x0, y0), + ctx_f (CTX_CONT, r0, x1), + ctx_f (CTX_CONT, y1, r1) + }; + ctx_process (ctx, command); +} + +void ctx_preserve (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_PRESERVE); +} + +void ctx_fill (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_FILL); +} + +void ctx_stroke (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_STROKE); +} + + +static void ctx_empty (Ctx *ctx) +{ +#if CTX_RASTERIZER + if (ctx->renderer == NULL) +#endif + { + ctx->drawlist.count = 0; + ctx->drawlist.bitpack_pos = 0; + } +} + +void _ctx_set_store_clear (Ctx *ctx) +{ + ctx->transformation |= CTX_TRANSFORMATION_STORE_CLEAR; +} + +#if CTX_EVENTS +static void +ctx_event_free (void *event, void *user_data) +{ + free (event); +} + +static void +ctx_collect_events (CtxEvent *event, void *data, void *data2) +{ + Ctx *ctx = (Ctx*)data; + CtxEvent *copy; + if (event->type == CTX_KEY_PRESS && !strcmp (event->string, "idle")) + return; + copy = (CtxEvent*)malloc (sizeof (CtxEvent)); + *copy = *event; + if (copy->string) + copy->string = strdup (event->string); + ctx_list_append_full (&ctx->events.events, copy, ctx_event_free, NULL); +} +#endif + +#if CTX_EVENTS +static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2); +#endif + +void ctx_reset (Ctx *ctx) +{ + /* we do the callback reset first - maybe we need two cbs, + * one for before and one after default impl? + * + * tiled fb and sdl needs to sync + */ + if (ctx->renderer && ctx->renderer->reset) + ctx->renderer->reset (ctx->renderer); + + //CTX_PROCESS_VOID (CTX_RESET); + //if (ctx->transformation & CTX_TRANSFORMATION_STORE_CLEAR) + // { return; } + ctx_empty (ctx); + ctx_state_init (&ctx->state); +#if CTX_EVENTS + ctx_list_free (&ctx->events.items); + ctx->events.last_item = NULL; + + if (ctx->events.ctx_get_event_enabled) + { + ctx_clear_bindings (ctx); + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_PRESS, _ctx_bindings_key_press, ctx, ctx, + NULL, NULL); + + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_UP, ctx_collect_events, ctx, ctx, + NULL, NULL); + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_DOWN, ctx_collect_events, ctx, ctx, + NULL, NULL); + + ctx_listen_full (ctx, 0, 0, ctx->events.width, ctx->events.height, + (CtxEventType)(CTX_PRESS|CTX_RELEASE|CTX_MOTION), + ctx_collect_events, ctx, ctx, + NULL, NULL); + } +#endif +} + +void ctx_begin_path (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_BEGIN_PATH); +} + +void ctx_clip (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_CLIP); +} + +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len); + +void ctx_save (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_SAVE); +} +void ctx_restore (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_RESTORE); +} + +void ctx_start_group (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_START_GROUP); +} + +void ctx_end_group (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_END_GROUP); +} + +void ctx_line_width (Ctx *ctx, float x) +{ + if (ctx->state.gstate.line_width != x) + CTX_PROCESS_F1 (CTX_LINE_WIDTH, x); +} + +float ctx_get_miter_limit (Ctx *ctx) +{ + return ctx->state.gstate.miter_limit; +} + +float ctx_get_line_dash_offset (Ctx *ctx) +{ + return ctx->state.gstate.line_dash_offset; +} + +void ctx_line_dash_offset (Ctx *ctx, float x) +{ + if (ctx->state.gstate.line_dash_offset != x) + CTX_PROCESS_F1 (CTX_LINE_DASH_OFFSET, x); +} + +int ctx_get_image_smoothing (Ctx *ctx) +{ + return ctx->state.gstate.image_smoothing; +} + +void ctx_image_smoothing (Ctx *ctx, int enabled) +{ + if (ctx_get_image_smoothing (ctx) != enabled) + CTX_PROCESS_U8 (CTX_IMAGE_SMOOTHING, enabled); +} + + +void ctx_line_dash (Ctx *ctx, float *dashes, int count) +{ + ctx_process_cmd_str_with_len (ctx, CTX_LINE_DASH, (char*)(dashes), count, 0, count * 4); +} + +void ctx_shadow_blur (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_blur != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_BLUR, x); +} + +void ctx_shadow_rgba (Ctx *ctx, float r, float g, float b, float a) +{ + CtxEntry command[3]= + { + ctx_f (CTX_SHADOW_COLOR, CTX_RGBA, r), + ctx_f (CTX_CONT, g, b), + ctx_f (CTX_CONT, a, 0) + }; + ctx_process (ctx, command); +} + +void ctx_shadow_offset_x (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_offset_x != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_X, x); +} + +void ctx_shadow_offset_y (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_offset_y != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_Y, x); +} + +void +ctx_global_alpha (Ctx *ctx, float global_alpha) +{ + if (ctx->state.gstate.global_alpha_f != global_alpha) + CTX_PROCESS_F1 (CTX_GLOBAL_ALPHA, global_alpha); +} + +float +ctx_get_global_alpha (Ctx *ctx) +{ + return ctx->state.gstate.global_alpha_f; +} + +void +ctx_font_size (Ctx *ctx, float x) +{ + CTX_PROCESS_F1 (CTX_FONT_SIZE, x); +} + +float ctx_get_font_size (Ctx *ctx) +{ + return ctx->state.gstate.font_size; +} + +void +ctx_miter_limit (Ctx *ctx, float limit) +{ + CTX_PROCESS_F1 (CTX_MITER_LIMIT, limit); +} + +float ctx_get_line_width (Ctx *ctx) +{ + return ctx->state.gstate.line_width; +} + +void +_ctx_font (Ctx *ctx, const char *name) +{ + ctx->state.gstate.font = ctx_resolve_font (name); +} + +#if 0 +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len) +{ + if (len <= 0) len = strlen (string); + ctx_process_cmd_str (ctx, CTX_SET, string, key_hash, len); +} + +const char * +ctx_get (Ctx *ctx, const char *key) +{ + static char retbuf[32]; + int len = 0; + CTX_PROCESS_U32(CTX_GET, ctx_strhash (key, 0), 0); + while (read (STDIN_FILENO, &retbuf[len], 1) != -1) + { + if(retbuf[len]=='\n') + break; + retbuf[++len]=0; + } + return retbuf; +} +#endif + +void +ctx_font_family (Ctx *ctx, const char *name) +{ +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_FONT, name, 0, 0); + _ctx_font (ctx, name); +#else + _ctx_font (ctx, name); +#endif +} + +void +ctx_font (Ctx *ctx, const char *family_name) +{ + // should also parse size + ctx_font_family (ctx, family_name); +} + +const char * +ctx_get_font (Ctx *ctx) +{ + return ctx_fonts[ctx->state.gstate.font].name; +} + +void ctx_line_to (Ctx *ctx, float x, float y) +{ + if (CTX_UNLIKELY(!ctx->state.has_moved)) + { CTX_PROCESS_F (CTX_MOVE_TO, x, y); } + else + { CTX_PROCESS_F (CTX_LINE_TO, x, y); } +} + +void ctx_move_to (Ctx *ctx, float x, float y) +{ + CTX_PROCESS_F (CTX_MOVE_TO,x,y); +} + +void ctx_curve_to (Ctx *ctx, float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + CtxEntry command[3]= + { + ctx_f (CTX_CURVE_TO, x0, y0), + ctx_f (CTX_CONT, x1, y1), + ctx_f (CTX_CONT, x2, y2) + }; + ctx_process (ctx, command); +} + +void ctx_round_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h, + float radius) +{ + CtxEntry command[3]= + { + ctx_f (CTX_ROUND_RECTANGLE, x0, y0), + ctx_f (CTX_CONT, w, h), + ctx_f (CTX_CONT, radius, 0) + }; + ctx_process (ctx, command); +} + +void ctx_view_box (Ctx *ctx, + float x0, float y0, + float w, float h) +{ + CtxEntry command[3]= + { + ctx_f (CTX_VIEW_BOX, x0, y0), + ctx_f (CTX_CONT, w, h) + }; + ctx_process (ctx, command); +} + +void ctx_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h) +{ + CtxEntry command[3]= + { + ctx_f (CTX_RECTANGLE, x0, y0), + ctx_f (CTX_CONT, w, h) + }; + ctx_process (ctx, command); +} + +void ctx_rel_line_to (Ctx *ctx, float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CTX_PROCESS_F (CTX_REL_LINE_TO,x,y); +} + +void ctx_rel_move_to (Ctx *ctx, float x, float y) +{ + if (!ctx->state.has_moved) + { + CTX_PROCESS_F (CTX_MOVE_TO,x,y); + return; + } + CTX_PROCESS_F (CTX_REL_MOVE_TO,x,y); +} + +CtxLineJoin ctx_get_line_join (Ctx *ctx) +{ + return ctx->state.gstate.line_join; +} + +CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx) +{ + return ctx->state.gstate.compositing_mode; +} + +CtxBlend ctx_get_blend_mode (Ctx *ctx) +{ + return ctx->state.gstate.blend_mode; +} + +CtxTextAlign ctx_get_text_align (Ctx *ctx) +{ + return (CtxTextAlign)ctx_state_get (&ctx->state, CTX_text_align); +} + +CtxTextBaseline ctx_get_text_baseline (Ctx *ctx) +{ + return (CtxTextBaseline)ctx_state_get (&ctx->state, CTX_text_baseline); +} + +CtxLineCap ctx_get_line_cap (Ctx *ctx) +{ + return ctx->state.gstate.line_cap; +} + +CtxFillRule ctx_get_fill_rule (Ctx *ctx) +{ + return ctx->state.gstate.fill_rule; +} + +void ctx_line_cap (Ctx *ctx, CtxLineCap cap) +{ + if (ctx->state.gstate.line_cap != cap) + CTX_PROCESS_U8 (CTX_LINE_CAP, cap); +} + +void ctx_fill_rule (Ctx *ctx, CtxFillRule fill_rule) +{ + if (ctx->state.gstate.fill_rule != fill_rule) + CTX_PROCESS_U8 (CTX_FILL_RULE, fill_rule); +} +void ctx_line_join (Ctx *ctx, CtxLineJoin join) +{ + if (ctx->state.gstate.line_join != join) + CTX_PROCESS_U8 (CTX_LINE_JOIN, join); +} +void ctx_blend_mode (Ctx *ctx, CtxBlend mode) +{ + if (ctx->state.gstate.blend_mode != mode) + CTX_PROCESS_U32 (CTX_BLEND_MODE, mode, 0); +} +void ctx_compositing_mode (Ctx *ctx, CtxCompositingMode mode) +{ + if (ctx->state.gstate.compositing_mode != mode) + CTX_PROCESS_U32 (CTX_COMPOSITING_MODE, mode, 0); +} +void ctx_text_align (Ctx *ctx, CtxTextAlign text_align) +{ + CTX_PROCESS_U8 (CTX_TEXT_ALIGN, text_align); +} +void ctx_text_baseline (Ctx *ctx, CtxTextBaseline text_baseline) +{ + CTX_PROCESS_U8 (CTX_TEXT_BASELINE, text_baseline); +} +void ctx_text_direction (Ctx *ctx, CtxTextDirection text_direction) +{ + CTX_PROCESS_U8 (CTX_TEXT_DIRECTION, text_direction); +} + +void +ctx_rel_curve_to (Ctx *ctx, + float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[3]= + { + ctx_f (CTX_REL_CURVE_TO, x0, y0), + ctx_f (CTX_CONT, x1, y1), + ctx_f (CTX_CONT, x2, y2) + }; + ctx_process (ctx, command); +} + +void +ctx_rel_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[2]= + { + ctx_f (CTX_REL_QUAD_TO, cx, cy), + ctx_f (CTX_CONT, x, y) + }; + ctx_process (ctx, command); +} + +void +ctx_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[2]= + { + ctx_f (CTX_QUAD_TO, cx, cy), + ctx_f (CTX_CONT, x, y) + }; + ctx_process (ctx, command); +} + +void ctx_arc (Ctx *ctx, + float x0, float y0, + float radius, + float angle1, float angle2, + int direction) +{ + CtxEntry command[3]= + { + ctx_f (CTX_ARC, x0, y0), + ctx_f (CTX_CONT, radius, angle1), + ctx_f (CTX_CONT, angle2, direction) + }; + ctx_process (ctx, command); +} + +static int ctx_coords_equal (float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static float +ctx_point_seg_dist_sq (float x, float y, + float vx, float vy, float wx, float wy) +{ + float l2 = ctx_pow2 (vx-wx) + ctx_pow2 (vy-wy); + if (l2 < 0.0001) + { return ctx_pow2 (x-vx) + ctx_pow2 (y-vy); } + float t = ( (x - vx) * (wx - vx) + (y - vy) * (wy - vy) ) / l2; + t = ctx_maxf (0, ctx_minf (1, t) ); + float ix = vx + t * (wx - vx); + float iy = vy + t * (wy - vy); + return ctx_pow2 (x-ix) + ctx_pow2 (y-iy); +} + +static void +ctx_normalize (float *x, float *y) +{ + float length = ctx_hypotf ( (*x), (*y) ); + if (length > 1e-6f) + { + float r = 1.0f / length; + *x *= r; + *y *= r; + } +} + +void +ctx_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius) +{ + // XXX : should partially move into rasterizer to preserve comand + // even if an arc preserves all geometry, just to ensure roundtripping + // of data + /* from nanovg - but not quite working ; uncertain if arc or wrong + * transfusion is the cause. + */ + float x0 = ctx->state.x; + float y0 = ctx->state.y; + float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1; + int dir; + if (!ctx->state.has_moved) + { return; } + if (1) + { + // Handle degenerate cases. + if (ctx_coords_equal (x0,y0, x1,y1, 0.5f) || + ctx_coords_equal (x1,y1, x2,y2, 0.5f) || + ctx_point_seg_dist_sq (x1,y1, x0,y0, x2,y2) < 0.5 || + radius < 0.5) + { + ctx_line_to (ctx, x1,y1); + return; + } + } + // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). + dx0 = x0-x1; + dy0 = y0-y1; + dx1 = x2-x1; + dy1 = y2-y1; + ctx_normalize (&dx0,&dy0); + ctx_normalize (&dx1,&dy1); + a = ctx_acosf (dx0*dx1 + dy0*dy1); + d = radius / ctx_tanf (a/2.0f); +#if 0 + if (d > 10000.0f) + { + ctx_line_to (ctx, x1, y1); + return; + } +#endif + if ( (dx1*dy0 - dx0*dy1) > 0.0f) + { + cx = x1 + dx0*d + dy0*radius; + cy = y1 + dy0*d + -dx0*radius; + a0 = ctx_atan2f (dx0, -dy0); + a1 = ctx_atan2f (-dx1, dy1); + dir = 0; + } + else + { + cx = x1 + dx0*d + -dy0*radius; + cy = y1 + dy0*d + dx0*radius; + a0 = ctx_atan2f (-dx0, dy0); + a1 = ctx_atan2f (dx1, -dy1); + dir = 1; + } + ctx_arc (ctx, cx, cy, radius, a0, a1, dir); +} + +void +ctx_rel_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius) +{ + x1 += ctx->state.x; + y1 += ctx->state.y; + x2 += ctx->state.x; + y2 += ctx->state.y; + ctx_arc_to (ctx, x1, y1, x2, y2, radius); +} + +void +ctx_exit (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_EXIT); +} + +void +ctx_flush (Ctx *ctx) +{ + /* XXX: should be fully moved into the renderers + * to permit different behavior and get rid + * of the extranous flush() vfunc. + */ + ctx->rev++; +// CTX_PROCESS_VOID (CTX_FLUSH); +#if 0 + //printf (" \e[?2222h"); + ctx_drawlist_compact (&ctx->drawlist); + for (int i = 0; i < ctx->drawlist.count - 1; i++) + { + CtxEntry *entry = &ctx->drawlist.entries[i]; + fwrite (entry, 9, 1, stdout); +#if 0 + uint8_t *buf = (void *) entry; + for (int j = 0; j < 9; j++) + { printf ("%c", buf[j]); } +#endif + } + printf ("Xx.Xx.Xx."); + fflush (NULL); +#endif + if (ctx->renderer && ctx->renderer->flush) + ctx->renderer->flush (ctx->renderer); + ctx->frame++; + if (ctx->texture_cache != ctx) + ctx->texture_cache->frame++; + ctx->drawlist.count = 0; + ctx_state_init (&ctx->state); +} + +//////////////////////////////////////// + +static inline void +ctx_interpret_style (CtxState *state, CtxEntry *entry, void *data) +{ + CtxCommand *c = (CtxCommand *) entry; + switch (entry->code) + { + case CTX_LINE_DASH_OFFSET: + state->gstate.line_dash_offset = ctx_arg_float (0); + break; + case CTX_LINE_WIDTH: + state->gstate.line_width = ctx_arg_float (0); + break; +#if CTX_ENABLE_SHADOW_BLUR + case CTX_SHADOW_BLUR: + state->gstate.shadow_blur = ctx_arg_float (0); + break; + case CTX_SHADOW_OFFSET_X: + state->gstate.shadow_offset_x = ctx_arg_float (0); + break; + case CTX_SHADOW_OFFSET_Y: + state->gstate.shadow_offset_y = ctx_arg_float (0); + break; +#endif + case CTX_LINE_CAP: + state->gstate.line_cap = (CtxLineCap) ctx_arg_u8 (0); + break; + case CTX_FILL_RULE: + state->gstate.fill_rule = (CtxFillRule) ctx_arg_u8 (0); + break; + case CTX_LINE_JOIN: + state->gstate.line_join = (CtxLineJoin) ctx_arg_u8 (0); + break; + case CTX_COMPOSITING_MODE: + state->gstate.compositing_mode = (CtxCompositingMode) ctx_arg_u32 (0); + break; + case CTX_BLEND_MODE: + state->gstate.blend_mode = (CtxBlend) ctx_arg_u32 (0); + break; + case CTX_TEXT_ALIGN: + ctx_state_set (state, CTX_text_align, ctx_arg_u8 (0) ); + break; + case CTX_TEXT_BASELINE: + ctx_state_set (state, CTX_text_baseline, ctx_arg_u8 (0) ); + break; + case CTX_TEXT_DIRECTION: + ctx_state_set (state, CTX_text_direction, ctx_arg_u8 (0) ); + break; + case CTX_GLOBAL_ALPHA: + state->gstate.global_alpha_u8 = ctx_float_to_u8 (ctx_arg_float (0) ); + state->gstate.global_alpha_f = ctx_arg_float (0); + break; + case CTX_FONT_SIZE: + state->gstate.font_size = ctx_arg_float (0); + break; + case CTX_MITER_LIMIT: + state->gstate.miter_limit = ctx_arg_float (0); + break; + case CTX_COLOR_SPACE: + /* move this out of this function and only do it in rasterizer? XXX */ + ctx_rasterizer_colorspace_icc (state, (CtxColorSpace)c->colorspace.space_slot, + (char*)c->colorspace.data, + c->colorspace.data_len); + break; + case CTX_IMAGE_SMOOTHING: + state->gstate.image_smoothing = c->entry.data.u8[0]; + break; + case CTX_STROKE_SOURCE: + state->source = 1; + break; + + case CTX_COLOR: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = 0; + + source->type = CTX_SOURCE_COLOR; + + //float components[5]={c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a}; + switch ( ((int) ctx_arg_float (0)) & 511) // XXX remove 511 after stroke source is complete + { + case CTX_RGB: + ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f); + break; + case CTX_RGBA: + ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; + case CTX_DRGBA: + ctx_color_set_drgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYKA: + ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_CMYK: + ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; + case CTX_DCMYKA: + ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; +#endif + case CTX_GRAYA: + ctx_color_set_graya (state, &source->color, c->graya.g, c->graya.a); + break; + case CTX_GRAY: + ctx_color_set_graya (state, &source->color, c->graya.g, 1.0f); + break; + } + } + break; + case CTX_SET_RGBA_U8: + //ctx_source_deinit (&state->gstate.source); + //state->gstate.source_fill.type = CTX_SOURCE_COLOR; + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = 0; + + source->type = CTX_SOURCE_COLOR; + + ctx_color_set_RGBA8 (state, &source->color, + ctx_arg_u8 (0), + ctx_arg_u8 (1), + ctx_arg_u8 (2), + ctx_arg_u8 (3) ); + } + //for (int i = 0; i < 4; i ++) + // state->gstate.source.color.rgba[i] = ctx_arg_u8(i); + break; + //case CTX_TEXTURE: + // state->gstate.source.type = CTX_SOURCE_ + // break; + case CTX_LINEAR_GRADIENT: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = is_stroke ? 2 : 0; + + float x0 = ctx_arg_float (0); + float y0 = ctx_arg_float (1); + float x1 = ctx_arg_float (2); + float y1 = ctx_arg_float (3); + float dx, dy, length, start, end; + + length = ctx_hypotf (x1-x0,y1-y0); + dx = (x1-x0) / length; + dy = (y1-y0) / length; + start = (x0 * dx + y0 * dy) / length; + end = (x1 * dx + y1 * dy) / length; + source->linear_gradient.length = length; + source->linear_gradient.dx = dx; + source->linear_gradient.dy = dy; + source->linear_gradient.start = start; + source->linear_gradient.end = end; + source->linear_gradient.rdelta = (end-start)!=0.0?1.0f/(end - start):1.0; + source->type = CTX_SOURCE_LINEAR_GRADIENT; + source->transform = state->gstate.transform; + ctx_matrix_invert (&source->transform); + } + break; + case CTX_RADIAL_GRADIENT: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = is_stroke ? 2 : 0; + + float x0 = ctx_arg_float (0); + float y0 = ctx_arg_float (1); + float r0 = ctx_arg_float (2); + float x1 = ctx_arg_float (3); + float y1 = ctx_arg_float (4); + float r1 = ctx_arg_float (5); + source->radial_gradient.x0 = x0; + source->radial_gradient.y0 = y0; + source->radial_gradient.r0 = r0; + source->radial_gradient.x1 = x1; + source->radial_gradient.y1 = y1; + source->radial_gradient.r1 = r1; + source->radial_gradient.rdelta = (r1 - r0) != 0.0 ? 1.0f/(r1-r0):0.0; + source->type = CTX_SOURCE_RADIAL_GRADIENT; + source->transform = state->gstate.transform; + ctx_matrix_invert (&source->transform); + } + break; + } +} + +static inline void +ctx_interpret_transforms (CtxState *state, CtxEntry *entry, void *data) +{ + switch (entry->code) + { + case CTX_SAVE: + ctx_gstate_push (state); + break; + case CTX_RESTORE: + ctx_gstate_pop (state); + break; + case CTX_IDENTITY: + _ctx_matrix_identity (&state->gstate.transform); + break; + case CTX_TRANSLATE: + ctx_matrix_translate (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_SCALE: + ctx_matrix_scale (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_ROTATE: + ctx_matrix_rotate (&state->gstate.transform, ctx_arg_float (0) ); + break; + case CTX_APPLY_TRANSFORM: + { + CtxMatrix m; + ctx_matrix_set (&m, + ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + _ctx_matrix_multiply (&state->gstate.transform, + &state->gstate.transform, &m); // XXX verify order + } +#if 0 + ctx_matrix_set (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); +#endif + break; + } +} + +/* + * this transforms the contents of entry according to ctx->transformation + */ +static inline void +ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data) +{ + CtxCommand *c = (CtxCommand *) entry; + float start_x = state->x; + float start_y = state->y; + int had_moved = state->has_moved; + switch (entry->code) + { + case CTX_MOVE_TO: + case CTX_LINE_TO: + { + float x = c->c.x0; + float y = c->c.y0; + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + _ctx_user_to_device (state, &x, &y); + ctx_arg_float (0) = x; + ctx_arg_float (1) = y; + } + } + break; + case CTX_ARC: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + float temp; + _ctx_user_to_device (state, &c->arc.x, &c->arc.y); + temp = 0; + _ctx_user_to_device_distance (state, &c->arc.radius, &temp); + } + break; + case CTX_LINEAR_GRADIENT: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + _ctx_user_to_device (state, &c->linear_gradient.x1, &c->linear_gradient.y1); + _ctx_user_to_device (state, &c->linear_gradient.x2, &c->linear_gradient.y2); + } + break; + case CTX_RADIAL_GRADIENT: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + float temp; + _ctx_user_to_device (state, &c->radial_gradient.x1, &c->radial_gradient.y1); + temp = 0; + _ctx_user_to_device_distance (state, &c->radial_gradient.r1, &temp); + _ctx_user_to_device (state, &c->radial_gradient.x2, &c->radial_gradient.y2); + temp = 0; + _ctx_user_to_device_distance (state, &c->radial_gradient.r2, &temp); + } + break; + case CTX_CURVE_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 3; c ++) + { + float x = entry[c].data.f[0]; + float y = entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + } + break; + case CTX_QUAD_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 2; c ++) + { + float x = entry[c].data.f[0]; + float y = entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + } + break; + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 1; c ++) + { + float x = state->x; + float y = state->y; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + if (entry->code == CTX_REL_MOVE_TO) + { entry->code = CTX_MOVE_TO; } + else + { entry->code = CTX_LINE_TO; } + } + break; + case CTX_REL_CURVE_TO: + { + float nx = state->x + ctx_arg_float (4); + float ny = state->y + ctx_arg_float (5); + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 3; c ++) + { + float x = nx + entry[c].data.f[0]; + float y = ny + entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + entry->code = CTX_CURVE_TO; + } + } + break; + case CTX_REL_QUAD_TO: + { + float nx = state->x + ctx_arg_float (2); + float ny = state->y + ctx_arg_float (3); + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 2; c ++) + { + float x = nx + entry[c].data.f[0]; + float y = ny + entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + entry->code = CTX_QUAD_TO; + } + } + break; + } + if ((((Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE)) + { + int components = 0; + _ctx_user_to_device (state, &start_x, &start_y); + switch (entry->code) + { + case CTX_MOVE_TO: + if (had_moved) { components = 1; } + break; + case CTX_LINE_TO: + components = 1; + break; + case CTX_CURVE_TO: + components = 3; + break; + case CTX_QUAD_TO: + components = 2; + break; + } + if (components) + { + for (int c = 0; c < components; c++) + { + entry[c].data.f[0] -= start_x; + entry[c].data.f[1] -= start_y; + } + switch (entry->code) + { + case CTX_MOVE_TO: + entry[0].code = CTX_REL_MOVE_TO; + break; + case CTX_LINE_TO: + entry[0].code = CTX_REL_LINE_TO; + break; + break; + case CTX_CURVE_TO: + entry[0].code = CTX_REL_CURVE_TO; + break; + case CTX_QUAD_TO: + entry[0].code = CTX_REL_QUAD_TO; + break; + } + } + } +} + +static inline void +ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data) +{ + switch (entry->code) + { + case CTX_RESET: + ctx_state_init (state); + break; + case CTX_CLIP: + case CTX_BEGIN_PATH: + case CTX_FILL: + case CTX_STROKE: + state->has_moved = 0; + break; + case CTX_MOVE_TO: + case CTX_LINE_TO: + state->x = ctx_arg_float (0); + state->y = ctx_arg_float (1); + state->has_moved = 1; + break; + case CTX_CURVE_TO: + state->x = ctx_arg_float (4); + state->y = ctx_arg_float (5); + state->has_moved = 1; + break; + case CTX_QUAD_TO: + state->x = ctx_arg_float (2); + state->y = ctx_arg_float (3); + state->has_moved = 1; + break; + case CTX_ARC: + state->x = ctx_arg_float (0) + ctx_cosf (ctx_arg_float (4) ) * ctx_arg_float (2); + state->y = ctx_arg_float (1) + ctx_sinf (ctx_arg_float (4) ) * ctx_arg_float (2); + break; + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + state->x += ctx_arg_float (0); + state->y += ctx_arg_float (1); + break; + case CTX_REL_CURVE_TO: + state->x += ctx_arg_float (4); + state->y += ctx_arg_float (5); + break; + case CTX_REL_QUAD_TO: + state->x += ctx_arg_float (2); + state->y += ctx_arg_float (3); + break; + // XXX missing some smooths + } +} + +static inline void +ctx_interpret_pos (CtxState *state, CtxEntry *entry, void *data) +{ + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) || + ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE) ) + { + ctx_interpret_pos_transform (state, entry, data); + } + ctx_interpret_pos_bare (state, entry, data); +} + +#if CTX_BABL +void ctx_colorspace_babl (CtxState *state, + CtxColorSpace icc_slot, + const Babl *space); +#endif + +static void +ctx_state_init (CtxState *state) +{ + ctx_memset (state, 0, sizeof (CtxState) ); + state->gstate.global_alpha_u8 = 255; + state->gstate.global_alpha_f = 1.0; + state->gstate.font_size = 12; + state->gstate.line_width = 2.0; + state->gstate.image_smoothing = 1; + state->gstate.source_stroke.type = CTX_SOURCE_INHERIT_FILL; + ctx_state_set (state, CTX_line_spacing, 1.0f); + state->min_x = 8192; + state->min_y = 8192; + state->max_x = -8192; + state->max_y = -8192; + _ctx_matrix_identity (&state->gstate.transform); +#if CTX_CM +#if CTX_BABL + //ctx_colorspace_babl (state, CTX_COLOR_SPACE_USER_RGB, babl_space ("sRGB")); + //ctx_colorspace_babl (state, CTX_COLOR_SPACE_DEVICE_RGB, babl_space ("ACEScg")); +#endif +#endif +} + +void _ctx_set_transformation (Ctx *ctx, int transformation) +{ + ctx->transformation = transformation; +} + +static void +_ctx_init (Ctx *ctx) +{ + for (int i = 0; i <256;i++) + ctx_u8_float[i] = i/255.0f; + + ctx_state_init (&ctx->state); + + ctx->renderer = NULL; +#if CTX_CURRENT_PATH + ctx->current_path.flags |= CTX_DRAWLIST_CURRENT_PATH; +#endif + //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_SCREEN_SPACE; + //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_RELATIVE; +#if CTX_BITPACK + ctx->drawlist.flags |= CTX_TRANSFORMATION_BITPACK; +#endif + ctx->texture_cache = ctx; +} + +static void ctx_setup (); + +#if CTX_DRAWLIST_STATIC +static Ctx ctx_state; +#endif + +void ctx_set_renderer (Ctx *ctx, + void *renderer) +{ + if (ctx->renderer && ctx->renderer->free) + ctx->renderer->free (ctx->renderer); + ctx->renderer = (CtxImplementation*)renderer; +} + +void *ctx_get_renderer (Ctx *ctx) +{ + return ctx->renderer; +} + +Ctx * +ctx_new (void) +{ + ctx_setup (); +#if CTX_DRAWLIST_STATIC + Ctx *ctx = &ctx_state; +#else + Ctx *ctx = (Ctx *) malloc (sizeof (Ctx) ); +#endif + ctx_memset (ctx, 0, sizeof (Ctx) ); + _ctx_init (ctx); + return ctx; +} + +static inline void +ctx_drawlist_deinit (CtxDrawlist *drawlist) +{ +#if !CTX_DRAWLIST_STATIC + if (drawlist->entries && ! (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) ) + { + free (drawlist->entries); + } +#endif + drawlist->entries = NULL; + drawlist->size = 0; +} + +static void ctx_deinit (Ctx *ctx) +{ + if (ctx->renderer) + { + if (ctx->renderer->free) + ctx->renderer->free (ctx->renderer); + ctx->renderer = NULL; + } + ctx_drawlist_deinit (&ctx->drawlist); +#if CTX_CURRENT_PATH + ctx_drawlist_deinit (&ctx->current_path); +#endif +} + +void ctx_free (Ctx *ctx) +{ + if (!ctx) + { return; } +#if CTX_EVENTS + ctx_clear_bindings (ctx); +#endif + ctx_deinit (ctx); +#if !CTX_DRAWLIST_STATIC + free (ctx); +#endif +} + +Ctx *ctx_new_for_drawlist (void *data, size_t length) +{ + Ctx *ctx = ctx_new (); + ctx->drawlist.flags |= CTX_DRAWLIST_DOESNT_OWN_ENTRIES; + ctx->drawlist.entries = (CtxEntry *) data; + ctx->drawlist.count = length / sizeof (CtxEntry); + return ctx; +} + +static void ctx_setup () +{ + ctx_font_setup (); +} + +void +ctx_render_ctx (Ctx *ctx, Ctx *d_ctx) +{ + CtxIterator iterator; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { + ctx_process (d_ctx, &command->entry); + } +} + +void +ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx) +{ + CtxIterator iterator; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { + switch (command->code) + { + default: + //fprintf (stderr, "[%c]", command->code); + break; + case CTX_TEXTURE: + //fprintf (stderr, "t:%s\n", command->texture.eid); + ctx_process (d_ctx, &command->entry); + break; + case CTX_DEFINE_TEXTURE: + //fprintf (stderr, "d:%s\n", command->define_texture.eid); + ctx_process (d_ctx, &command->entry); + break; + } + } +} + +void ctx_quit (Ctx *ctx) +{ +#if CTX_EVENTS + ctx->quit ++; +#endif +} + +int ctx_has_quit (Ctx *ctx) +{ +#if CTX_EVENTS + return (ctx->quit); +#else + return 1; +#endif +} + +int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->bpp; + return -1; +} + +int ctx_pixel_format_get_stride (CtxPixelFormat format, int width) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + { + switch (info->bpp) + { + case 0: + case 1: + return (width + 7)/8; + case 2: + return (width + 3)/4; + case 4: + return (width + 1)/2; + default: + return width * (info->bpp / 8); + } + } + return width; +} + +int ctx_pixel_format_ebpp (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->ebpp; + return -1; +} + +int ctx_pixel_format_components (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->components; + return -1; +} + +#if CTX_EVENTS +void ctx_set_cursor (Ctx *ctx, CtxCursor cursor) +{ + if (ctx->cursor != cursor) + { + ctx_set_dirty (ctx, 1); + ctx->cursor = cursor; + } +} +CtxCursor ctx_get_cursor (Ctx *ctx) +{ + return ctx->cursor; +} + +void ctx_set_clipboard (Ctx *ctx, const char *text) +{ + if (ctx->renderer && ctx->renderer->set_clipboard) + { + ctx->renderer->set_clipboard (ctx->renderer, text); + return; + } +} + +char *ctx_get_clipboard (Ctx *ctx) +{ + if (ctx->renderer && ctx->renderer->get_clipboard) + { + return ctx->renderer->get_clipboard (ctx->renderer); + } + return strdup (""); +} + +void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source) +{ + ((CtxRasterizer*)ctx->renderer)->texture_source = texture_source; +} + +void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache) +{ + ctx->texture_cache = texture_cache; +} + +void ctx_set_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f) +{ + ctx_identity (ctx); + ctx_apply_transform (ctx, a, b, c, d, e, f); +} +#ifndef NO_LIBCURL +#include <curl/curl.h> +static size_t +ctx_string_append_callback (void *contents, size_t size, size_t nmemb, void *userp) +{ + CtxString *string = (CtxString*)userp; + ctx_string_append_data ((CtxString*)string, contents, size * nmemb); + return size * nmemb; +} + +#endif + +int +ctx_get_contents (const char *uri, + unsigned char **contents, + long *length) +{ + char *temp_uri = NULL; // XXX XXX breaks with data uri's + int success = -1; + + if (uri[0] == '/') + { + temp_uri = (char*) malloc (strlen (uri) + 8); + sprintf (temp_uri, "file://%s", uri); + uri = temp_uri; + } + + if (strchr (uri, '#')) + strchr (uri, '#')[0]=0; + + for (CtxList *l = registered_contents; l; l = l->next) + { + CtxFileContent *c = (CtxFileContent*)l->data; + if (!strcmp (c->path, uri)) + { + contents = malloc (c->length+1); + contents[c->length]=0; + if (length) *length = c->length; + free (temp_uri); + return 0; + } + } + + if (!strncmp (uri, "file://", 5)) + { + if (strchr (uri, '?')) + strchr (uri, '?')[0]=0; + } + + if (!strncmp (uri, "file://", 7)) + success = __ctx_file_get_contents (uri + 7, contents, length); + else + { +#ifndef NO_LIBCURL + CURL *curl = curl_easy_init (); + CURLcode res; + + curl_easy_setopt(curl, CURLOPT_URL, uri); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + CtxString *string = ctx_string_new (""); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctx_string_append_callback); + /* we pass our 'chunk' struct to the callback function */ + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)string); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ctx/0.0"); + + res = curl_easy_perform(curl); + /* check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + curl_easy_cleanup (curl); + } + else + { + *contents = (unsigned char*)string->str; + *length = string->length; + ctx_string_free (string, 0); + curl_easy_cleanup (curl); + success = 0; + } +#else + success = __ctx_file_get_contents (uri, contents, length); +#endif + } + free (temp_uri); + return success; +} + + +#endif + +#endif // CTX_IMPLEMENTATION +#ifndef __CTX_CLIENTS_H +#define __CTX_CLIENTS_H + +typedef enum CtxClientFlags { + ITK_CLIENT_UI_RESIZABLE = 1<<0, + ITK_CLIENT_CAN_LAUNCH = 1<<1, + ITK_CLIENT_MAXIMIZED = 1<<2, + ITK_CLIENT_ICONIFIED = 1<<3, + ITK_CLIENT_SHADED = 1<<4, + ITK_CLIENT_TITLEBAR = 1<<5 +} CtxClientFlags; + +struct _CtxClient { + VT *vt; + Ctx *ctx; + char *title; + int x; + int y; + int width; + int height; + CtxClientFlags flags; +#if 0 + int shaded; + int iconified; + int maximized; + int resizable; +#endif + int unmaximized_x; + int unmaximized_y; + int unmaximized_width; + int unmaximized_height; + int do_quit; + long drawn_rev; + int id; + int internal; // render a settings window rather than a vt +#if CTX_THREADS + mtx_t mtx; +#endif +#if VT_RECORD + Ctx *recording; +#endif +}; + +typedef struct _CtxClient CtxClient; + + +extern CtxList *clients; +extern CtxClient *active; +extern CtxClient *active_tab; + + +int ctx_client_resize (int id, int width, int height); +void ctx_client_maximize (int id); + +CtxClient *vt_get_client (VT *vt); +CtxClient *ctx_client_new (Ctx *ctx, const char *commandline, + int x, int y, int width, int height, + CtxClientFlags flags); +CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags); +int ctx_clients_need_redraw (Ctx *ctx); + +extern float ctx_shape_cache_rate; +extern int _ctx_max_threads; + +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int w, int h); +void ctx_client_shade_toggle (int id); +float ctx_client_min_y_pos (Ctx *ctx); +float ctx_client_max_y_pos (Ctx *ctx); + +CtxClient *client_by_id (int id); + +void ctx_client_remove (Ctx *ctx, CtxClient *client); + +int ctx_client_height (int id); + +int ctx_client_x (int id); +int ctx_client_y (int id); +void ctx_client_raise_top (int id); +void ctx_client_lower_bottom (int id); +void ctx_client_iconify (int id); +int ctx_client_is_iconified (int id); +void ctx_client_uniconify (int id); +void ctx_client_maximize (int id); +int ctx_client_is_maximized (int id); +void ctx_client_unmaximize (int id); +void ctx_client_maximized_toggle (int id); +void ctx_client_shade (int id); +int ctx_client_is_shaded (int id); +void ctx_client_unshade (int id); +void ctx_client_toggle_maximized (int id); +void ctx_client_shade_toggle (int id); +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int width, int height); + + +#endif +#ifndef MRG_UTF8_H +#define MRG_UTF8_H + +#if !__COSMOPOLITAN__ +#include <string.h> +#include <stdint.h> +#endif + +static inline int mrg_utf8_len (const unsigned char first_byte) +{ + if ((first_byte & 0x80) == 0) + return 1; /* ASCII */ + else if ((first_byte & 0xE0) == 0xC0) + return 2; + else if ((first_byte & 0xF0) == 0xE0) + return 3; + else if ((first_byte & 0xF8) == 0xF0) + return 4; + return 1; +} + +static inline const char *mrg_utf8_skip (const char *s, int utf8_length) +{ + int count; + if (!s) + return NULL; + for (count = 0; *s; s++) + { + if ((*s & 0xC0) != 0x80) + count++; + if (count == utf8_length+1) + return s; + } + return s; +} + +int mrg_unichar_to_utf8 (unsigned int ch, + unsigned char *dest); +unsigned int mrg_utf8_to_unichar (unsigned char *utf8); + +////////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 1 + +static const uint8_t utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 +}; + +static inline uint32_t +utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state*16 + type]; + return *state; +} + +#endif +#if CTX_VT + +/* mrg - MicroRaptor Gui + * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef VT_LINE_H +#define VT_LINE_H + +#include "ctx.h" + +#ifndef CTX_UNLIKELY +#define CTX_UNLIKELY(x) __builtin_expect(!!(x), 0) +#define CTX_LIKELY(x) __builtin_expect(!!(x), 1) +#endif +#ifndef CTX_MAX +#define CTX_MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +typedef struct _VtLine VtLine; + +struct _VtLine +{ + CtxString string; + /* line extends string, permitting string ops to operate on it */ + + uint64_t *style; + int style_size; + + void *ctx; // each line can have an attached ctx context; + char *prev; + int prev_length; + CtxString *frame; + + int wrapped; + + void *ctx_copy; // each line can have an attached ctx context; + // clearing should be brutal enough to unset the context of the current + // at least in alt-screen mode + int double_width; + int double_height_top; + int double_height_bottom; + int contains_proportional; + float xscale; + float yscale; + float y_offset; + int in_scrolling_region; + + /* XXX: needs refactoring to a CtxList of links/images */ + void *images[4]; + int image_col[4]; + float image_X[4]; // 0.0 - 1.0 offset in cell + float image_Y[4]; + int image_rows[4]; + int image_cols[4]; + int image_subx[4]; + int image_suby[4]; + int image_subw[4]; + int image_subh[4]; +}; + + +static inline uint64_t vt_line_get_style (VtLine *string, int pos) +{ + if (string->string.is_line==0) + return 0; + if (pos < 0 || pos >= string->style_size) + return 0; + return string->style[pos]; +} + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#endif + +static inline void vt_line_set_style (VtLine *string, int pos, uint64_t style) +{ + if (string->string.is_line==0) + return; + if (pos < 0 || pos >= 512) + return; + if (pos >= string->style_size) + { + int new_size = pos + 16; + string->style = realloc (string->style, new_size * sizeof (uint64_t) ); + memset (&string->style[string->style_size], 0, (new_size - string->style_size) * sizeof (uint64_t) ); + string->style_size = new_size; + } + string->style[pos] = style; +} + +VtLine *vt_line_new_with_size (const char *initial, int initial_size); +VtLine *vt_line_new (const char *initial); + +static inline void vt_line_free (VtLine *line, int freealloc) +{ + CtxString *string = (CtxString*)line; + +#if 1 + //if (string->is_line) + { + VtLine *line = (VtLine*)string; + if (line->frame) + ctx_string_free (line->frame, 1); + if (line->style) + { free (line->style); } + if (line->ctx) + { ctx_free (line->ctx); } + if (line->ctx_copy) + { ctx_free (line->ctx_copy); } + } +#endif + + ctx_string_free (string, freealloc); +} +static inline const char *vt_line_get (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get (string); +} +static inline uint32_t vt_line_get_unichar (VtLine *line, int pos) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_unichar (string, pos); +} +static inline int vt_line_get_length (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_length (string); +} +static inline int vt_line_get_utf8length (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_utf8length (string); +} +static inline void vt_line_set (VtLine *line, const char *new_string) +{ + CtxString *string = (CtxString*)line; + ctx_string_set (string, new_string); +} +static inline void vt_line_clear (VtLine *line) +{ + CtxString *string = (CtxString*)line; + ctx_string_clear (string); +} +static inline void vt_line_append_str (VtLine *line, const char *str) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_str (string, str); +} + +#if 0 +static inline void _ctx_string_append_byte (CtxString *string, char val) +{ + if (CTX_LIKELY((val & 0xC0) != 0x80)) + { string->utf8_length++; } + if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length)) + { + char *old = string->str; + string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2); + string->str = (char*)realloc (old, string->allocated_length); + } + string->str[string->length++] = val; + string->str[string->length] = '\0'; +} +#endif + +static inline void vt_line_append_byte (VtLine *line, char val) +{ + CtxString *string = (CtxString*)line; + _ctx_string_append_byte (string, val); +} +static inline void vt_line_append_string (VtLine *line, CtxString *string2) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_string (string, string2); +} +static inline void vt_line_append_unichar (VtLine *line, unsigned int unichar) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_unichar (string, unichar); +} + + +static inline void vt_line_append_data (VtLine *line, const char *data, int len) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_data (string, data, len); +} +static inline void vt_line_append_utf8char (VtLine *line, const char *str) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_utf8char (string, str); +} +static inline void vt_line_replace_utf8 (VtLine *line, int pos, const char *new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_replace_utf8 (string, pos, new_glyph); +} +static inline void vt_line_insert_utf8 (VtLine *line, int pos, const char *new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_insert_utf8 (string, pos, new_glyph); + int len = vt_line_get_length (line); + for (int i = pos; i < len; i++) + vt_line_set_style (line, i, vt_line_get_style (line, i-1)); +} + +static inline void vt_line_insert_unichar (VtLine *line, int pos, uint32_t new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_insert_unichar (string, pos, new_glyph); + int len = vt_line_get_length (line); + for (int i = 1; i < len; i++) + vt_line_set_style (line, i, vt_line_get_style (line, i-1)); +} +static inline void vt_line_replace_unichar (VtLine *line, int pos, uint32_t unichar) +{ + CtxString *string = (CtxString*)line; + ctx_string_replace_unichar (string, pos, unichar); +} + +static inline void vt_line_remove (VtLine *line, int pos) +{ + CtxString *string = (CtxString*)line; + ctx_string_remove (string, pos); + + for (int i = pos; i < line->style_size-1; i++) + { + line->style[i] = line->style[i+1]; + } +} + + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#endif +/* mrg - MicroRaptor Gui + * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#if !__COSMOPOLITAN__ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#endif + +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +#define mrg_unichar_to_utf8 ctx_unichar_to_utf8 +void ctx_string_init (CtxString *string, int initial_size); + +VtLine *vt_line_new_with_size (const char *initial, int initial_size) +{ + VtLine *line = calloc (sizeof (VtLine), 1); + CtxString *string = (CtxString*)line; + ctx_string_init (string, initial_size); + if (initial) + { ctx_string_append_str (string, initial); } + line->style = calloc (sizeof (uint64_t), initial_size); + line->style_size = initial_size; + string->is_line = 1; + return line; +} + +VtLine *vt_line_new (const char *initial) +{ + return vt_line_new_with_size (initial, 8); +} + +typedef struct VtPty +{ + int pty; + pid_t pid; + int done; +} VtPty; +ssize_t vtpty_read (void *vtpty, void *buf, size_t count); +ssize_t vtpty_write (void *vtpty, const void *buf, size_t count); +void vtpty_resize (void *vtpty, int cols, int rows, + int px_width, int px_height); +int vtpty_waitdata (void *vtpty, int timeout); +extern CtxList *vts; +#define MAX_COLS 2048 // used for tabstops + + +typedef struct AudioState +{ + int action; + int samplerate; // 8000 + int channels; // 1 + int bits; // 8 + int type; // 'u' u-law f-loat s-igned u-nsigned + int buffer_size; // desired size of audiofragment in frames + // (both for feeding SDL and as desired chunking + // size) + + + int mic; // <- should + // request permisson, + // and if gotten, start streaming + // audio packets in the incoming direction + // + int encoding; // 'a' ascci85 'b' base64 + int compression; // '0': none , 'z': zlib 'o': opus(reserved) + + int frames; + + uint8_t *data; + int data_size; +} AudioState; + +typedef struct GfxState +{ + int action; + int id; + int buf_width; + int buf_height; + int format; + int compression; + int transmission; + int multichunk; + int buf_size; + int x; + int y; + int w; + int h; + int x_cell_offset; + int y_cell_offset; + int columns; + int rows; + int z_index; + int delete; + + uint8_t *data; + int data_size; +} GfxState; + +struct _VT +{ + VtPty vtpty; + int id; + unsigned char buf[BUFSIZ]; // need one per vt + int keyrepeat; + int lastx; + int lasty; + int result; + long rev; + //SDL_Rect dirty; + float dirtpad; + float dirtpad1; + float dirtpad2; + float dirtpad3; + + void *client; + + ssize_t (*write) (void *serial_obj, const void *buf, size_t count); + ssize_t (*read) (void *serial_obj, void *buf, size_t count); + int (*waitdata)(void *serial_obj, int timeout); + void (*resize) (void *serial_obj, int cols, int rows, int px_width, int px_height); + + + char *title; + void (*state) (VT *vt, int byte); + + AudioState audio; // < want to move this one level up and share impl + GfxState gfx; + + CtxList *saved_lines; + int in_alt_screen; + int saved_line_count; + CtxList *lines; + int line_count; + CtxList *scrollback; + int scrollback_count; + int leds[4]; + uint64_t cstyle; + + uint8_t fg_color[3]; + uint8_t bg_color[3]; + + int in_smooth_scroll; + int smooth_scroll; + float scroll_offset; + int debug; + int bell; + int origin; + int at_line_home; + int charset[4]; + int saved_charset[4]; + int shifted_in; + int reverse_video; + int echo; + int bracket_paste; + int ctx_events; + int font_is_mono; + int palette_no; + int has_blink; // if any of the set characters are blinking + // updated on each draw of the screen + + int can_launch; + + int unit_pixels; + int mouse; + int mouse_drag; + int mouse_all; + int mouse_decimal; + + + uint8_t utf8_holding[64]; /* only 4 needed for utf8 - but it's purpose + is also overloaded for ctx journal command + buffering , and the bigger sizes for the svg-like + ctx parsing mode */ + int utf8_expected_bytes; + int utf8_pos; + + + int ref_len; + char reference[16]; + int in_prev_match; + CtxParser *ctxp; + // text related data + float letter_spacing; + + float word_spacing; + float font_stretch; // horizontal expansion + float font_size_adjust; + // font-variant + // font-weight + // text-decoration + + int encoding; // 0 = utf8 1=pc vga 2=ascii + + int local_editing; /* terminal operates without pty */ + + int insert_mode; + int autowrap; + int justify; + float cursor_x; + int cursor_y; + int cols; + int rows; + VtLine *current_line; + + + int cr_on_lf; + int cursor_visible; + int saved_x; + int saved_y; + uint32_t saved_style; + int saved_origin; + int cursor_key_application; + int margin_top; + int margin_bottom; + int margin_left; + int margin_right; + + int left_right_margin_mode; + + int scrollback_limit; + float scroll; + int scroll_on_input; + int scroll_on_output; + + char *argument_buf; + int argument_buf_len; + int argument_buf_cap; + uint8_t tabs[MAX_COLS]; + int inert; + + int width; + int height; + + int cw; // cell width + int ch; // cell height + float font_to_cell_scale; + float font_size; // when set with set_font_size, cw and ch are recomputed + float line_spacing; // using line_spacing + float scale_x; + float scale_y; + + int ctx_pos; // 1 is graphics above text, 0 or -1 is below text + Ctx *root_ctx; /* only used for knowledge of top-level dimensions */ + + int blink_state; + + FILE *log; + + int cursor_down; + + int select_begin_col; + int select_begin_row; + int select_start_col; + int select_start_row; + int select_end_col; + int select_end_row; + int select_begin_x; + int select_begin_y; + int select_active; + + int popped; + + /* used to make runs of background on one line be drawn + * as a single filled rectangle + */ + int bg_active; + float bg_x0; + float bg_y0; + float bg_width; + float bg_height; + uint8_t bg_rgba[4]; +}; + + +VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch); + +void vt_open_log (VT *vt, const char *path); + +void vt_set_px_size (VT *vt, int width, int height); +void vt_set_term_size (VT *vt, int cols, int rows); + +int vt_cw (VT *vt); +int vt_ch (VT *vt); +void vt_set_font_size (VT *vt, float font_size); +float vt_get_font_size (VT *vt); +void vt_set_line_spacing (VT *vt, float line_spacing); + +const char *vt_find_shell_command (void); + +int vt_keyrepeat (VT *vt); + +int vt_get_result (VT *vt); +int vt_is_done (VT *vt); +int vt_poll (VT *vt, int timeout); +long vt_rev (VT *vt); +void vt_destroy (VT *vt); +int vt_has_blink (VT *vt); + +/* this is how mrg/mmm based key-events are fed into the vt engine + */ +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str); + +void vt_paste (VT *vt, const char *str); + +/* not needed when passing a commandline for command to + * run, but could be used for injecting commands, or + * output from stored shell commands/sessions to display + */ +//void vt_feed_byte (VT *vt, int byte); + +//)#define DEFAULT_SCROLLBACK (1<<16) +#define DEFAULT_SCROLLBACK (1<<13) +#define DEFAULT_ROWS 24 +#define DEFAULT_COLS 80 + +int vt_get_line_count (VT *vt); + +pid_t vt_get_pid (VT *vt); + +const char *vt_get_line (VT *vt, int no); + +void vt_set_scrollback_lines (VT *vt, int scrollback_lines); +int vt_get_scrollback_lines (VT *vt); + +void vt_set_scroll (VT *vt, int scroll); +int vt_get_scroll (VT *vt); + +int vt_get_cols (VT *vt); +int vt_get_rows (VT *vt); + +char *vt_get_selection (VT *vt); +int vt_get_cursor_x (VT *vt); +int vt_get_cursor_y (VT *vt); + +void vt_draw (VT *vt, Ctx *ctx, double x, double y); +void vt_register_events (VT *vt, Ctx *ctx, double x0, double y0); + +void vt_rev_inc (VT *vt); + +int vt_mic (VT *vt); +void vt_set_ctx (VT *vt, Ctx *ctx); /* XXX: rename, this sets the parent/global ctx */ + + +int vt_get_local (VT *vt); // this is a hack for the settings tab +void vt_set_local (VT *vt, int local); + + +typedef enum VtMouseEvent +{ + VT_MOUSE_MOTION = 0, + VT_MOUSE_PRESS, + VT_MOUSE_DRAG, + VT_MOUSE_RELEASE, +} VtMouseEvent; + +void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y); + +static ssize_t vt_write (VT *vt, const void *buf, size_t count) +{ + if (!vt->write) { return 0; } + return vt->write (&vt->vtpty, buf, count); +} +static ssize_t vt_read (VT *vt, void *buf, size_t count) +{ + if (!vt->read) { return 0; } + return vt->read (&vt->vtpty, buf, count); +} +static int vt_waitdata (VT *vt, int timeout) +{ + if (!vt->waitdata) { return 0; } + return vt->waitdata (&vt->vtpty, timeout); +} +static void vt_resize (VT *vt, int cols, int rows, int px_width, int px_height) +{ + if (vt && vt->resize) + { vt->resize (&vt->vtpty, cols, rows, px_width, px_height); } +} + +/* atty - audio interface and driver for terminals + * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef NO_SDL +#include <SDL.h> +#include <zlib.h> + +static int ydec (const void *srcp, void *dstp, int count) +{ + const char *src = srcp; + char *dst = dstp; + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = src[i]; + switch (o) + { + case '=': + i++; + o = src[i]; + o = (o-42-64) % 256; + break; + case '\n': + case '\033': + case '\r': + case '\0': + break; + default: + o = (o-42) % 256; + break; + } + dst[out_len++] = o; + } + dst[out_len]=0; + return out_len; +} + +#ifndef NO_SDL +static SDL_AudioDeviceID speaker_device = 0; +#endif + +//#define AUDIO_CHUNK_SIZE 512 + +// our pcm queue is currently always 16 bit +// signed stereo + +static int16_t pcm_queue[1<<18]; +static int pcm_write_pos = 0; +static int pcm_read_pos = 0; + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right) +{ + if (pcm_write_pos >= (1<<18)-1) + { + /* TODO : fix cyclic buffer */ + pcm_write_pos = 0; + pcm_read_pos = 0; + } + pcm_queue[pcm_write_pos++]=sample_left; + pcm_queue[pcm_write_pos++]=sample_right; +} + +float click_volume = 0.05; + +void vt_feed_audio (VT *vt, void *samples, int bytes); +int mic_device = 0; // when non 0 we have an active mic device + + +/* https://jonathanhays.me/2018/11/14/mu-law-and-a-law-compression-tutorial/ + */ + +#if 0 +static char MuLawCompressTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +unsigned char LinearToMuLawSample(int16_t sample) +{ + const int cBias = 0x84; + const int cClip = 32635; + int sign = (sample >> 8) & 0x80; + + if (sign) + sample = (int16_t)-sample; + + if (sample > cClip) + sample = cClip; + + sample = (int16_t)(sample + cBias); + + int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF]; + int mantissa = (sample >> (exponent+3)) & 0x0F; + + int compressedByte = ~ (sign | (exponent << 4) | mantissa); + + return (unsigned char)compressedByte; +} +#endif + +void vt_feed_audio (VT *vt, void *samples, int bytes) +{ + char buf[256]; + AudioState *audio = &vt->audio; + uint8_t *data = samples; + int frames = bytes / (audio->bits/8) / audio->channels; + + if (audio->compression == 'z') + { + uLongf len = compressBound(bytes); + data = malloc (len); + int z_result = compress (data, &len, samples, len); + if (z_result != Z_OK) + { + char buf[256]= "\033_Ao=z;zlib error2\033\\"; + vt_write (vt, buf, strlen(buf)); + data = samples; + } + else + { + bytes = len; + } + } + + char *encoded = malloc (bytes * 2); + encoded[0]=0; + if (audio->encoding == 'a') + { + ctx_a85enc (data, encoded, bytes); + } + else /* if (audio->encoding == 'b') */ + { + ctx_bin2base64 (data, bytes, encoded); + } + + sprintf (buf, "\033[_Af=%i;", frames); + vt_write (vt, buf, strlen (buf)); + vt_write (vt, encoded, strlen(encoded)); + free (encoded); + + if (data != samples) + free (data); + + //vt_write (vt, samples, bytes); + buf[0]='\033'; + buf[1]='\\'; + buf[2]=0; + vt_write (vt, buf, 2); +} + +#define MIC_BUF_LEN 40960 + +uint8_t mic_buf[MIC_BUF_LEN]; +int mic_buf_pos = 0; + +static void mic_callback(void* userdata, + uint8_t * stream, + int len) +{ + AudioState *audio = userdata; + int16_t *sstream = (void*)stream; + int frames; + int channels = audio->channels; + + frames = len / 2; + + if (audio->bits == 8) + { + if (audio->type == 'u') + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < channels; c++) + { + mic_buf[mic_buf_pos++] = LinearToMuLawSample (sstream[i]); + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } + else + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < audio->channels; c++) + { + mic_buf[mic_buf_pos++] = (sstream[i]) / 256; + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } + } + else + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < audio->channels; c++) + { + *((int16_t*)(&mic_buf[mic_buf_pos])) = (sstream[i]); + mic_buf_pos+=2; + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } +} + +static long int ticks (void) +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return tp.tv_sec * 1000 + tp.tv_usec / 1000; +} + +static long int silence_start = 0; + +static void sdl_audio_init () +{ + static int done = 0; + if (!done) + { +#ifndef NO_SDL + if (SDL_Init(SDL_INIT_AUDIO) < 0) + { + fprintf (stderr, "sdl audio init fail\n"); + } +#endif + done = 1; + } +} + +void vt_audio_task (VT *vt, int click) +{ + if (!vt) return; + AudioState *audio = &vt->audio; +#ifndef NO_SDL + + if (audio->mic) + { + if (mic_device == 0) + { + SDL_AudioSpec spec_want, spec_got; + sdl_audio_init (); + + spec_want.freq = audio->samplerate; + spec_want.channels = 1; + spec_want.format = AUDIO_S16; + spec_want.samples = audio->buffer_size; + spec_want.callback = mic_callback; + spec_want.userdata = audio; + mic_device = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, SDL_TRUE), 1, &spec_want, &spec_got, 0); + + SDL_PauseAudioDevice(mic_device, 0); + } + + if (mic_buf_pos) + { + SDL_LockAudioDevice (mic_device); + vt_feed_audio (vt, mic_buf, mic_buf_pos); + mic_buf_pos = 0; + SDL_UnlockAudioDevice (mic_device); + } + } + else + { + if (mic_device) + { + SDL_PauseAudioDevice(mic_device, 1); + SDL_CloseAudioDevice(mic_device); + mic_device = 0; + } + } + + int free_frames = audio->buffer_size - SDL_GetQueuedAudioSize(speaker_device); + int queued = (pcm_write_pos - pcm_read_pos)/2; // 2 for stereo + //if (free_frames > 6) free_frames -= 4; + int frames = queued; + + if (frames > free_frames) frames = free_frames; + if (frames > 0) + { + if (speaker_device == 0) + { + SDL_AudioSpec spec_want, spec_got; + sdl_audio_init (); + + spec_want.freq = audio->samplerate; + if (audio->bits == 8 && audio->type == 'u') + { + spec_want.format = AUDIO_S16; + spec_want.channels = 2; + } + else if (audio->bits == 8 && audio->type == 's') + { + spec_want.format = AUDIO_S8; + spec_want.channels = audio->channels; + } + else if (audio->bits == 16 && audio->type == 's') + { + spec_want.format = AUDIO_S16; + spec_want.channels = audio->channels; + } + else + { + spec_want.format = AUDIO_S16; // XXX : error + spec_want.channels = audio->channels; + } + + /* In SDL we always set 16bit stereo, but with the + * requested sample rate. + */ + spec_want.format = AUDIO_S16; + spec_want.channels = 2; + + spec_want.samples = audio->buffer_size; + spec_want.callback = NULL; + + speaker_device = SDL_OpenAudioDevice (NULL, 0, &spec_want, &spec_got, 0); + if (!speaker_device){ + fprintf (stderr, "sdl openaudiodevice fail\n"); + } + SDL_PauseAudioDevice (speaker_device, 0); + } + +#if 0 + { + int i; + unsigned char *b = (void*)(&pcm_queue[pcm_read_pos]); + for (i = 0; i < frames * 4; i++) + { + if ((b[i] > ' ') && (b[i] <= '~')) + fprintf (stderr, "[%c]", b[i]); + else + fprintf (stderr, "[%i]", b[i]); + } + } +#endif + SDL_QueueAudio (speaker_device, (void*)&pcm_queue[pcm_read_pos], frames * 4); + pcm_read_pos += frames*2; + silence_start = ticks(); + } + else + { + if (speaker_device && (ticks() - silence_start > 2000)) + { + SDL_PauseAudioDevice(speaker_device, 1); + SDL_CloseAudioDevice(speaker_device); + speaker_device = 0; + } + } +#endif +} + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right); + +static unsigned char vt_bell_audio[] = { +#if 1 + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +#else + 0x7e, 0xfe, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0xff, + 0xff, 0xfe, 0xfe, 0x7e, 0xff, 0xfe, 0xfd, 0xfd, 0xfe, 0xfe, 0xfd, 0xfd, + 0xfe, 0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, + 0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0xfd, 0xfd, 0x7e, 0x7e, 0xfd, 0xfe, 0xfe, + 0xfe, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0xfe, 0xfe, 0xff, 0xfe, + 0xfe, 0xfe, 0x7d, 0x7c, 0xfb, 0xfa, 0xfc, 0xfd, 0xfc, 0x76, 0x75, 0xfa, + 0xfb, 0x7b, 0xfc, 0xef, 0xf6, 0x77, 0x6d, 0x7b, 0xf8, 0x78, 0x78, 0xfa, + 0xf7, 0xfd, 0xfd, 0xfc, 0xfc, 0xfa, 0xf5, 0xf7, 0x7d, 0x7b, 0x78, 0x77, + 0x7c, 0x6f, 0x7b, 0xf5, 0xfb, 0x7b, 0x7c, 0x78, 0x76, 0xea, 0xf2, 0x6d, + 0xfd, 0xed, 0x7a, 0x6d, 0x6e, 0x71, 0xfe, 0x76, 0x6d, 0xfb, 0xef, 0x7e, + 0xfa, 0xef, 0xec, 0xed, 0xf8, 0xf0, 0xea, 0xf9, 0x70, 0x7c, 0x7c, 0x6b, + 0x6d, 0x75, 0xfb, 0xf1, 0xf9, 0xfe, 0xec, 0xea, 0x7c, 0x75, 0xff, 0xfb, + 0x7d, 0x77, 0x7a, 0x71, 0x6e, 0x6c, 0x6e, 0x7b, 0x7e, 0x7a, 0x7c, 0xf4, + 0xf9, 0x7b, 0x7b, 0xfa, 0xfe, 0x73, 0x79, 0xfe, 0x7b, 0x76, 0xfe, 0xf3, + 0xf9, 0x76, 0x77, 0x7e, 0x7e, 0x7d, 0x7c, 0xf9, 0xee, 0xf2, 0x7d, 0xf8, + 0xec, 0xee, 0xf7, 0xfa, 0xf7, 0xf6, 0xfd, 0x77, 0x75, 0x7b, 0xfa, 0xfe, + 0x78, 0x79, 0x7c, 0x76, 0x7e, 0xf7, 0xfb, 0xf5, 0xf6, 0x75, 0x6f, 0x74, + 0x6e, 0x6e, 0x6d, 0x6c, 0x7a, 0xf9, 0x75, 0x77, 0xf4, 0xf0, 0xf0, 0xf1, + 0xef, 0xf3, 0xf6, 0xfd, 0xfc, 0xfb, 0xfd, 0xfc, 0xf6, 0xf8, 0xfb, 0xf9, + 0xfa, 0xfd, 0xfb, 0xfc, 0x7a, 0x7c, 0x77, 0x75, 0x78, 0x7a, 0x7a, 0x78, + 0x7a, 0xfa, 0xf9, 0x7c, 0xff, 0xfb, 0x7d, 0x77, 0x73, 0x6c, 0x6e, 0x7b, + 0xfc, 0xfe, 0x7e, 0xfb, 0xf1, 0xeb, 0xee, 0xf6, 0xf6, 0xef, 0xf7, 0x7c, + 0x76, 0x76, 0x7b, 0x7a, 0x7b, 0x73, 0x73, 0x7c, 0x79, 0x70, 0x79, 0xfb, + 0xfd, 0xf8, 0xf9, 0xfc, 0xfc, 0xf8, 0xfb, 0xff, 0xfc, 0xf9, 0x75, 0x6f, + 0x74, 0xfe, 0xff, 0xfd, 0x7d, 0xf5, 0xef, 0xee, 0xf8, 0xfd, 0xfd, 0xf3, + 0xfa, 0xfe, 0xfe, 0x7c, 0x77, 0x7a, 0xfb, 0x79, 0x7e, 0x7b, 0xfd, 0x6d, + 0xfc, 0x7a, 0xf0, 0x74, 0xee, 0x79, 0xea, 0x79, 0xf9, 0x6d, 0xf7, 0x71, + 0x79, 0x76, 0x7c, 0x77, 0x6f, 0xf3, 0x6c, 0xe8, 0x67, 0xe3, 0x5e, 0xdc, + 0x58, 0xd8, 0x4e, 0xce, 0x46, 0xc5, 0x40, 0x67, 0xba, 0x49, 0xac, 0x26, + 0xba, 0x3e, 0xc5, 0xc8, 0x2b, 0xa8, 0x32, 0xbd, 0xe4, 0x3e, 0xb7, 0x3b, + 0xb7, 0x3a, 0x33, 0xab, 0x3f, 0xc8, 0x46, 0x5f, 0xb7, 0x69, 0xd4, 0x3d, + 0xc0, 0x4c, 0xf2, 0xdb, 0x3b, 0xdd, 0x69, 0xc5, 0x5f, 0xd8, 0xd8, 0xda, + 0xc6, 0x39, 0xba, 0x3f, 0x35, 0xb3, 0x3e, 0xbb, 0x4a, 0x4a, 0xe7, 0x60, + 0xae, 0x2c, 0xcb, 0x53, 0x45, 0xaf, 0x2a, 0xae, 0x3e, 0x4a, 0xae, 0x2a, + 0xad, 0x38, 0xcc, 0xbb, 0x36, 0xae, 0x2c, 0xc6, 0xce, 0x38, 0xb1, 0x2f, + 0xb9, 0x54, 0x7c, 0xb3, 0x28, 0xae, 0x3d, 0xcf, 0xbb, 0x2e, 0xb4, 0x41, + 0xc6, 0x78, 0x39, 0xbc, 0x41, 0xc8, 0x59, 0x5b, 0xc7, 0x43, 0xbc, 0x45, + 0xf3, 0xdc, 0x69, 0xd6, 0x48, 0xc9, 0x4e, 0xd9, 0x59, 0x61, 0xde, 0x4b, + 0xc9, 0x44, 0xc8, 0xf5, 0x43, 0xc5, 0x37, 0xba, 0x65, 0x4d, 0xc8, 0x31, + 0xaf, 0x47, 0xdb, 0xd6, 0x36, 0xad, 0x37, 0xbb, 0x61, 0x3a, 0xae, 0x2d, + 0xb4, 0x47, 0x49, 0xb2, 0x30, 0xac, 0x3a, 0xcd, 0xbc, 0x2e, 0xaf, 0x32, + 0xbd, 0xd7, 0x34, 0xaf, 0x32, 0xbb, 0x55, 0x4a, 0xb4, 0x30, 0xbb, 0x40, + 0xeb, 0xbf, 0x39, 0xba, 0x3a, 0xd6, 0xd3, 0x48, 0xc0, 0x3b, 0xce, 0x5e, + 0xe7, 0xd3, 0x46, 0xcb, 0x4c, 0xce, 0x74, 0x7e, 0x7e, 0x55, 0xcf, 0x44, + 0xc4, 0x5b, 0x7c, 0xd3, 0x3f, 0xbc, 0x44, 0xcb, 0xfa, 0x46, 0xb9, 0x37, + 0xb8, 0x51, 0x54, 0xbe, 0x33, 0xb1, 0x3d, 0xce, 0xc4, 0x34, 0xaf, 0x2f, + 0xbd, 0xf8, 0x37, 0xb0, 0x2d, 0xb1, 0x4c, 0x4a, 0xb3, 0x2c, 0xb0, 0x3c, + 0xe4, 0xbf, 0x2f, 0xaf, 0x35, 0xc0, 0xdb, 0x39, 0xb3, 0x31, 0xbb, 0x5d, + 0x4c, 0xb8, 0x37, 0xb9, 0x48, 0xe8, 0xc7, 0x3d, 0xba, 0x43, 0xce, 0xdd, + 0x52, 0xc6, 0x46, 0xce, 0x55, 0xdf, 0xe8, 0x52, 0xd5, 0x48, 0xca, 0x4d, + 0xef, 0x68, 0x4c, 0xc7, 0x42, 0xc2, 0x49, 0x78, 0xce, 0x3e, 0xb9, 0x3c, + 0xc8, 0xef, 0x43, 0xb7, 0x35, 0xb8, 0x4a, 0x53, 0xb8, 0x32, 0xaf, 0x3b, + 0xde, 0xc1, 0x34, 0xaf, 0x32, 0xc3, 0xde, 0x3b, 0xaf, 0x2e, 0xb6, 0x4e, + 0x48, 0xb4, 0x2e, 0xb2, 0x3d, 0xf0, 0xbf, 0x33, 0xb2, 0x37, 0xc8, 0xd9, + 0x3d, 0xb5, 0x36, 0xbc, 0x56, 0x4f, 0xbc, 0x39, 0xbc, 0x47, 0xf6, 0xcf, + 0x44, 0xbf, 0x46, 0xce, 0x68, 0x5b, 0xd0, 0x4a, 0xcc, 0x4d, 0xd3, 0x60, + 0x6a, 0xcf, 0x49, 0xc8, 0x45, 0xd0, 0x7b, 0x58, 0xc3, 0x3c, 0xbf, 0x48, + 0xe2, 0xc9, 0x3b, 0xb7, 0x39, 0xc5, 0xdb, 0x40, 0xb6, 0x31, 0xb9, 0x50, + 0x50, 0xb9, 0x2f, 0xb3, 0x3b, 0xdc, 0xbf, 0x33, 0xaf, 0x32, 0xc1, 0xd6, + 0x3b, 0xb0, 0x2f, 0xb8, 0x54, 0x4a, 0xb6, 0x30, 0xb4, 0x3f, 0xfd, 0xc0, + 0x36, 0xb5, 0x39, 0xcc, 0xd9, 0x41, 0xb9, 0x39, 0xc2, 0x59, 0x57, 0xc1, + 0x3e, 0xc2, 0x49, 0xe2, 0xd7, 0x4c, 0xcb, 0x47, 0xcf, 0x5b, 0xec, 0xe0, + 0x53, 0xcb, 0x4b, 0xca, 0x55, 0xf6, 0xdb, 0x48, 0xc0, 0x43, 0xc9, 0x5f, + 0x54, 0xc0, 0x3c, 0xbb, 0x43, 0xe8, 0xc8, 0x39, 0xb5, 0x39, 0xc6, 0xde, + 0x3d, 0xb4, 0x32, 0xba, 0x4f, 0x4c, 0xb9, 0x30, 0xb2, 0x3c, 0xec, 0xc1, + 0x33, 0xaf, 0x35, 0xc4, 0xd7, 0x3a, 0xb2, 0x31, 0xba, 0x56, 0x48, 0xb9, + 0x33, 0xb7, 0x44, 0x7e, 0xc3, 0x39, 0xb7, 0x3d, 0xcd, 0xe3, 0x42, 0xbd, + 0x3d, 0xc2, 0x58, 0x5d, 0xcb, 0x43, 0xc4, 0x4c, 0xd8, 0xf8, 0x58, 0xcd, + 0x4c, 0xcb, 0x4e, 0xda, 0x71, 0x5c, 0xcc, 0x46, 0xc4, 0x49, 0xdc, 0xdc, + 0x46, 0xbe, 0x3d, 0xc4, 0x59, 0x53, 0xbe, 0x38, 0xb8, 0x41, 0xe1, 0xc5, + 0x39, 0xb3, 0x38, 0xc4, 0xde, 0x3d, 0xb2, 0x32, 0xb9, 0x4e, 0x4b, 0xb7, + 0x30, 0xb3, 0x3d, 0xf2, 0xbf, 0x33, 0xb1, 0x36, 0xc9, 0xd9, 0x3a, 0xb4, + 0x33, 0xbc, 0x58, 0x49, 0xba, 0x36, 0xb9, 0x46, 0x7e, 0xc8, 0x3c, 0xba, + 0x3f, 0xcd, 0xe8, 0x4b, 0xc1, 0x41, 0xc7, 0x57, 0xfe, 0xd3, 0x4e, 0xc9, + 0x4d, 0xd0, 0x5e, 0x7c, 0xda, 0x4e, 0xca, 0x47, 0xcd, 0x5b, 0x68, 0xcc, + 0x40, 0xbf, 0x42, 0xd2, 0xe4, 0x42, 0xbd, 0x3a, 0xbf, 0x56, 0x50, 0xbd, + 0x36, 0xb6, 0x40, 0xe2, 0xc5, 0x36, 0xb2, 0x37, 0xc5, 0xde, 0x3c, 0xb3, + 0x32, 0xba, 0x52, 0x4a, 0xb7, 0x31, 0xb4, 0x3f, 0xef, 0xbf, 0x34, 0xb2, + 0x39, 0xc8, 0xd3, 0x3c, 0xb6, 0x37, 0xbe, 0x5c, 0x4c, 0xbd, 0x39, 0xbc, + 0x49, 0xf2, 0xcc, 0x3f, 0xbf, 0x44, 0xcf, 0xfd, 0x51, 0xca, 0x48, 0xcb, + 0x54, 0xe4, 0xeb, 0x57, 0xcf, 0x4d, 0xcc, 0x4f, 0xe0, 0xee, 0x51, 0xc7, + 0x44, 0xc6, 0x4f, 0x78, 0xcc, 0x3f, 0xbd, 0x3e, 0xce, 0xe5, 0x42, 0xba, + 0x38, 0xbe, 0x50, 0x4f, 0xbb, 0x35, 0xb6, 0x3e, 0xe8, 0xc2, 0x36, 0xb2, + 0x37, 0xc6, 0xda, 0x3c, 0xb3, 0x32, 0xba, 0x55, 0x4a, 0xb7, 0x33, 0xb5, + 0x41, 0x7e, 0xbf, 0x37, 0xb4, 0x3b, 0xcd, 0xd8, 0x3e, 0xb8, 0x39, 0xc2, + 0x5b, 0x4f, 0xc0, 0x3c, 0xbf, 0x4a, 0xee, 0xd1, 0x47, 0xc5, 0x47, 0xd0, + 0x68, 0x63, 0xd0, 0x4d, 0xcd, 0x4e, 0xd6, 0x67, 0x68, 0xd6, 0x4b, 0xc9, + 0x4a, 0xd1, 0x6e, 0x52, 0xc5, 0x3f, 0xc0, 0x4b, 0xfd, 0xcb, 0x3d, 0xba, + 0x3d, 0xcc, 0xe2, 0x41, 0xb8, 0x37, 0xbc, 0x53, 0x4e, 0xba, 0x34, 0xb6, + 0x3f, 0xee, 0xc1, 0x36, 0xb2, 0x38, 0xc8, 0xd6, 0x3c, 0xb3, 0x34, 0xbc, + 0x58, 0x49, 0xb9, 0x34, 0xb7, 0x44, 0x73, 0xc3, 0x38, 0xb7, 0x3d, 0xce, + 0xd9, 0x40, 0xbc, 0x3c, 0xc5, 0x5e, 0x55, 0xc6, 0x40, 0xc3, 0x4d, 0xe5, + 0xde, 0x4d, 0xca, 0x4b, 0xce, 0x5c, 0xfa, 0xe1, 0x54, 0xcd, 0x4d, 0xcd, + 0x56, 0xf3, 0xd9, 0x4a, 0xc4, 0x44, 0xcb, 0x67, 0x53, 0xc3, 0x3d, 0xbe, + 0x48, 0xf0, 0xca, 0x3c, 0xb8, 0x3b, 0xca, 0xdf, 0x3f, 0xb7, 0x36, 0xbc, + 0x54, 0x4c, 0xb9, 0x34, 0xb6, 0x40, 0xf7, 0xc1, 0x36, 0xb3, 0x39, 0xca, + 0xd6, 0x3c, 0xb4, 0x35, 0xbe, 0x5b, 0x49, 0xba, 0x36, 0xba, 0x47, 0x6f, + 0xc5, 0x3b, 0xba, 0x3f, 0xd2, 0xdd, 0x46, 0xbe, 0x3f, 0xc9, 0x5c, 0x5d, + 0xcc, 0x47, 0xc8, 0x4e, 0xdd, 0xf5, 0x5a, 0xd1, 0x4e, 0xcf, 0x52, 0xde, + 0x7d, 0x5c, 0xcf, 0x49, 0xc9, 0x4d, 0xdd, 0xde, 0x49, 0xc1, 0x3f, 0xc6, + 0x5d, 0x53, 0xc0, 0x3b, 0xbc, 0x46, 0xeb, 0xc8, 0x3b, 0xb7, 0x3b, 0xc8, + 0xde, 0x3e, 0xb6, 0x35, 0xbb, 0x57, 0x4c, 0xb9, 0x34, 0xb6, 0x42, 0xff, + 0xc1, 0x36, 0xb4, 0x3a, 0xcc, 0xd7, 0x3d, 0xb7, 0x37, 0xbf, 0x5e, 0x4a, + 0xbc, 0x38, 0xbc, 0x4a, 0x6e, 0xc9, 0x3e, 0xbe, 0x43, 0xd5, 0xe2, 0x4b, + 0xc5, 0x45, 0xcb, 0x5e, 0x6e, 0xd6, 0x4e, 0xcd, 0x51, 0xd7, 0x65, 0x74, + 0xdc, 0x54, 0xcd, 0x4d, 0xd1, 0x5e, 0x6b, 0xcf, 0x46, 0xc4, 0x47, 0xd7, + 0xe3, 0x48, 0xbe, 0x3d, 0xc3, 0x58, 0x54, 0xbe, 0x39, 0xba, 0x43, 0xee, + 0xc7, 0x3a, 0xb6, 0x3a, 0xc9, 0xdc, 0x3e, 0xb5, 0x35, 0xbd, 0x57, 0x4b, + 0xb9, 0x34, 0xb7, 0x43, 0x6f, 0xc1, 0x38, 0xb6, 0x3c, 0xcf, 0xd3, 0x3e, + 0xb8, 0x3a, 0xc3, 0x64, 0x4c, 0xbe, 0x3c, 0xbf, 0x4d, 0x72, 0xcc, 0x42, + 0xc1, 0x48, 0xd9, 0xed, 0x51, 0xcc, 0x4b, 0xcf, 0x5a, 0xef, 0xe8, 0x59, + 0xd3, 0x50, 0xd1, 0x58, 0xe1, 0xec, 0x56, 0xcc, 0x49, 0xca, 0x55, 0x7c, + 0xcf, 0x44, 0xbf, 0x43, 0xcf, 0xe5, 0x47, 0xbc, 0x3b, 0xbf, 0x56, 0x52, + 0xbe, 0x38, 0xb9, 0x42, 0xee, 0xc6, 0x39, 0xb6, 0x3a, 0xca, 0xdc, 0x3d, + 0xb6, 0x36, 0xbd, 0x59, 0x4a, 0xba, 0x35, 0xb9, 0x45, 0x6b, 0xc2, 0x39, + 0xb8, 0x3d, 0xd2, 0xd5, 0x3f, 0xbb, 0x3c, 0xc6, 0x69, 0x4f, 0xc1, 0x3f, + 0xc3, 0x50, 0x7d, 0xd0, 0x49, 0xc8, 0x4c, 0xd8, 0x7d, 0x5d, 0xd4, 0x4f, + 0xd2, 0x57, 0xde, 0x6e, 0x69, 0xda, 0x50, 0xcd, 0x4e, 0xd6, 0x71, 0x59, + 0xca, 0x44, 0xc5, 0x4d, 0xf3, 0xce, 0x41, 0xbd, 0x3f, 0xcd, 0xe5, 0x45, + 0xbb, 0x3a, 0xbf, 0x55, 0x51, 0xbd, 0x37, 0xb9, 0x43, 0xf1, 0xc5, 0x39, + 0xb6, 0x3b, 0xcc, 0xd9, 0x3d, 0xb7, 0x37, 0xbf, 0x5d, 0x49, 0xbb, 0x37, + 0xba, 0x48, 0x69, 0xc4, 0x3b, 0xba, 0x40, 0xd5, 0xd7, 0x42, 0xbd, 0x3f, + 0xc9, 0x69, 0x52, 0xc7, 0x44, 0xc7, 0x52, 0xfa, 0xda, 0x4f, 0xcd, 0x4f, + 0xd8, 0x66, 0x72, 0xdf, 0x59, 0xd4, 0x52, 0xd6, 0x5d, 0xef, 0xde, 0x4f, + 0xca, 0x49, 0xce, 0x64, 0x5a, 0xc8, 0x40, 0xc1, 0x4a, 0xec, 0xcd, 0x3f, + 0xbc, 0x3e, 0xcc, 0xe5, 0x43, 0xba, 0x39, 0xbe, 0x55, 0x4f, 0xbc, 0x37, + 0xb9, 0x43, 0xf8, 0xc4, 0x39, 0xb6, 0x3b, 0xcd, 0xd7, 0x3e, 0xb7, 0x38, + 0xc0, 0x60, 0x4b, 0xbc, 0x39, 0xbc, 0x4b, 0x6b, 0xc6, 0x3d, 0xbd, 0x43, + 0xd9, 0xda, 0x47, 0xc1, 0x42, 0xcd, 0x66, 0x5a, 0xcd, 0x48, 0xcc, 0x54, + 0xe9, 0xe9, 0x59, 0xd5, 0x54, 0xd5, 0x5c, 0xe4, 0xfb, 0x61, 0xd4, 0x4f, + 0xcd, 0x51, 0xdf, 0xe3, 0x4e, 0xc5, 0x45, 0xca, 0x5e, 0x5b, 0xc6, 0x3e, + 0xbf, 0x48, 0xea, 0xcc, 0x3e, 0xbb, 0x3d, 0xcb, 0xe4, 0x42, 0xba, 0x38, + 0xbe, 0x56, 0x4e, 0xbc, 0x37, 0xb9, 0x44, 0x7b, 0xc4, 0x39, 0xb7, 0x3c, + 0xcf, 0xd7, 0x3e, 0xb9, 0x3a, 0xc2, 0x62, 0x4c, 0xbd, 0x3b, 0xbe, 0x4d, + 0x6b, 0xc9, 0x3f, 0xbf, 0x46, 0xd9, 0xde, 0x4b, 0xc5, 0x47, 0xce, 0x63, + 0x65, 0xd3, 0x4e, 0xcf, 0x55, 0xdf, 0x74, 0x67, 0xdc, 0x55, 0xd3, 0x54, + 0xda, 0x68, 0x67, 0xd5, 0x4c, 0xca, 0x4d, 0xdc, 0xe7, 0x4d, 0xc4, 0x42, + 0xc8, 0x5b, 0x59, 0xc4, 0x3d, 0xbe, 0x47, 0xec, 0xcc, 0x3d, 0xba, 0x3d, + 0xcc, 0xe1, 0x42, 0xba, 0x39, 0xbf, 0x5a, 0x4f, 0xbc, 0x38, 0xba, 0x46, + 0x7d, 0xc5, 0x3b, 0xb9, 0x3e, 0xd0, 0xd8, 0x40, 0xbb, 0x3c, 0xc5, 0x63, + 0x4d, 0xc0, 0x3d, 0xc1, 0x4e, 0x6e, 0xcd, 0x44, 0xc4, 0x49, 0xdb, 0xec, + 0x50, 0xcc, 0x4a, 0xd1, 0x5c, 0x7b, 0xde, 0x56, 0xd2, 0x54, 0xd8, 0x62, + 0xf2, 0xe2, 0x58, 0xcf, 0x4e, 0xd0, 0x5d, 0x72, 0xd3, 0x4a, 0xc5, 0x49, + 0xd6, 0xe8, 0x4b, 0xc0, 0x3f, 0xc5, 0x5b, 0x58, 0xc2, 0x3c, 0xbd, 0x47, + 0xee, 0xca, 0x3d, 0xba, 0x3d, 0xcd, 0xdf, 0x41, 0xba, 0x3a, 0xc0, 0x5b, + 0x4d, 0xbd, 0x39, 0xbc, 0x48, 0x73, 0xc6, 0x3c, 0xbb, 0x3f, 0xd4, 0xd9, + 0x43, 0xbd, 0x3e, 0xc8, 0x67, 0x50, 0xc4, 0x40, 0xc4, 0x51, 0x7b, 0xd1, + 0x4a, 0xc8, 0x4d, 0xd9, 0xf6, 0x5c, 0xd0, 0x51, 0xd2, 0x5c, 0xe8, 0xf2, + 0x62, 0xd8, 0x55, 0xd4, 0x56, 0xe1, 0x7c, 0x59, 0xcf, 0x49, 0xcc, 0x53, + 0x7a, 0xd4, 0x46, 0xc4, 0x45, 0xd5, 0xef, 0x49, 0xc0, 0x3d, 0xc5, 0x57, + 0x55, 0xc2, 0x3b, 0xbd, 0x47, 0xed, 0xc9, 0x3d, 0xb9, 0x3e, 0xcc, 0xdb, + 0x43, 0xb9, 0x3b, 0xc0, 0x61, 0x4f, 0xbd, 0x3b, 0xbc, 0x4b, 0x75, 0xc6, + 0x3d, 0xbc, 0x43, 0xd7, 0xd9, 0x45, 0xbf, 0x40, 0xcc, 0x68, 0x54, 0xc9, + 0x44, 0xca, 0x53, 0x7d, 0xda, 0x4d, 0xce, 0x4f, 0xdb, 0x6d, 0x68, 0xdd, + 0x55, 0xd6, 0x56, 0xdc, 0x67, 0x71, 0xde, 0x53, 0xce, 0x4f, 0xd6, 0x6e, + 0x5c, 0xcc, 0x47, 0xc7, 0x4f, 0xef, 0xd0, 0x45, 0xbf, 0x44, 0xcf, 0xe6, + 0x48, 0xbe, 0x3d, 0xc3, 0x5a, 0x54, 0xc0, 0x3b, 0xbc, 0x47, 0xfa, 0xc9, + 0x3c, 0xba, 0x3e, 0xd0, 0xdc, 0x41, 0xbb, 0x3b, 0xc4, 0x5f, 0x4d, 0xbf, + 0x3c, 0xbf, 0x4c, 0x6d, 0xc9, 0x3f, 0xbe, 0x46, 0xda, 0xdc, 0x49, 0xc3, + 0x45, 0xce, 0x6b, 0x5b, 0xcd, 0x4a, 0xcd, 0x57, 0xee, 0xe4, 0x58, 0xd4, + 0x54, 0xd9, 0x60, 0xf1, 0xed, 0x5f, 0xd7, 0x53, 0xd3, 0x5a, 0xeb, 0xe1, + 0x52, 0xca, 0x4b, 0xce, 0x68, 0x5c, 0xc9, 0x44, 0xc4, 0x4e, 0xec, 0xce, + 0x43, 0xbe, 0x42, 0xcf, 0xe4, 0x47, 0xbd, 0x3c, 0xc3, 0x5b, 0x50, 0xbf, + 0x3b, 0xbd, 0x48, 0x73, 0xc8, 0x3c, 0xbb, 0x3f, 0xd4, 0xda, 0x41, 0xbc, + 0x3d, 0xc7, 0x67, 0x4d, 0xc0, 0x3d, 0xc2, 0x4f, 0x68, 0xcb, 0x42, 0xc2, + 0x4a, 0xdd, 0xdd, 0x4d, 0xc7, 0x4a, 0xd1, 0x6d, 0x65, 0xd3, 0x52, 0xd1, + 0x5b, 0xe3, 0xf8, 0x68, 0xdd, 0x5a, 0xd8, 0x59, 0xde, 0x6d, 0x68, 0xd9, + 0x4e, 0xce, 0x4f, 0xe1, 0xeb, 0x4e, 0xc9, 0x45, 0xcd, 0x5d, 0x58, 0xc9, + 0x3f, 0xc2, 0x4b, 0xf6, 0xce, 0x3f, 0xbd, 0x40, 0xcf, 0xe0, 0x45, 0xbc, + 0x3d, 0xc3, 0x5e, 0x51, 0xbf, 0x3c, 0xbd, 0x4b, 0x7b, 0xc7, 0x3e, 0xbb, + 0x42, 0xd4, 0xd6, 0x45, 0xbd, 0x3f, 0xc9, 0x6f, 0x50, 0xc3, 0x40, 0xc5, + 0x53, 0x6c, 0xce, 0x46, 0xc7, 0x4c, 0xe0, 0xeb, 0x51, 0xce, 0x4d, 0xd7, + 0x5f, 0x6c, 0xe3, 0x57, 0xd9, 0x57, 0xde, 0x64, 0xfd, 0xe9, 0x5b, 0xd5, + 0x52, 0xd5, 0x61, 0x78, 0xd6, 0x4d, 0xc9, 0x4e, 0xd8, 0xe5, 0x4e, 0xc4, + 0x44, 0xc9, 0x5f, 0x5a, 0xc6, 0x3f, 0xc0, 0x4b, 0xf5, 0xcd, 0x3f, 0xbd, + 0x40, 0xd2, 0xdf, 0x43, 0xbd, 0x3c, 0xc6, 0x5e, 0x4e, 0xbf, 0x3b, 0xbf, + 0x4c, 0x6b, 0xc9, 0x3e, 0xbd, 0x44, 0xda, 0xd8, 0x45, 0xbf, 0x42, 0xcc, + 0x75, 0x52, 0xc6, 0x45, 0xc8, 0x58, 0x76, 0xd2, 0x4c, 0xca, 0x51, 0xdd, + 0xed, 0x5d, 0xd3, 0x55, 0xd7, 0x61, 0xec, 0xef, 0x65, 0xdc, 0x58, 0xd7, + 0x5a, 0xe4, 0xfd, 0x5c, 0xd2, 0x4c, 0xcf, 0x57, 0x77, 0xd8, 0x49, 0xc7, + 0x48, 0xd8, 0xeb, 0x4b, 0xc3, 0x40, 0xc8, 0x5d, 0x57, 0xc4, 0x3e, 0xbf, + 0x4b, 0xf8, 0xca, 0x3f, 0xbc, 0x42, 0xd1, 0xd9, 0x45, 0xbc, 0x3e, 0xc6, + 0x6a, 0x4f, 0xbf, 0x3d, 0xc0, 0x4f, 0x67, 0xc9, 0x3f, 0xbf, 0x47, 0xdf, + 0xdb, 0x47, 0xc4, 0x44, 0xd1, 0x6c, 0x53, 0xcc, 0x48, 0xce, 0x58, 0x74, + 0xdc, 0x50, 0xd1, 0x54, 0xdf, 0x74, 0x6a, 0xde, 0x5b, 0xd9, 0x5d, 0xdd, + 0x6e, 0xfc, 0xdd, 0x5a, 0xcf, 0x54, 0xd6, 0x7b, 0x60, 0xcd, 0x4b, 0xc9, + 0x55, 0xf2, 0xd2, 0x48, 0xc3, 0x47, 0xd5, 0xe6, 0x4a, 0xc1, 0x3f, 0xc8, + 0x5d, 0x52, 0xc4, 0x3d, 0xc0, 0x4b, 0x71, 0xcb, 0x3e, 0xbd, 0x41, 0xd7, + 0xdc, 0x43, 0xbe, 0x3e, 0xc9, 0x6a, 0x4e, 0xc1, 0x3e, 0xc3, 0x52, 0x6a, + 0xca, 0x43, 0xc1, 0x4b, 0xdd, 0xda, 0x4c, 0xc6, 0x4a, 0xd1, 0x77, 0x5d, + 0xce, 0x4e, 0xcf, 0x5c, 0xf3, 0xe5, 0x5a, 0xd8, 0x58, 0xdd, 0x62, 0xfb, + 0xf4, 0x5e, 0xdc, 0x54, 0xd9, 0x5a, 0xf6, 0xea, 0x52, 0xce, 0x4c, 0xd4, + 0x67, 0x5c, 0xcc, 0x46, 0xc8, 0x50, 0xf8, 0xd0, 0x45, 0xc0, 0x46, 0xd3, + 0xe1, 0x49, 0xbf, 0x3f, 0xc6, 0x63, 0x54, 0xc1, 0x3e, 0xbf, 0x4d, 0x76, + 0xc9, 0x3f, 0xbd, 0x44, 0xd8, 0xda, 0x44, 0xbe, 0x3f, 0xcb, 0x6b, 0x4e, + 0xc4, 0x3f, 0xc7, 0x53, 0x66, 0xcd, 0x45, 0xc6, 0x4d, 0xe3, 0xdf, 0x4e, + 0xcb, 0x4d, 0xd5, 0x6f, 0x65, 0xd6, 0x55, 0xd4, 0x5e, 0xe5, 0xf1, 0x6b, + 0xdc, 0x5d, 0xd8, 0x5e, 0xdf, 0x79, 0x6d, 0xd9, 0x53, 0xcf, 0x56, 0xe3, + 0xe8, 0x52, 0xcb, 0x49, 0xcf, 0x61, 0x5a, 0xcb, 0x43, 0xc6, 0x4d, 0x7c, + 0xd1, 0x42, 0xc0, 0x43, 0xd6, 0xe3, 0x47, 0xbf, 0x3e, 0xc8, 0x63, 0x51, + 0xc1, 0x3e, 0xc0, 0x4e, 0x6f, 0xc9, 0x3f, 0xbe, 0x47, 0xd9, 0xd7, 0x47, + 0xbf, 0x43, 0xcc, 0x79, 0x51, 0xc6, 0x44, 0xc9, 0x58, 0x6a, 0xd0, 0x49, + 0xca, 0x4f, 0xe6, 0xea, 0x54, 0xd1, 0x50, 0xdb, 0x65, 0x6e, 0xe4, 0x5a, + 0xdc, 0x5a, 0xe1, 0x67, 0xfe, 0xea, 0x5d, 0xd7, 0x55, 0xd8, 0x63, 0x74, + 0xd8, 0x4f, 0xcb, 0x4f, 0xdc, 0xe5, 0x50, 0xc7, 0x47, 0xcc, 0x65, 0x5b, + 0xc8, 0x43, 0xc4, 0x4e, 0xfa, 0xcd, 0x42, 0xbf, 0x44, 0xd6, 0xdf, 0x46, + 0xbf, 0x3f, 0xc9, 0x64, 0x4f, 0xc3, 0x3e, 0xc3, 0x4f, 0x68, 0xcb, 0x40, + 0xc0, 0x48, 0xde, 0xd9, 0x48, 0xc2, 0x45, 0xcf, 0x7b, 0x55, 0xc9, 0x48, + 0xcb, 0x5c, 0x75, 0xd3, 0x4f, 0xcd, 0x56, 0xe0, 0xed, 0x5e, 0xd7, 0x58, + 0xdb, 0x63, 0xef, 0xf5, 0x65, 0xdf, 0x5a, 0xdb, 0x5b, 0xea, 0x7d, 0x5d, + 0xd6, 0x4e, 0xd3, 0x59, 0x73, 0xd9, 0x4b, 0xca, 0x4b, 0xdc, 0xeb, 0x4d, + 0xc6, 0x44, 0xcb, 0x61, 0x59, 0xc7, 0x41, 0xc2, 0x4e, 0xfb, 0xcc, 0x42, + 0xbe, 0x46, 0xd5, 0xdb, 0x47, 0xbe, 0x40, 0xc9, 0x6e, 0x50, 0xc2, 0x3f, + 0xc4, 0x52, 0x69, 0xcb, 0x42, 0xc3, 0x4a, 0xe2, 0xdc, 0x49, 0xc6, 0x48, + 0xd4, 0x71, 0x56, 0xcd, 0x4a, 0xcf, 0x5b, 0x73, 0xdd, 0x53, 0xd3, 0x57, + 0xe2, 0x78, 0x6a, 0xdf, 0x5d, 0xdb, 0x5e, 0xe1, 0x6f, 0x7a, 0xe0, 0x5b, + 0xd4, 0x57, 0xdb, 0x79, 0x5f, 0xd0, 0x4d, 0xcd, 0x58, 0xfd, 0xd6, 0x4a, + 0xc7, 0x4a, 0xd9, 0xe8, 0x4c, 0xc5, 0x42, 0xcb, 0x5f, 0x55, 0xc7, 0x3f, + 0xc4, 0x4e, 0x71, 0xcd, 0x41, 0xbf, 0x46, 0xd9, 0xdb, 0x47, 0xbf, 0x41, + 0xcb, 0x70, 0x51, 0xc4, 0x42, 0xc5, 0x57, 0x6b, 0xcc, 0x46, 0xc4, 0x4d, + 0xe0, 0xdb, 0x4d, 0xc8, 0x4c, 0xd5, 0x78, 0x5d, 0xd1, 0x4f, 0xd3, 0x5d, + 0xfb, 0xe6, 0x5a, 0xda, 0x59, 0xe1, 0x67, 0x7c, 0xf1, 0x5f, 0xde, 0x58, + 0xdc, 0x5e, 0xf9, 0xe8, 0x56, 0xd1, 0x4f, 0xd7, 0x6d, 0x5e, 0xce, 0x4a, + 0xcb, 0x55, 0xf7, 0xd3, 0x49, 0xc4, 0x4a, 0xd7, 0xe3, 0x4c, 0xc2, 0x43, + 0xca, 0x66, 0x56, 0xc5, 0x40, 0xc3, 0x4f, 0x74, 0xcc, 0x42, 0xc0, 0x47, + 0xdb, 0xdc, 0x47, 0xc1, 0x42, 0xce, 0x6e, 0x50, 0xc7, 0x43, 0xc9, 0x57, + 0x67, 0xcf, 0x48, 0xc9, 0x4e, 0xe6, 0xe0, 0x50, 0xcd, 0x4e, 0xd8, 0x6f, + 0x65, 0xd7, 0x55, 0xd7, 0x5f, 0xeb, 0xf3, 0x68, 0xde, 0x5e, 0xdc, 0x60, + 0xe5, 0x7b, 0x6b, 0xdc, 0x56, 0xd4, 0x59, 0xe8, 0xe8, 0x55, 0xcd, 0x4c, + 0xd3, 0x68, 0x5c, 0xcd, 0x47, 0xc9, 0x52, 0xfe, 0xd2, 0x47, 0xc4, 0x48, + 0xd8, 0xe2, 0x4a, 0xc2, 0x42, 0xcb, 0x68, 0x54, 0xc5, 0x40, 0xc4, 0x51, + 0x6e, 0xcc, 0x42, 0xc1, 0x49, 0xdd, 0xda, 0x49, 0xc3, 0x46, 0xcf, 0x7a, + 0x53, 0xc8, 0x46, 0xcb, 0x5b, 0x6a, 0xd2, 0x4b, 0xcc, 0x52, 0xe7, 0xe7, + 0x56, 0xd2, 0x53, 0xdc, 0x6a, 0x6d, 0xe2, 0x5b, 0xdc, 0x5e, 0xe5, 0x6d, + 0x7a, 0xea, 0x5f, 0xda, 0x59, 0xdc, 0x68, 0x70, 0xdb, 0x52, 0xcf, 0x53, + 0xe0, 0xe9, 0x53, 0xcb, 0x4a, 0xcf, 0x66, 0x5c, 0xcb, 0x46, 0xc7, 0x51, + 0xfb, 0xcf, 0x46, 0xc2, 0x48, 0xd8, 0xdf, 0x4a, 0xc2, 0x42, 0xcb, 0x69, + 0x53, 0xc5, 0x41, 0xc5, 0x53, 0x6b, 0xcc, 0x44, 0xc3, 0x4a, 0xe0, 0xdb, + 0x4a, 0xc5, 0x48, 0xd2, 0x78, 0x55, 0xcb, 0x49, 0xce, 0x5c, 0x6e, 0xd7, + 0x4f, 0xcf, 0x56, 0xe6, 0xef, 0x5d, 0xd8, 0x59, 0xdc, 0x67, 0xf6, 0xee, + 0x65, 0xdf, 0x5d, 0xdd, 0x61, 0xec, 0xf4, 0x5f, 0xd8, 0x54, 0xd6, 0x5f, + 0x78, 0xda, 0x4f, 0xcc, 0x4f, 0xde, 0xea, 0x50, 0xc9, 0x48, 0xce, 0x64, + 0x5a, 0xca, 0x45, 0xc7, 0x50, 0x7b, 0xcf, 0x45, 0xc3, 0x48, 0xda, 0xde, + 0x4a, 0xc2, 0x43, 0xcc, 0x6d, 0x53, 0xc6, 0x43, 0xc7, 0x56, 0x6a, 0xcd, + 0x46, 0xc5, 0x4d, 0xe2, 0xdc, 0x4c, 0xc8, 0x4b, 0xd5, 0x7a, 0x59, 0xce, + 0x4d, 0xd0, 0x5e, 0x76, 0xdc, 0x55, 0xd4, 0x5a, 0xe5, 0x7d, 0x68, 0xdf, + 0x5d, 0xde, 0x60, 0xe9, 0x73, 0x70, 0xe4, 0x5c, 0xd9, 0x59, 0xe1, 0x7a, + 0x60, 0xd5, 0x4f, 0xd1, 0x5b, 0x7e, 0xd9, 0x4d, 0xca, 0x4d, 0xdb, 0xe8, + 0x4f, 0xc8, 0x47, 0xcd, 0x66, 0x5a, 0xc9, 0x44, 0xc6, 0x51, 0x78, 0xce, + 0x45, 0xc3, 0x49, 0xdb, 0xdd, 0x49, 0xc3, 0x44, 0xce, 0x6f, 0x53, 0xc7, + 0x44, 0xc9, 0x57, 0x69, 0xce, 0x48, 0xc8, 0x4e, 0xe6, 0xde, 0x4e, 0xcb, + 0x4d, 0xd8, 0x78, 0x5d, 0xd2, 0x50, 0xd5, 0x5f, 0xfc, 0xe4, 0x5c, 0xda, + 0x5c, 0xe2, 0x6e, 0x7d, 0xeb, 0x63, 0xde, 0x5d, 0xde, 0x65, 0xf9, 0xe7, + 0x5a, 0xd5, 0x54, 0xdb, 0x6f, 0x5f, 0xd2, 0x4d, 0xce, 0x58, 0x7d, 0xd7, + 0x4b, 0xc9, 0x4c, 0xdb, 0xe7, 0x4e, 0xc6, 0x46, 0xcd, 0x67, 0x58, 0xc8, + 0x44, 0xc7, 0x53, 0x72, 0xce, 0x45, 0xc3, 0x4a, 0xdd, 0xdc, 0x4a, 0xc4, + 0x46, 0xcf, 0x76, 0x54, 0xc9, 0x46, 0xcb, 0x5a, 0x69, 0xd0, 0x4a, 0xcb, + 0x51, 0xe8, 0xe1, 0x52, 0xce, 0x50, 0xdb, 0x72, 0x63, 0xda, 0x56, 0xda, + 0x5f, 0xf1, 0xf3, 0x65, 0xe0, 0x5e, 0xe0, 0x63, 0xec, 0x7c, 0x6a, 0xde, + 0x59, 0xd8, 0x5c, 0xec, 0xe9, 0x58, 0xd0, 0x4f, 0xd7, 0x6c, 0x5f, 0xcf, + 0x4b, 0xcc, 0x56, 0xfc, 0xd5, 0x4a, 0xc7, 0x4b, 0xdb, 0xe4, 0x4d, 0xc6, + 0x46, 0xcd, 0x69, 0x57, 0xc8, 0x44, 0xc7, 0x54, 0x6e, 0xce, 0x46, 0xc5, + 0x4b, 0xdf, 0xdc, 0x4b, 0xc6, 0x48, 0xd2, 0x79, 0x55, 0xcb, 0x49, 0xcd, + 0x5d, 0x6c, 0xd4, 0x4d, 0xcd, 0x55, 0xe8, 0xe6, 0x58, 0xd3, 0x55, 0xdd, + 0x6e, 0x6d, 0xe0, 0x5d, 0xdd, 0x5f, 0xe9, 0x73, 0x75, 0xe9, 0x60, 0xdd, + 0x5c, 0xe0, 0x6c, 0x6e, 0xde, 0x55, 0xd4, 0x57, 0xe6, 0xeb, 0x56, 0xce, + 0x4d, 0xd4, 0x6a, 0x5e, 0xce, 0x49, 0xcb, 0x55, 0xfe, 0xd3, 0x49, 0xc6, + 0x4b, 0xdb, 0xe2, 0x4c, 0xc5, 0x46, 0xce, 0x6c, 0x56, 0xc8, 0x45, 0xc8, + 0x56, 0x6c, 0xce, 0x47, 0xc6, 0x4d, 0xe3, 0xdd, 0x4c, 0xc8, 0x4a, 0xd5, + 0x7a, 0x57, 0xcd, 0x4b, 0xcf, 0x5e, 0x6d, 0xd8, 0x50, 0xd1, 0x58, 0xe9, + 0xee, 0x5d, 0xd9, 0x5a, 0xde, 0x6a, 0xfe, 0xec, 0x65, 0xe0, 0x5f, 0xe0, + 0x67, 0xf0, 0xf1, 0x63, 0xda, 0x58, 0xda, 0x65, 0x78, 0xdc, 0x53, 0xcf, + 0x54, 0xe1, 0xeb, 0x54, 0xcc, 0x4b, 0xd1, 0x68, 0x5d, 0xcd, 0x48, 0xca, + 0x54, 0x7b, 0xd2, 0x48, 0xc6, 0x4b, 0xdc, 0xe0, 0x4c, 0xc6, 0x47, 0xcf, + 0x6e, 0x55, 0xc9, 0x45, 0xca, 0x58, 0x6b, 0xcf, 0x48, 0xc8, 0x4e, 0xe5, + 0xdd, 0x4e, 0xca, 0x4c, 0xd8, 0x7b, 0x5a, 0xcf, 0x4e, 0xd3, 0x60, 0x73, + 0xdd, 0x56, 0xd6, 0x5b, 0xe8, 0xfc, 0x67, 0xdf, 0x5e, 0xdf, 0x65, 0xed, + 0x7b, 0x6e, 0xe5, 0x5e, 0xdc, 0x5d, 0xe6, 0xff, 0x63, 0xd8, 0x53, 0xd5, + 0x5e, 0x7c, 0xdb, 0x4f, 0xcd, 0x50, 0xde, 0xea, 0x52, 0xcb, 0x4a, 0xcf, + 0x68, 0x5c, 0xcc, 0x47, 0xc9, 0x54, 0x79, 0xd1, 0x48, 0xc6, 0x4b, 0xdd, + 0xdf, 0x4c, 0xc6, 0x47, 0xd0, 0x6f, 0x56, 0xca, 0x47, 0xcb, 0x5a, 0x6b, + 0xd1, 0x4a, 0xca, 0x50, 0xe7, 0xdf, 0x50, 0xcd, 0x4f, 0xda, 0x79, 0x5e, + 0xd5, 0x52, 0xd7, 0x62, 0xff, 0xe4, 0x5c, 0xdb, 0x5d, 0xe5, 0x73, 0x77, + 0xea, 0x64, 0xe0, 0x5f, 0xe2, 0x6b, 0xfe, 0xe8, 0x5c, 0xd8, 0x58, 0xde, + 0x76, 0x63, 0xd5, 0x50, 0xd1, 0x5b, 0xff, 0xda, 0x4e, 0xcb, 0x4f, 0xdd, + 0xe9, 0x50, 0xca, 0x49, 0xcf, 0x69, 0x5b, 0xcb, 0x47, 0xc9, 0x55, 0x75, + 0xd1, 0x48, 0xc7, 0x4c, 0xde, 0xde, 0x4c, 0xc7, 0x49, 0xd2, 0x76, 0x57, + 0xcb, 0x49, 0xcd, 0x5c, 0x6b, 0xd3, 0x4c, 0xcd, 0x54, 0xe9, 0xe3, 0x54, + 0xcf, 0x52, 0xdc, 0x75, 0x64, 0xda, 0x58, 0xdb, 0x63, 0xf4, 0xee, 0x65, + 0xe0, 0x5f, 0xe3, 0x68, 0xf1, 0xf9, 0x6a, 0xe0, 0x5d, 0xdc, 0x60, 0xef, + 0xeb, 0x5b, 0xd5, 0x54, 0xda, 0x6d, 0x62, 0xd3, 0x4e, 0xcf, 0x59, 0xfd, + 0xd9, 0x4d, 0xca, 0x4e, 0xdd, 0xe8, 0x4f, 0xc9, 0x49, 0xcf, 0x69, 0x5a, + 0xcb, 0x47, 0xca, 0x56, 0x73, 0xd1, 0x49, 0xc7, 0x4d, 0xdf, 0xdf, 0x4d, + 0xc8, 0x4a, 0xd4, 0x77, 0x58, 0xcd, 0x4b, 0xcf, 0x5d, 0x6d, 0xd6, 0x4e, + 0xcf, 0x56, 0xea, 0xe9, 0x59, 0xd5, 0x56, 0xde, 0x6f, 0x6d, 0xdf, 0x5d, + 0xdd, 0x62, 0xeb, 0x7c, 0x71, 0xe8, 0x62, 0xdf, 0x60, 0xe5, 0x73, 0x6f, + 0xdf, 0x5a, 0xd7, 0x5c, 0xe9, 0xec, 0x5a, 0xd1, 0x50, 0xd7, 0x6c, 0x61, + 0xd1, 0x4c, 0xcd, 0x58, 0xfd, 0xd7, 0x4c, 0xc9, 0x4d, 0xdd, 0xe7, 0x4f, + 0xc9, 0x49, 0xcf, 0x6b, 0x59, 0xcb, 0x47, 0xcb, 0x57, 0x6f, 0xd1, 0x49, + 0xc9, 0x4e, 0xe2, 0xdf, 0x4e, 0xca, 0x4c, 0xd6, 0x78, 0x5a, 0xcf, 0x4d, + 0xd1, 0x5f, 0x70, 0xda, 0x52, 0xd3, 0x59, 0xe9, 0xef, 0x5e, 0xda, 0x5a, + 0xdf, 0x6b, 0x7b, 0xeb, 0x63, 0xe1, 0x61, 0xe5, 0x6b, 0xfa, 0xf0, 0x64, + 0xdd, 0x5b, 0xde, 0x68, 0x75, 0xdf, 0x56, 0xd4, 0x58, 0xe4, 0xed, 0x58, + 0xcf, 0x4e, 0xd5, 0x6a, 0x60, 0xcf, 0x4b, 0xcc, 0x57, 0xfd, 0xd6, 0x4b, + 0xc9, 0x4d, 0xdd, 0xe5, 0x4f, 0xc9, 0x49, 0xd0, 0x6d, 0x5a, 0xcc, 0x48, + 0xcc, 0x59, 0x6f, 0xd3, 0x4b, 0xca, 0x4f, 0xe5, 0xe1, 0x50, 0xcc, 0x4e, + 0xd9, 0x77, 0x5d, 0xd2, 0x4f, 0xd5, 0x60, 0x77, 0xde, 0x57, 0xd7, 0x5c, + 0xe8, 0xfa, 0x67, 0xdf, 0x5e, 0xe0, 0x67, 0xf0, 0xfc, 0x6d, 0xe5, 0x5f, + 0xdf, 0x61, 0xeb, 0xfb, 0x65, 0xdb, 0x57, 0xd9, 0x61, 0x7c, 0xde, 0x54, + 0xd0, 0x54, 0xe1, 0xed, 0x56, 0xcd, 0x4d, 0xd3, 0x69, 0x5f, 0xce, 0x4b, + 0xcc, 0x57, 0x7d, 0xd5, 0x4b, 0xc9, 0x4e, 0xde, 0xe4, 0x4f, 0xc9, 0x4a, + 0xd2, 0x6f, 0x5a, 0xcc, 0x4a, 0xcd, 0x5b, 0x6f, 0xd4, 0x4c, 0xcc, 0x52, + 0xe7, 0xe4, 0x53, 0xce, 0x50, 0xdb, 0x76, 0x60, 0xd6, 0x54, 0xd8, 0x62, + 0xff, 0xe5, 0x5d, 0xdc, 0x5e, 0xe7, 0x75, 0x73, 0xe9, 0x64, 0xe2, 0x63, + 0xe7, 0x6f, 0x7b, 0xe9, 0x5f, 0xdb, 0x5c, 0xe2, 0x78, 0x65, 0xd9, 0x54, + 0xd6, 0x5e, 0xfe, 0xdd, 0x51, 0xce, 0x52, 0xdf, 0xec, 0x54, 0xcd, 0x4c, + 0xd2, 0x69, 0x5d, 0xce, 0x4a, 0xcc, 0x57, 0x7a, 0xd5, 0x4b, 0xc9, 0x4e, + 0xdf, 0xe3, 0x4f, 0xca, 0x4b, 0xd4, 0x70, 0x5a, 0xcd, 0x4b, 0xce, 0x5c, + 0x6f, 0xd6, 0x4e, 0xce, 0x55, 0xe8, 0xe7, 0x57, 0xd2, 0x54, 0xdd, 0x73, + 0x66, 0xdb, 0x59, 0xdb, 0x63, 0xf5, 0xee, 0x65, 0xe0, 0x60, 0xe5, 0x6b, + 0xf7, 0xf6, 0x69, 0xe2, 0x5f, 0xdf, 0x65, 0xf5, 0xeb, 0x5d, 0xd8, 0x58, + 0xdd, 0x71, 0x65, 0xd7, 0x51, 0xd2, 0x5c, 0xfd, 0xdb, 0x4f, 0xcd, 0x50, + 0xde, 0xea, 0x53, 0xcc, 0x4c, 0xd2, 0x6a, 0x5d, 0xce, 0x4a, 0xcc, 0x58, + 0x78, 0xd4, 0x4b, 0xca, 0x4f, 0xe1, 0xe3, 0x4f, 0xcb, 0x4c, 0xd6, 0x74, + 0x5b, 0xcf, 0x4d, 0xd0, 0x5e, 0x70, 0xd9, 0x50, 0xd0, 0x58, 0xea, 0xeb, + 0x5b, 0xd6, 0x58, 0xdf, 0x6f, 0x6d, 0xe1, 0x5d, 0xde, 0x64, 0xed, 0xff, + 0x6e, 0xe8, 0x63, 0xe2, 0x64, 0xe9, 0x77, 0x6e, 0xe2, 0x5c, 0xdb, 0x5e, + 0xec, 0xed, 0x5c, 0xd5, 0x54, 0xda, 0x6d, 0x64, 0xd5, 0x4f, 0xd0, 0x5a, + 0xfd, 0xda, 0x4e, 0xcc, 0x4f, 0xde, 0xe9, 0x52, 0xcb, 0x4b, 0xd3, 0x6c, + 0x5c, 0xce, 0x4a, 0xcd, 0x5a, 0x74, 0xd5, 0x4c, 0xcb, 0x50, 0xe4, 0xe3, + 0x51, 0xcc, 0x4e, 0xd8, 0x77, 0x5c, 0xd1, 0x4f, 0xd4, 0x60, 0x72, 0xdc, + 0x55, 0xd5, 0x5b, 0xeb, 0xef, 0x5f, 0xdb, 0x5c, 0xe1, 0x6d, 0x7a, 0xeb, + 0x65, 0xe3, 0x64, 0xe8, 0x6e, 0xfc, 0xef, 0x66, 0xdf, 0x5e, 0xe0, 0x6b, + 0x76, 0xe0, 0x59, 0xd7, 0x5a, 0xe8, 0xee, 0x5a, 0xd2, 0x51, 0xd8, 0x6c, + 0x62, 0xd3, 0x4e, 0xcf, 0x5a, 0xff, 0xd9, 0x4e, 0xcc, 0x4f, 0xdf, 0xe7, + 0x51, 0xcb, 0x4c, 0xd4, 0x6e, 0x5c, 0xce, 0x4b, 0xce, 0x5b, 0x71, 0xd5, + 0x4d, 0xcd, 0x53, 0xe7, 0xe3, 0x53, 0xce, 0x50, 0xdb, 0x78, 0x5e, 0xd5, + 0x52, 0xd7, 0x63, 0x78, 0xdf, 0x5a, 0xd9, 0x5e, 0xea, 0xf9, 0x69, 0xe1, + 0x60, 0xe3, 0x6a, 0xf2, 0xfa, 0x6e, 0xe7, 0x63, 0xe1, 0x65, 0xed, 0xfa, + 0x67, 0xdd, 0x5a, 0xdc, 0x65, 0x7b, 0xdf, 0x57, 0xd4, 0x57, 0xe4, 0xee, + 0x59, 0xd0, 0x4f, 0xd6, 0x6b, 0x60, 0xd2, 0x4d, 0xce, 0x59, 0x7d, 0xd8, + 0x4d, 0xcc, 0x4f, 0xe0, 0xe7, 0x51, 0xcc, 0x4c, 0xd5, 0x6f, 0x5b, 0xce, + 0x4c, 0xcf, 0x5c, 0x70, 0xd7, 0x4e, 0xce, 0x55, 0xe9, 0xe5, 0x56, 0xd1, + 0x53, 0xdd, 0x78, 0x62, 0xd9, 0x57, 0xda, 0x66, 0x7e, 0xe6, 0x5e, 0xde, + 0x60, 0xe9, 0x78, 0x73, 0xea, 0x66, 0xe4, 0x66, 0xea, 0x71, 0x7a, 0xea, + 0x61, 0xde, 0x5e, 0xe5, 0x7a, 0x67, 0xdb, 0x57, 0xd8, 0x60, 0x7e, 0xde, + 0x54, 0xd1, 0x55, 0xe2, 0xed, 0x57, 0xcf, 0x4e, 0xd6, 0x6b, 0x5f, 0xd0, + 0x4c, 0xce, 0x5a, 0x7a, 0xd8, 0x4d, 0xcc, 0x50, 0xe3, 0xe5, 0x51, 0xcc, + 0x4d, 0xd7, 0x74, 0x5c, 0xcf, 0x4d, 0xd1, 0x5e, 0x6f, 0xd9, 0x50, 0xd0, + 0x58, 0xea, 0xe7, 0x59, 0xd4, 0x57, 0xde, 0x77, 0x68, 0xdd, 0x5b, 0xdd, + 0x66, 0xf7, 0xee, 0x67, 0xe3, 0x64, 0xe7, 0x6d, 0xf9, 0xf5, 0x6b, 0xe5, + 0x62, 0xe2, 0x68, 0xf6, 0xed, 0x5f, 0xdb, 0x5a, 0xdf, 0x72, 0x67, 0xd9, + 0x54, 0xd5, 0x5e, 0xfd, 0xdd, 0x52, 0xcf, 0x53, 0xe1, 0xed, 0x56, 0xce, + 0x4e, 0xd5, 0x6b, 0x5e, 0xd0, 0x4c, 0xce, 0x5a, 0x77, 0xd7, 0x4d, 0xcc, + 0x52, 0xe4, 0xe5, 0x53, 0xcd, 0x4e, 0xd8, 0x76, 0x5d, 0xd1, 0x4f, 0xd3, + 0x5f, 0x71, 0xdb, 0x53, 0xd4, 0x5a, 0xec, 0xec, 0x5d, 0xd9, 0x5a, 0xe1, + 0x72, 0x6e, 0xe3, 0x5f, 0xe0, 0x66, 0xef, 0xfe, 0x6f, 0xe9, 0x66, 0xe5, + 0x67, 0xec, 0x79, 0x70, 0xe5, 0x5e, 0xdd, 0x60, 0xee, 0xee, 0x5e, 0xd8, + 0x57, 0xdc, 0x6f, 0x67, 0xd8, 0x52, 0xd3, 0x5d, 0xfd, 0xdc, 0x51, 0xce, + 0x53, 0xe0, 0xeb, 0x55, 0xce, 0x4e, 0xd6, 0x6d, 0x5e, 0xd0, 0x4c, 0xcf, + 0x5b, 0x75, 0xd8, 0x4e, 0xcd, 0x53, 0xe6, 0xe5, 0x54, 0xce, 0x50, 0xda, + 0x78, 0x5e, 0xd4, 0x51, 0xd6, 0x63, 0x74, 0xdd, 0x57, 0xd7, 0x5d, 0xec, + 0xf0, 0x61, 0xdd, 0x5e, 0xe4, 0x6f, 0x7a, 0xec, 0x67, 0xe5, 0x67, 0xea, + 0x70, 0xfe, 0xf0, 0x68, 0xe2, 0x61, 0xe3, 0x6d, 0x77, 0xe4, 0x5c, 0xda, + 0x5d, 0xea, 0xef, 0x5d, 0xd6, 0x54, 0xdb, 0x6c, 0x65, 0xd6, 0x50, 0xd2, + 0x5c, 0xfe, 0xdc, 0x50, 0xce, 0x52, 0xe1, 0xea, 0x55, 0xcd, 0x4e, 0xd6, + 0x6e, 0x5e, 0xd0, 0x4d, 0xcf, 0x5d, 0x74, 0xd8, 0x4f, 0xce, 0x55, 0xe8, + 0xe6, 0x56, 0xd0, 0x53, 0xdc, 0x78, 0x60, 0xd7, 0x55, 0xd9, 0x65, 0x78, + 0xe2, 0x5b, 0xdb, 0x5f, 0xec, 0xfa, 0x69, 0xe3, 0x62, 0xe6, 0x6b, 0xf6, + 0xfa, 0x6e, 0xe9, 0x66, 0xe5, 0x68, 0xee, 0xfb, 0x69, 0xdf, 0x5d, 0xde, + 0x68, 0x7c, 0xe3, 0x5a, 0xd7, 0x5a, 0xe6, 0xef, 0x5c, 0xd3, 0x52, 0xd9, + 0x6c, 0x64, 0xd5, 0x4f, 0xd1, 0x5b, 0xff, 0xdb, 0x4f, 0xce, 0x53, 0xe2, + 0xe9, 0x55, 0xce, 0x4e, 0xd7, 0x70, 0x5e, 0xd1, 0x4e, 0xd1, 0x5e, 0x73, + 0xd9, 0x50, 0xd0, 0x58, 0xea, 0xe7, 0x58, 0xd3, 0x56, 0xde, 0x78, 0x64, + 0xdb, 0x59, 0xdc, 0x67, 0x7d, 0xe8, 0x5f, 0xdf, 0x62, 0xeb, 0x79, 0x72, + 0xeb, 0x67, 0xe7, 0x68, 0xec, 0x74, 0x79, 0xec, 0x64, 0xe0, 0x60, 0xe8, + 0x7a, 0x6a, 0xde, 0x5a, 0xdb, 0x63, 0xfe, 0xe1, 0x58, 0xd4, 0x58, 0xe4, + 0xef, 0x5a, 0xd2, 0x51, 0xd8, 0x6c, 0x63, 0xd4, 0x4f, 0xd0, 0x5c, 0x7d, + 0xda, 0x4f, 0xce, 0x53, 0xe3, 0xe8, 0x55, 0xce, 0x4f, 0xd9, 0x73, 0x5e, + 0xd2, 0x4f, 0xd4, 0x5f, 0x72, 0xdb, 0x53, 0xd3, 0x5a, 0xeb, 0xea, 0x5b, + 0xd7, 0x59, 0xe0, 0x75, 0x6a, 0xde, 0x5d, 0xde, 0x68, 0xf9, 0xef, 0x67, + 0xe5, 0x65, 0xe9, 0x6f, 0xfb, 0xf5, 0x6c, 0xe7, 0x64, 0xe5, 0x6a, 0xf8, + 0xee, 0x62, 0xdd, 0x5d, 0xe2, 0x73, 0x6a, 0xdc, 0x57, 0xd8, 0x5f, 0xfb, + 0xe0, 0x56, 0xd2, 0x57, 0xe2, 0xee, 0x59, 0xd1, 0x50, 0xd8, 0x6d, 0x62, + 0xd3, 0x4e, 0xd0, 0x5c, 0x7a, 0xda, 0x4f, 0xce, 0x54, 0xe5, 0xe8, 0x56, + 0xcf, 0x50, 0xda, 0x75, 0x5f, 0xd4, 0x51, 0xd6, 0x62, 0x74, 0xdd, 0x56, + 0xd6, 0x5c, 0xec, 0xed, 0x5e, 0xdb, 0x5c, 0xe3, 0x73, 0x6e, 0xe5, 0x60, + 0xe3, 0x68, 0xf1, 0xfd, 0x6f, 0xeb, 0x67, 0xe7, 0x69, 0xee, 0x7a, 0x71, + 0xe7, 0x61, 0xdf, 0x64, 0xef, 0xef, 0x60, 0xda, 0x5a, 0xde, 0x70, 0x69, + 0xda, 0x55, 0xd6, 0x5e, 0xfb, 0xde, 0x55, 0xd1, 0x56, 0xe2, 0xed, 0x59, + 0xd0, 0x50, 0xd8, 0x6d, 0x60, 0xd3, 0x4f, 0xd1, 0x5d, 0x79, 0xda, 0x50, + 0xcf, 0x56, 0xe7, 0xe8, 0x57, 0xd1, 0x53, 0xdc, 0x77, 0x60, 0xd7, 0x54, + 0xd8, 0x64, 0x76, 0xdf, 0x59, 0xd9, 0x5e, 0xed, 0xf2, 0x64, 0xde, 0x5f, + 0xe5, 0x6f, 0x79, 0xec, 0x68, 0xe7, 0x68, 0xec, 0x73, 0x7e, 0xf1, 0x6a, + 0xe5, 0x63, 0xe7, 0x6f, 0x77, 0xe7, 0x5e, 0xdc, 0x5e, 0xeb, 0xf2, 0x5f, + 0xd9, 0x57, 0xdc, 0x6d, 0x68, 0xd9, 0x53, 0xd5, 0x5d, 0xfc, 0xde, 0x53, + 0xd0, 0x55, 0xe3, 0xed, 0x58, 0xd0, 0x50, 0xd8, 0x6e, 0x60, 0xd3, 0x4f, + 0xd2, 0x5e, 0x77, 0xdb, 0x52, 0xd1, 0x57, 0xe8, 0xe9, 0x59, 0xd3, 0x55, + 0xdd, 0x78, 0x64, 0xd9, 0x57, 0xdb, 0x66, 0x7b, 0xe4, 0x5d, 0xdc, 0x61, + 0xec, 0xfa, 0x6b, 0xe4, 0x64, 0xe7, 0x6d, 0xf7, 0xf8, 0x6f, 0xea, 0x68, + 0xe8, 0x6a, 0xf2, 0xfa, 0x6b, 0xe3, 0x5f, 0xe1, 0x69, 0x7c, 0xe6, 0x5c, + 0xda, 0x5c, 0xe9, 0xf3, 0x5e, 0xd7, 0x55, 0xdb, 0x6c, 0x67, 0xd8, 0x52, + 0xd4, 0x5d, 0xfd, 0xdd, 0x53, 0xd0, 0x55, 0xe3, 0xec, 0x58, 0xd0, 0x50, + 0xd9, 0x6f, 0x61, 0xd4, 0x50, 0xd4, 0x5f, 0x77, 0xdc, 0x54, 0xd3, 0x59, + 0xea, 0xea, 0x5b, 0xd6, 0x58, 0xdf, 0x77, 0x67, 0xdc, 0x5a, 0xdd, 0x68, + 0xfe, 0xea, 0x62, 0xdf, 0x64, 0xeb, 0x7b, 0x73, 0xeb, 0x68, 0xe8, 0x6a, + 0xee, 0x77, 0x79, 0xed, 0x67, 0xe3, 0x64, 0xeb, 0x7c, 0x6b, 0xe0, 0x5c, + 0xdd, 0x65, 0xfe, 0xe5, 0x5a, 0xd8, 0x5a, 0xe6, 0xf3, 0x5d, 0xd5, 0x54, + 0xda, 0x6d, 0x67, 0xd7, 0x51, 0xd3, 0x5d, 0xfe, 0xdd, 0x53, 0xd0, 0x56, + 0xe5, 0xeb, 0x58, 0xd1, 0x52, 0xda, 0x71, 0x61, 0xd6, 0x52, 0xd6, 0x61, + 0x77, 0xdd, 0x56, 0xd5, 0x5b, 0xeb, 0xec, 0x5d, 0xd9, 0x5a, 0xe2, 0x75, + 0x6b, 0xe0, 0x5e, 0xe0, 0x68, 0xf9, 0xf0, 0x68, 0xe5, 0x66, 0xeb, 0x70, + 0xfe, 0xf4, 0x6c, 0xe9, 0x67, 0xe8, 0x6d, 0xfa, 0xef, 0x65, 0xdf, 0x5f, + 0xe6, 0x75, 0x6b, 0xde, 0x5a, 0xdb, 0x62, 0xfc, 0xe3, 0x59, 0xd6, 0x59, + 0xe5, 0xf2, 0x5c, 0xd4, 0x53, 0xda, 0x6d, 0x65, 0xd7, 0x51, 0xd4, 0x5e, + 0x7e, 0xdd, 0x53, 0xd1, 0x57, 0xe6, 0xeb, 0x59, 0xd2, 0x53, 0xdc, 0x75, + 0x62, 0xd7, 0x54, 0xd8, 0x64, 0x78, 0xdf, 0x59, 0xd8, 0x5d, 0xec, 0xee, + 0x60, 0xdc, 0x5d, 0xe4, 0x74, 0x70, 0xe6, 0x63, 0xe3, 0x6a, 0xf2, 0xfc, + 0x70, 0xeb, 0x69, 0xe9, 0x6b, 0xf0, 0x7d, 0x72, 0xe9, 0x64, 0xe3, 0x67, + 0xf2, 0xf2, 0x63, 0xdd, 0x5c, 0xe1, 0x70, 0x6b, 0xdd, 0x58, 0xd9, 0x5f, + 0xfb, 0xe2, 0x58, 0xd4, 0x58, 0xe5, 0xf1, 0x5b, 0xd4, 0x53, 0xda, 0x6d, + 0x64, 0xd6, 0x51, 0xd4, 0x5e, 0x7b, 0xdd, 0x54, 0xd2, 0x58, 0xe8, 0xeb, + 0x5a, 0xd4, 0x55, 0xdd, 0x76, 0x64, 0xd9, 0x57, 0xda, 0x65, 0x79, 0xe2, + 0x5b, 0xdb, 0x5f, 0xed, 0xf3, 0x66, 0xdf, 0x61, 0xe7, 0x71, 0x7a, 0xed, + 0x69, 0xe8, 0x6a, 0xed, 0x76, 0x7e, 0xf1, 0x6b, 0xe7, 0x66, 0xea, 0x71, + 0x77, 0xe9, 0x60, 0xde, 0x61, 0xed, 0xf5, 0x61, 0xdb, 0x5a, 0xde, 0x6e, + 0x6b, 0xdc, 0x57, 0xd8, 0x5f, 0xfb, 0xe1, 0x57, 0xd4, 0x57, 0xe5, 0xef, + 0x5b, 0xd3, 0x53, 0xda, 0x6e, 0x64, 0xd7, 0x52, 0xd5, 0x5f, 0x7b, 0xdd, + 0x55, 0xd3, 0x59, 0xe9, 0xeb, 0x5b, 0xd6, 0x58, 0xde, 0x77, 0x67, 0xdb, + 0x59, 0xdc, 0x68, 0x7d, 0xe6, 0x5f, 0xde, 0x63, 0xed, 0xfb, 0x6b, 0xe6, + 0x65, 0xe8, 0x6e, 0xfa, 0xf8, 0x6e, 0xeb, 0x69, 0xea, 0x6c, 0xf5, 0xfa, + 0x6c, 0xe5, 0x61, 0xe4, 0x6c, 0x7c, 0xe8, 0x5e, 0xdc, 0x5e, 0xea, 0xf4, + 0x60, 0xd9, 0x58, 0xdd, 0x6e, 0x6a, 0xdb, 0x55, 0xd7, 0x5e, 0xfc, 0xdf, + 0x56, 0xd3, 0x58, 0xe5, 0xee, 0x5b, 0xd3, 0x54, 0xdb, 0x6f, 0x64, 0xd7, + 0x53, 0xd7, 0x60, 0x79, 0xde, 0x56, 0xd5, 0x5b, 0xeb, 0xed, 0x5d, 0xd8, + 0x5a, 0xe1, 0x76, 0x69, 0xde, 0x5c, 0xde, 0x69, 0xfd, 0xeb, 0x64, 0xe2, + 0x66, 0xed, 0x7c, 0x74, 0xec, 0x6a, 0xe9, 0x6c, 0xef, 0x7a, 0x78, 0xee, + 0x68, 0xe6, 0x67, 0xed, 0x7c, 0x6d, 0xe3, 0x5e, 0xdf, 0x68, 0xfe, 0xe7, + 0x5d, 0xda, 0x5d, 0xe8, 0xf4, 0x5f, 0xd8, 0x57, 0xdc, 0x6e, 0x69, 0xda, + 0x55, 0xd6, 0x5e, 0xfd, 0xdf, 0x56, 0xd3, 0x58, 0xe6, 0xed, 0x5a, 0xd4, + 0x54, 0xdc, 0x72, 0x64, 0xd8, 0x55, 0xd8, 0x63, 0x78, 0xdf, 0x58, 0xd8, + 0x5d, 0xed, 0xee, 0x5f, 0xdb, 0x5c, 0xe4, 0x76, 0x6d, 0xe2, 0x5f, 0xe2, + 0x6a, 0xfa, 0xf1, 0x6a, 0xe7, 0x68, 0xec, 0x72, 0x7e, 0xf5, 0x6e, 0xea, + 0x69, 0xea, 0x6e, 0xfc, 0xf0, 0x68, 0xe2, 0x62, 0xe7, 0x77, 0x6d, 0xe0, + 0x5d, 0xdd, 0x65, 0xfb, 0xe6, 0x5b, 0xd9, 0x5b, 0xe7, 0xf4, 0x5e, 0xd7, + 0x56, 0xdc, 0x6e, 0x68, 0xd9, 0x54, 0xd6, 0x5f, 0xfe, 0xdf, 0x56, 0xd4, + 0x59, 0xe7, 0xed, 0x5b, 0xd5, 0x56, 0xdd, 0x73, 0x65, 0xda, 0x57, 0xda, + 0x65, 0x79, 0xe2, 0x5b, 0xda, 0x5f, 0xed, 0xf1, 0x63, 0xde, 0x5f, 0xe6, + 0x75, 0x72, 0xe8, 0x64, 0xe6, 0x6b, 0xf4, 0xfb, 0x70, 0xec, 0x6a, 0xeb, + 0x6d, 0xf3, 0x7e, 0x72, 0xeb, 0x66, 0xe5, 0x69, 0xf5, 0xf3, 0x66, 0xdf, + 0x5f, 0xe4, 0x73, 0x6d, 0xdf, 0x5b, 0xdc, 0x63, 0xfb, 0xe5, 0x5a, 0xd7, + 0x5a, 0xe6, 0xf3, 0x5d, 0xd7, 0x56, 0xdc, 0x6e, 0x67, 0xd9, 0x55, 0xd7, + 0x5f, 0x7e, 0xdf, 0x57, 0xd5, 0x5a, 0xe9, 0xed, 0x5c, 0xd6, 0x58, 0xde, + 0x76, 0x67, 0xdb, 0x59, 0xdc, 0x66, 0x7b, 0xe5, 0x5d, 0xdc, 0x61, 0xee, + 0xf6, 0x67, 0xe2, 0x62, 0xe8, 0x71, 0x7a, 0xee, 0x69, 0xe9, 0x6b, 0xef, + 0x78, 0x7c, 0xf0, 0x6c, 0xe9, 0x69, 0xec, 0x76, 0x77, 0xea, 0x63, 0xe1, + 0x65, 0xee, 0xf4, 0x65, 0xdd, 0x5d, 0xe1, 0x70, 0x6d, 0xde, 0x59, 0xda, + 0x61, 0xfb, 0xe4, 0x59, 0xd7, 0x5a, 0xe6, 0xf2, 0x5d, 0xd6, 0x56, 0xdc, + 0x6e, 0x67, 0xd9, 0x55, 0xd8, 0x61, 0x7c, 0xdf, 0x58, 0xd6, 0x5b, 0xea, + 0xee, 0x5d, 0xd8, 0x59, 0xe0, 0x76, 0x69, 0xdd, 0x5b, 0xde, 0x68, 0x7d, + 0xe9, 0x5f, 0xdf, 0x63, 0xee, 0xfc, 0x6c, 0xe7, 0x66, 0xe9, 0x6f, 0xfb, + 0xf7, 0x6e, 0xec, 0x6a, 0xec, 0x6e, 0xf8, 0xf9, 0x6d, 0xe7, 0x65, 0xe7, + 0x6e, 0x7c, 0xea, 0x61, 0xde, 0x61, 0xec, 0xf6, 0x64, 0xdc, 0x5b, 0xdf, + 0x6f, 0x6c, 0xdd, 0x58, 0xd9, 0x61, 0xfb, 0xe3, 0x59, 0xd6, 0x5a, 0xe7, + 0xf1, 0x5d, 0xd6, 0x56, 0xdd, 0x6f, 0x67, 0xda, 0x56, 0xd9, 0x62, 0x7c, + 0xe0, 0x59, 0xd8, 0x5d, 0xeb, 0xee, 0x5f, 0xda, 0x5b, 0xe2, 0x76, 0x6b, + 0xe0, 0x5d, 0xe0, 0x6a, 0xfe, 0xed, 0x65, 0xe4, 0x67, 0xee, 0x7b, 0x73, + 0xed, 0x6a, 0xeb, 0x6d, 0xf2, 0x7b, 0x77, 0xee, 0x6a, 0xe9, 0x6a, 0xef, + 0x7e, 0x6e, 0xe6, 0x61, 0xe2, 0x6a, 0xfe, 0xe9, 0x5f, 0xdc, 0x5f, 0xea, + 0xf7, 0x62, 0xdb, 0x5a, 0xde, 0x6e, 0x6c, 0xdc, 0x58, 0xd9, 0x60, 0xfc, + 0xe2, 0x59, 0xd6, 0x5a, 0xe7, 0xef, 0x5d, 0xd7, 0x57, 0xde, 0x72, 0x67, + 0xdb, 0x57, 0xda, 0x64, 0x7b, 0xe2, 0x5b, 0xda, 0x5e, 0xed, 0xf0, 0x61, + 0xdd, 0x5e, 0xe5, 0x77, 0x6e, 0xe4, 0x61, 0xe3, 0x6c, 0xf9, 0xf1, 0x6b, + 0xe8, 0x6a, 0xed, 0x75, 0x7e, 0xf5, 0x6e, 0xec, 0x6b, 0xec, 0x71, 0xfe, + 0xf1, 0x69, 0xe5, 0x65, 0xea, 0x78, 0x6e, 0xe4, 0x5e, 0xdf, 0x67, 0xfc, + 0xe9, 0x5e, 0xdb, 0x5d, 0xe9, 0xf6, 0x61, 0xda, 0x59, 0xde, 0x6e, 0x6b, + 0xdc, 0x57, 0xd9, 0x61, 0xfd, 0xe2, 0x59, 0xd7, 0x5b, 0xe9, 0xef, 0x5d, + 0xd8, 0x58, 0xdf, 0x73, 0x68, 0xdc, 0x59, 0xdc, 0x66, 0x7b, 0xe4, 0x5c, + 0xdc, 0x60, 0xee, 0xf4, 0x65, 0xdf, 0x60, 0xe7, 0x75, 0x73, 0xe9, 0x66, + 0xe7, 0x6c, 0xf5, 0xfa, 0x71, 0xed, 0x6b, 0xec, 0x6e, 0xf6, 0xfe, 0x73, + 0xec, 0x68, 0xe9, 0x6b, 0xf7, 0xf5, 0x68, 0xe2, 0x61, 0xe7, 0x73, 0x6e, + 0xe2, 0x5d, 0xde, 0x65, 0xfa, 0xe8, 0x5d, 0xda, 0x5d, 0xe8, 0xf6, 0x5f, + 0xd9, 0x59, 0xdd, 0x6f, 0x6a, 0xdc, 0x58, 0xd9, 0x62, 0xfe, 0xe2, 0x59, + 0xd7, 0x5c, 0xe9, 0xef, 0x5e, 0xd9, 0x5a, 0xdf, 0x75, 0x69, 0xdd, 0x5b, + 0xdd, 0x68, 0x7d, 0xe7, 0x5f, 0xde, 0x63, 0xee, 0xf7, 0x69, 0xe4, 0x64, + 0xe9, 0x72, 0x7b, 0xee, 0x6b, 0xea, 0x6c, 0xf0, 0x79, 0x7b, 0xf2, 0x6d, + 0xeb, 0x6b, 0xee, 0x76, 0x78, 0xec, 0x66, 0xe4, 0x67, 0xf0, 0xf7, 0x67, + 0xdf, 0x5e, 0xe4, 0x70, 0x6e, 0xe0, 0x5c, 0xdc, 0x63, 0xfa, 0xe7, 0x5c, + 0xd9, 0x5c, 0xe8, 0xf5, 0x5f, 0xd9, 0x58, 0xde, 0x6f, 0x6a, 0xdc, 0x58, + 0xda, 0x62, 0x7e, 0xe3, 0x5a, 0xd9, 0x5d, 0xeb, 0xf0, 0x5f, 0xdb, 0x5b, + 0xe2, 0x74, 0x6b, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xea, 0x63, 0xe1, 0x65, + 0xee, 0xfd, 0x6e, 0xe8, 0x68, 0xea, 0x6f, 0xfc, 0xf8, 0x6f, 0xed, 0x6c, + 0xed, 0x70, 0xf9, 0xf9, 0x6e, 0xe9, 0x68, 0xe9, 0x6f, 0x7c, 0xec, 0x64, + 0xe1, 0x64, 0xed, 0xf8, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6e, 0xdf, 0x5b, + 0xdc, 0x63, 0xfb, 0xe6, 0x5b, 0xd9, 0x5c, 0xe8, 0xf4, 0x5f, 0xd9, 0x59, + 0xde, 0x70, 0x6a, 0xdc, 0x59, 0xdb, 0x64, 0x7e, 0xe3, 0x5b, 0xda, 0x5e, + 0xec, 0xf0, 0x61, 0xdc, 0x5d, 0xe4, 0x75, 0x6d, 0xe2, 0x5f, 0xe1, 0x6b, + 0xfb, 0xee, 0x67, 0xe5, 0x68, 0xee, 0x7c, 0x75, 0xed, 0x6b, 0xec, 0x6e, + 0xf4, 0x7d, 0x77, 0xef, 0x6c, 0xea, 0x6b, 0xf1, 0xff, 0x6f, 0xe8, 0x64, + 0xe6, 0x6c, 0xff, 0xeb, 0x62, 0xdf, 0x61, 0xec, 0xf8, 0x65, 0xdd, 0x5c, + 0xe0, 0x6f, 0x6d, 0xdf, 0x5a, 0xdb, 0x63, 0xfa, 0xe6, 0x5b, 0xd9, 0x5c, + 0xe8, 0xf3, 0x5f, 0xda, 0x59, 0xdf, 0x71, 0x6a, 0xdd, 0x59, 0xdc, 0x65, + 0x7d, 0xe5, 0x5d, 0xdc, 0x5f, 0xed, 0xf3, 0x64, 0xde, 0x5f, 0xe6, 0x75, + 0x70, 0xe6, 0x63, 0xe5, 0x6c, 0xf8, 0xf3, 0x6c, 0xe9, 0x6a, 0xed, 0x76, + 0x7e, 0xf5, 0x6e, 0xec, 0x6c, 0xee, 0x74, 0xfe, 0xf2, 0x6b, 0xe8, 0x68, + 0xec, 0x7a, 0x6f, 0xe6, 0x61, 0xe2, 0x6a, 0xfc, 0xea, 0x60, 0xdd, 0x5f, + 0xea, 0xf8, 0x64, 0xdc, 0x5b, 0xdf, 0x6e, 0x6d, 0xde, 0x5a, 0xdb, 0x63, + 0xfb, 0xe5, 0x5b, 0xd9, 0x5d, 0xea, 0xf3, 0x5f, 0xda, 0x5a, 0xe0, 0x72, + 0x6b, 0xde, 0x5b, 0xdd, 0x67, 0x7e, 0xe7, 0x5e, 0xdd, 0x62, 0xee, 0xf6, + 0x68, 0xe1, 0x62, 0xe8, 0x74, 0x75, 0xeb, 0x68, 0xe8, 0x6c, 0xf6, 0xfb, + 0x72, 0xed, 0x6d, 0xed, 0x70, 0xf7, 0xfe, 0x74, 0xed, 0x6a, 0xea, 0x6d, + 0xf7, 0xf6, 0x6a, 0xe5, 0x64, 0xe9, 0x75, 0x70, 0xe5, 0x5f, 0xdf, 0x67, + 0xfa, 0xea, 0x5f, 0xdc, 0x5e, 0xe9, 0xf8, 0x63, 0xdc, 0x5b, 0xdf, 0x6f, + 0x6c, 0xde, 0x5a, 0xdb, 0x63, 0xfc, 0xe6, 0x5c, 0xda, 0x5e, 0xea, 0xf3, + 0x61, 0xdb, 0x5c, 0xe2, 0x74, 0x6c, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xe9, + 0x61, 0xdf, 0x64, 0xef, 0xfa, 0x6b, 0xe6, 0x65, 0xea, 0x72, 0x7b, 0xef, + 0x6c, 0xeb, 0x6d, 0xf1, 0x7a, 0x7a, 0xf3, 0x6e, 0xec, 0x6c, 0xef, 0x78, + 0x78, 0xed, 0x68, 0xe7, 0x6a, 0xf2, 0xf7, 0x6a, 0xe2, 0x61, 0xe6, 0x72, + 0x70, 0xe3, 0x5e, 0xde, 0x66, 0xfa, 0xe9, 0x5e, 0xdc, 0x5e, 0xe9, 0xf8, + 0x62, 0xdb, 0x5b, 0xdf, 0x6f, 0x6c, 0xde, 0x5a, 0xdc, 0x64, 0xfd, 0xe6, + 0x5c, 0xdb, 0x5e, 0xeb, 0xf4, 0x62, 0xdd, 0x5d, 0xe3, 0x74, 0x6d, 0xe2, + 0x5e, 0xe1, 0x69, 0xfd, 0xed, 0x65, 0xe3, 0x67, 0xee, 0xfe, 0x6f, 0xea, + 0x69, 0xeb, 0x70, 0xfb, 0xf7, 0x70, 0xed, 0x6d, 0xee, 0x72, 0xfa, 0xf9, + 0x6f, 0xeb, 0x6a, 0xeb, 0x71, 0x7c, 0xed, 0x67, 0xe3, 0x66, 0xee, 0xf9, + 0x69, 0xe0, 0x5f, 0xe4, 0x71, 0x71, 0xe2, 0x5d, 0xdd, 0x65, 0xf8, 0xe9, + 0x5e, 0xdb, 0x5e, 0xe8, 0xf7, 0x63, 0xdb, 0x5b, 0xdf, 0x70, 0x6d, 0xde, + 0x5b, 0xdc, 0x65, 0xfc, 0xe7, 0x5d, 0xdc, 0x5f, 0xec, 0xf5, 0x64, 0xde, + 0x5e, 0xe5, 0x73, 0x6f, 0xe5, 0x60, 0xe3, 0x6a, 0xfc, 0xf0, 0x68, 0xe7, + 0x69, 0xef, 0x7b, 0x75, 0xee, 0x6c, 0xec, 0x6f, 0xf5, 0x7d, 0x77, 0xef, + 0x6d, 0xec, 0x6d, 0xf3, 0xff, 0x70, 0xea, 0x66, 0xe8, 0x6e, 0xfe, 0xed, + 0x65, 0xe1, 0x64, 0xed, 0xfa, 0x68, 0xdf, 0x5e, 0xe2, 0x6f, 0x6f, 0xe1, + 0x5d, 0xdd, 0x65, 0xf9, 0xe9, 0x5e, 0xdb, 0x5e, 0xe9, 0xf6, 0x63, 0xdc, + 0x5b, 0xe0, 0x71, 0x6d, 0xdf, 0x5c, 0xdd, 0x66, 0xfe, 0xe9, 0x5f, 0xdd, + 0x61, 0xed, 0xf7, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x73, 0xe8, 0x64, 0xe6, + 0x6c, 0xf8, 0xf5, 0x6c, 0xeb, 0x6b, 0xee, 0x76, 0x7d, 0xf4, 0x6f, 0xed, + 0x6d, 0xef, 0x75, 0x7e, 0xf4, 0x6c, 0xe9, 0x6a, 0xee, 0x7a, 0x71, 0xe9, + 0x64, 0xe5, 0x6b, 0xfc, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfa, 0x67, 0xde, + 0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd, 0x64, 0xf9, 0xe8, 0x5e, 0xdb, + 0x5e, 0xea, 0xf6, 0x64, 0xdc, 0x5c, 0xe1, 0x72, 0x6d, 0xe0, 0x5d, 0xde, + 0x68, 0xfd, 0xea, 0x61, 0xdf, 0x63, 0xed, 0xf9, 0x6a, 0xe4, 0x63, 0xe9, + 0x74, 0x77, 0xec, 0x69, 0xe9, 0x6d, 0xf6, 0xfd, 0x73, 0xee, 0x6d, 0xee, + 0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6c, 0xec, 0x6f, 0xf9, 0xf6, 0x6c, 0xe8, + 0x66, 0xeb, 0x76, 0x72, 0xe8, 0x62, 0xe2, 0x69, 0xfa, 0xec, 0x62, 0xde, + 0x60, 0xea, 0xfb, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd, + 0x65, 0xfa, 0xe9, 0x5e, 0xdc, 0x5f, 0xeb, 0xf7, 0x64, 0xdd, 0x5d, 0xe3, + 0x72, 0x6e, 0xe2, 0x5e, 0xe0, 0x69, 0xfc, 0xec, 0x64, 0xe1, 0x65, 0xef, + 0xfc, 0x6c, 0xe7, 0x66, 0xeb, 0x72, 0x7c, 0xf1, 0x6c, 0xec, 0x6d, 0xf2, + 0x7b, 0x7a, 0xf2, 0x6e, 0xed, 0x6e, 0xf1, 0x7a, 0x79, 0xee, 0x6a, 0xe9, + 0x6c, 0xf4, 0xf9, 0x6b, 0xe5, 0x64, 0xe8, 0x73, 0x73, 0xe6, 0x60, 0xe0, + 0x68, 0xf9, 0xec, 0x61, 0xde, 0x5f, 0xea, 0xfa, 0x66, 0xde, 0x5d, 0xe1, + 0x6f, 0x6f, 0xe1, 0x5c, 0xdd, 0x66, 0xfb, 0xe9, 0x5e, 0xdd, 0x5f, 0xec, + 0xf7, 0x65, 0xde, 0x5e, 0xe5, 0x74, 0x6f, 0xe5, 0x60, 0xe2, 0x6a, 0xfc, + 0xee, 0x67, 0xe5, 0x68, 0xef, 0xfe, 0x71, 0xeb, 0x6a, 0xec, 0x72, 0xfc, + 0xf8, 0x71, 0xee, 0x6e, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xed, 0x6c, 0xed, + 0x73, 0x7d, 0xef, 0x69, 0xe6, 0x68, 0xef, 0xfa, 0x6b, 0xe3, 0x62, 0xe6, + 0x72, 0x73, 0xe5, 0x5f, 0xdf, 0x67, 0xf8, 0xeb, 0x60, 0xdd, 0x5f, 0xea, + 0xfb, 0x65, 0xde, 0x5d, 0xe2, 0x6f, 0x6e, 0xe1, 0x5d, 0xde, 0x66, 0xfd, + 0xea, 0x5f, 0xde, 0x61, 0xed, 0xf8, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x71, + 0xe8, 0x63, 0xe5, 0x6c, 0xfa, 0xf1, 0x6b, 0xe8, 0x6a, 0xef, 0x7c, 0x77, + 0xef, 0x6d, 0xed, 0x71, 0xf5, 0x7e, 0x79, 0xf0, 0x6e, 0xed, 0x6f, 0xf4, + 0xfe, 0x73, 0xeb, 0x69, 0xe9, 0x6f, 0xfd, 0xee, 0x67, 0xe4, 0x66, 0xed, + 0xfb, 0x6a, 0xe2, 0x60, 0xe5, 0x70, 0x72, 0xe5, 0x5e, 0xdf, 0x66, 0xf8, + 0xeb, 0x5f, 0xdd, 0x5f, 0xea, 0xfa, 0x65, 0xde, 0x5d, 0xe3, 0x6f, 0x6e, + 0xe2, 0x5d, 0xdf, 0x67, 0xfc, 0xeb, 0x60, 0xdf, 0x63, 0xed, 0xf9, 0x69, + 0xe3, 0x63, 0xe9, 0x74, 0x75, 0xea, 0x67, 0xe8, 0x6d, 0xf9, 0xf7, 0x6e, + 0xec, 0x6c, 0xef, 0x78, 0x7e, 0xf5, 0x71, 0xee, 0x6e, 0xf0, 0x77, 0xfe, + 0xf4, 0x6e, 0xeb, 0x6c, 0xef, 0x7b, 0x74, 0xea, 0x67, 0xe7, 0x6d, 0xfb, + 0xee, 0x66, 0xe1, 0x64, 0xec, 0xfc, 0x69, 0xe1, 0x5f, 0xe4, 0x6f, 0x72, + 0xe4, 0x5e, 0xdf, 0x66, 0xf9, 0xeb, 0x5f, 0xdd, 0x5f, 0xeb, 0xf9, 0x66, + 0xde, 0x5e, 0xe3, 0x71, 0x6f, 0xe4, 0x5f, 0xe0, 0x69, 0xfc, 0xec, 0x63, + 0xe1, 0x65, 0xef, 0xfb, 0x6b, 0xe6, 0x65, 0xea, 0x74, 0x79, 0xed, 0x6a, + 0xea, 0x6e, 0xf6, 0xfc, 0x73, 0xef, 0x6e, 0xef, 0x73, 0xf9, 0xfd, 0x75, + 0xef, 0x6d, 0xed, 0x70, 0xfa, 0xf7, 0x6e, 0xe9, 0x69, 0xec, 0x77, 0x74, + 0xe9, 0x65, 0xe5, 0x6b, 0xfa, 0xee, 0x64, 0xe0, 0x63, 0xec, 0xfc, 0x69, + 0xe0, 0x5f, 0xe3, 0x6f, 0x72, 0xe4, 0x5e, 0xdf, 0x66, 0xfa, 0xeb, 0x60, + 0xde, 0x60, 0xeb, 0xf9, 0x67, 0xdf, 0x5f, 0xe5, 0x72, 0x70, 0xe5, 0x60, + 0xe2, 0x6a, 0xfc, 0xee, 0x66, 0xe4, 0x67, 0xef, 0xfd, 0x6e, 0xe9, 0x68, + 0xec, 0x73, 0x7d, 0xf3, 0x6e, 0xed, 0x6e, 0xf3, 0x7b, 0x7b, 0xf5, 0x70, + 0xee, 0x6f, 0xf3, 0x7a, 0x7a, 0xef, 0x6c, 0xeb, 0x6d, 0xf5, 0xf9, 0x6d, + 0xe7, 0x67, 0xea, 0x75, 0x75, 0xe8, 0x63, 0xe3, 0x69, 0xf8, 0xed, 0x64, + 0xdf, 0x62, 0xeb, 0xfc, 0x69, 0xdf, 0x5f, 0xe3, 0x70, 0x71, 0xe3, 0x5e, + 0xdf, 0x67, 0xfa, 0xeb, 0x61, 0xdf, 0x61, 0xed, 0xfa, 0x68, 0xe1, 0x60, + 0xe7, 0x73, 0x71, 0xe7, 0x62, 0xe5, 0x6b, 0xfb, 0xf0, 0x69, 0xe7, 0x69, + 0xef, 0x7e, 0x72, 0xed, 0x6b, 0xed, 0x72, 0xfc, 0xf9, 0x72, 0xef, 0x6e, + 0xf1, 0x74, 0xfc, 0xf9, 0x71, 0xee, 0x6d, 0xee, 0x75, 0x7d, 0xf0, 0x6b, + 0xe8, 0x6b, 0xf0, 0xfa, 0x6d, 0xe6, 0x64, 0xe8, 0x72, 0x75, 0xe8, 0x62, + 0xe2, 0x69, 0xf8, 0xed, 0x63, 0xdf, 0x61, 0xeb, 0xfc, 0x68, 0xdf, 0x5f, + 0xe4, 0x70, 0x71, 0xe4, 0x5f, 0xe0, 0x68, 0xfb, 0xec, 0x62, 0xdf, 0x63, + 0xed, 0xfa, 0x69, 0xe3, 0x62, 0xe8, 0x74, 0x74, 0xea, 0x65, 0xe7, 0x6c, + 0xfa, 0xf4, 0x6c, 0xea, 0x6b, 0xef, 0x7a, 0x78, 0xf0, 0x6e, 0xee, 0x71, + 0xf6, 0xff, 0x79, 0xf2, 0x6f, 0xee, 0x70, 0xf5, 0xfd, 0x74, 0xed, 0x6b, + 0xec, 0x70, 0xfe, 0xf0, 0x69, 0xe7, 0x68, 0xef, 0xfd, 0x6c, 0xe5, 0x62, + 0xe7, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x62, 0xdf, 0x61, + 0xeb, 0xfb, 0x68, 0xe0, 0x5f, 0xe4, 0x70, 0x71, 0xe5, 0x5f, 0xe1, 0x69, + 0xfb, 0xed, 0x64, 0xe1, 0x65, 0xee, 0xfb, 0x6b, 0xe5, 0x65, 0xe9, 0x74, + 0x78, 0xec, 0x69, 0xe9, 0x6e, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xef, 0x77, + 0x7e, 0xf6, 0x72, 0xef, 0x70, 0xf2, 0x78, 0x7e, 0xf6, 0x6f, 0xed, 0x6d, + 0xf0, 0x7b, 0x75, 0xed, 0x69, 0xe9, 0x6e, 0xfb, 0xf0, 0x68, 0xe5, 0x66, + 0xee, 0xfd, 0x6b, 0xe4, 0x61, 0xe6, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67, + 0xf8, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfb, 0x69, 0xe0, 0x5f, 0xe5, 0x71, + 0x72, 0xe6, 0x61, 0xe2, 0x6a, 0xfa, 0xee, 0x66, 0xe3, 0x67, 0xee, 0xfc, + 0x6e, 0xe8, 0x67, 0xeb, 0x74, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfd, + 0x75, 0xf0, 0x6f, 0xef, 0x74, 0xfa, 0xfd, 0x75, 0xf0, 0x6e, 0xef, 0x73, + 0xfb, 0xf8, 0x6e, 0xeb, 0x6a, 0xee, 0x78, 0x76, 0xeb, 0x67, 0xe7, 0x6c, + 0xfa, 0xf0, 0x67, 0xe3, 0x65, 0xed, 0xfe, 0x6b, 0xe3, 0x61, 0xe5, 0x6f, + 0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x63, 0xdf, 0x62, 0xec, 0xfc, + 0x69, 0xe2, 0x60, 0xe6, 0x72, 0x73, 0xe8, 0x63, 0xe4, 0x6b, 0xfa, 0xef, + 0x68, 0xe5, 0x69, 0xef, 0xfe, 0x70, 0xea, 0x6a, 0xec, 0x74, 0x7e, 0xf3, + 0x6f, 0xed, 0x6f, 0xf3, 0x7c, 0x7b, 0xf4, 0x71, 0xef, 0x71, 0xf4, 0x7c, + 0x7a, 0xf0, 0x6e, 0xec, 0x6f, 0xf6, 0xfa, 0x6f, 0xea, 0x69, 0xeb, 0x76, + 0x77, 0xeb, 0x66, 0xe5, 0x6b, 0xf8, 0xef, 0x67, 0xe2, 0x64, 0xed, 0xfe, + 0x6b, 0xe3, 0x60, 0xe5, 0x6f, 0x74, 0xe7, 0x60, 0xe1, 0x68, 0xf9, 0xee, + 0x63, 0xe1, 0x63, 0xed, 0xfd, 0x6a, 0xe4, 0x62, 0xe8, 0x72, 0x75, 0xe9, + 0x65, 0xe6, 0x6c, 0xfa, 0xf2, 0x6b, 0xe8, 0x6a, 0xef, 0x7d, 0x74, 0xed, + 0x6d, 0xed, 0x73, 0xfb, 0xf9, 0x74, 0xef, 0x70, 0xf1, 0x77, 0xfd, 0xfa, + 0x73, 0xef, 0x6e, 0xef, 0x76, 0x7d, 0xf2, 0x6c, 0xeb, 0x6c, 0xf2, 0xfc, + 0x6e, 0xe9, 0x67, 0xea, 0x72, 0x77, 0xea, 0x65, 0xe4, 0x6a, 0xf8, 0xef, + 0x66, 0xe2, 0x63, 0xec, 0xff, 0x6b, 0xe3, 0x60, 0xe5, 0x70, 0x75, 0xe7, + 0x61, 0xe2, 0x69, 0xf9, 0xee, 0x65, 0xe2, 0x64, 0xed, 0xfd, 0x6c, 0xe5, + 0x64, 0xe9, 0x73, 0x77, 0xeb, 0x67, 0xe8, 0x6d, 0xf9, 0xf6, 0x6d, 0xeb, + 0x6c, 0xef, 0x7a, 0x7a, 0xf1, 0x6f, 0xee, 0x72, 0xf7, 0xff, 0x78, 0xf3, + 0x70, 0xef, 0x72, 0xf7, 0xfe, 0x75, 0xee, 0x6c, 0xed, 0x72, 0xfd, 0xf2, + 0x6b, 0xe8, 0x6a, 0xef, 0xfd, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x78, 0xea, + 0x63, 0xe4, 0x69, 0xf7, 0xef, 0x66, 0xe1, 0x64, 0xec, 0xff, 0x6b, 0xe3, + 0x61, 0xe6, 0x70, 0x75, 0xe8, 0x62, 0xe3, 0x6a, 0xf9, 0xef, 0x67, 0xe3, + 0x66, 0xee, 0xfe, 0x6d, 0xe7, 0x66, 0xea, 0x74, 0x7a, 0xee, 0x6a, 0xea, + 0x6e, 0xf7, 0xfa, 0x70, 0xee, 0x6e, 0xf1, 0x77, 0x7e, 0xf7, 0x72, 0xf0, + 0x71, 0xf4, 0x79, 0x7d, 0xf6, 0x71, 0xee, 0x6f, 0xf2, 0x7c, 0x77, 0xee, + 0x6b, 0xeb, 0x6f, 0xfb, 0xf2, 0x6b, 0xe7, 0x68, 0xee, 0xff, 0x6e, 0xe6, + 0x64, 0xe7, 0x70, 0x78, 0xea, 0x63, 0xe3, 0x69, 0xf7, 0xef, 0x66, 0xe2, + 0x64, 0xec, 0xfe, 0x6b, 0xe4, 0x62, 0xe6, 0x71, 0x76, 0xe9, 0x63, 0xe4, + 0x6a, 0xf9, 0xf1, 0x68, 0xe5, 0x67, 0xef, 0x7e, 0x6f, 0xea, 0x68, 0xec, + 0x74, 0x7c, 0xf1, 0x6c, 0xec, 0x6f, 0xf6, 0xfe, 0x76, 0xf1, 0x6f, 0xf0, + 0x75, 0xfb, 0xfd, 0x76, 0xf1, 0x6f, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xec, + 0x6d, 0xee, 0x79, 0x78, 0xed, 0x6a, 0xe9, 0x6d, 0xf9, 0xf2, 0x6a, 0xe6, + 0x67, 0xed, 0x7e, 0x6d, 0xe6, 0x63, 0xe7, 0x70, 0x78, 0xe9, 0x63, 0xe3, + 0x69, 0xf7, 0xf0, 0x66, 0xe2, 0x64, 0xec, 0xfe, 0x6c, 0xe5, 0x63, 0xe7, + 0x71, 0x77, 0xea, 0x65, 0xe6, 0x6b, 0xf8, 0xf3, 0x6a, 0xe8, 0x69, 0xef, + 0x7e, 0x72, 0xec, 0x6b, 0xed, 0x74, 0xfe, 0xf5, 0x6f, 0xee, 0x6f, 0xf4, + 0x7c, 0x7b, 0xf5, 0x71, 0xf0, 0x72, 0xf6, 0x7b, 0x79, 0xf3, 0x6e, 0xee, + 0x6f, 0xf7, 0xfb, 0x6f, 0xec, 0x6b, 0xed, 0x76, 0x79, 0xed, 0x68, 0xe7, + 0x6c, 0xf7, 0xf2, 0x69, 0xe5, 0x66, 0xec, 0x7e, 0x6e, 0xe5, 0x63, 0xe7, + 0x6f, 0x78, 0xe9, 0x63, 0xe4, 0x69, 0xf7, 0xf0, 0x67, 0xe3, 0x65, 0xed, + 0xff, 0x6d, 0xe6, 0x65, 0xe9, 0x73, 0x78, 0xeb, 0x67, 0xe7, 0x6c, 0xf8, + 0xf5, 0x6d, 0xea, 0x6b, 0xf0, 0x7c, 0x76, 0xef, 0x6d, 0xee, 0x72, 0xfc, + 0xfb, 0x74, 0xf2, 0x70, 0xf3, 0x76, 0xfe, 0xfb, 0x75, 0xf0, 0x6f, 0xf1, + 0x77, 0x7d, 0xf4, 0x6e, 0xec, 0x6d, 0xf3, 0xfe, 0x70, 0xeb, 0x69, 0xeb, + 0x73, 0x7a, 0xed, 0x67, 0xe7, 0x6b, 0xf7, 0xf4, 0x69, 0xe5, 0x66, 0xec, + 0x7d, 0x6d, 0xe5, 0x63, 0xe6, 0x6f, 0x79, 0xea, 0x64, 0xe3, 0x6a, 0xf6, + 0xf0, 0x68, 0xe4, 0x66, 0xed, 0xff, 0x6e, 0xe7, 0x66, 0xea, 0x73, 0x7a, + 0xed, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a, + 0xf3, 0x6f, 0xf0, 0x72, 0xf8, 0x7e, 0x78, 0xf5, 0x70, 0xf1, 0x72, 0xf9, + 0xff, 0x75, 0xf0, 0x6d, 0xef, 0x72, 0xfd, 0xf4, 0x6d, 0xeb, 0x6b, 0xf1, + 0xff, 0x70, 0xea, 0x67, 0xea, 0x72, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6, + 0xf3, 0x68, 0xe4, 0x66, 0xec, 0x7d, 0x6e, 0xe6, 0x64, 0xe7, 0x70, 0x79, + 0xea, 0x65, 0xe4, 0x6b, 0xf6, 0xf2, 0x69, 0xe5, 0x68, 0xed, 0xff, 0x6f, + 0xe9, 0x68, 0xeb, 0x73, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfc, 0x72, + 0xee, 0x6e, 0xf1, 0x77, 0xfe, 0xf8, 0x73, 0xf2, 0x71, 0xf5, 0x79, 0x7d, + 0xf7, 0x72, 0xef, 0x6f, 0xf4, 0x7b, 0x77, 0xf0, 0x6c, 0xec, 0x6f, 0xfb, + 0xf5, 0x6c, 0xe9, 0x6a, 0xef, 0x7d, 0x6f, 0xe9, 0x66, 0xe9, 0x70, 0x7a, + 0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf3, 0x69, 0xe4, 0x65, 0xec, 0x7d, 0x6e, + 0xe7, 0x64, 0xe8, 0x70, 0x79, 0xeb, 0x66, 0xe6, 0x6b, 0xf7, 0xf4, 0x6b, + 0xe7, 0x69, 0xee, 0x7d, 0x72, 0xeb, 0x6a, 0xec, 0x73, 0xff, 0xf2, 0x6e, + 0xed, 0x6f, 0xf5, 0xfe, 0x78, 0xf2, 0x70, 0xf1, 0x75, 0xfb, 0xfd, 0x77, + 0xf3, 0x70, 0xf1, 0x75, 0xfb, 0xfa, 0x73, 0xee, 0x6e, 0xf0, 0x78, 0x79, + 0xef, 0x6b, 0xeb, 0x6e, 0xf8, 0xf5, 0x6c, 0xe8, 0x69, 0xee, 0x7c, 0x6f, + 0xe9, 0x66, 0xe8, 0x6f, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf4, 0x69, + 0xe5, 0x66, 0xed, 0x7c, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x7a, 0xec, 0x68, + 0xe7, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6b, 0xef, 0x7c, 0x75, 0xed, 0x6c, + 0xed, 0x74, 0xfd, 0xf7, 0x71, 0xef, 0x70, 0xf4, 0x7c, 0x7c, 0xf6, 0x74, + 0xf1, 0x73, 0xf6, 0x7d, 0x7b, 0xf3, 0x70, 0xef, 0x71, 0xf7, 0xfd, 0x72, + 0xee, 0x6c, 0xee, 0x75, 0x7a, 0xef, 0x6a, 0xea, 0x6d, 0xf7, 0xf6, 0x6b, + 0xe7, 0x68, 0xed, 0x7b, 0x6f, 0xe8, 0x66, 0xe9, 0x6f, 0x7b, 0xec, 0x66, + 0xe5, 0x6a, 0xf6, 0xf4, 0x6a, 0xe6, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x66, + 0xe9, 0x72, 0x7a, 0xee, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6e, 0xeb, 0x6c, + 0xf0, 0x7b, 0x78, 0xf0, 0x6e, 0xef, 0x74, 0xfa, 0xfb, 0x76, 0xf1, 0x71, + 0xf3, 0x78, 0xfd, 0xfa, 0x76, 0xf1, 0x71, 0xf3, 0x78, 0x7e, 0xf5, 0x6f, + 0xed, 0x6f, 0xf4, 0xfe, 0x72, 0xec, 0x6b, 0xec, 0x74, 0x7c, 0xee, 0x6a, + 0xe8, 0x6c, 0xf6, 0xf5, 0x6b, 0xe7, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x65, + 0xe9, 0x6f, 0x7a, 0xed, 0x66, 0xe6, 0x6a, 0xf6, 0xf5, 0x6a, 0xe7, 0x68, + 0xee, 0x7c, 0x70, 0xea, 0x68, 0xeb, 0x72, 0x7c, 0xef, 0x6b, 0xeb, 0x6e, + 0xf7, 0xfa, 0x70, 0xee, 0x6d, 0xf1, 0x79, 0x7b, 0xf5, 0x70, 0xf0, 0x74, + 0xf7, 0x7e, 0x7a, 0xf4, 0x73, 0xf1, 0x75, 0xf9, 0xfe, 0x77, 0xf1, 0x6f, + 0xef, 0x74, 0xfc, 0xf6, 0x6f, 0xec, 0x6d, 0xf1, 0x7e, 0x73, 0xeb, 0x6a, + 0xeb, 0x73, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6, 0x6b, 0xe7, 0x67, + 0xed, 0x7b, 0x6f, 0xe9, 0x66, 0xe9, 0x6f, 0x7b, 0xed, 0x67, 0xe7, 0x6b, + 0xf7, 0xf6, 0x6b, 0xe8, 0x68, 0xee, 0x7c, 0x72, 0xeb, 0x69, 0xec, 0x73, + 0x7d, 0xf2, 0x6d, 0xec, 0x6f, 0xf7, 0xfc, 0x75, 0xef, 0x6f, 0xf1, 0x78, + 0xfe, 0xf9, 0x75, 0xf2, 0x73, 0xf5, 0x7a, 0x7e, 0xf8, 0x74, 0xf1, 0x71, + 0xf4, 0x7c, 0x79, 0xf1, 0x6e, 0xee, 0x71, 0xfb, 0xf7, 0x6e, 0xeb, 0x6b, + 0xf0, 0x7c, 0x72, 0xeb, 0x69, 0xea, 0x71, 0x7d, 0xee, 0x68, 0xe7, 0x6b, + 0xf5, 0xf6, 0x6b, 0xe7, 0x68, 0xed, 0x7c, 0x70, 0xe9, 0x67, 0xe9, 0x70, + 0x7b, 0xed, 0x68, 0xe8, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6a, 0xef, 0x7b, + 0x74, 0xed, 0x6b, 0xed, 0x73, 0xff, 0xf5, 0x6f, 0xee, 0x6f, 0xf6, 0x7e, + 0x79, 0xf3, 0x71, 0xf2, 0x76, 0xfb, 0xfd, 0x78, 0xf5, 0x72, 0xf3, 0x76, + 0xfc, 0xfa, 0x74, 0xef, 0x6f, 0xf2, 0x79, 0x7a, 0xf1, 0x6d, 0xec, 0x6f, + 0xf9, 0xf7, 0x6d, 0xea, 0x6b, 0xee, 0x7c, 0x72, 0xeb, 0x68, 0xea, 0x70, + 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf4, 0xf6, 0x6c, 0xe7, 0x68, 0xed, 0x7b, + 0x71, 0xea, 0x67, 0xea, 0x71, 0x7c, 0xee, 0x69, 0xe9, 0x6c, 0xf6, 0xf8, + 0x6e, 0xeb, 0x6b, 0xf0, 0x7a, 0x76, 0xef, 0x6d, 0xee, 0x73, 0xfd, 0xf9, + 0x72, 0xf0, 0x70, 0xf5, 0x7b, 0x7c, 0xf7, 0x73, 0xf3, 0x74, 0xf8, 0x7c, + 0x7b, 0xf5, 0x71, 0xf1, 0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6d, 0xef, 0x77, + 0x7c, 0xf1, 0x6c, 0xeb, 0x6e, 0xf7, 0xf7, 0x6d, 0xe9, 0x6a, 0xee, 0x7c, + 0x72, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6, + 0x6c, 0xe7, 0x69, 0xed, 0x7c, 0x72, 0xea, 0x68, 0xeb, 0x71, 0x7d, 0xef, + 0x6b, 0xea, 0x6d, 0xf7, 0xfa, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a, 0xf2, + 0x6f, 0xef, 0x74, 0xfa, 0xfc, 0x76, 0xf3, 0x72, 0xf4, 0x78, 0xfe, 0xfb, + 0x75, 0xf3, 0x72, 0xf4, 0x79, 0x7e, 0xf7, 0x70, 0xef, 0x70, 0xf5, 0xfe, + 0x75, 0xee, 0x6c, 0xee, 0x75, 0x7d, 0xf0, 0x6c, 0xea, 0x6d, 0xf7, 0xf8, + 0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x73, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee, + 0x69, 0xe8, 0x6b, 0xf6, 0xf7, 0x6c, 0xe8, 0x69, 0xee, 0x7b, 0x73, 0xec, + 0x6a, 0xec, 0x71, 0x7d, 0xf2, 0x6c, 0xec, 0x6e, 0xf7, 0xfc, 0x73, 0xee, + 0x6e, 0xf1, 0x79, 0x7c, 0xf6, 0x72, 0xf1, 0x74, 0xf9, 0x7e, 0x7a, 0xf6, + 0x73, 0xf3, 0x75, 0xfa, 0xfe, 0x78, 0xf3, 0x70, 0xf1, 0x76, 0xfd, 0xf7, + 0x70, 0xed, 0x6e, 0xf3, 0x7e, 0x75, 0xed, 0x6c, 0xed, 0x73, 0x7e, 0xf0, + 0x6b, 0xea, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x72, 0xeb, + 0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe9, 0x6c, 0xf5, 0xf8, 0x6d, 0xea, + 0x6a, 0xef, 0x7b, 0x75, 0xed, 0x6b, 0xed, 0x72, 0xff, 0xf4, 0x6e, 0xed, + 0x6f, 0xf6, 0xfe, 0x75, 0xf1, 0x70, 0xf3, 0x78, 0xfe, 0xf9, 0x75, 0xf3, + 0x73, 0xf6, 0x7b, 0x7d, 0xf9, 0x74, 0xf2, 0x72, 0xf7, 0x7c, 0x7a, 0xf3, + 0x6f, 0xef, 0x73, 0xfb, 0xf8, 0x6f, 0xed, 0x6d, 0xf1, 0x7c, 0x75, 0xed, + 0x6b, 0xec, 0x73, 0x7d, 0xef, 0x6b, 0xe9, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9, + 0x69, 0xee, 0x7a, 0x73, 0xeb, 0x69, 0xeb, 0x71, 0x7e, 0xef, 0x6b, 0xe9, + 0x6d, 0xf6, 0xf8, 0x6e, 0xea, 0x6c, 0xef, 0x7c, 0x77, 0xee, 0x6d, 0xee, + 0x74, 0xfe, 0xf7, 0x70, 0xef, 0x70, 0xf6, 0x7d, 0x78, 0xf5, 0x71, 0xf4, + 0x76, 0xfb, 0xfe, 0x78, 0xf6, 0x73, 0xf5, 0x76, 0xfd, 0xfb, 0x75, 0xf2, + 0x70, 0xf3, 0x7a, 0x7b, 0xf3, 0x6e, 0xed, 0x71, 0xf9, 0xf8, 0x6f, 0xec, + 0x6c, 0xef, 0x7c, 0x75, 0xec, 0x6a, 0xec, 0x71, 0x7e, 0xf0, 0x6a, 0xe9, + 0x6c, 0xf6, 0xf9, 0x6d, 0xe9, 0x69, 0xee, 0x7a, 0x73, 0xec, 0x69, 0xeb, + 0x71, 0x7e, 0xf0, 0x6b, 0xea, 0x6d, 0xf6, 0xfa, 0x6f, 0xec, 0x6d, 0xf0, + 0x7b, 0x79, 0xf0, 0x6e, 0xef, 0x75, 0xfc, 0xf9, 0x74, 0xf1, 0x72, 0xf6, + 0x7b, 0x7c, 0xf8, 0x75, 0xf4, 0x74, 0xf8, 0x7d, 0x7c, 0xf7, 0x73, 0xf2, + 0x74, 0xf9, 0xfd, 0x76, 0xf0, 0x6f, 0xf0, 0x77, 0x7d, 0xf3, 0x6d, 0xed, + 0x6f, 0xf8, 0xfa, 0x6f, 0xeb, 0x6b, 0xef, 0x7b, 0x75, 0xec, 0x6a, 0xeb, + 0x71, 0x7e, 0xf0, 0x6a, 0xe9, 0x6c, 0xf5, 0xf9, 0x6d, 0xea, 0x6a, 0xee, + 0x7a, 0x74, 0xed, 0x6a, 0xec, 0x71, 0x7e, 0xf2, 0x6c, 0xec, 0x6e, 0xf5, + 0xfc, 0x72, 0xed, 0x6e, 0xf1, 0x79, 0x7b, 0xf3, 0x70, 0xef, 0x74, 0xfa, + 0xfc, 0x78, 0xf3, 0x73, 0xf4, 0x79, 0xfc, 0xfa, 0x77, 0xf3, 0x74, 0xf4, + 0x7a, 0xfe, 0xf7, 0x73, 0xef, 0x71, 0xf7, 0x7e, 0x76, 0xef, 0x6e, 0xef, + 0x75, 0x7d, 0xf3, 0x6d, 0xec, 0x6e, 0xf7, 0xfa, 0x6e, 0xeb, 0x6b, 0xef, + 0x79, 0x74, 0xed, 0x69, 0xeb, 0x6f, 0x7e, 0xf1, 0x6a, 0xea, 0x6c, 0xf6, + 0xfa, 0x6e, 0xeb, 0x6a, 0xef, 0x7a, 0x76, 0xee, 0x6b, 0xed, 0x72, 0xfe, + 0xf4, 0x6e, 0xed, 0x6f, 0xf6, 0xfd, 0x75, 0xef, 0x6f, 0xf1, 0x79, 0x7e, + 0xf6, 0x75, 0xf1, 0x76, 0xf8, 0xfd, 0x7a, 0xf5, 0x74, 0xf2, 0x76, 0xf8, + 0x7e, 0x7a, 0xf6, 0x71, 0xec, 0x76, 0xfa, 0x7e, 0x7b, 0xf9, 0x72, 0xf5, + 0x7d, 0xfb, 0xfb, 0x76, 0xf9, 0x75, 0x7b, 0xf1, 0x77, 0xfe, 0x7a, 0xfb, + 0x7e, 0x73, 0xfa, 0x7e, 0xfb, 0x7d, 0x77, 0xf7, 0x7e, 0x7c, 0xfe, 0x78, + 0xfa, 0x7e, 0xff, 0x7e, 0x76, 0xf9, 0xfd, 0xf9, 0xfe, 0x75, 0xf1, 0x76, + 0x7c, 0xfc, 0x7a, 0xf3, 0x70, 0xfd, 0x7e, 0x7d, 0xf3, 0x70, 0xf7, 0x78, + 0x7e, 0xf9, 0x72, 0xef, 0x77, 0xfc, 0xfd, 0x75, 0xee, 0x72, 0xf7, 0xfb +#endif +}; + +static short MuLawDecompressTable[256] = +{ + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, -1, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + + +void vt_bell (VT *vt) +{ + if (vt->bell < 2) + return; + for (int i = 0; i < (int)sizeof (vt_bell_audio); i++) + { + int16_t val = MuLawDecompressTable[vt_bell_audio[i]] * vt->bell / 8; + terminal_queue_pcm (val, val); + } +} + + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right); + +void vt_audio (VT *vt, const char *command) +{ + AudioState *audio = &vt->audio; + // the simplest form of audio is raw audio + // _As=8000,c=2,b=8,e=u + // + // multiple voices: + // ids to queue - store samples as images... + // + // reusing samples + // .. pitch bend and be able to do a mod player? + const char *payload = NULL; + char key = 0; + int value; + int pos = 1; + + audio->frames=0; + audio->action='t'; + + int configure = 0; + while (command[pos] != ';') + { + pos ++; // G or , + if (command[pos] == ';') break; + key = command[pos]; pos++; + if (command[pos] == ';') break; + pos ++; // = + if (command[pos] == ';') break; + + if (command[pos] >= '0' && command[pos] <= '9') + value = atoi(&command[pos]); + else + value = command[pos]; + while (command[pos] && + command[pos] != ',' && + command[pos] != ';') pos++; + + if (value=='?') + { + char buf[256]; + const char *range=""; + switch (key) + { + case 's':range="8000,16000,24000,48000";break; + case 'b':range="8,16";break; + case 'B':range="512-65536";break; + case 'c':range="1";break; + case 'T':range="u,s,f";break; + case 'e':range="b,a";break; + case 'o':range="z,0";break; + case 'a':range="t,q";break; + default:range="unknown";break; + } + sprintf (buf, "\033_A%c=?;%s\033\\", key, range); + vt_write (vt, buf, strlen(buf)); + return; + } + + switch (key) + { + case 's': audio->samplerate = value; configure = 1; break; + case 'b': audio->bits = value; configure = 1; break; + case 'B': audio->buffer_size = value; configure = 1; break; + case 'c': audio->channels = value; configure = 1; break; + case 'a': audio->action = value; configure = 1; break; + case 'T': audio->type = value; configure = 1; break; + case 'f': audio->frames = value; configure = 1; break; + case 'e': audio->encoding = value; configure = 1; break; + case 'o': audio->compression = value; configure = 1; break; + case 'm': + audio->mic = value?1:0; + break; + } + + if (configure) + { + /* these are the specific sample rates supported by opus, + * instead of enabling anything SDL supports, the initial + * implementation limits itself to the opus sample rates + */ + if (audio->samplerate <= 8000) + { + audio->samplerate = 8000; + } + else if (audio->samplerate <= 16000) + { + audio->samplerate = 16000; + } + else if (audio->samplerate <= 24000) + { + audio->samplerate = 24000; + } + else + { + audio->samplerate = 48000; + } + + if (audio->bits != 8 && audio->bits != 16) + audio->bits = 8; + + if (audio->buffer_size > 2048) + audio->buffer_size = 2048; + else if (audio->buffer_size < 512) + audio->buffer_size = 512; + + switch (audio->type) + { + case 'u': + case 's': + case 'f': + break; + default: + audio->type = 's'; + } + + /* only 1 and 2 channels supported */ + if (audio->channels <= 0 || audio->channels > 2) + { + audio->channels = 1; + } + } + } + + if (audio->frames || audio->action != 'd') + { + payload = &command[pos+1]; + + // accumulate incoming data + { + int chunk_size = strlen (payload); + int old_size = audio->data_size; + if (audio->data == NULL) + { + audio->data_size = chunk_size; + audio->data = malloc (audio->data_size + 1); + } + else + { + audio->data_size += chunk_size; + audio->data = realloc (audio->data, audio->data_size + 1); + } + memcpy (audio->data + old_size, payload, chunk_size); + audio->data[audio->data_size]=0; + } + + if (audio->frames) + switch (audio->encoding) + { + case 'y': + audio->data_size = ydec (audio->data, audio->data, audio->data_size); + break; + case 'a': + { + int bin_length = audio->data_size; + if (bin_length) + { + uint8_t *data2 = malloc ((unsigned int)ctx_a85len ((char*)audio->data, audio->data_size) + 1); + // a85len is inaccurate but gives an upper bound, + // should be fixed. + bin_length = ctx_a85dec ((char*)audio->data, + (void*)data2, + bin_length); + free (audio->data); + audio->data = data2; + audio->data_size = bin_length; + } + } + break; + + case 'b': + { + int bin_length = audio->data_size; + uint8_t *data2 = malloc (audio->data_size); + bin_length = ctx_base642bin ((char*)audio->data, + &bin_length, + data2); + memcpy (audio->data, data2, bin_length + 1); + audio->data_size = bin_length; + free (data2); + } + break; + } + + if (audio->frames) + switch (audio->compression) + { + case 'z': + { + unsigned long actual_uncompressed_size = audio->frames * audio->bits/8 * audio->channels + 512; + unsigned char *data2 = malloc (actual_uncompressed_size); + /* if a buf size is set (rather compression, but + * this works first..) then */ + int z_result = uncompress (data2, &actual_uncompressed_size, + audio->data, + audio->data_size); + if (z_result != Z_OK) + { + // fprintf (stderr, "[z error %i %i]", __LINE__, z_result); + } + +#if 0 + // XXX : we seem to get buf-error (-5) here, which indicates not enough + // space in output buffer, which is odd + // + // it is non fatal though so we ignore it and use the validly + // decompressed bits. + { + char buf[256]; + sprintf (buf, "\e_Ao=z;zlib error1 %i\e\\", z_result); + vt_write (vt, buf, strlen(buf)); + //goto cleanup; + } +#endif + free (audio->data); + audio->data = data2; + audio->data_size = actual_uncompressed_size; + } + + break; + case 'o': + break; + default: + break; + } + + if (audio->frames == 0) + { + /* implicit frame count */ + audio->frames = audio->data_size / + (audio->bits/8) / + audio->channels; + } + + +#if 0 + if (audio->format == 100/* opus */) + { + int channels; + uint8_t *new_data = NULL;//stbi_load_from_memory (audio->data, audio->data_size, &audio->buf_width, &audio->buf_height, &channels, 4); + + if (!new_data) + { + char buf[256]= "\e_Gf=100;audio decode error\e\\"; + vt_write (vt, buf, strlen(buf)); + goto cleanup; + } + audio->format = 32; + free (audio->data); + audio->data = new_data; + audio->data_size = audio->buf_width * audio->buf_height * 4; + } +#endif + + switch (audio->action) + { + case 't': // transfer + if (audio->type == 'u') // implied 8bit + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = MuLawDecompressTable[audio->data[i*2]]; + int val_right = MuLawDecompressTable[audio->data[i*2+1]]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = MuLawDecompressTable[audio->data[i]]; + terminal_queue_pcm (val, val); + } + } + } + else if (audio->type == 's') + { + if (audio->bits == 8) + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = 256*((int8_t*)(audio->data))[i*2]; + int val_right = 256*((int8_t*)(audio->data))[i*2+1]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = 256*((int8_t*)(audio->data))[i]; + terminal_queue_pcm (val, val); + } + } + } + else + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = ((int16_t*)(audio->data))[i*2]; + int val_right = ((int16_t*)(audio->data))[i*2+1]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = ((int16_t*)(audio->data))[i]; + terminal_queue_pcm (val, val); + } + } + } + } + free (audio->data); + audio->data = NULL; + audio->data_size=0; + break; + case 'q': // query + { + char buf[512]; + sprintf (buf, "\033_As=%i,b=%i,c=%i,T=%c,B=%i,e=%c,o=%c;OK\033\\", + audio->samplerate, audio->bits, audio->channels, audio->type, + audio->buffer_size, + audio->encoding?audio->encoding:'0', + audio->compression?audio->compression:'0' + /*audio->transmission*/); + + vt_write (vt, buf, strlen(buf)); + } + break; + } + } + +//cleanup: + if (audio->data) + free (audio->data); + audio->data = NULL; + audio->data_size=0; +} +#endif +/* DEC terminals/xterm family terminal with ANSI, utf8, vector graphics and + * audio. + * + * Copyright (c) 2014, 2016, 2018, 2020 Øyvind Kolås <pippin@gimp.org> + * + * Adhering to the standards with modern extensions. + * + * Features: + * vt100 - 101 points on scoresheet + * UTF8, cp437 + * dim, bold, strikethrough, underline, italic, reverse + * ANSI colors, 256 colors (non-redefineable), 24bit color + * realtime audio transmission + * raster sprites (kitty spec) + * vector graphics + * vt320 - horizontal margins + * proportional fonts + * + * BBS/ANSI-art mode + * + * 8bit clean + * + * + * Todo: + * DECCIR - cursor state report https://vt100.net/docs/vt510-rm/DECCIR.html + * make absolute positioning take proportional into account + * HTML / PNG / SVG / PDF export of scrollback / screen + * sixels + * + */ + +#define _GNU_SOURCE +#define _BSD_SOURCE +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 // for posix_openpt +#endif + +#if !__COSMOPOLITAN__ +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <assert.h> + +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <zlib.h> +#endif + +#include "ctx.h" + + +#define CTX_VT_USE_FRAMEDIFF 0 // is a larger drain than neccesary when everything is per-byte? + // is anyways currently disabled also in ctx + +//#define STB_IMAGE_IMPLEMENTATION +//#include "stb_image.h" + +//#include "vt-line.h" +//#include "vt.h" +//#include "ctx-clients.h" + + + +#define VT_LOG_INFO (1<<0) +#define VT_LOG_CURSOR (1<<1) +#define VT_LOG_COMMAND (1<<2) +#define VT_LOG_WARNING (1<<3) +#define VT_LOG_ERROR (1<<4) +#define VT_LOG_INPUT (1<<5) +#define VT_LOG_ALL 0xff + +static int vt_log_mask = VT_LOG_INPUT; +//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR;// | VT_LOG_COMMAND;// | VT_LOG_INFO | VT_LOG_COMMAND; +//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR | VT_LOG_INFO | VT_LOG_COMMAND | VT_LOG_INPUT; +//static int vt_log_mask = VT_LOG_ALL; + +#if 0 +#define vt_log(domain, fmt, ...) + +#define VT_input(str, ...) +#define VT_info(str, ...) +#define VT_command(str, ...) +#define VT_cursor(str, ...) +#define VT_warning(str, ...) +#define VT_error(str, ...) +#else +#define vt_log(domain, line, a...) \ + do {fprintf (stderr, "%i %s ", line, domain);fprintf(stderr, ##a);fprintf(stderr, "\n");}while(0) +#define VT_info(a...) if (vt_log_mask & VT_LOG_INFO) vt_log ("INFO ", __LINE__, ##a) +#define VT_input(a...) if (vt_log_mask & VT_LOG_INPUT) vt_log ("INPUT ", __LINE__, ##a) +#define VT_command(a...) if (vt_log_mask & VT_LOG_COMMAND) vt_log ("CMD ", __LINE__, ##a) +#define VT_cursor(a...) if (vt_log_mask & VT_LOG_CURSOR) vt_log ("CURSOR",__LINE__, ##a) +#define VT_warning(a...) if (vt_log_mask & VT_LOG_WARNING) vt_log ("WARN ",__LINE__, ##a) +#define VT_error(a...) if (vt_log_mask & VT_LOG_ERROR) vt_log ("ERROR",__LINE__, ##a) + +#endif + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +static void vt_state_neutral (VT *vt, int byte); +static void vt_state_esc (VT *vt, int byte); +static void vt_state_osc (VT *vt, int byte); +static void vt_state_apc (VT *vt, int byte); +static void vt_state_apc_generic (VT *vt, int byte); +static void vt_state_sixel (VT *vt, int byte); +static void vt_state_esc_sequence (VT *vt, int byte); +static void vt_state_esc_foo (VT *vt, int byte); +static void vt_state_swallow (VT *vt, int byte); +static void vt_state_ctx (VT *vt, int byte); +static void vt_state_vt52 (VT *vt, int byte); + +#if 0 +/* barebones linked list */ + +typedef struct _CtxList CtxList; +struct _CtxList +{ + void *data; + CtxList *next; +}; + +static inline int ctx_list_length (CtxList *list) +{ + int length = 0; + for (CtxList *l = list; l; l = l->next, length++); + return length; +} + +static inline void ctx_list_prepend (CtxList **list, void *data) +{ + CtxList *new_=calloc (sizeof (CtxList), 1); + new_->next = *list; + new_->data = data; + *list = new_; +} + +static inline void *ctx_list_last (CtxList *list) +{ + if (list) + { + CtxList *last; + for (last = list; last->next; last=last->next); + return last->data; + } + return NULL; +} + +static inline void ctx_list_append (CtxList **list, void *data) +{ + CtxList *new_= calloc (sizeof (CtxList), 1); + new_->data=data; + if (*list) + { + CtxList *last; + for (last = *list; last->next; last=last->next); + last->next = new_; + return; + } + *list = new_; + return; +} + +static inline void ctx_list_remove (CtxList **list, void *data) +{ + CtxList *iter, *prev = NULL; + if ( (*list)->data == data) + { + prev = (void *) (*list)->next; + free (*list); + *list = prev; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter->data == data) + { + prev->next = iter->next; + free (iter); + break; + } + else + { prev = iter; } +} + +static inline void +ctx_list_insert_before (CtxList **list, CtxList *sibling, + void *data) +{ + if (*list == NULL || *list == sibling) + { + ctx_list_prepend (list, data); + } + else + { + CtxList *prev = NULL; + for (CtxList *l = *list; l; l=l->next) + { + if (l == sibling) + { break; } + prev = l; + } + if (prev) + { + CtxList *new_=calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + } + } +} +#endif + + +typedef enum +{ + STYLE_REVERSE = 1 << 0, + STYLE_BOLD = 1 << 1, + STYLE_BLINK = 1 << 2, + STYLE_UNDERLINE = 1 << 3, + STYLE_DIM = 1 << 4, + STYLE_HIDDEN = 1 << 5, + STYLE_ITALIC = 1 << 6, + STYLE_UNDERLINE_VAR = 1 << 7, + STYLE_STRIKETHROUGH = 1 << 8, + STYLE_OVERLINE = 1 << 9, + STYLE_BLINK_FAST = 1 << 10, + STYLE_PROPORTIONAL = 1 << 11, + STYLE_FG_COLOR_SET = 1 << 12, + STYLE_BG_COLOR_SET = 1 << 13, + STYLE_FG24_COLOR_SET = 1 << 14, + STYLE_BG24_COLOR_SET = 1 << 15, + //STYLE_NONERASABLE = 1 << 16 // needed for selective erase +} TerminalStyle; + +typedef struct Image +{ + int kitty_format; + int width; + int height; + int id; + int eid_no; + int size; + uint8_t *data; +} Image; + +#define MAX_IMAGES 128 + +static Image image_db[MAX_IMAGES]= {{0,},}; + +static Image *image_query (int id) +{ + for (int i = 0; i < MAX_IMAGES; i++) + { + Image *image = &image_db[i]; + if (image->id == id) + { return image; } + } + return NULL; +} + +static int image_eid_no = 0; + +static Image *image_add (int width, + int height, + int id, + int format, + int size, + uint8_t *data) +{ + // look for id if id is not 0 + Image *image; + for (int i = 0; i < MAX_IMAGES; i++) + { + image = &image_db[i]; + if (image->data == NULL) + { break; } + } + if (image->data) + { + // not a good eviction strategy + image = &image_db[random() %MAX_IMAGES]; + } + if (image->data) + { free (image->data); } + image->kitty_format = format; + image->width = width; + image->height = height; + image->id = id; + image->size = size; + image->data = data; + image->eid_no = image_eid_no++; + return image; +} + + + +void vtpty_resize (void *data, int cols, int rows, int px_width, int px_height) +{ + VtPty *vtpty = data; + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = cols; + ws.ws_xpixel = px_width; + ws.ws_ypixel = px_height; + ioctl (vtpty->pty, TIOCSWINSZ, &ws); +} + +ssize_t vtpty_write (void *data, const void *buf, size_t count) +{ + VtPty *vtpty = data; + return write (vtpty->pty, buf, count); +} + +ssize_t vtpty_read (void *data, void *buf, size_t count) +{ + VtPty *vtpty = data; + return read (vtpty->pty, buf, count); +} + +int vtpty_waitdata (void *data, int timeout) +{ + VtPty *vtpty = data; + struct timeval tv; + fd_set fdset; + FD_ZERO (&fdset); + FD_SET (vtpty->pty, &fdset); + tv.tv_sec = 0; + tv.tv_usec = timeout; + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + if (select (vtpty->pty+1, &fdset, NULL, NULL, &tv) == -1) + { + perror ("select"); + return 0; + } + if (FD_ISSET (vtpty->pty, &fdset) ) + { + return 1; + } + return 0; +} + + +/* on current line */ +static int vt_col_to_pos (VT *vt, int col) +{ + int pos = col; + if (vt->current_line->contains_proportional) + { + Ctx *ctx = ctx_new (); + ctx_font (ctx, "regular"); + ctx_font_size (ctx, vt->font_size); + int x = 0; + pos = 0; + int prev_prop = 0; + while (x <= col * vt->cw) + { + if (vt_line_get_style (vt->current_line, pos) & STYLE_PROPORTIONAL) + { + x += ctx_glyph_width (ctx, vt_line_get_unichar (vt->current_line, pos) ); + prev_prop = 1; + } + else + { + if (prev_prop) + { + int new_cw = vt->cw - ( (x % vt->cw) ); + if (new_cw < vt->cw*3/2) + { new_cw += vt->cw; } + x += new_cw; + } + else + { + x += vt->cw; + } + prev_prop = 0; + } + pos ++; + } + pos --; + ctx_free (ctx); + } + return pos; +} + +static int vt_margin_left (VT *vt) +{ + int left = vt->left_right_margin_mode?vt->margin_left:1; + return vt_col_to_pos (vt, left); +} + +#define VT_MARGIN_LEFT vt_margin_left(vt) + +static int vt_margin_right (VT *vt) +{ + int right = vt->left_right_margin_mode?vt->margin_right:vt->cols; + return vt_col_to_pos (vt, right); +} + +#define VT_MARGIN_RIGHT vt_margin_right(vt) + + +void vt_rev_inc (VT *vt) +{ + if (vt) + vt->rev++; +} + +long vt_rev (VT *vt) +{ + return vt?vt->rev:0; +} + +static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence); +int vt_set_prop (VT *vt, uint32_t key_hash, const char *val); +uint64_t ctx_strhash (const char *utf8, int ignored); + +static void vt_set_title (VT *vt, const char *new_title) +{ + if (vt->inert) return; + + if (vt->title) + { free (vt->title); } + vt->title = strdup (new_title); + vt_set_prop (vt, ctx_strhash ("title", 0), (char*)new_title); +} + +const char *vt_get_title (VT *vt) +{ + return vt->title; +} + +static void vt_run_command (VT *vt, const char *command, const char *term); +static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence); +static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence); +static void _vt_move_to (VT *vt, int y, int x); + +static void vtcmd_clear (VT *vt, const char *sequence) +{ + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + } + vt->lines = NULL; + vt->line_count = 0; + + if (1) + { /* TODO: detect if this is neccesary.. due to images present + in lines in scrollback */ + for (int i=0; i<vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->scrollback, vt->current_line); + vt->scrollback_count++; + } + } + + /* populate lines */ + for (int i=0; i<vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } +} + +#define set_fg_rgb(r, g, b) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\ + vt->cstyle |= ((uint64_t)(r)<<16);\ + vt->cstyle |= ((uint64_t)(g)<<(16+8));\ + vt->cstyle |= ((uint64_t)(b)<<(16+8+8));\ + vt->cstyle |= STYLE_FG_COLOR_SET;\ + vt->cstyle |= STYLE_FG24_COLOR_SET;\ + +#define set_bg_rgb(r, g, b) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\ + vt->cstyle |= ((uint64_t)(r)<<40);\ + vt->cstyle |= ((uint64_t)(g)<<(40+8));\ + vt->cstyle |= ((uint64_t)(b)<<(40+8+8));\ + vt->cstyle |= STYLE_BG_COLOR_SET;\ + vt->cstyle |= STYLE_BG24_COLOR_SET;\ + +#define set_fg_idx(idx) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\ + vt->cstyle ^= (vt->cstyle & STYLE_FG24_COLOR_SET);\ + vt->cstyle |= ((idx)<<16);\ + vt->cstyle |= STYLE_FG_COLOR_SET; + +#define set_bg_idx(idx) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\ + vt->cstyle ^= (vt->cstyle & STYLE_BG24_COLOR_SET);\ + vt->cstyle |= ((int64_t)(idx)<<40) ;\ + vt->cstyle |= STYLE_BG_COLOR_SET; + + +static void _vt_compute_cw_ch (VT *vt) +{ + vt->cw = (vt->font_size / vt->line_spacing * vt->scale_x) + 0.99; + vt->ch = vt->font_size; +} + +static void vtcmd_set_132_col (VT *vt, int set) +{ + // this should probably force the window as well + if (set == 0 && vt->scale_x == 1.0f) return; + if (set == 1 && vt->scale_x != 1.0f) return; + if (set) // 132 col + { + vt->scale_x = 74.0/132.0; // show all - po + //vt->scale_x = 80.0/132.0; + vt->scale_y = 1.0; + _vt_compute_cw_ch (vt); + vt_set_term_size (vt, vt->cols * 132/80.0, vt->rows); + } + else // 80 col + { + vt->scale_x = 1.0; + vt->scale_y = 1.0; + _vt_compute_cw_ch (vt); + vt_set_term_size (vt, vt->cols * 80/132.0, vt->rows); + } +} + +static void vt_line_feed (VT *vt); +static void vt_carriage_return (VT *vt); + +static int vt_trimlines (VT *vt, int max); +static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence) +{ + VT_info ("reset %s", sequence); + if (getenv ("VT_DEBUG") ) + { vt->debug = 1; } + vtcmd_clear (vt, sequence); + vt->encoding = 0; + vt->bracket_paste = 0; + vt->ctx_events = 0; + vt->cr_on_lf = 0; + vtcmd_set_top_and_bottom_margins (vt, "[r"); + vtcmd_set_left_and_right_margins (vt, "[s"); + vt->autowrap = 1; + vt->justify = 0; + vt->cursor_visible = 1; + vt->charset[0] = 0; + vt->charset[1] = 0; + vt->charset[2] = 0; + vt->charset[3] = 0; + vt->bell = 3; + vt->scale_x = 1.0; + vt->scale_y = 1.0; + vt->saved_x = 1; + vt->saved_y = 1; + vt->saved_style = 1; + vt->reverse_video = 0; + vt->cstyle = 0; + vt->keyrepeat = 1; + vt->cursor_key_application = 0; + vt->argument_buf_len = 0; + vt->argument_buf[0] = 0; + vt->vtpty.done = 0; + vt->result = -1; + vt->state = vt_state_neutral; + vt->scroll_on_output = 0; + vt->scroll_on_input = 1; + vt->unit_pixels = 0; + vt->mouse = 0; + vt->mouse_drag = 0; + vt->mouse_all = 0; + vt->mouse_decimal = 0; + _vt_compute_cw_ch (vt); + for (int i = 0; i < MAX_COLS; i++) + { vt->tabs[i] = i % 8 == 0? 1 : 0; } + _vt_move_to (vt, vt->margin_top, vt->cursor_x); + vt_carriage_return (vt); + //if (vt->ctx) + // { ctx_reset (vt->ctx); } + vt->audio.bits = 8; + vt->audio.channels = 1; + vt->audio.type = 'u'; + vt->audio.samplerate = 8000; + vt->audio.buffer_size = 1024; + vt->audio.encoding = 'a'; + vt->audio.compression = '0'; + vt->audio.mic = 0; + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + vt->scrollback_count = 0; +} + +void vt_set_font_size (VT *vt, float font_size) +{ + vt->font_size = font_size; + _vt_compute_cw_ch (vt); +} + +float vt_get_font_size (VT *vt) +{ + return vt->font_size; +} + +void vt_set_line_spacing (VT *vt, float line_spacing) +{ + vt->line_spacing = line_spacing; + _vt_compute_cw_ch (vt); +} + +VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch) +{ + VT *vt = calloc (sizeof (VT), 1); + vt->id = id; + vt->lastx = -1; + vt->lasty = -1; + vt->state = vt_state_neutral; + vt->smooth_scroll = 0; + vt->can_launch = can_launch; + vt->scroll_offset = 0.0; + vt->waitdata = vtpty_waitdata; + vt->read = vtpty_read; + vt->write = vtpty_write; + vt->resize = vtpty_resize; + vt->font_to_cell_scale = 0.98; + vt->cursor_visible = 1; + vt->lines = NULL; + vt->line_count = 0; + vt->current_line = NULL; + vt->cols = 0; + vt->rows = 0; + + vt->scrollback_limit = DEFAULT_SCROLLBACK; + vt->argument_buf_len = 0; + vt->argument_buf_cap = 64; + vt->argument_buf = malloc (vt->argument_buf_cap); + vt->argument_buf[0] = 0; + vt->vtpty.done = 0; + vt->result = -1; + vt->line_spacing = 1.0; + vt->scale_x = 1.0; + vt->scale_y = 1.0; + vt_set_font_size (vt, font_size); + vt_set_line_spacing (vt, line_spacing); + if (command) + { + vt_run_command (vt, command, NULL); + } + if (width <= 0) width = 640; + if (height <= 0) width = 480; + vt_set_px_size (vt, width, height); + + vt->fg_color[0] = 216; + vt->fg_color[1] = 216; + vt->fg_color[2] = 216; + vt->bg_color[0] = 0; + vt->bg_color[1] = 0; + vt->bg_color[2] = 0; + vtcmd_reset_to_initial_state (vt, NULL); + //vt->ctx = ctx_new (); + ctx_list_prepend (&vts, vt); + return vt; +} + +int vt_cw (VT *vt) +{ + return vt->cw; +} + +int vt_ch (VT *vt) +{ + return vt->ch; +} + +static int vt_trimlines (VT *vt, int max) +{ + CtxList *chop_point = NULL; + CtxList *l; + int i; + if (vt->line_count < max) + { + return 0; + } + for (l = vt->lines, i = 0; l && i < max-1; l = l->next, i++); + if (l) + { + chop_point = l->next; + l->next = NULL; + } + while (chop_point) + { + if (vt->in_alt_screen) + { + vt_line_free (chop_point->data, 1); + } + else + { + ctx_list_prepend (&vt->scrollback, chop_point->data); + vt->scrollback_count ++; + } + ctx_list_remove (&chop_point, chop_point->data); + vt->line_count--; + } + if (vt->scrollback_count > vt->scrollback_limit + 1024) + { + CtxList *l = vt->scrollback; + int no = 0; + while (l && no < vt->scrollback_limit) + { + l = l->next; + no++; + } + chop_point = NULL; + if (l) + { + chop_point = l->next; + l->next = NULL; + } + while (chop_point) + { + vt_line_free (chop_point->data, 1); + ctx_list_remove (&chop_point, chop_point->data); + vt->scrollback_count --; + } + } + return 0; +} + +static void vt_rewrap_pair (VT *vt, VtLine *topline, VtLine *bottomline, int max_col) +{ + int toplen = 0; + + while ((toplen = vt_line_get_utf8length (topline)) > max_col) + { + uint32_t unichar = vt_line_get_unichar (topline, toplen-1); + uint32_t style = vt_line_get_style (topline, toplen-1); + vt_line_insert_unichar (bottomline, 0, unichar); + vt_line_remove (topline, toplen-1); + vt_line_set_style (bottomline, 0, style); + } + + while (vt_line_get_length (bottomline) && + (toplen = vt_line_get_utf8length (topline)) < max_col) + { + uint32_t unichar = vt_line_get_unichar (bottomline, 0); + uint32_t style = vt_line_get_style (bottomline, 0); + vt_line_append_unichar (topline, unichar); + vt_line_set_style (topline, toplen, style); + vt_line_remove (bottomline, 0); + } +} + +static void vt_rewrap (VT *vt, int max_col) +{ + if (max_col < 8) max_col = 8; + CtxList *list = NULL; + + for (CtxList *l = vt->lines; l;) + { + CtxList *next = l->next; + ctx_list_prepend (&list, l->data); + ctx_list_remove (&vt->lines, l->data); + l = next; + } + for (CtxList *l = vt->scrollback; l;) + { + CtxList *next = l->next; + ctx_list_prepend (&list, l->data); + ctx_list_remove (&vt->scrollback, l->data); + l = next; + } + + for (CtxList *l = list; l; l = l->next) + { + VtLine *line = l->data; + VtLine *next = l->next ?l->next->data:NULL; + + if (vt_line_get_utf8length (line) >= max_col || (next && next->wrapped)) + { + if (!next) + { + ctx_list_append (&list, vt_line_new ("")); + next = l->next->data; + next->wrapped = 1; + } + else if (!next->wrapped) + { + ctx_list_insert_before (&list, l->next, vt_line_new ("")); + next = l->next->data; + next->wrapped = 1; + } + vt_rewrap_pair (vt, line, next, max_col); + if (vt_line_get_utf8length (next) == 0) + ctx_list_remove (&list, l->next->data); + } + } + + int rows = vt->rows; + int total_rows = ctx_list_length (list); + + int scrollback_rows = total_rows - rows; + + int c = 0; + CtxList *l; + for (l = list; l && c < scrollback_rows;) + { + CtxList *next = l->next; + ctx_list_prepend (&vt->scrollback, l->data); + ctx_list_remove (&list, l->data); + l = next; + c++; + } + for (; l ;) + { + CtxList *next = l->next; + ctx_list_prepend (&vt->lines, l->data); + ctx_list_remove (&list, l->data); + l = next; + c++; + } +} + +void vt_set_term_size (VT *vt, int icols, int irows) +{ + if (vt->rows == irows && vt->cols == icols) + return; + + + if (vt->state == vt_state_ctx) + { + // we should queue a pending resize instead, + // .. or set a flag indicating that the last + // rendered frame is discarded? + return; + } + + if(1)vt_rewrap (vt, icols); + + while (irows > vt->rows) + { + if (vt->scrollback_count) + { + vt->scrollback_count--; + ctx_list_append (&vt->lines, vt->scrollback->data); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + vt->cursor_y++; + } + else + { + ctx_list_prepend (&vt->lines, vt_line_new_with_size ("", vt->cols) ); + } + vt->line_count++; + vt->rows++; + } + while (irows < vt->rows) + { + vt->cursor_y--; + vt->rows--; + } + vt->rows = irows; + vt->cols = icols; + vt_resize (vt, vt->cols, vt->rows, vt->width, vt->height); + vt_trimlines (vt, vt->rows); + vt->margin_top = 1; + vt->margin_left = 1; + vt->margin_bottom = vt->rows; + vt->margin_right = vt->cols; + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->rev++; + VT_info ("resize %i %i", irows, icols); + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + vt->ctxp = NULL; +} + +void vt_set_px_size (VT *vt, int width, int height) +{ + int cols = width / vt->cw; + int rows = height / vt->ch; + vt->width = width; + vt->height = height; + vt_set_term_size (vt, cols, rows); +} + + + +static void vt_argument_buf_reset (VT *vt, const char *start) +{ + if (start) + { + strcpy (vt->argument_buf, start); + vt->argument_buf_len = strlen (start); + } + else + { vt->argument_buf[vt->argument_buf_len=0]=0; } +} + +static inline void vt_argument_buf_add (VT *vt, int ch) +{ + if (vt->argument_buf_len + 1 >= 1024 * 1024 * 16) + return; + // + if (vt->argument_buf_len + 1 >= + vt->argument_buf_cap) + { + vt->argument_buf_cap = vt->argument_buf_cap * 2; + vt->argument_buf = realloc (vt->argument_buf, vt->argument_buf_cap); + } + vt->argument_buf[vt->argument_buf_len] = ch; + vt->argument_buf[++vt->argument_buf_len] = 0; +} + +static void +_vt_move_to (VT *vt, int y, int x) +{ + int i; + x = x < 1 ? 1 : (x > vt->cols ? vt->cols : x); + y = y < 1 ? 1 : (y > vt->rows ? vt->rows : y); + vt->at_line_home = 0; + vt->cursor_x = x; + vt->cursor_y = y; + i = vt->rows - y; + CtxList *l; + for (l = vt->lines; l && i >= 1; l = l->next, i--); + if (l) + { + vt->current_line = l->data; + } + else + { + for (; i > 0; i--) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_append (&vt->lines, vt->current_line); + vt->line_count++; + } + } + VT_cursor ("%i,%i (_vt_move_to)", y, x); + vt->rev++; +} + +static void vt_scroll (VT *vt, int amount); + +static void _vt_add_str (VT *vt, const char *str) +{ + int logical_margin_right = VT_MARGIN_RIGHT; + if (vt->cstyle & STYLE_PROPORTIONAL) + { vt->current_line->contains_proportional = 1; } + if (vt->cursor_x > logical_margin_right) + { + if (vt->autowrap) + { + int chars = 0; + int old_x = vt->cursor_x; + VtLine *old_line = vt->current_line; + if (vt->justify && str[0] != ' ') + { + while (old_x-1-chars >1 && vt_line_get_unichar (vt->current_line, + old_x-1-chars) !=' ') + { + chars++; + } + chars--; + if (chars > (vt->margin_right - vt->margin_left) * 3 / 2) + { chars = 0; } + } + if (vt->cursor_y == vt->margin_bottom) + { + vt_scroll (vt, -1); + } + else + { + _vt_move_to (vt, vt->cursor_y+1, 1); + } + vt->current_line->wrapped=1; + vt_carriage_return (vt); + for (int i = 0; i < chars; i++) + { + vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle); + vt_line_replace_unichar (vt->current_line, vt->cursor_x - 1, + vt_line_get_unichar (old_line, old_x-1-chars+i) ); + vt->cursor_x++; + } + for (int i = 0; i < chars; i++) + { + vt_line_replace_unichar (old_line, old_x-1-chars+i, ' '); + } + if (str[0] == ' ') + return; + } + else + { + vt->cursor_x = logical_margin_right; + } + } + if (vt->insert_mode) + { + vt_line_insert_utf8 (vt->current_line, vt->cursor_x - 1, str); + while (vt->current_line->string.utf8_length > logical_margin_right) + { vt_line_remove (vt->current_line, logical_margin_right); } + } + else + { + vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1, str); + } + vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle); + vt->cursor_x += 1; + vt->at_line_home = 0; + vt->rev++; +} + +static void _vt_backspace (VT *vt) +{ + if (vt->current_line) + { + vt->cursor_x --; + if (vt->cursor_x == VT_MARGIN_RIGHT) { vt->cursor_x--; } + if (vt->cursor_x < VT_MARGIN_LEFT) + { + vt->cursor_x = VT_MARGIN_LEFT; + vt->at_line_home = 1; + } + VT_cursor ("backspace"); + } + vt->rev++; +} + +static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence) +{ + int top = 1, bottom = vt->rows; + /* w3m issues this; causing reset of cursor position, why it is issued + * is unknown + */ + if (!strcmp (sequence, "[?1001r")) + return; + if (strlen (sequence) > 2) + { + sscanf (sequence, "[%i;%ir", &top, &bottom); + } + VT_info ("margins: %i %i", top, bottom); + if (top <1) { top = 1; } + if (top > vt->rows) { top = vt->rows; } + if (bottom > vt->rows) { bottom = vt->rows; } + if (bottom < top) { bottom = top; } + vt->margin_top = top; + vt->margin_bottom = bottom; +#if 0 + _vt_move_to (vt, top, 1); +#endif + vt_carriage_return (vt); + VT_cursor ("%i, %i (home)", top, 1); +} +static void vtcmd_save_cursor_position (VT *vt, const char *sequence); + +static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence) +{ + int left = 1, right = vt->cols; + if (!vt->left_right_margin_mode) + { + vtcmd_save_cursor_position (vt, sequence); + return; + } + if (strlen (sequence) > 2) + { + sscanf (sequence, "[%i;%is", &left, &right); + } + VT_info ("hor margins: %i %i", left, right); + if (left <1) { left = 1; } + if (left > vt->cols) { left = vt->cols; } + if (right > vt->cols) { right = vt->cols; } + if (right < left) { right = left; } + vt->margin_left = left; + vt->margin_right = right; + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt_carriage_return (vt); + //VT_cursor ("%i, %i (home)", left, 1); +} + +static inline int parse_int (const char *arg, int def_val) +{ + if (!isdigit (arg[1]) || strlen (arg) == 2) + { return def_val; } + return atoi (arg+1); +} + + +static void vtcmd_set_line_home (VT *vt, const char *sequence) +{ + int val = parse_int (sequence, 1); + char buf[256]; + vt->left_right_margin_mode = 1; + sprintf (buf, "[%i;%it", val, vt->margin_right); + vtcmd_set_left_and_right_margins (vt, buf); +} + +static void vtcmd_set_line_limit (VT *vt, const char *sequence) +{ + int val = parse_int (sequence, 0); + char buf[256]; + vt->left_right_margin_mode = 1; + if (val < vt->margin_left) { val = vt->margin_left; } + sprintf (buf, "[%i;%it", vt->margin_left, val); + vtcmd_set_left_and_right_margins (vt, buf); +} + +static void vt_scroll (VT *vt, int amount) +{ + int remove_no, insert_before; + VtLine *string = NULL; + if (amount == 0) { amount = 1; } + if (amount < 0) + { + remove_no = vt->margin_top; + insert_before = vt->margin_bottom; + } + else + { + remove_no = vt->margin_bottom; + insert_before = vt->margin_top; + } + CtxList *l; + int i; + for (i=vt->rows, l = vt->lines; i > 0 && l; l=l->next, i--) + { + if (i == remove_no) + { + string = l->data; + ctx_list_remove (&vt->lines, string); + break; + } + } + if (string) + { + if (!vt->in_alt_screen && + (vt->margin_top == 1 && vt->margin_bottom == vt->rows) ) + { + ctx_list_prepend (&vt->scrollback, string); + vt->scrollback_count ++; + } + else + { + vt_line_free (string, 1); + } + } + string = vt_line_new_with_size ("", vt->cols/4); + if (amount > 0 && vt->margin_top == 1) + { + ctx_list_append (&vt->lines, string); + } + else + { + for (i=vt->rows, l = vt->lines; l; l=l->next, i--) + { + if (i == insert_before) + { + ctx_list_insert_before (&vt->lines, l, string); + break; + } + } + if (i != insert_before) + { + ctx_list_append (&vt->lines, string); + } + } + vt->current_line = string; + /* not updating line count since we should always remove one and add one */ + if (vt->smooth_scroll) + { + if (amount < 0) + { + vt->scroll_offset = -1.0; + vt->in_smooth_scroll = -1; + } + else + { + vt->scroll_offset = 1.0; + vt->in_smooth_scroll = 1; + } + } + + { + vt->select_begin_row += amount; + vt->select_end_row += amount; + vt->select_start_row += amount; + } +} + +typedef struct Sequence +{ + const char *prefix; + char suffix; + void (*vtcmd) (VT *vt, const char *sequence); + uint32_t compat; +} Sequence; + +static void vtcmd_cursor_position (VT *vt, const char *sequence) +{ + int y = 1, x = 1; + const char *semi; + if (sequence[0] != 'H' && sequence[0] != 'f') + { + y = parse_int (sequence, 1); + if ( (semi = strchr (sequence, ';') ) ) + { + x = parse_int (semi, 1); + } + } + if (x == 0) { x = 1; } + if (y == 0) { y = 1; } + if (vt->origin) + { + y += vt->margin_top - 1; + _vt_move_to (vt, y, vt->cursor_x); + x += VT_MARGIN_LEFT - 1; + } + VT_cursor ("%i %i CUP", y, x); + _vt_move_to (vt, y, x); +} + + +static void vtcmd_horizontal_position_absolute (VT *vt, const char *sequence) +{ + int x = parse_int (sequence, 1); + if (x<=0) { x = 1; } + _vt_move_to (vt, vt->cursor_y, x); +} + +static void vtcmd_goto_row (VT *vt, const char *sequence) +{ + int y = parse_int (sequence, 1); + _vt_move_to (vt, y, vt->cursor_x); +} + +static void vtcmd_cursor_forward (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + vt->cursor_x++; + } + if (vt->cursor_x > VT_MARGIN_RIGHT) + { vt->cursor_x = VT_MARGIN_RIGHT; } +} + +static void vtcmd_cursor_backward (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + vt->cursor_x--; + } + if (vt->cursor_x < VT_MARGIN_LEFT) + { + vt->cursor_x = VT_MARGIN_LEFT; // should this wrap?? + vt->at_line_home = 1; + } +} + +static void vtcmd_reverse_index (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_top) + { + vt_scroll (vt, 1); + _vt_move_to (vt, vt->margin_top, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x); + } + } +} + +static void vtcmd_cursor_up (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_top) + { + //_vt_move_to (vt, 1, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x); + } + } +} + +static void vtcmd_back_index (VT *vt, const char *sequence) +{ + // XXX implement +} + +static void vtcmd_forward_index (VT *vt, const char *sequence) +{ + // XXX implement +} + +static void vtcmd_index (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_bottom) + { + vt_scroll (vt, -1); + _vt_move_to (vt, vt->margin_bottom, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x); + } + } +} + +static void vtcmd_cursor_down (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y >= vt->margin_bottom) + { + _vt_move_to (vt, vt->margin_bottom, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x); + } + } +} + +static void vtcmd_next_line (VT *vt, const char *sequence) +{ + vtcmd_index (vt, sequence); + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt_carriage_return (vt); + vt->cursor_x = VT_MARGIN_LEFT; +} + +static void vtcmd_cursor_preceding_line (VT *vt, const char *sequence) +{ + vtcmd_cursor_up (vt, sequence); + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->cursor_x = VT_MARGIN_LEFT; +} + +static void vtcmd_erase_in_line (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 0); + switch (n) + { + case 0: // clear to end of line + { + char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1); + if (p) { *p = 0; } + // XXX : this is chopping lines + for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col - 1, vt->cstyle); } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); + } + break; + case 1: // clear from beginning to cursor + { + for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++) + { + vt_line_replace_utf8 (vt->current_line, col-1, " "); + } + for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); // should be a nop + } + break; + case 2: // clear entire line + for (int col = VT_MARGIN_LEFT; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + vt_line_set (vt->current_line, ""); // XXX not all + break; + } +} + +static void vtcmd_erase_in_display (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 0); + switch (n) + { + case 0: // clear to end of screen + { + char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1); + if (p) { *p = 0; } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); + } + for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + { + CtxList *l; + int no = vt->rows; + for (l = vt->lines; l->data != vt->current_line; l = l->next, no--) + { + VtLine *buf = l->data; + buf->string.str[0] = 0; + buf->string.length = 0; + buf->string.utf8_length = 0; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (buf, col-1, vt->cstyle); } + } + } + break; + case 1: // clear from beginning to cursor + { + for (int col = 1; col <= vt->cursor_x; col++) + { + vt_line_replace_utf8 (vt->current_line, col-1, " "); + vt_line_set_style (vt->current_line, col-1, vt->cstyle); + } + } + { + CtxList *l; + int there_yet = 0; + int no = vt->rows; + for (l = vt->lines; l; l = l->next, no--) + { + VtLine *buf = l->data; + if (there_yet) + { + buf->string.str[0] = 0; + buf->string.length = 0; + buf->string.utf8_length = 0; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (buf, col-1, vt->cstyle); } + } + if (buf == vt->current_line) + { + there_yet = 1; + } + } + } + break; + case 3: // also clear scrollback + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + vt->scrollback_count = 0; + /* FALLTHROUGH */ + case 2: // clear entire screen but keep cursor; + { + int tx = vt->cursor_x; + int ty = vt->cursor_y; + vtcmd_clear (vt, ""); + _vt_move_to (vt, ty, tx); + for (CtxList *l = vt->lines; l; l = l->next) + { + VtLine *line = l->data; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (line, col-1, vt->cstyle); } + } + } + break; + } +} + +static void vtcmd_screen_alignment_display (VT *vt, const char *sequence) +{ + for (int y = 1; y <= vt->rows; y++) + { + _vt_move_to (vt, y, 1); + for (int x = 1; x <= vt->cols; x++) + { + _vt_add_str (vt, "E"); + } + } +} + +#if 0 +static int find_idx (int r, int g, int b) +{ + r = r / 255.0 * 5; + g = g / 255.0 * 5; + b = b / 255.0 * 5; + return 16 + r * 6 * 6 + g * 6 + b; +} +#endif + +static void vtcmd_set_graphics_rendition (VT *vt, const char *sequence) +{ + const char *s = sequence; + if (s[0]) { s++; } + while (s && *s) + { + int n = parse_int (s - 1, 0); // works until color + // both fg and bg could be set in 256 color mode FIXME + // + /* S_GR@38@Set forground color@foo bar baz@ */ + if (n == 38) // set foreground + { + s = strchr (s, ';'); + if (!s) + { + VT_warning ("incomplete [38m expected ; %s", sequence); + return; + } + n = parse_int (s, 0); + if (n == 5) + { + s++; + if (strchr (s, ';') ) + { s = strchr (s, ';'); } + else + { s = strchr (s, ':'); } + if (s) + { + n = parse_int (s, 0); + set_fg_idx (n); + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + else if (n == 2) + { + int r = 0, g = 0, b = 0; + s++; + if (strchr (s, ';') ) + { + s = strchr (s, ';'); + if (s) + { sscanf (s, ";%i;%i;%i", &r, &g, &b); } + } + else + { + s = strchr (s, ':'); + if (s) + { sscanf (s, ":%i:%i:%i", &r, &g, &b); } + } + if (s) + for (int i = 0; i < 3; i++) + { + if (*s) + { + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + set_fg_rgb (r,g,b); + } + else + { + VT_warning ("unhandled %s %i", sequence, n); + return; + } + //return; // XXX we should continue, and allow further style set after complex color + } + else if (n == 48) // set background + { + s = strchr (s, ';'); + if (!s) + { + VT_warning ("incomplete [38m expected ; %s", sequence); + return; + } + n = parse_int (s, 0); + if (n == 5) + { + s++; + if (strchr (s, ';') ) + { s = strchr (s, ';'); } + else + { s = strchr (s, ':'); } + if (s) + { n = parse_int (s, 0); } + set_bg_idx (n); + if (s) + { + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + else if (n == 2) + { + int r = 0, g = 0, b = 0; + s++; + if (strchr (s, ';') ) + { + s = strchr (s, ';'); + if (s) + { sscanf (s, ";%i;%i;%i", &r, &g, &b); } + } + else + { + s = strchr (s, ':'); + if (s) + { sscanf (s, ":%i:%i:%i", &r, &g, &b); } + } + if (s) + for (int i = 0; i < 3; i++) + { + s++; + while (*s >= '0' && *s <='9') { s++; } + } + set_bg_rgb (r,g,b); + } + else + { + VT_warning ("unhandled %s %i", sequence, n); + return; + } + //return; // we XXX should continue, and allow further style set after complex color + } + else + switch (n) + { + case 0: /* SGR@0@Style reset@@ */ + if (vt->cstyle & STYLE_PROPORTIONAL) + { vt->cstyle = STYLE_PROPORTIONAL; } + else + { vt->cstyle = 0; } + break; + case 1: /* SGR@@Bold@@ */ + vt->cstyle |= STYLE_BOLD; + break; + case 2: /* SGR@@Dim@@ */ + vt->cstyle |= STYLE_DIM; + break; + case 3: /* SGR@@Italic@@ */ + vt->cstyle |= STYLE_ITALIC; + break; + case 4: /* SGR@@Underscore@@ */ + /* SGR@4:2@Double underscore@@ */ + /* SGR@4:3@Curvy underscore@@ */ + if (s[1] == ':') + { + switch (s[2]) + { + case '0': + break; + case '1': + vt->cstyle |= STYLE_UNDERLINE; + break; + case '2': + vt->cstyle |= STYLE_UNDERLINE| + STYLE_UNDERLINE_VAR; + break; + default: + case '3': + vt->cstyle |= STYLE_UNDERLINE_VAR; + break; + } + } + else + { + vt->cstyle |= STYLE_UNDERLINE; + } + break; + case 5: /* SGR@@Blink@@ */ + vt->cstyle |= STYLE_BLINK; + break; + case 6: /* SGR@@Blink Fast@@ */ + vt->cstyle |= STYLE_BLINK_FAST; + break; + case 7: /* SGR@@Reverse@@ */ + vt->cstyle |= STYLE_REVERSE; + break; + case 8: /* SGR@@Hidden@@ */ + vt->cstyle |= STYLE_HIDDEN; + break; + case 9: /* SGR@@Strikethrough@@ */ + vt->cstyle |= STYLE_STRIKETHROUGH; + break; + case 10: /* SGR@@Font 0@@ */ + break; + case 11: /* SGR@@Font 1@@ */ + break; + case 12: /* SGR@@Font 2(ignored)@@ */ + case 13: /* SGR@@Font 3(ignored)@@ */ + case 14: /* SGR@@Font 4(ignored)@@ */ + break; + case 22: /* SGR@@Bold off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_BOLD); + vt->cstyle ^= (vt->cstyle & STYLE_DIM); + break; + case 23: /* SGR@@Italic off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_ITALIC); + break; + case 24: /* SGR@@Underscore off@@ */ + vt->cstyle ^= (vt->cstyle & (STYLE_UNDERLINE|STYLE_UNDERLINE_VAR) ); + break; + case 25: /* SGR@@Blink off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_BLINK); + vt->cstyle ^= (vt->cstyle & STYLE_BLINK_FAST); + break; + case 26: /* SGR@@Proportional spacing @@ */ + vt->cstyle |= STYLE_PROPORTIONAL; + break; + case 27: /* SGR@@Reverse off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_REVERSE); + break; + case 28: /* SGR@@Hidden off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_HIDDEN); + break; + case 29: /* SGR@@Strikethrough off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_STRIKETHROUGH); + break; + case 30: /* SGR@@black text color@@ */ + set_fg_idx (0); + break; + case 31: /* SGR@@red text color@@ */ + set_fg_idx (1); + break; + case 32: /* SGR@@green text color@@ */ + set_fg_idx (2); + break; + case 33: /* SGR@@yellow text color@@ */ + set_fg_idx (3); + break; + case 34: /* SGR@@blue text color@@ */ + set_fg_idx (4); + break; + case 35: /* SGR@@magenta text color@@ */ + set_fg_idx (5); + break; + case 36: /* SGR@@cyan text color@@ */ + set_fg_idx (6); + break; + case 37: /* SGR@@light gray text color@@ */ + set_fg_idx (7); + break; + /* SGR@38;5;Pn@256 color index foreground color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6 RGB cube and in the end a grayscale without white and black.@ */ + /* SGR@38;2;Pr;Pg;Pb@24 bit RGB foreground color each of Pr Pg and Pb have 0-255 range@@ */ + case 39: /* SGR@@default text color@@ */ + set_fg_idx (vt->reverse_video?0:15); + vt->cstyle ^= (vt->cstyle & STYLE_FG_COLOR_SET); + break; + case 40: /* SGR@@black background color@@ */ + set_bg_idx (0); + break; + case 41: /* SGR@@red background color@@ */ + set_bg_idx (1); + break; + case 42: /* SGR@@green background color@@ */ + set_bg_idx (2); + break; + case 43: /* SGR@@yellow background color@@ */ + set_bg_idx (3); + break; + case 44: /* SGR@@blue background color@@ */ + set_bg_idx (4); + break; + case 45: /* SGR@@magenta background color@@ */ + set_bg_idx (5); + break; + case 46: /* SGR@@cyan background color@@ */ + set_bg_idx (6); + break; + case 47: /* SGR@@light gray background color@@ */ + set_bg_idx (7); + break; + + /* SGR@48;5;Pn@256 color index background color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6 RGB cube and in the end a grayscale without white and black.@ */ + /* SGR@48;2;Pr;Pg;Pb@24 bit RGB background color@Where Pr Pg and Pb have 0-255 range@ */ + + case 49: /* SGR@@default background color@@ */ + set_bg_idx (vt->reverse_video?15:0); + vt->cstyle ^= (vt->cstyle & STYLE_BG_COLOR_SET); + break; + case 50: /* SGR@@Proportional spacing off @@ */ + vt->cstyle ^= (vt->cstyle & STYLE_PROPORTIONAL); + break; + // 51 : framed + // 52 : encircled + case 53: /* SGR@@Overlined@@ */ + vt->cstyle |= STYLE_OVERLINE; + break; + case 55: /* SGR@@Not Overlined@@ */ + vt->cstyle ^= (vt->cstyle&STYLE_OVERLINE); + break; + case 90: /* SGR@@dark gray text color@@ */ + set_fg_idx (8); + break; + case 91: /* SGR@@light red text color@@ */ + set_fg_idx (9); + break; + case 92: /* SGR@@light green text color@@ */ + set_fg_idx (10); + break; + case 93: /* SGR@@light yellow text color@@ */ + set_fg_idx (11); + break; + case 94: /* SGR@@light blue text color@@ */ + set_fg_idx (12); + break; + case 95: /* SGR@@light magenta text color@@ */ + set_fg_idx (13); + break; + case 96: /* SGR@@light cyan text color@@ */ + set_fg_idx (14); + break; + case 97: /* SGR@@white text color@@ */ + set_fg_idx (15); + break; + case 100: /* SGR@@dark gray background color@@ */ + set_bg_idx (8); + break; + case 101: /* SGR@@light red background color@@ */ + set_bg_idx (9); + break; + case 102: /* SGR@@light green background color@@ */ + set_bg_idx (10); + break; + case 103: /* SGR@@light yellow background color@@ */ + set_bg_idx (11); + break; + case 104: /* SGR@@light blue background color@@ */ + set_bg_idx (12); + break; + case 105: /* SGR@@light magenta background color@@ */ + set_bg_idx (13); + break; + case 106: /* SGR@@light cyan background color@@ */ + set_bg_idx (14); + break; + case 107: /* SGR@@white background color@@ */ + set_bg_idx (15); + break; + default: + VT_warning ("unhandled style code %i in sequence \\033%s\n", n, sequence); + return; + } + while (s && *s && *s != ';') { s++; } + if (s && *s == ';') { s++; } + } +} + +static void vtcmd_ignore (VT *vt, const char *sequence) +{ + VT_info ("ignoring sequence %s", sequence); +} + +static void vtcmd_clear_all_tabs (VT *vt, const char *sequence) +{ + memset (vt->tabs, 0, sizeof (vt->tabs) ); +} + +static void vtcmd_clear_current_tab (VT *vt, const char *sequence) +{ + vt->tabs[ (int) (vt->cursor_x-1)] = 0; +} + +static void vtcmd_horizontal_tab_set (VT *vt, const char *sequence) +{ + vt->tabs[ (int) vt->cursor_x-1] = 1; +} + +static void vtcmd_save_cursor_position (VT *vt, const char *sequence) +{ + vt->saved_x = vt->cursor_x; + vt->saved_y = vt->cursor_y; +} + +static void vtcmd_restore_cursor_position (VT *vt, const char *sequence) +{ + _vt_move_to (vt, vt->saved_y, vt->saved_x); +} + + +static void vtcmd_save_cursor (VT *vt, const char *sequence) +{ + vt->saved_style = vt->cstyle; + vt->saved_origin = vt->origin; + vtcmd_save_cursor_position (vt, sequence); + for (int i = 0; i < 4; i++) + { vt->saved_charset[i] = vt->charset[i]; } +} + +static void vtcmd_restore_cursor (VT *vt, const char *sequence) +{ + vtcmd_restore_cursor_position (vt, sequence); + vt->cstyle = vt->saved_style; + vt->origin = vt->saved_origin; + for (int i = 0; i < 4; i++) + { vt->charset[i] = vt->saved_charset[i]; } +} + +static void vtcmd_erase_n_chars (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1 + n, " "); + vt_line_set_style (vt->current_line, vt->cursor_x + n - 1, vt->cstyle); + } +} + +static void vtcmd_delete_n_chars (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + int count = n; + while (count--) + { + vt_line_remove (vt->current_line, vt->cursor_x - 1); + } +} + +static void vtcmd_delete_n_lines (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + for (int a = 0; a < n; a++) + { + int i; + CtxList *l; + VtLine *string = vt->current_line; + vt_line_set (string, ""); + ctx_list_remove (&vt->lines, vt->current_line); + for (i=vt->rows, l = vt->lines; l; l=l->next, i--) + { + if (i == vt->margin_bottom) + { + vt->current_line = string; + ctx_list_insert_before (&vt->lines, l, string); + break; + } + } + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); // updates current_line + } +} + +static void vtcmd_insert_character (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + vt_line_insert_utf8 (vt->current_line, vt->cursor_x-1, " "); + } + while (vt->current_line->string.utf8_length > vt->cols) + { vt_line_remove (vt->current_line, vt->cols); } + // XXX update style +} + +static void vtcmd_scroll_up (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + while (n--) + { vt_scroll (vt, -1); } +} + +static void vtcmd_scroll_down (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + while (n--) + { vt_scroll (vt, 1); } +} + +static void vtcmd_insert_blank_lines (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + { + int st = vt->margin_top; + int sb = vt->margin_bottom; + vt->margin_top = vt->cursor_y; + while (n--) + { + vt_scroll (vt, 1); + } + vt->margin_top = st; + vt->margin_bottom = sb; + } +} + +static void vtcmd_set_default_font (VT *vt, const char *sequence) +{ + vt->charset[0] = 0; +} + +static void vtcmd_set_alternate_font (VT *vt, const char *sequence) +{ + vt->charset[0] = 1; +} + +int _ctx_set_frame (Ctx *ctx, int frame); +int _ctx_frame (Ctx *ctx); + +static void vt_ctx_exit (void *data) +{ + VT *vt = data; + vt->state = vt_state_neutral; + vt->rev ++; + if (!vt->current_line) + return; +#if 0 + fprintf (stderr, "\n"); + if (vt->current_line->prev) + fprintf (stderr, "---prev(%i)----\n%s", (int)strlen(vt->current_line->prev),vt->current_line->prev); + fprintf (stderr, "---new(%i)----\n%s", (int)strlen(vt->current_line->frame->str),vt->current_line->frame->str); + fprintf (stderr, "--------\n"); +#endif + +#if CTX_VT_USE_FRAME_DIFF + if (vt->current_line->prev) + free (vt->current_line->prev); + vt->current_line->prev = NULL; + if (vt->current_line->frame) + { + vt->current_line->prev = vt->current_line->frame->str; + vt->current_line->prev_length = vt->current_line->frame->length; + + ctx_string_free (vt->current_line->frame, 0); + vt->current_line->frame = NULL; + } +#endif + + void *tmp = vt->current_line->ctx; + vt->current_line->ctx = vt->current_line->ctx_copy; + vt->current_line->ctx_copy = tmp; + + _ctx_set_frame (vt->current_line->ctx, _ctx_frame (vt->current_line->ctx) + 1); + _ctx_set_frame (vt->current_line->ctx_copy, _ctx_frame (vt->current_line->ctx)); +#if 1 + if (vt->ctxp) // XXX: ugly hack to aid double buffering + ((void**)vt->ctxp)[0]= vt->current_line->ctx; +#endif + + //ctx_parser_free (vt->ctxp); + //vt->ctxp = NULL; +} +#if 0 +#define CTX_x CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_y CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0) +#define CTX_lower CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0) +#define CTX_raise CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0) +#define CTX_raise_top CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0) +#define CTX_terminate CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0) +#define CTX_maximize CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0) +#define CTX_unmaximize CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0) +#define CTX_width CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0) +#define CTX_title CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0) +#define CTX_action CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0) +#define CTX_height CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0) +#endif + + +static int vt_get_prop (VT *vt, const char *key, const char **val, int *len) +{ +#if 0 + uint32_t key_hash = ctx_strhash (key, 0); + char str[4096]=""; + fprintf (stderr, "%s: %s %i\n", __FUNCTION__, key, key_hash); + CtxClient *client = ctx_client_by_id (ct->id); + if (!client) + return 0; + switch (key_hash) + { + case CTX_title: + sprintf (str, "setkey %s %s\n", key, client->title); + break; + case CTX_x: + sprintf (str, "setkey %s %i\n", key, client->x); + break; + case CTX_y: + sprintf (str, "setkey %s %i\n", key, client->y); + break; + case CTX_width: + sprintf (str, "setkey %s %i\n", key, client->width); + break; + case CTX_height: + sprintf (str, "setkey %s %i\n", key, client->width); + break; + default: + sprintf (str, "setkey %s undefined\n", key); + break; + } + if (str[0]) + { + vtpty_write ((void*)ct, str, strlen (str)); + fprintf (stderr, "%s", str); + } +#endif + return 0; +} + +static void vtcmd_set_mode (VT *vt, const char *sequence) +{ + int set = 1; + if (sequence[strlen (sequence)-1]=='l') + { set = 0; } + if (sequence[1]=='?') + { + int qval; + sequence++; +qagain: + qval = parse_int (sequence, 1); + switch (qval) + { + case 1: /*MODE;DECCKM;Cursor key mode;Application;Cursor;*/ + vt->cursor_key_application = set; + break; + case 2: /*MODE;DECANM;VT52 emulation;on;off; */ + if (set==0) + { vt->state = vt_state_vt52; } + break; + case 3: /*MODE;DECCOLM;Column mode;132 columns;80 columns;*/ + vtcmd_set_132_col (vt, set); + break; // set 132 col + case 4: /*MODE;DECSCLM;Scrolling mode;smooth;jump;*/ + vt->smooth_scroll = set; + break; // set 132 col + case 5: /*MODE;DECSCNM;Screen mode;Reverse;Normal;*/ + vt->reverse_video = set; + break; + case 6: /*MODE;DECOM;Origin mode;Relative;Absolute;*/ + vt->origin = set; + if (set) + { + _vt_move_to (vt, vt->margin_top, 1); + vt_carriage_return (vt); + } + else + { _vt_move_to (vt, 1, 1); } + break; + case 7: /*MODE;DECAWM;Autowrap;on;off;*/ + vt->autowrap = set; + break; + case 8: /*MODE;DECARM;Auto repeat;on;off;*/ + vt->keyrepeat = set; + break; + // case 9: // send mouse x & y on button press + + // 10 - Block DECEDM + // 18 - Print form feed DECPFF default off + // 19 - Print extent fullscreen DECPEX default on + case 12: + vtcmd_ignore (vt, sequence); + break; // blinking_cursor + case 25:/*MODE;DECTCEM;Cursor visible;on;off; */ + vt->cursor_visible = set; + break; + case 30: // from rxvt - show/hide scrollbar + break; + case 34: // DECRLM - right to left mode + break; + case 38: // DECTEK - enter tektronix mode + break; + case 60: // horizontal cursor coupling + case 61: // vertical cursor coupling + break; + case 69:/*MODE;DECVSSM;Left right margin mode;on;off; */ + vt->left_right_margin_mode = set; + break; + case 80:/* DECSDM Sixel scrolling */ + break; + case 437:/*MODE;;Encoding/cp437mode;cp437;utf8; */ + vt->encoding = set ? 1 : 0; + break; + case 1000:/*MODE;;Mouse reporting;on;off;*/ + vt->mouse = set; + break; + case 1001: + case 1002:/*MODE;;Mouse drag;on;off;*/ + vt->mouse_drag = set; + break; + case 1003:/*MODE;;Mouse all;on;off;*/ + vt->mouse_all = set; + break; + case 1006:/*MODE;;Mouse decimal;on;off;*/ + vt->mouse_decimal = set; + break; + case 47: + case 1047: + //case 1048: + case 1049:/*MODE;;Alt screen;on;off;*/ + if (set) + { + if (vt->in_alt_screen) + { + } + else + { + vtcmd_save_cursor (vt, ""); + vt->saved_lines = vt->lines; + vt->saved_line_count = vt->line_count; + vt->line_count = 0; + vt->lines = NULL; + for (int i = 0; i < vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_append (&vt->lines, vt->current_line); + vt->line_count++; + } + vt->in_alt_screen = 1; + vt_line_feed (vt); + _vt_move_to (vt, 1, 1); + vt_carriage_return (vt); + } + } + else + { + if (vt->in_alt_screen) + { + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + } + vt->line_count = vt->saved_line_count; + vt->lines = vt->saved_lines; + vtcmd_restore_cursor (vt, ""); + vt->saved_lines = NULL; + vt->in_alt_screen = 0; + } + else + { + } + } + break; // alt screen + case 1010: /*MODE;;scroll on output;on;off; */ //rxvt + vt->scroll_on_output = set; + break; + case 1011:/*MODE:;scroll on input;on;off; */ //rxvt) + vt->scroll_on_input = set; + break; + case 2004:/*MODE;;bracketed paste;on;off; */ + vt->bracket_paste = set; + break; + case 201:/*MODE;;ctx-events;on;off;*/ + vt->ctx_events = set; + break; + + case 200:/*MODE;;ctx vector graphics mode;on;;*/ + if (set) + { + if (!vt->current_line->ctx) + { + vt->current_line->ctx = ctx_new (); + vt->current_line->ctx_copy = ctx_new (); + ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx); + _ctx_set_transformation (vt->current_line->ctx, 0); + _ctx_set_transformation (vt->current_line->ctx_copy, 0); + + //ctx_set_texture_cache (vt->current_line->ctx, vt->current_line->ctx_copy); + //ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx); +#if CTX_VT_USE_FRAME_DIFF + vt->current_line->frame = ctx_string_new (""); +#endif + } + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + + vt->ctxp = ctx_parser_new (vt->current_line->ctx, + vt->cols * vt->cw, vt->rows * vt->ch, + vt->cw, vt->ch, vt->cursor_x, vt->cursor_y, + (void*)vt_set_prop, (void*)vt_get_prop, vt, vt_ctx_exit, vt); + vt->utf8_holding[vt->utf8_pos=0]=0; // XXX : needed? + vt->state = vt_state_ctx; + } + break; + default: + VT_warning ("unhandled CSI ? %i%s", qval, set?"h":"l"); + return; + } + if (strchr (sequence + 1, ';') ) + { + sequence = strchr (sequence + 1, ';'); + goto qagain; + } + } + else + { + int val; +again: + val = parse_int (sequence, 1); + switch (val) + { + case 1:/*GATM - transfer enhanced data */ + case 2:/*KAM - keyboard action mode */ + break; + case 3:/*CRM - control representation mode */ + /* show control chars? */ + break; + case 4:/*MODE2;IRM;Insert Mode;Insert;Replace; */ + vt->insert_mode = set; + break; + case 9: /* interlace mode */ + break; + case 12:/*MODE2;SRM;Local echo;on;off; */ + vt->echo = set; + break; + case 20:/*MODE2;LNM;Carriage Return on LF/Newline;on;off;*/ + ; + vt->cr_on_lf = set; + break; + case 21: // GRCM - whether SGR accumulates or a reset on each command + break; + case 32: // WYCRTSAVM - screen saver + break; + case 33: // WYSTCURM - steady cursor + break; + case 34: // WYULCURM - underline cursor + break; + default: + VT_warning ("unhandled CSI %ih", val); + return; + } + if (strchr (sequence, ';') && sequence[0] != ';') + { + sequence = strchr (sequence, ';'); + goto again; + } + } +} + +static void vtcmd_request_mode (VT *vt, const char *sequence) +{ + char buf[64]=""; + if (sequence[1]=='?') + { + int qval; + sequence++; + qval = parse_int (sequence, 1); + int is_set = -1; // -1 undefiend 0 reset 1 set 1000 perm_reset 1001 perm_set + switch (qval) + { + case 1: + is_set = vt->cursor_key_application; + break; + case 2: /*VT52 emulation;;enable; */ + //if (set==0) vt->in_vt52 = 1; + is_set = 1001; + break; + case 3: + is_set = 0; + break; + case 4: + is_set = vt->smooth_scroll; + break; + case 5: + is_set = vt->reverse_video; + break; + case 6: + is_set = vt->origin; + break; + case 7: + is_set = vt->autowrap; + break; + case 8: + is_set = vt->keyrepeat; + break; + case 9: // should be dynamic + is_set = 1000; + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 16: + case 18: + case 19: + is_set = 1000; + break; + case 25: + is_set = vt->cursor_visible; + break; + case 45: + is_set = 1000; + break; + case 47: + is_set = vt->in_alt_screen; + break; + case 69: + is_set = vt->left_right_margin_mode; + break; + case 437: + is_set = vt->encoding; + break; + case 1000: + is_set = vt->mouse; + break; + // 1001 hilite tracking + case 1002: + is_set = vt->mouse_drag; + break; + case 1003: + is_set = vt->mouse_all; + break; + case 1006: + is_set = vt->mouse_decimal; + break; + case 201: + is_set = vt->ctx_events; + break; + case 2004: + is_set = vt->bracket_paste; + break; + case 1010: // scroll to bottom on tty output (rxvt) + is_set = vt->scroll_on_output; + break; + case 1011: // scroll to bottom on key press (rxvt) + is_set = vt->scroll_on_input; + break; + case 1049: + is_set = vt->in_alt_screen; + break; + break; + case 200:/*ctx protocol;On;;*/ + is_set = (vt->state == vt_state_ctx); + break; + case 80:/* DECSDM Sixel scrolling */ + case 30: // from rxvt - show/hide scrollbar + case 34: // DECRLM - right to left mode + case 60: // horizontal cursor coupling + case 61: // vertical cursor coupling + default: + break; + } + switch (is_set) + { + case 0: + sprintf (buf, "\033[?%i;%i$y", qval, 2); + break; + case 1: + { sprintf (buf, "\033[?%i;%i$y", qval, 1); } + break; + case 1000: + sprintf (buf, "\033[?%i;%i$y", qval, 4); + break; + case 1001: + sprintf (buf, "\033[?%i;%i$y", qval, 3); + break; + case -1: + { sprintf (buf, "\033[?%i;%i$y", qval, 0); } + } + } + else + { + int val; + val = parse_int (sequence, 1); + switch (val) + { + case 1: + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 2:/* AM - keyboard action mode */ + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 3:/*CRM - control representation mode */ + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 4:/*Insert Mode;Insert;Replace; */ + sprintf (buf, "\033[%i;%i$y", val, vt->insert_mode?1:2); + break; + case 9: /* interlace mode */ + sprintf (buf, "\033[%i;%i$y", val, 1); + break; + case 12: + sprintf (buf, "\033[%i;%i$y", val, vt->echo?1:2); + break; + case 20:/*Carriage Return on LF/Newline;on;off;*/ + ; + sprintf (buf, "\033[%i;%i$y", val, vt->cr_on_lf?1:2); + break; + case 21: // GRCM - whether SGR accumulates or a reset on each command + default: + sprintf (buf, "\033[%i;%i$y", val, 0); + } + } + if (buf[0]) + { vt_write (vt, buf, strlen (buf) ); } +} + +static void vtcmd_set_t (VT *vt, const char *sequence) +{ + /* \e[21y is request title - allows inserting keychars */ + if (!strcmp (sequence, "[1t")) { ctx_client_unshade (vt->id); } + else if (!strcmp (sequence, "[2t")) { ctx_client_shade (vt->id); } + else if (!strncmp (sequence, "[3;", 3)) { + int x=0,y=0; + sscanf (sequence, "[3;%i;%ir", &y, &x); + ctx_client_move (vt->id, x, y); + } + else if (!strncmp (sequence, "[4;", 3)) + { + int width = 0, height = 0; + sscanf (sequence, "[4;%i;%ir", &height , &width); + if (width < 0) width = vt->cols * vt->cw; + if (height < 0) height = vt->rows * vt->ch; + if (width == 0) width = ctx_width (vt->root_ctx); + if (height == 0) height = ctx_height (vt->root_ctx); + ctx_client_resize (vt->id, width, height); + } + else if (!strcmp (sequence, "[5t") ) { ctx_client_raise_top (vt->id); } + else if (!strcmp (sequence, "[6t") ) { ctx_client_lower_bottom (vt->id); } + else if (!strcmp (sequence, "[7t") ) { vt->rev++; /* refresh */ } + else if (!strncmp (sequence, "[8;", 3) ) + { + int cols = 0, rows = 0; + sscanf (sequence, "[8;%i;%ir", &rows, &cols); + if (cols < 0) cols = vt->cols; + if (rows < 0) rows = vt->rows; + if (cols == 0) cols = ctx_width (vt->root_ctx) / vt->cw; + if (rows == 0) rows = ctx_height (vt->root_ctx) / vt->ch; + ctx_client_resize (vt->id, cols * vt->cw, rows * vt->ch); + } + else if (!strcmp (sequence, "[9;0t") ) { ctx_client_unmaximize (vt->id); } + else if (!strcmp (sequence, "[9;1t") ) { ctx_client_maximize (vt->id);} + + /* should actually be full-screen */ + else if (!strcmp (sequence, "[10;0t") ) { ctx_client_unmaximize (vt->id); } + else if (!strcmp (sequence, "[10;1t") ) { ctx_client_maximize (vt->id);} + else if (!strcmp (sequence, "[10;2t") ) { ctx_client_toggle_maximized (vt->id);} + + else if (!strcmp (sequence, "[11t") ) /* report window state */ + { + char buf[128]; + if (ctx_client_is_iconified (vt->id)) + sprintf (buf, "\033[2t"); + else + sprintf (buf, "\033[1t"); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[13t") ) /* request terminal position */ + { + char buf[128]; + sprintf (buf, "\033[3;%i;%it", ctx_client_y (vt->id), ctx_client_x (vt->id)); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[14t") ) /* request terminal dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[4;%i;%it", vt->rows * vt->ch, vt->cols * vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[15t") ) /* request root dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[5;%i;%it", ctx_height (vt->root_ctx), ctx_width(vt->root_ctx)); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[16t") ) /* request char dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[6;%i;%it", vt->ch, vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[18t") ) /* request terminal dimensions */ + { + char buf[128]; + sprintf (buf, "\033[8;%i;%it", vt->rows, vt->cols); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[19t") ) /* request root window size in char */ + { + char buf[128]; + sprintf (buf, "\033[9;%i;%it", ctx_height(vt->root_ctx)/vt->ch, ctx_width (vt->root_ctx)/vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + +#if 0 + {"[", 's', foo, VT100}, /*args:PnSP id:DECSWBV Set warning bell volume */ +#endif + else if (sequence[strlen (sequence)-2]==' ') /* DECSWBV */ + { + int val = parse_int (sequence, 0); + if (val <= 1) { vt->bell = 0; } + if (val <= 8) { vt->bell = val; } + } + else + { + // XXX: X for ints >=24 resize to that number of lines + VT_info ("unhandled subsequence %s", sequence); + } +} + +static void _vt_htab (VT *vt) +{ + do + { + vt->cursor_x ++; + } + while (vt->cursor_x < VT_MARGIN_RIGHT && ! vt->tabs[ (int) vt->cursor_x-1]); + if (vt->cursor_x > VT_MARGIN_RIGHT) + { vt->cursor_x = VT_MARGIN_RIGHT; } +} + +static void _vt_rev_htab (VT *vt) +{ + do + { + vt->cursor_x--; + } + while ( ! vt->tabs[ (int) vt->cursor_x-1] && vt->cursor_x > 1); + if (vt->cursor_x < VT_MARGIN_LEFT) + { vt_carriage_return (vt); } +} + +static void vtcmd_insert_n_tabs (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + _vt_htab (vt); + } +} + +static void vtcmd_rev_n_tabs (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + _vt_rev_htab (vt); + } +} + +static void vtcmd_set_double_width_double_height_top_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 1; + vt->current_line->double_height_bottom = 0; +} +static void vtcmd_set_double_width_double_height_bottom_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 1; +} +static void vtcmd_set_single_width_single_height_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 0; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 0; +} +static void +vtcmd_set_double_width_single_height_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 0; +} + +static void vtcmd_set_led (VT *vt, const char *sequence) +{ + int val = 0; + //fprintf (stderr, "%s\n", sequence); + for (const char *s = sequence; *s; s++) + { + switch (*s) + { + case '0': val = 0; break; + case '1': val = 1; break; + case '2': val = 2; break; + case '3': val = 3; break; + case '4': val = 4; break; + case ';': + case 'q': + if (val == 0) + { vt->leds[0] = vt->leds[1] = vt->leds[2] = vt->leds[3] = 0; } + else + { vt->leds[val-1] = 1; } + val = 0; + break; + } + } +} + +static void vtcmd_char_at_cursor (VT *vt, const char *sequence) +{ + char *buf=""; + vt_write (vt, buf, strlen (buf) ); +} + +static void vtcmd_DECELR (VT *vt, const char *sequence) +{ + int ps1 = parse_int (sequence, 0); + int ps2 = 0; + const char *s = strchr (sequence, ';'); + if (ps1) {/* unused */}; + if (s) + { ps2 = parse_int (s, 0); } + if (ps2 == 1) + { vt->unit_pixels = 1; } + else + { vt->unit_pixels = 0; } +} + +static void vtcmd_graphics (VT *vt, const char *sequence) +{ + fprintf (stderr, "gfx intro [%s]\n",sequence); // maybe implement such as well? +} + +static void vtcmd_report (VT *vt, const char *sequence) +{ + char buf[64]=""; + if (!strcmp (sequence, "[5n") ) // DSR device status report + { + sprintf (buf, "\033[0n"); // we're always OK :) + } + else if (!strcmp (sequence, "[?15n") ) // printer status + { + sprintf (buf, "\033[?13n"); // no printer + } + else if (!strcmp (sequence, "[?26n") ) // keyboard dialect + { + sprintf (buf, "\033[?27;1n"); // north american/ascii + } + else if (!strcmp (sequence, "[?25n") ) // User Defined Key status + { + sprintf (buf, "\033[?21n"); // locked + } +#if 0 + {"[6n", 0, }, /* id:DSR cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */ +#endif + else if (!strcmp (sequence, "[6n") ) // DSR cursor position report + { + sprintf (buf, "\033[%i;%iR", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) ); + } + else if (!strcmp (sequence, "[?6n") ) // DECXPR extended cursor position report + { +#if 0 + {"[?6n", 0, }, /* id:DEXCPR extended cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */ +#endif + sprintf (buf, "\033[?%i;%i;1R", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) ); + } + else if (!strcmp (sequence, "[>c") ) + { + sprintf (buf, "\033[>23;01;1c"); + } + else if (sequence[strlen (sequence)-1]=='c') // device attributes + { + //buf = "\033[?1;2c"; // what rxvt reports + //buf = "\033[?1;6c"; // VT100 with AVO ang GPO + //buf = "\033[?2c"; // VT102 + sprintf (buf, "\033[?63;14;4;22c"); + } + else if (sequence[strlen (sequence)-1]=='x') // terminal parameters + { + if (!strcmp (sequence, "[1x") ) + { sprintf (buf, "\033[3;1;1;120;120;1;0x"); } + else + { sprintf (buf, "\033[2;1;1;120;120;1;0x"); } + } + if (buf[0]) + { vt_write (vt, buf, strlen (buf) ); } +} + +static char *charmap_cp437[]= +{ + " ","☺","☻","♥","♦","♣","♠","•","◘","○","◙","♂","♀","♪","♫","☼", + "►","◄","↕","‼","¶","§","▬","↨","↑","↓","→","←","∟","↔","▲","▼", + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/", + "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", + "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", + "p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","⌂", + "Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å", + "É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","¢","£","¥","₧","ƒ", + "á","í","ó","ú","ñ","Ñ","ª","º","¿","⌐","¬","½","¼","¡","«","»", + "░","▒","▓","│","┤","╡","╢","╖","╕","╣","║","╗","╝","╜","╛","┐", + "└","┴","┬","├","─","┼","╞","╟","╚","╔","╩","╦","╠","═","╬","╧", + "╨","╤","╥","╙","╘","╒","╓","╫","╪","┘","┌","█","▄","▌","▐","▀", + "α","ß","Γ","π","Σ","σ","µ","τ","Φ","Θ","Ω","δ","∞","φ","ε","∩", + "≡","±","≥","≤","⌠","⌡","÷","≈","°","∙","·","√","ⁿ","²","■"," " +}; + + +static char *charmap_graphics[]= +{ + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "◆","▒","␉","␌","␍","␊","°","±","","␋","┘","┐","┌","└","┼","⎺","⎻", + "─","⎼","⎽","├","┤","┴","┬","│","≤","≥","π","≠","£","·"," " +}; + +static char *charmap_uk[]= +{ + " ","!","\"","£","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", + "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," " +}; + +static char *charmap_ascii[]= +{ + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", + "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," " +}; + +static void vtcmd_justify (VT *vt, const char *sequence) +{ + int n = parse_int (vt->argument_buf, 0); + switch (n) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + vt->justify = n; + break; + default: + vt->justify = 0; + } +} + +static void vtcmd_sixel_related_req (VT *vt, const char *sequence) +{ + fprintf (stderr, "it happens!\n"); +} + +static void vtcmd_set_charmap (VT *vt, const char *sequence) +{ + int slot = 0; + int set = sequence[1]; + if (sequence[0] == ')') { slot = 1; } + if (set == 'G') { set = 'B'; } + vt->charset[slot] = set; +} +#if 0 + +CSI Pm ' } ' +Insert Ps Column (s) (default = 1) (DECIC), VT420 and up. + +CSI Pm ' ~ ' +Delete Ps Column (s) (default = 1) (DECDC), VT420 and up. + + +in text. When bracketed paste mode is set, the program will receive: +ESC [ 2 0 0 ~, + followed by the pasted text, followed by + ESC [ 2 0 1 ~ . + + + CSI I + when the terminal gains focus, and CSI O when it loses focus. +#endif + +#define COMPAT_FLAG_LEVEL_0 (1<<1) +#define COMPAT_FLAG_LEVEL_1 (1<<2) +#define COMPAT_FLAG_LEVEL_2 (1<<3) +#define COMPAT_FLAG_LEVEL_3 (1<<4) +#define COMPAT_FLAG_LEVEL_4 (1<<5) +#define COMPAT_FLAG_LEVEL_5 (1<<6) + +#define COMPAT_FLAG_LEVEL_102 (1<<7) + +#define COMPAT_FLAG_ANSI (1<<8) +#define COMPAT_FLAG_OBSOLETE (1<<9) + +#define COMPAT_FLAG_ANSI_COLOR (1<<10) +#define COMPAT_FLAG_256_COLOR (1<<11) +#define COMPAT_FLAG_24_COLOR (1<<12) + +#define COMPAT_FLAG_MOUSE_REPORT (1<<13) + +#define COMPAT_FLAG_AUDIO (1<<14) +#define COMPAT_FLAG_GRAPHICS (1<<15) + +#define ANSI COMPAT_FLAG_ANSI +#define OBS COMPAT_FLAG_OBSOLETE + +#define VT100 (COMPAT_FLAG_LEVEL_0|COMPAT_FLAG_LEVEL_1) +#define VT102 (VT100|COMPAT_FLAG_LEVEL_102) +#define VT200 (VT102|COMPAT_FLAG_LEVEL_2) +#define VT300 (VT200|COMPAT_FLAG_LEVEL_3) +#define VT400 (VT300|COMPAT_FLAG_LEVEL_4) +#define VT220 VT200 +#define VT320 VT300 + +#define XTERM (VT400|COMPAT_FLAG_24_COLOR|COMPAT_FLAG_256_COLOR|COMPAT_FLAG_ANSI_COLOR) + +#define VT2020 (XTERM|COMPAT_FLAG_GRAPHICS|COMPAT_FLAG_AUDIO) + + + static Sequence sequences[]= + { + /* + prefix suffix command */ + //{"B", 0, vtcmd_break_permitted}, + //{"C", 0, vtcmd_nobreak_here}, + {"D", 0, vtcmd_index, VT100}, /* args: id:IND Index */ + {"E", 0, vtcmd_next_line}, /* ref:none id: Next line */ + {"_", 'G', vtcmd_graphics}, + {"H", 0, vtcmd_horizontal_tab_set, VT100}, /* id:HTS Horizontal Tab Set */ + + //{"I", 0, vtcmd_char_tabulation_with_justification}, + //{"K", 0, PLD partial line down + //{"L", 0, PLU partial line up + {"M", 0, vtcmd_reverse_index, VT100}, /* ref:none id:RI Reverse Index */ + //{"N", 0, vtcmd_ignore}, /* Set Single Shift 2 - SS2*/ + //{"O", 0, vtcmd_ignore}, /* Set Single Shift 3 - SS3*/ + +#if 0 + {"[0F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY disable justification and wordwrap */ // needs special link to ANSI standard + {"[1F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY enable wordwrap */ +#endif + + /* these need to occur before vtcmd_preceding_line to have precedence */ + {"[0 F", 0, vtcmd_justify, ANSI}, + {"[1 F", 0, vtcmd_justify, ANSI}, + {"[2 F", 0, vtcmd_justify}, + {"[3 F", 0, vtcmd_justify}, + {"[4 F", 0, vtcmd_justify}, + {"[5 F", 0, vtcmd_justify}, + {"[6 F", 0, vtcmd_justify}, + {"[7 F", 0, vtcmd_justify}, + {"[8 F", 0, vtcmd_justify}, +// XXX missing DECIC DECDC insert and delete column + {"[", 'A', vtcmd_cursor_up, VT100}, /* args:Pn id:CUU Cursor Up */ + {"[", 'B', vtcmd_cursor_down, VT100}, /* args:Pn id:CUD Cursor Down */ + {"[", 'C', vtcmd_cursor_forward, VT100}, /* args:Pn id:CUF Cursor Forward */ + {"[", 'D', vtcmd_cursor_backward, VT100}, /* args:Pn id:CUB Cursor Backward */ + {"[", 'j', vtcmd_cursor_backward, ANSI}, /* args:Pn ref:none id:HPB Horizontal Position Backward */ + {"[", 'k', vtcmd_cursor_up, ANSI}, /* args:Pn ref:none id:VPB Vertical Position Backward */ + {"[", 'E', vtcmd_next_line, VT100}, /* args:Pn id:CNL Cursor Next Line */ + {"[", 'F', vtcmd_cursor_preceding_line, VT100}, /* args:Pn id:CPL Cursor Preceding Line */ + {"[", 'G', vtcmd_horizontal_position_absolute}, /* args:Pn id:CHA Cursor Horizontal Absolute */ + {"[", 'H', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:CUP Cursor Position */ + {"[", 'I', vtcmd_insert_n_tabs}, /* args:Pn id:CHT Cursor Horizontal Forward Tabulation */ + {"[", 'J', vtcmd_erase_in_display, VT100}, /* args:Ps id:ED Erase in Display */ + {"[", 'K', vtcmd_erase_in_line, VT100}, /* args:Ps id:EL Erase in Line */ + {"[", 'L', vtcmd_insert_blank_lines, VT102}, /* args:Pn id:IL Insert Line */ + {"[", 'M', vtcmd_delete_n_lines, VT102}, /* args:Pn id:DL Delete Line */ + // [ N is EA - Erase in field + // [ O is EA - Erase in area + {"[", 'P', vtcmd_delete_n_chars, VT102}, /* args:Pn id:DCH Delete Character */ + // [ Q is SEE - Set editing extent + // [ R is CPR - active cursor position report + {"[?", 'S', vtcmd_sixel_related_req}, + {"[", 'S', vtcmd_scroll_up, VT100}, /* args:Pn id:SU Scroll Up */ + {"[", 'T', vtcmd_scroll_down, VT100}, /* args:Pn id:SD Scroll Down */ + {"[",/*SP*/'U', vtcmd_set_line_home, ANSI}, /* args:PnSP id=SLH Set Line Home */ + {"[",/*SP*/'V', vtcmd_set_line_limit, ANSI},/* args:PnSP id=SLL Set Line Limit */ + // [ W is cursor tabulation control + // [ Pn Y - cursor line tabulation + // + {"[", 'X', vtcmd_erase_n_chars}, /* args:Pn id:ECH Erase Character */ + {"[", 'Z', vtcmd_rev_n_tabs}, /* args:Pn id:CBT Cursor Backward Tabulation */ + {"[", '^', vtcmd_scroll_down} , /* muphry alternate from ECMA */ + {"[", '@', vtcmd_insert_character, VT102}, /* args:Pn id:ICH Insert Character */ + + {"[", 'a', vtcmd_cursor_forward, ANSI}, /* args:Pn id:HPR Horizontal Position Relative */ + {"[", 'b', vtcmd_cursor_forward, ANSI}, /* REP previous char XXX incomplete */ + {"[", 'c', vtcmd_report}, /* ref:none id:DA args:... Device Attributes */ + {"[", 'd', vtcmd_goto_row}, /* args:Pn id:VPA Vertical Position Absolute */ + {"[", 'e', vtcmd_cursor_down}, /* args:Pn id:VPR Vertical Position Relative */ + {"[", 'f', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:HVP Cursor Position */ + {"[g", 0, vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */ + {"[0g", 0, vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */ + {"[3g", 0, vtcmd_clear_all_tabs, VT100}, /* id:TBC clear all tabs */ + {"[", 'm', vtcmd_set_graphics_rendition, VT100}, /* args:Ps;Ps;.. id:SGR Select Graphics Rendition */ + {"[", 'n', vtcmd_report, VT200}, /* id:DSR args:... CPR Cursor Position Report */ + {"[", 'r', vtcmd_set_top_and_bottom_margins, VT100}, /* args:Pt;Pb id:DECSTBM Set Top and Bottom Margins */ +#if 0 + // handled by set_left_and_right_margins - in if 0 to be documented + {"[s", 0, vtcmd_save_cursor_position, VT100}, /*ref:none id:SCP Save Cursor Position */ +#endif + {"[u", 0, vtcmd_restore_cursor_position, VT100}, /*ref:none id:RCP Restore Cursor Position */ + {"[", 's', vtcmd_set_left_and_right_margins, VT400}, /* args:Pl;Pr id:DECSLRM Set Left and Right Margins */ + {"[", '`', vtcmd_horizontal_position_absolute, ANSI}, /* args:Pn id:HPA Horizontal Position Absolute */ + + {"[", 'h', vtcmd_set_mode, VT100}, /* args:Pn[;...] id:SM Set Mode */ + {"[", 'l', vtcmd_set_mode, VT100}, /* args:Pn[;...] id:RM Reset Mode */ + {"[", 't', vtcmd_set_t}, + {"[", 'q', vtcmd_set_led, VT100}, /* args:Ps id:DECLL Load LEDs */ + {"[", 'x', vtcmd_report}, /* ref:none id:DECREQTPARM */ + {"[", 'z', vtcmd_DECELR}, /* ref:none id:DECELR set locator res */ + + {"5", 0, vtcmd_char_at_cursor, VT300}, /* ref:none id:DECXMIT */ + {"6", 0, vtcmd_back_index, VT400}, /* id:DECBI Back index (hor. scroll) */ + {"7", 0, vtcmd_save_cursor, VT100}, /* id:DECSC Save Cursor */ + {"8", 0, vtcmd_restore_cursor, VT100}, /* id:DECRC Restore Cursor */ + {"9", 0, vtcmd_forward_index, VT400}, /* id:DECFI Forward index (hor. scroll)*/ + + //{"Z", 0, vtcmd_device_attributes}, + //{"%G",0, vtcmd_set_default_font}, // set_alternate_font + + + {"(0", 0, vtcmd_set_charmap}, + {"(1", 0, vtcmd_set_charmap}, + {"(2", 0, vtcmd_set_charmap}, + {"(A", 0, vtcmd_set_charmap}, + {"(B", 0, vtcmd_set_charmap}, + {")0", 0, vtcmd_set_charmap}, + {")1", 0, vtcmd_set_charmap}, + {")2", 0, vtcmd_set_charmap}, + {")A", 0, vtcmd_set_charmap}, + {")B", 0, vtcmd_set_charmap}, + {"%G", 0, vtcmd_set_charmap}, + + {"#3", 0, vtcmd_set_double_width_double_height_top_line, VT100}, /*id:DECDHL Top half of double-width, double-height line */ + {"#4", 0, vtcmd_set_double_width_double_height_bottom_line, VT100}, /*id:DECDHL Bottom half of double-width, double-height line */ + {"#5", 0, vtcmd_set_single_width_single_height_line, VT100}, /* id:DECSWL Single-width line */ + {"#6", 0, vtcmd_set_double_width_single_height_line, VT100}, /* id:DECDWL Double-width line */ + + {"#8", 0, vtcmd_screen_alignment_display, VT100}, /* id:DECALN Screen Alignment Pattern */ + {"=", 0, vtcmd_ignore}, // keypad mode change + {">", 0, vtcmd_ignore}, // keypad mode change + {"c", 0, vtcmd_reset_to_initial_state, VT100}, /* id:RIS Reset to Initial State */ + {"[!", 'p', vtcmd_ignore}, // soft reset? + {"[", 'p', vtcmd_request_mode}, /* args:Pa$ id:DECRQM Request ANSI Mode */ +#if 0 + {"[?", 'p', vtcmd_request_mode}, /* args:Pd$ id:DECRQM Request DEC Mode */ +#endif + + {NULL, 0, NULL} + }; + + static void handle_sequence (VT *vt, const char *sequence) +{ + int i0 = strlen (sequence)-1; + int i; + vt->rev ++; + for (i = 0; sequences[i].prefix; i++) + { + if (!strncmp (sequence, sequences[i].prefix, strlen (sequences[i].prefix) ) ) + { + if (! (sequences[i].suffix && (sequence[i0] != sequences[i].suffix) ) ) + { + VT_command ("%s", sequence); + sequences[i].vtcmd (vt, sequence); + return; + } + } + } +#ifndef ASANBUILD + VT_warning ("unhandled: %c%c%c%c%c%c%c%c%c\n", sequence[0], sequence[1], sequence[2], sequence[3], sequence[4], sequence[5], sequence[6], sequence[7], sequence[8]); +#endif +} + +static void vt_line_feed (VT *vt) +{ + int was_home = vt->at_line_home; + if (vt->margin_top == 1 && vt->margin_bottom == vt->rows) + { + if (vt->lines == NULL || + (vt->lines->data == vt->current_line && vt->cursor_y != vt->rows) ) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } + if (vt->cursor_y >= vt->margin_bottom) + { + vt->cursor_y = vt->margin_bottom; + vt_scroll (vt, -1); + } + else + { + vt->cursor_y++; + } + } + else + { + if (vt->lines->data == vt->current_line && + (vt->cursor_y != vt->margin_bottom) && 0) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } + vt->cursor_y++; + if (vt->cursor_y > vt->margin_bottom) + { + vt->cursor_y = vt->margin_bottom; + vt_scroll (vt, -1); + } + } + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + if (vt->cr_on_lf) + { vt_carriage_return (vt); } + vt_trimlines (vt, vt->rows); + if (was_home) + { vt_carriage_return (vt); } +} + +//#include "vt-encodings.h" + +#ifndef NO_SDL +static void vt_state_apc_audio (VT *vt, int byte) +{ + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt_audio (vt, vt->argument_buf); + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + } +} + +#else + +void vt_audio_task (VT *vt, int click) +{ +} + +void vt_bell (VT *vt) +{ +} +static void vt_state_apc_audio (VT *vt, int byte) +{ + vt->state = vt_state_apc_generic; +} + +#endif + +static void +vt_carriage_return (VT *vt) +{ + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->cursor_x = VT_MARGIN_LEFT; + vt->at_line_home = 1; +} + +/* if the byte is a non-print control character, handle it and return 1 + * oterhwise return 0*/ +static int _vt_handle_control (VT *vt, int byte) +{ + /* the big difference between ANSI-BBS mode and VT100+ mode is that + * most C0 characters are printable + */ + if (CTX_UNLIKELY(vt->encoding == 1)) // this codepage is for ansi-bbs use + switch (byte) + { + case '\0': + return 1; + case 1: /* SOH start of heading */ + case 2: /* STX start of text */ + case 3: /* ETX end of text */ + case 4: /* EOT end of transmission */ + case 5: /* ENQuiry */ + case 6: /* ACKnolwedge */ + case '\v': /* VT vertical tab */ + case '\f': /* VF form feed */ + case 14: /* SO shift in - alternate charset */ + case 15: /* SI shift out - (back to normal) */ + case 16: /* DLE data link escape */ + case 17: /* DC1 device control 1 - XON */ + case 18: /* DC2 device control 2 */ + case 19: /* DC3 device control 3 - XOFF */ + case 20: /* DC4 device control 4 */ + case 21: /* NAK negative ack */ + case 22: /* SYNchronous idle */ + case 23: /* ETB end of trans. blk */ + case 24: /* CANcel (vt100 aborts sequence) */ + case 25: /* EM end of medium */ + case 26: /* SUB stitute */ + case 28: /* FS file separator */ + case 29: /* GS group separator */ + case 30: /* RS record separator */ + case 31: /* US unit separator */ + _vt_add_str (vt, charmap_cp437[byte]); + return 1; + } + switch (byte) + { + case '\0': + case 1: /* SOH start of heading */ + case 2: /* STX start of text */ + case 3: /* ETX end of text */ + case 4: /* EOT end of transmission */ + case 6: /* ACKnolwedge */ + return 1; + case 5: /* ENQuiry */ + { + const char *reply = getenv ("TERM_ENQ_REPLY"); + if (reply) + { + char *copy = strdup (reply); + for (uint8_t *c = (uint8_t *) copy; *c; c++) + { + if (*c < ' ' || * c > 127) { *c = 0; } + } + vt_write (vt, reply, strlen (reply) ); + free (copy); + } + } + return 1; + case '\a': /* BELl */ + vt_bell (vt); + return 1; + case '\b': /* BS */ + _vt_backspace (vt); + return 1; + case '\t': /* HT tab */ + _vt_htab (vt); + return 1; + case '\v': /* VT vertical tab */ + case '\f': /* VF form feed */ + case '\n': /* LF line ffed */ + vt_line_feed (vt); + return 1; + case '\r': /* CR carriage return */ + vt_carriage_return (vt); + return 1; + case 14: /* SO shift in - alternate charset */ + vt->shifted_in = 1; // XXX not in vt52 + return 1; + case 15: /* SI shift out - (back to normal) */ + vt->shifted_in = 0; + return 1; + case 16: /* DLE data link escape */ + case 17: /* DC1 device control 1 - XON */ + case 18: /* DC2 device control 2 */ + case 19: /* DC3 device control 3 - XOFF */ + case 20: /* DC4 device control 4 */ + case 21: /* NAK negative ack */ + case 22: /* SYNchronous idle */ + case 23: /* ETB end of trans. blk */ + case 24: /* CANcel (vt100 aborts sequence) */ + case 25: /* EM end of medium */ + case 26: /* SUB stitute */ + _vt_add_str (vt, "¿"); // in vt52? XXX + return 1; + case 27: /* ESCape */ + return 0; + break; + case 28: /* FS file separator */ + case 29: /* GS group separator */ + case 30: /* RS record separator */ + case 31: /* US unit separator */ + case 127: /* DEL */ + return 1; + default: + return 0; + } +} + +void vt_open_log (VT *vt, const char *path) +{ + unlink (path); + vt->log = fopen (path, "w"); +} + +/* the function shared by sixels, kitty mode and iterm2 mode for + * doing inline images. it attaches an image to the current line + */ +static void display_image (VT *vt, Image *image, + int col, + float xoffset, + float yoffset, + int rows, + int cols, + int subx, + int suby, + int subw, + int subh + ) +{ + int i = 0; + for (i = 0; vt->current_line->images[i] && i < 4; i++) + { + if (vt->current_line->image_col[i] == vt->cursor_x) + break; + } + //for (i = 0; vt->current_line->images[i] && i < 4; i++); + if (i >= 4) { i = 3; } + /* this needs a struct and dynamic allocation */ + vt->current_line->images[i] = image; + vt->current_line->image_col[i] = vt->cursor_x; + vt->current_line->image_X[i] = xoffset; + vt->current_line->image_Y[i] = yoffset; + vt->current_line->image_subx[i] = subx; + vt->current_line->image_suby[i] = suby; + vt->current_line->image_subw[i] = subw; + vt->current_line->image_subh[i] = subh; + vt->current_line->image_rows[i] = rows; + vt->current_line->image_cols[i] = cols; +} + +static int vt_gfx_pending=0; + +void vt_gfx (VT *vt, const char *command) +{ + const char *payload = NULL; + char key = 0; + int value; + int pos = 1; + if (vt->gfx.multichunk == 0) + { + memset (&vt->gfx, 0, sizeof (GfxState) ); + vt->gfx.action='t'; + vt->gfx.transmission='d'; + } + while (command[pos] != ';') + { + pos ++; // G or , + if (command[pos] == ';') { break; } + key = command[pos]; + pos++; + if (command[pos] == ';') { break; } + pos ++; // = + if (command[pos] == ';') { break; } + if (command[pos] >= '0' && command[pos] <= '9') + { value = atoi (&command[pos]); } + else + { value = command[pos]; } + while (command[pos] && + command[pos] != ',' && + command[pos] != ';') { pos++; } + switch (key) + { + case 'a': + vt->gfx.action = value; + break; + case 'd': + vt->gfx.delete = value; + break; + case 'i': + vt->gfx.id = value; + break; + case 'S': + vt->gfx.buf_size = value; + break; + case 's': + vt->gfx.buf_width = value; + break; + case 'v': + vt->gfx.buf_height = value; + break; + case 'f': + vt->gfx.format = value; + break; + case 'm': + vt->gfx.multichunk = value; + break; + case 'o': + vt->gfx.compression = value; + break; + case 't': + vt->gfx.transmission = value; + break; + case 'x': + vt->gfx.x = value; + break; + case 'y': + vt->gfx.y = value; + break; + case 'w': + vt->gfx.w = value; + break; + case 'h': + vt->gfx.h = value; + break; + case 'X': + vt->gfx.x_cell_offset = value; + break; + case 'Y': + vt->gfx.y_cell_offset = value; + break; + case 'c': + vt->gfx.columns = value; + break; + case 'r': + vt->gfx.rows = value; + break; + case 'z': + vt->gfx.z_index = value; + break; + } + } + payload = &command[pos+1]; + { + int chunk_size = strlen (payload); + int old_size = vt->gfx.data_size; + // accumulate incoming data + if (vt->gfx.data == NULL) + { + vt->gfx.data_size = chunk_size; + vt->gfx.data = malloc (vt->gfx.data_size + 1); + } + else + { + vt->gfx.data_size += chunk_size; + vt->gfx.data = realloc (vt->gfx.data, vt->gfx.data_size + 1); + } + memcpy (vt->gfx.data + old_size, payload, chunk_size); + vt->gfx.data[vt->gfx.data_size]=0; + } + if (vt->gfx.multichunk == 0) + { + if (vt->gfx.transmission != 'd') /* */ + { + char buf[256]; + sprintf (buf, "\033_Gi=%i;only direct transmission supported\033\\", + vt->gfx.id); + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + { + int bin_length = vt->gfx.data_size; + uint8_t *data2 = malloc (vt->gfx.data_size); + bin_length = ctx_base642bin ( (char *) vt->gfx.data, + &bin_length, + data2); + memcpy (vt->gfx.data, data2, bin_length + 1); + vt->gfx.data_size = bin_length; + free (data2); + } + if (vt->gfx.buf_width) + { + // implicit buf_size + vt->gfx.buf_size = vt->gfx.buf_width * vt->gfx.buf_height * + (vt->gfx.format == 24 ? 3 : 4); + } + if (vt->gfx.compression == 'z') + { + //vt->gfx.buf_size) + unsigned char *data2 = malloc (vt->gfx.buf_size + 1); + /* if a buf size is set (rather compression, but + * this works first..) then */ + unsigned long actual_uncompressed_size = vt->gfx.buf_size; + int z_result = uncompress (data2, &actual_uncompressed_size, + vt->gfx.data, + vt->gfx.data_size); + if (z_result != Z_OK) + { + char buf[256]= "\033_Go=z;zlib error\033\\"; + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + free (vt->gfx.data); + vt->gfx.data = data2; + vt->gfx.data_size = actual_uncompressed_size; + vt->gfx.compression = 0; + } + if (vt->gfx.format == 100) + { + int channels; + uint8_t *new_data = stbi_load_from_memory (vt->gfx.data, vt->gfx.data_size, &vt->gfx.buf_width, &vt->gfx.buf_height, &channels, 4); + if (!new_data) + { + char buf[256]= "\033_Gf=100;image decode error\033\\"; + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + vt->gfx.format = 32; + free (vt->gfx.data); + vt->gfx.data = new_data; + vt->gfx.data_size= vt->gfx.buf_width * vt->gfx.buf_height * 4; + } + Image *image = NULL; + switch (vt->gfx.action) + { + case 't': // transfer + case 'T': // transfer and present + switch (vt->gfx.format) + { + case 24: + case 32: + image = image_add (vt->gfx.buf_width, vt->gfx.buf_height, vt->gfx.id, + vt->gfx.format, vt->gfx.data_size, vt->gfx.data); + vt->gfx.data = NULL; + vt->gfx.data_size=0; + break; + } + if (vt->gfx.action == 't') + { break; } + // fallthrough + case 'p': // present + if (!image && vt->gfx.id) + { image = image_query (vt->gfx.id); } + if (image) + { + display_image (vt, image, vt->cursor_x, vt->gfx.rows, vt->gfx.columns, + vt->gfx.x_cell_offset * 1.0 / vt->cw, + vt->gfx.y_cell_offset * 1.0 / vt->ch, + vt->gfx.x, + vt->gfx.y, + vt->gfx.w, + vt->gfx.h); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + } + break; + case 'q': // query + if (image_query (vt->gfx.id) ) + { + char buf[256]; + sprintf (buf, "\033_Gi=%i;OK\033\\", vt->gfx.id); + vt_write (vt, buf, strlen (buf) ); + } + break; + case 'd': // delete + { + int row = vt->rows; // probably not right at start of session XXX + for (CtxList *l = vt->lines; l; l = l->next, row --) + { + VtLine *line = l->data; + for (int i = 0; i < 4; i ++) + { + int free_resource = 0; + int match = 0; + if (line->images[i]) + switch (vt->gfx.delete) + { + case 'A': + free_resource = 1; + /* FALLTHROUGH */ + case 'a': /* all images visible on screen */ + match = 1; + break; + case 'I': + free_resource = 1; + /* FALLTHROUGH */ + case 'i': /* all images with specified id */ + if ( ( (Image *) (line->images[i]) )->id == vt->gfx.id) + { match = 1; } + break; + case 'P': + free_resource = 1; + /* FALLTHROUGH */ + case 'p': /* all images intersecting cell + specified with x and y */ + if (line->image_col[i] == vt->gfx.x && + row == vt->gfx.y) + { match = 1; } + break; + case 'Q': + free_resource = 1; + /* FALLTHROUGH */ + case 'q': /* all images with specified cell (x), row(y) and z */ + if (line->image_col[i] == vt->gfx.x && + row == vt->gfx.y) + { match = 1; } + break; + case 'Y': + free_resource = 1; + /* FALLTHROUGH */ + case 'y': /* all images with specified row (y) */ + if (row == vt->gfx.y) + { match = 1; } + break; + case 'X': + free_resource = 1; + /* FALLTHROUGH */ + case 'x': /* all images with specified column (x) */ + if (line->image_col[i] == vt->gfx.x) + { match = 1; } + break; + case 'Z': + free_resource = 1; + /* FALLTHROUGH */ + case 'z': /* all images with specified z-index (z) */ + break; + } + if (match) + { + line->images[i] = NULL; + if (free_resource) + { + // XXX : NYI + } + } + } + } + } + break; + } +cleanup: + if (vt->gfx.data) + { free (vt->gfx.data); } + vt->gfx.data = NULL; + vt->gfx.data_size=0; + vt->gfx.multichunk=0; + vt_gfx_pending = 0; + } + else + vt_gfx_pending = 1; +} + +static void vt_state_vt52 (VT *vt, int byte) +{ + /* in vt52 mode, utf8_pos being non 0 means we got ESC prior */ + switch (vt->utf8_pos) + { + case 0: + if (_vt_handle_control (vt, byte) == 0) + switch (byte) + { + case 27: /* ESC */ + vt->utf8_pos = 1; + break; + default: + { + char str[2] = {byte & 127, 0}; + /* we're not validating utf8, and our utf8 manipulation + * functions are not robust against malformed utf8, + * hence we strip to ascii + */ + _vt_add_str (vt, str); + } + break; + } + break; + case 1: + vt->utf8_pos = 0; + switch (byte) + { + case 'A': + vtcmd_cursor_up (vt, " "); + break; + case 'B': + vtcmd_cursor_down (vt, " "); + break; + case 'C': + vtcmd_cursor_forward (vt, " "); + break; + case 'D': + vtcmd_cursor_backward (vt, " "); + break; + case 'F': + vtcmd_set_alternate_font (vt, " "); + break; + case 'G': + vtcmd_set_default_font (vt, " "); + break; + case 'H': + _vt_move_to (vt, 1, 1); + break; + case 'I': + vtcmd_reverse_index (vt, " "); + break; + case 'J': + vtcmd_erase_in_display (vt, "[0J"); + break; + case 'K': + vtcmd_erase_in_line (vt, "[0K"); + break; + case 'Y': + vt->utf8_pos = 2; + break; + case 'Z': + vt_write (vt, "\033/Z", 3); + break; + case '<': + vt->state = vt_state_neutral; + break; + default: + break; + } + break; + case 2: + _vt_move_to (vt, byte - 31, vt->cursor_x); + vt->utf8_pos = 3; + break; + case 3: + _vt_move_to (vt, vt->cursor_y, byte - 31); + vt->utf8_pos = 0; + break; + } +} + +static void vt_sixels (VT *vt, const char *sixels) +{ + uint8_t colors[256][3]; + int width = 0; + int height = 0; + int x = 0; + int y = 0; + Image *image; + uint8_t *pixels = NULL; + int repeat = 1; + const char *p = sixels; + int pal_no = 0; +#if 0 + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) ); + // should be 0 + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) ); + // if 1 then transparency is enabled - otherwise use bg color + for (; *p && *p != 'q'; p++); +#endif + //for (; *p && *p != '"'; p++); + while (*p && *p != 'q') { p++; } + if (*p == 'q') { p++; } + if (*p == '"') { p++; } + //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p)); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p)); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + width = atoi (p); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + height = atoi (p); + if (width * height > 2048 * 2048) + return; + if (width <= 0 || height <=0) + { + width = 0; + height = 0; + // XXX : a copy paste dry-run + for (const char *t=p; *t; t++) + { + if (*t == '#') + { + t++; + while (*t && *t >= '0' && *t <= '9') { t++; } + if (*t == ';') + { + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + while (*t && *t >= '0' && *t <= '9') { t++; } + t--; + } + else + { + t--; + } + } + else if (*t == '$') // carriage return + { + if (x > width) { width = x; } + x = 0; + } + else if (*t == '-') // line feed + { + y += 6; + x = 0; + } + else if (*t == '!') // repeat + { + t++; + repeat = atoi (t); + while (*t && *t >= '0' && *t <= '9') { t++; } + t--; + } + else if (*t >= '?' && *t <= '~') /* sixel data */ + { + x += repeat; + repeat = 1; + } + } + height = y; + } + x = 0; + y = 0; + pixels = calloc (width * (height + 6), 4); + image = image_add (width, height, 0, + 32, width*height*4, pixels); + uint8_t *dst = pixels; + for (; *p; p++) + { + if (*p == '#') + { + p++; + pal_no = atoi (p); + if (pal_no < 0 || pal_no > 255) { pal_no = 255; } + while (*p && *p >= '0' && *p <= '9') { p++; } + if (*p == ';') + { + /* define a palette */ + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + // color_model , 2 is rgb + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][0] = atoi (p) * 255 / 100; + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][1] = atoi (p) * 255 / 100; + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][2] = atoi (p) * 255 / 100; + while (*p && *p >= '0' && *p <= '9') { p++; } + p--; + } + else + { + p--; + } + } + else if (*p == '$') // carriage return + { + x = 0; + dst = &pixels[ (4 * width * y)]; + } + else if (*p == '-') // line feed + { + y += 6; + x = 0; + dst = &pixels[ (4 * width * y)]; + } + else if (*p == '!') // repeat + { + p++; + repeat = atoi (p); + while (*p && *p >= '0' && *p <= '9') { p++; } + p--; + } + else if (*p >= '?' && *p <= '~') /* sixel data */ + { + int sixel = (*p) - '?'; + if (x + repeat <= width && y < height) + { + for (int bit = 0; bit < 6; bit ++) + { + if (sixel & (1 << bit) ) + { + for (int u = 0; u < repeat; u++) + { + for (int c = 0; c < 3; c++) + { + dst[ (bit * width * 4) + u * 4 + c] = colors[pal_no][c]; + dst[ (bit * width * 4) + u * 4 + 3] = 255; + } + } + } + } + } + x += repeat; + dst += (repeat * 4); + repeat = 1; + } + } + if (image) + { + display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + vt_line_feed (vt); + vt_carriage_return (vt); + } + vt->rev++; +} + +static inline void vt_ctx_unrled (VT *vt, char byte) +{ +#if CTX_VT_USE_FRAMEDIFF + ctx_string_append_byte (vt->current_line->frame, byte); +#endif + ctx_parser_feed_byte (vt->ctxp, byte); +} + +static void vt_state_ctx (VT *vt, int byte) +{ +#if 0 + //fprintf (stderr, "%c", byte); + if (CTX_UNLIKELY(byte == CTX_CODEC_CHAR)) + { + if (CTX_UNLIKELY(vt->in_prev_match)) + { + char *prev = vt->current_line->prev; + int prev_length = vt->current_line->prev_length; + int start = atoi (vt->reference); + int len = 0; + if (strchr (vt->reference, ' ')) + len = atoi (strchr (vt->reference, ' ')+1); + + //fprintf (stderr, "%i-%i:", start, len); + + if (start < 0) start = 0; + if (start >= (prev_length))start = prev_length-1; + if (len + start > prev_length) + len = prev_length - start; + + //fprintf (stderr, "%i-%i\n", start, len); + + if (CTX_UNLIKELY (start == 0 && len == 0)) + { + vt_ctx_unrled (vt, CTX_CODEC_CHAR); + } + else + { + if (prev) + for (int i = 0; i < len && start + i < prev_length; i++) + { + vt_ctx_unrled (vt, prev[start + i]); + } + } + vt->ref_len = 0; + vt->reference[0]=0; + vt->in_prev_match = 0; + } + else + { + vt->reference[0]=0; + vt->ref_len = 0; + vt->in_prev_match = 1; + } + } + else + { + if (CTX_UNLIKELY(vt->in_prev_match)) + { + if (vt->ref_len < 15) + { + vt->reference[vt->ref_len++]=byte; + vt->reference[vt->ref_len]=0; + } + } + else + { + vt_ctx_unrled (vt, byte); + } + } +#else + vt_ctx_unrled (vt, byte); +#endif +} + +static int vt_decoder_feed (VT *vt, int byte) +{ + int encoding = vt->encoding; + switch (encoding) + { + case 0: /* utf8 */ + if (!vt->utf8_expected_bytes) + { + vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1; + vt->utf8_pos = 0; + } + if (vt->utf8_expected_bytes) + { + vt->utf8_holding[vt->utf8_pos++] = byte; + vt->utf8_holding[vt->utf8_pos] = 0; + if (vt->utf8_pos == vt->utf8_expected_bytes + 1) + { + vt->utf8_expected_bytes = 0; + vt->utf8_pos = 0; + } + else + { + return 1; + } + } + else + { + vt->utf8_holding[0] = byte; + vt->utf8_holding[0] &= 127; + vt->utf8_holding[1] = 0; + if (vt->utf8_holding[0] == 0) + { vt->utf8_holding[0] = 32; } + } + break; + case 1: + if ( ! (byte>=0 && byte < 256) ) + { byte = 255; } + strcpy ( (char *) &vt->utf8_holding[0], &charmap_cp437[byte][0]); + vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1; // ? + break; + default: + vt->utf8_holding[0] = byte & 127; + vt->utf8_holding[1] = 0; + break; + } + return 0; +} + +static void vt_state_swallow (VT *vt, int byte) +{ + vt->state = vt_state_neutral; +} + +static int vt_decode_hex_digit (char digit) +{ + if (digit >= '0' && digit <='9') + return digit - '0'; + if (digit >= 'a' && digit <='f') + return digit - 'a' + 10; + if (digit >= 'A' && digit <='F') + return digit - 'A' + 10; + return 0; +} + +static int vt_decode_hex (const char *two_digits) +{ + return vt_decode_hex_digit (two_digits[0]) * 16 + + vt_decode_hex_digit (two_digits[1]); +} + +static uint8_t palettes[][16][3]= +{ + { +{0, 0, 0}, +{160, 41, 41}, +{74, 160, 139}, +{135, 132, 83}, +{36, 36, 237}, +{171, 74, 223}, +{59, 107, 177}, +{195, 195, 195}, +{111, 111, 111}, +{237, 172, 130}, +{153, 237, 186}, +{233, 216, 8}, +{130, 180, 237}, +{214, 111, 237}, +{29, 225, 237}, +{255, 255, 255}, + + }, + + { + {0, 0, 0}, + {127, 0, 0}, + {90, 209, 88}, + {136, 109, 0}, + {3, 9, 235}, + {90, 4, 150}, + {43, 111, 150}, + {178, 178, 178}, + {87, 87, 87}, + {193, 122, 99}, + {110, 254, 174}, + {255, 200, 0}, + {10, 126, 254}, + {146, 155, 249}, + {184, 208, 254}, + {255, 255, 255}, + + },{ + {0, 0, 0}, + {147, 53, 38}, + {30, 171, 82}, + {188, 153, 0}, + {32, 71, 193}, + {236, 49, 188}, + {42, 182, 253}, + {149, 149, 149}, + {73, 73, 73}, + {210, 36, 0}, + {96, 239, 97}, + {247, 240, 2}, + {93, 11, 249}, + {222, 42, 255}, + {11, 227, 255}, + {233, 235, 235}, + }, + + + { {0, 0, 0},{97, 27, 0},{129, 180, 0},{127, 100, 0},{44, 15, 255},{135, 10, 167},{20, 133, 164},{174, 174, 174},{71, 71, 71},{167, 114, 90},{162, 214, 127},{255, 251, 83},{118, 77, 253},{192, 121, 255},{14, 217, 255},{255, 255, 255}, + },{ + + +#if 0 + { + {0, 0, 0}, + {144, 0, 0}, + {9, 154, 9}, + {255, 137, 113}, + {3, 0, 255}, + {56, 0, 132}, + {0, 131, 131}, + {204, 204, 204}, + {127, 127, 127}, + {255, 33, 0}, + {113, 255, 88}, + {255, 236, 8}, + {1, 122, 255}, + {235, 0, 222}, + {0, 217, 255}, + {255, 255, 255}, + },{ +#endif + + + {0, 0, 0}, + {139, 0, 0}, + {9, 154, 9}, + {255, 137, 113}, + {3, 0, 255}, + {56, 0, 132}, + {0, 111, 111}, + {204, 204, 204}, + {127, 127, 127}, + {255, 33, 0}, + {118, 255, 92}, + {255, 230, 15}, + {1, 122, 255}, + {232, 0, 220}, + {1, 217, 255}, + {255, 255, 255}, + }, + { + + {0, 0, 0}, + {191, 0, 0}, + {3, 187, 0}, + {254, 212, 0}, + {0, 0, 255}, + {80, 0, 128}, + {0, 156, 255}, + {166, 166, 166}, + {84, 84, 84}, + {255, 62, 0}, + {85, 255, 143}, + {255, 255, 0}, + {67, 80, 255}, + {243, 70, 255}, + {30, 255, 222}, + {255, 255, 255}, + }, + { + /* */ + { 32, 32, 32}, // 0 - background (black) + {165, 15, 21}, // 1 red + { 95,130, 10}, // 2 green + {205,145, 60}, // 3 yellow + { 49,130,189}, // 4 blue + {120, 40,160}, // 5 magenta + {120,230,230}, // 6 cyan + {196,196,196},// 7 light-gray + { 85, 85, 85},// 8 dark gray + + {251,106, 74},// 9 light red + {130,215,140},// 10 light green + {255,255, 0},// 11 light yellow + {107,174,214},// 12 light blue + {215,130,160},// 13 light magenta + {225,255,245},// 14 light cyan + {255,255,255},// 15 - foreground (white) + },{ + /* */ + { 32, 32, 32}, // 0 - background (black) + {160, 0, 0}, // 1 red + { 9,233, 0}, // 2 green + {220,110, 44}, // 3 yellow + { 0, 0,200}, // 4 blue + { 90, 0,130}, // 5 magenta + { 0,156,180}, // 6 cyan + {196,196,196}, // 7 light-gray + { 85, 85, 85}, // 8 dark gray + + {240, 60, 40}, // 9 light red + {170,240, 80}, // 10 light green + {248,248, 0}, // 11 light yellow + { 0, 40,255}, // 12 light blue + {204, 62,214}, // 13 light magenta + { 10,234,254}, // 14 light cyan + {255,255,255}, // 15 - foreground (white) + }, + /* inspired by DEC */ + { { 0, 0, 0}, // 0 - background black + {150, 10, 10}, // 1 red + { 21,133, 0}, // 2 green + + {103,103, 24}, // 3 yellow + { 44, 44,153}, // 4 blue + {123, 94,183}, // 5 magenta + + { 20,183,193}, // 6 cyan + + {177,177,177},// 7 light-gray + {100,100,100},// 8 dark gray + + {244, 39, 39},// 9 light red + { 61,224, 81},// 10 light green + {255,255, 0},// 11 light yellow + { 61, 61,244},// 12 light blue + {240, 11,240},// 13 light magenta + { 61,234,234},// 14 light cyan + + {255,255,255},// 15 - foreground white + }, +}; + +static void vt_state_osc (VT *vt, int byte) +{ + // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html + // and in "\033\" rather than just "\033", this would cause + // a stray char + //if (byte == '\a' || byte == 27 || byte == 0 || byte < 32) + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + int n = parse_int (vt->argument_buf, 0); + switch (n) + { + case 0: + case 1: + case 2: +#if 0 + {"]0;New_title\e\", 0, , }, /* id: set window title */ " +#endif + vt_set_title (vt, vt->argument_buf + 3); + break; + case 4: // set palette entry + { + int color_no = parse_int (vt->argument_buf + 2, 0); + char *rest = vt->argument_buf + 3; + rest = strchr (rest, ';'); + + if (rest++) + if (strlen(rest)>10 && + rest[0] == 'r' && + rest[1] == 'g' && + rest[2] == 'b' && + rest[3] == ':' && + rest[6] == '/' && + rest[9] == '/') + { + int red = vt_decode_hex (&rest[4]); + int green = vt_decode_hex (&rest[7]); + int blue = vt_decode_hex (&rest[10]); + // fprintf (stderr, "set color:%i %i %i %i\n", color_no, red, green, blue); + if (color_no >= 0 && color_no <= 15) + { + palettes[0][color_no][0]=red; + palettes[0][color_no][1]=green; + palettes[0][color_no][2]=blue; + } + } + } + break; + case 12: // text cursor color + break; + case 17: // highlight color + break; + case 19: // ?? + break; + + case 10: // text fg +#if 0 +#if 0 + {"]11;", 0, , }, /* id: set foreground color */ +#endif + { + /* request current foreground color, xterm does this to + determine if it can use 256 colors, when this test fails, + it still mixes in color 130 together with stock colors + */ + char buf[128]; + sprintf (buf, "\033]10;rgb:%2x/%2x/%2x\033\\", + vt->fg_color[0], vt->fg_color[1], vt->fg_color[2]); + vt_write (vt, buf, strlen (buf) ); + } +#endif + break; + case 11: // text bg +#if 0 + {"]11;", 0, , }, /* id: get background color */ + { + /* get background color */ + char buf[128]; + sprintf (buf, "\033]11;rgb:%2x/%2x/%2x\033\\", + vt->bg_color[0], vt->bg_color[1], vt->bg_color[2]); + vt_write (vt, buf, strlen (buf) ); + } +#endif + break; +#if 0 + {"]1337;key=value:base64data\b\", 0, vtcmd_erase_in_line, VT100}, /* args:keyvalue id: iterm2 graphics */ " +#endif + case 1337: + if (!strncmp (&vt->argument_buf[6], "File=", 5) ) + { + { + /* iTerm2 image protocol */ + int width = 0; + int height = 0; + int file_size = 0; + int show_inline = 0; + int preserve_aspect = 1; + char *name = NULL; + char *p = &vt->argument_buf[11]; + char key[128]=""; + char value[128]=""; + int in_key=1; + if (preserve_aspect) {}; /* XXX : NYI */ + for (; *p && *p!=':'; p++) + { + if (in_key) + { + if (*p == '=') + { in_key = 0; } + else + { + if (strlen (key) < 124) + { + key[strlen (key)+1] = 0; + key[strlen (key)] = *p; + } + } + } + else + { + if (*p == ';') + { + if (!strcmp (key, "name") ) + { + name = strdup (value); + } + else if (!strcmp (key, "width") ) + { + width = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + width = width / 100.0 * (vt->cw * vt->cols); + } + else + { /* chars */ width = width * vt->cw; } + } + else if (!strcmp (key, "height") ) + { + height = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + height = height / 100.0 * (vt->ch * vt->rows); + } + else + { /* chars */ height = height * vt->ch; } + } + else if (!strcmp (key, "preserveAspectRatio") ) + { + preserve_aspect = atoi (value); + } + else if (!strcmp (key, "inline") ) + { + show_inline = atoi (value); + } + key[0]=0; + value[0]=0; + in_key = 1; + } + else + { + if (strlen (value) < 124) + { + value[strlen (value)+1] = 0; + value[strlen (value)] = *p; + } + } + } + } + if (key[0]) + { + // code-dup + if (!strcmp (key, "name") ) + { + name = strdup (value); + } + else if (!strcmp (key, "width") ) + { + width = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + width = width / 100.0 * (vt->cw * vt->cols); + } + else + { /* chars */ width = width * vt->cw; } + } + else if (!strcmp (key, "height") ) + { + height = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + height = height / 100.0 * (vt->ch * vt->rows); + } + else + { /* chars */ height = height * vt->ch; } + } + else if (!strcmp (key, "preserveAspectRatio") ) + { + preserve_aspect = atoi (value); + } + else if (!strcmp (key, "inline") ) + { + show_inline = atoi (value); + } + } + if (*p == ':') + { + p++; + } + if (0) + fprintf (stderr, "%s %i %i %i %i{%s\n", name?name:"", + width, height, file_size, show_inline, + p); + Image *image = NULL; + { + int bin_length = vt->argument_buf_len; + uint8_t *data2 = malloc (bin_length); + bin_length = ctx_base642bin ( (char *) p, + &bin_length, + data2); + int channels = 4; + int buf_width = 0; + int buf_height = 0; + uint8_t *new_data = stbi_load_from_memory (data2, bin_length, &buf_width, &buf_height, &channels, 4); + free (data2); + if (new_data) + { + image = image_add (buf_width, buf_height, 0, + 32, buf_width*buf_height*4, new_data); + } + else + { + fprintf (stderr, "image decoding problem %s\n", stbi_failure_reason()); + fprintf (stderr, "len: %i\n", bin_length); + } + } + if (image) + { + display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + } + } + } + break; + case 104: + break; + case 8: + fprintf (stderr, "unhandled OSC 8, hyperlink\n"); + break; + default: + fprintf (stderr, "unhandled OSC %i\n", n); + break; + } + if (byte == 27) + { + vt->state = vt_state_swallow; + } + else + { + vt->state = vt_state_neutral; + } + } + else + { + vt_argument_buf_add (vt, byte); + } +} + + +static void vt_state_sixel (VT *vt, int byte) +{ + // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html + // and in "\033\" rather than just "\033", this would cause + // a stray char + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt_sixels (vt, vt->argument_buf); + if (byte == 27) + { + vt->state = vt_state_swallow; + } + else + { + vt->state = vt_state_neutral; + } + } + else + { + vt_argument_buf_add (vt, byte); + //fprintf (stderr, "\r%i ", vt->argument_buf_len); + } +} + +//void add_tab (Ctx *ctx, const char *commandline, int can_launch); +//void vt_screenshot (const char *output_path); + +static void vt_state_apc_generic (VT *vt, int byte) +{ + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + if (vt->argument_buf[1] == 'G') /* graphics - from kitty */ + { + vt_gfx (vt, vt->argument_buf); + } + else if (vt->argument_buf[1] == 'C') /* launch command */ + { + if (vt->can_launch) + { + int can_launch = 0; + int no_title = 0; + int no_move = 0; + int no_resize = 0; + int layer = 0; + // escape subsequent arguments so that we dont have to pass a string? + float x = -1.0; + float y = -1.0; + int z = 0; + float width = -1.0; + float height = -1.0; + + for (int i=2; vt->argument_buf[i]; i++) + { + if (!strncmp (&vt->argument_buf[i], "can_launch=1", strlen ("can_launch=1"))) + can_launch = 1; + if (!strncmp (&vt->argument_buf[i], "no_title=1", strlen("no_title=1"))) + no_title = 1; + if (!strncmp (&vt->argument_buf[i], "no_move=1", strlen("no_move=1"))) + no_move = 1; + else if (!strncmp (&vt->argument_buf[i], "z=", 2)) + z=atoi(&vt->argument_buf[i]+strlen("z=")); + else if (!strncmp (&vt->argument_buf[i], "x=", 2)) + x=atof(&vt->argument_buf[i]+strlen("x=")); + else if (!strncmp (&vt->argument_buf[i], "y=", 2)) + y=atof(&vt->argument_buf[i]+strlen("y=")); + else if (!strncmp (&vt->argument_buf[i], "width=", 6)) + width=atof(&vt->argument_buf[i]+strlen("width=")); + else if (!strncmp (&vt->argument_buf[i], "height=", 7)) + height=atof(&vt->argument_buf[i]+strlen("height=")); + } + + if (width + no_resize + layer + height + x + y + no_title + no_move + z + can_launch) {}; + + char *sep = strchr(vt->argument_buf, ';'); + if (sep) + { + //fprintf (stderr, "[%s]", sep + 1); + if (!strncmp (sep + 1, "fbsave", 6)) + { + // vt_screenshot (sep + 8); + } + else + { + // add_tab (ctx, sep + 1, can_launch); + } + } + } + + } + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + } +} + +#if 0 + {"_G..\e\", 0, vtcmd_delete_n_chars, VT102}, /* ref:none id: <a href='https://sw.kovidgoyal.net/kitty/graphics-protocol.html'>kitty graphics</a> */ " + {"_A..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id: <a href='https://github.com/hodefoting/atty/'>atty</a> audio input/output */ " + {"_C..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id: run command */ " +#endif +static void vt_state_apc (VT *vt, int byte) +{ + if (byte == 'A') + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_apc_audio; + } + else if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_apc_generic; + } +} + +static void vt_state_esc_foo (VT *vt, int byte) +{ + vt_argument_buf_add (vt, byte); + vt->state = vt_state_neutral; + handle_sequence (vt, vt->argument_buf); +} + +static void vt_state_esc_sequence (VT *vt, int byte) +{ + if (_vt_handle_control (vt, byte) == 0) + { + if (byte == 27) + { + } + else if (byte >= '@' && byte <= '~') + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_neutral; + handle_sequence (vt, vt->argument_buf); + } + else + { + vt_argument_buf_add (vt, byte); + } + } +} + +static void vt_state_esc (VT *vt, int byte) +{ + if (_vt_handle_control (vt, byte) == 0) + switch (byte) + { + case 27: /* ESCape */ + break; + case ')': + case '#': + case '(': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_esc_foo; + } + break; + case '[': + case '%': + case '+': + case '*': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_esc_sequence; + } + break; + +#if 0 + {"Psixel_data\e\", 0, , }, /* id: sixels */ " +#endif + + case 'P': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_sixel; + } + break; + case ']': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_osc; + } + break; + case '^': // privacy message + case '_': // APC + case 'X': // SOS + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_apc; + } + break; + default: + { + char tmp[]= {byte, '\0'}; + tmp[0]=byte; + vt->state = vt_state_neutral; + handle_sequence (vt, tmp); + } + break; + } +} + +static void vt_state_neutral (VT *vt, int byte) +{ + if (CTX_UNLIKELY(_vt_handle_control (vt, byte) != 0)) + return; + if (CTX_LIKELY(byte != 27)) + { + if (vt_decoder_feed (vt, byte) ) + return; + if (vt->charset[vt->shifted_in] != 0 && + vt->charset[vt->shifted_in] != 'B') + { + char **charmap; + switch (vt->charset[vt->shifted_in]) + { + case 'A': + charmap = charmap_uk; + break; + case 'B': + charmap = charmap_ascii; + break; + case '0': + charmap = charmap_graphics; + break; + case '1': + charmap = charmap_cp437; + break; + case '2': + charmap = charmap_graphics; + break; + default: + charmap = charmap_ascii; + break; + } + if ( (vt->utf8_holding[0] >= ' ') && (vt->utf8_holding[0] <= '~') ) + { + _vt_add_str (vt, charmap[vt->utf8_holding[0]-' ']); + } + } + else + { + // ensure vt->utf8_holding contains a valid utf8 + uint32_t codepoint; + uint32_t state = 0; + for (int i = 0; vt->utf8_holding[i]; i++) + { utf8_decode (&state, &codepoint, vt->utf8_holding[i]); } + if (state != UTF8_ACCEPT) + { + /* otherwise mangle it so that it does */ + vt->utf8_holding[0] &= 127; + vt->utf8_holding[1] = 0; + if (vt->utf8_holding[0] == 0) + { vt->utf8_holding[0] = 32; } + } + _vt_add_str (vt, (char *) vt->utf8_holding); + } + } + else // ESCape + { + vt->state = vt_state_esc; + } +} + +int vt_poll (VT *vt, int timeout) +{ + if (!vt) return 0; + int read_size = sizeof (vt->buf); + int got_data = 0; + + // read_size 1m1.142s + // read_size*10 52s + // read_size*5 53.8s + // read_size*4 53.78s + // read_size*3 .....s + // read_size*2 56.99s + int remaining_chars = read_size * 3;// * 100; + int len = 0; + vt_audio_task (vt, 0); +#if 1 + if (vt->cursor_visible && vt->smooth_scroll) + { + remaining_chars = vt->cols / 2; + } +#endif + read_size = MIN (read_size, remaining_chars); + long start_ticks = ctx_ticks (); + long ticks = start_ticks; + while (remaining_chars > 0 && + vt_waitdata (vt, 0) && + ( ticks - start_ticks < timeout || vt->state == vt_state_ctx)) + { + if (vt->in_smooth_scroll) + { + remaining_chars = 1; + // XXX : need a bail condition - + // /// so that we can stop accepting data until autowrap or similar + } + len = vt_read (vt, vt->buf, read_size); + if (len >0) + { + // fwrite (vt->buf, len, 1, vt->log); + // fwrite (vt->buf, len, 1, stdout); + } + for (int i = 0; i < len; i++) + { vt->state (vt, vt->buf[i]); } + // XXX allow state to break out in ctx mode on flush + got_data+=len; + remaining_chars -= len; + if (vt->state == vt_state_ctx) { + if (remaining_chars < read_size) + { + remaining_chars = read_size * 2; + } + } + vt_audio_task (vt, 0); + ticks = ctx_ticks (); + } + if (got_data < 0) + { + if (kill (vt->vtpty.pid, 0) != 0) + { + vt->vtpty.done = 1; + } + } + return got_data; +} + +/******/ + +static const char *keymap_vt52[][2]= +{ + {"up", "\033A" }, + {"down", "\033B" }, + {"right", "\033C" }, + {"left", "\033D" }, +}; + +static const char *keymap_application[][2]= +{ + {"up", "\033OA" }, + {"down", "\033OB" }, + {"right", "\033OC" }, + {"left", "\033OD" }, +}; + +static const char *keymap_general[][2]= +{ + {"up", "\033[A"}, + {"down", "\033[B"}, + {"right", "\033[C"}, + {"left", "\033[D"}, + {"end", "\033[F"}, + {"home", "\033[H"}, + {"shift-up", "\033[1;2A"}, + {"shift-down", "\033[1;2B"}, + {"shift-right", "\033[1;2C"}, + {"shift-left", "\033[1;2D"}, + {"alt-a", "\033a"}, + {"alt-b", "\033b"}, + {"alt-c", "\033c"}, + {"alt-d", "\033d"}, + {"alt-e", "\033e"}, + {"alt-f", "\033f"}, + {"alt-g", "\033g"}, + {"alt-h", "\033h"}, + {"alt-i", "\033i"}, + {"alt-j", "\033j"}, + {"alt-k", "\033k"}, + {"alt-l", "\033l"}, + {"alt-m", "\033m"}, + {"alt-n", "\033n"}, + {"alt-o", "\033o"}, + {"alt-p", "\033p"}, + {"alt-q", "\033q"}, + {"alt-r", "\033r"}, + {"alt-s", "\033s"}, + {"alt-t", "\033t"}, + {"alt-u", "\033u"}, + {"alt-v", "\033v"}, + {"alt-w", "\033w"}, + {"alt-x", "\033x"}, + {"alt-y", "\033y"}, + {"alt-z", "\033z"}, + {"alt- ", "\033 "}, + {"alt-space", "\033 "}, + {"alt-0", "\0330"}, + {"alt-1", "\0331"}, + {"alt-2", "\0332"}, + {"alt-3", "\0333"}, + {"alt-4", "\0334"}, + {"alt-5", "\0335"}, + {"alt-6", "\0336"}, + {"alt-7", "\0337"}, + {"alt-8", "\0338"}, + {"alt-9", "\0339"}, + {"alt-return", "\033\r"}, + {"alt-backspace", "\033\177"}, + {"alt-up", "\033[1;3A"}, + {"alt-down", "\033[1;3B"}, + {"alt-right", "\033[1;3C"}, + {"alt-left", "\033[1;3D"}, + {"shift-alt-up", "\033[1;4A"}, + {"shift-alt-down", "\033[1;4B"}, + {"shift-alt-right","\033[1;4C"}, + {"shift-alt-left", "\033[1;4D"}, + {"control-space", "\000"}, + {"control-up", "\033[1;5A"}, + {"control-down", "\033[1;5B"}, + {"control-right", "\033[1;5C"}, + {"control-left", "\033[1;5D"}, + {"shift-control-up", "\033[1;6A"}, + {"shift-control-down", "\033[1;6B"}, + {"shift-control-right", "\033[1;6C"}, + {"shift-control-left", "\033[1;6D"}, + {"insert", "\033[2~"}, + {"delete", "\033[3~"}, + {"control-delete", "\033[3,5~"}, + {"shift-delete", "\033[3,2~"}, + {"control-shift-delete", "\033[3,6~"}, + {"page-up", "\033[5~"}, + {"page-down", "\033[6~"}, + {"return", "\r"}, + {"shift-tab", "\033Z"}, + {"shift-return", "\r"}, + {"control-return", "\r"}, + {"space", " "}, + {"shift-space", " "}, + {"control-a", "\001"}, + {"control-b", "\002"}, + {"control-c", "\003"}, + {"control-d", "\004"}, + {"control-e", "\005"}, + {"control-f", "\006"}, + {"control-g", "\007"}, + {"control-h", "\010"}, + {"control-i", "\011"}, + {"control-j", "\012"}, + {"control-k", "\013"}, + {"control-l", "\014"}, + {"control-m", "\015"}, + {"control-n", "\016"}, + {"control-o", "\017"}, + {"control-p", "\020"}, + {"control-q", "\021"}, + {"control-r", "\022"}, + {"control-s", "\023"}, + {"control-t", "\024"}, + {"control-u", "\025"}, + {"control-v", "\026"}, + {"control-w", "\027"}, + {"control-x", "\030"}, + {"control-y", "\031"}, + {"control-z", "\032"}, + {"escape", "\033"}, + {"tab", "\t"}, + {"backspace", "\177"}, + {"control-backspace", "\177"}, + {"shift-backspace","\177"}, + {"shift-tab", "\033[Z"}, + + {"control-F1", "\033[>11~"}, + {"control-F2", "\033[>12~"}, + {"control-F3", "\033[>13~"}, + {"control-F4", "\033[>14~"}, + {"control-F5", "\033[>15~"}, + + {"shift-F1", "\033[?11~"}, + {"shift-F2", "\033[?12~"}, + {"shift-F3", "\033[?13~"}, + {"shift-F4", "\033[?14~"}, + {"shift-F5", "\033[?15~"}, + + {"F1", "\033[11~"}, // hold screen // ESC O P + {"F2", "\033[12~"}, // print screen // Q + {"F3", "\033[13~"}, // set-up R + {"F4", "\033[14~"}, // data/talk S + {"F5", "\033[15~"}, // break + {"F6", "\033[17~"}, + {"F7", "\033[18~"}, + {"F8", "\033[19~"}, + {"F9", "\033[20~"}, + {"F10", "\033[21~"}, + {"F11", "\033[22~"}, + {"F12", "\033[23~"}, + {"control-/", "\037"}, + {"shift-control-/", "\037"}, + {"control-[", "\033"}, + {"control-]", "\035"}, + {"shift-control-[", "\033"}, + {"shift-control-]", "\031"}, + {"shift-control-`", "\036"}, + {"control-'", "'"}, + {"shift-control-'", "'"}, + {"control-;", ";"}, + {"shift-control-;", ";"}, + {"control-.", "."}, + {"shift-control-.", "."}, + {"control-,", ","}, + {"shift-control-,", ","}, + {"control-\\", "\034"}, + {"control-1", "1"}, + {"control-3", "\033"}, + {"control-4", "\034"}, + {"control-5", "\035"}, + {"control-6", "\036"}, + {"shift-control-6", "\036"}, + {"control-7", "\037"}, + {"shift-control-7", "\036"}, + {"control-8", "\177"}, + {"control-9", "9"}, + + +}; + +void ctx_client_lock (CtxClient *client); +void ctx_client_unlock (CtxClient *client); + +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str) +{ + if (vt->ctx_events) + { + if (!strcmp (str, "control-l") ) + { + vt->ctx_events = 0; + return; + } + vt_write (vt, str, strlen (str) ); + vt_write (vt, "\n", 1); + return; + } + if (!strncmp (str, "keyup", 5)) return; + if (!strncmp (str, "keydown", 7)) return; + + if (!strcmp (str, "capslock")) return; + +#if 0 + if (!strstr (str, "-page")) + vt_set_scroll (vt, 0); +#endif + + if (!strcmp (str, "idle") ) + return; + else if (!strcmp (str, "shift-control-home")) + { + vt_set_scroll (vt, vt->scrollback_count); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-end")) + { + int new_scroll = 0; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-down")) + { + int new_scroll = vt_get_scroll (vt) - 1; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-up")) + { + int new_scroll = vt_get_scroll (vt) + 1; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-page-up") || + !strcmp (str, "shift-control-page-up")) + { + int new_scroll = vt_get_scroll (vt) + vt_get_rows (vt) /2; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-page-down") || + !strcmp (str, "shift-control-page-down")) + { + int new_scroll = vt_get_scroll (vt) - vt_get_rows (vt) /2; + if (new_scroll < 0) { new_scroll = 0; } + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control--") || + !strcmp (str, "control--") ) + { + float font_size = vt_get_font_size (vt); + //font_size /= 1.15; + font_size -=2;//= roundf (font_size); + if (font_size < 2) { font_size = 2; } + vt_set_font_size (vt, font_size); + vt_set_px_size (vt, vt->width, vt->height); + return; + } + else if (!strcmp (str, "shift-control-=") || + !strcmp (str, "control-=") ) + { + float font_size = vt_get_font_size (vt); + float old = font_size; + //font_size *= 1.15; + // + //font_size = roundf (font_size); + font_size+=2; + + if (old == font_size) { font_size = old+1; } + if (font_size > 200) { font_size = 200; } + vt_set_font_size (vt, font_size); + vt_set_px_size (vt, vt->width, vt->height); + + return; + } + else if (!strcmp (str, "shift-control-r") ) + { + vt_open_log (vt, "/tmp/ctx-vt"); + return; + } + else if (!strcmp (str, "shift-control-l") ) + { + vt_set_local (vt, !vt_get_local (vt) ); + return; + } + else if (!strncmp (str, "mouse-", 5) ) + { + int cw = vt_cw (vt); + int ch = vt_ch (vt); + if (!strncmp (str + 6, "motion", 6) ) + { + int x = 0, y = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + vt_mouse (vt, event, VT_MOUSE_MOTION, 1, x/cw + 1, y/ch + 1, x, y); + } + } + } + else if (!strncmp (str + 6, "press", 5) ) + { + int x = 0, y = 0, b = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_PRESS, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + } + else if (!strncmp (str + 6, "drag", 4) ) + { + int x = 0, y = 0, b = 0; // XXX initialize B + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_DRAG, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + } + else if (!strncmp (str + 6, "release", 7) ) + { + int x = 0, y = 0, b = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_RELEASE, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + // queue-draw + } + return; + } + + if (vt->scroll_on_input) + { + vt->scroll = 0.0; + } + + + if (vt->state == vt_state_vt52) + { + for (unsigned int i = 0; i<sizeof (keymap_vt52) /sizeof (keymap_vt52[0]); i++) + if (!strcmp (str, keymap_vt52[i][0]) ) + { str = keymap_vt52[i][1]; goto done; } + } + else + { + if (vt->cursor_key_application) + { + for (unsigned int i = 0; i<sizeof (keymap_application) /sizeof (keymap_application[0]); i++) + if (!strcmp (str, keymap_application[i][0]) ) + { str = keymap_application[i][1]; goto done; } + } + } + + + if (!strcmp (str, "return") ) + { + if (vt->cr_on_lf) + { str = "\r\n"; } + else + { str = "\r"; } + goto done; + } + if (!strcmp (str, "control-space") || + !strcmp (str, "control-`") || + !strcmp (str, "control-2") || + !strcmp (str, "shift-control-2") || + !strcmp (str, "shift-control-space") ) + { + str = "\0\0"; + vt_write (vt, str, 1); + return; + } + for (unsigned int i = 0; i< sizeof (keymap_general) / + sizeof (keymap_general[0]); i++) + if (!strcmp (str, keymap_general[i][0]) ) + { + str = keymap_general[i][1]; + break; + } +done: + if (strlen (str) ) + { + if (vt->local_editing) + { + for (int i = 0; str[i]; i++) + { + vt->state (vt, str[i]); + } + } + else + { + vt_write (vt, str, strlen (str) ); + } + } +} + +void vt_paste (VT *vt, const char *str) +{ + if (vt->bracket_paste) + { + vt_write (vt, "\033[200~", 6); + } + vt_feed_keystring (vt, NULL, str); + if (vt->bracket_paste) + { + vt_write (vt, "\033[201~", 6); + } +} + +const char *vt_find_shell_command (void) +{ + if (access ("/.flatpak-info", F_OK) != -1) + { + static char ret[512]; + char buf[256]; + FILE *fp = popen("flatpak-spawn --host getent passwd $USER|cut -f 7 -d :", "r"); + if (fp) + { + while (fgets (buf, sizeof(buf), fp) != NULL) + { + if (buf[strlen(buf)-1]=='\n') + buf[strlen(buf)-1]=0; + sprintf (ret, "flatpak-spawn --env=TERM=xterm --host %s", buf); + } + pclose (fp); + return ret; + } + } + + if (getenv ("SHELL")) + { + return getenv ("SHELL"); + } + int i; + const char *command = NULL; + struct stat stat_buf; + static char *alts[][2] = + { + {"/bin/bash", "/bin/bash"}, + {"/usr/bin/bash", "/usr/bin/bash"}, + {"/bin/sh", "/bin/sh"}, + {"/usr/bin/sh", "/usr/bin/sh"}, + {NULL, NULL} + }; + for (i = 0; alts[i][0] && !command; i++) + { + lstat (alts[i][0], &stat_buf); + if (S_ISREG (stat_buf.st_mode) || S_ISLNK (stat_buf.st_mode) ) + { command = alts[i][1]; } + } + return command; +} + +static char *string_chop_head (char *orig) /* return pointer to reset after arg */ +{ + int j=0; + int eat=0; /* number of chars to eat at start */ + + if(orig) + { + int got_more; + char *o = orig; + while(o[j] == ' ') + {j++;eat++;} + + if (o[j]=='"') + { + eat++;j++; + while(o[j] != '"' && + o[j] != 0) + j++; + o[j]='\0'; + j++; + } + else if (o[j]=='\'') + { + eat++;j++; + while(o[j] != '\'' && + o[j] != 0) + j++; + o[j]='\0'; + j++; + } + else + { + while(o[j] != ' ' && + o[j] != 0 && + o[j] != ';') + j++; + } + if (o[j] == 0 || + o[j] == ';') + got_more = 0; + else + got_more = 1; + o[j]=0; /* XXX: this is where foo;bar won't work but foo ;bar works*/ + + if(eat) + { + int k; + for (k=0; k<j-eat; k++) + orig[k] = orig[k+eat]; + } + if (got_more) + return &orig[j+1]; + } + return NULL; +} + +void _ctx_add_listen_fd (int fd); +void _ctx_remove_listen_fd (int fd); + +static pid_t +vt_forkpty (int *amaster, + char *aname, + const struct termios *termp, + const struct winsize *winsize) +{ + pid_t pid; + int master = posix_openpt (O_RDWR|O_NOCTTY); + int slave; + + if (master < 0) + return -1; + if (grantpt (master) != 0) + return -1; + if (unlockpt (master) != 0) + return -1; +#if 0 + char name[1024]; + if (ptsname_r (master, name, sizeof(name)-1)) + return -1; +#else + char *name = NULL; + if ((name = ptsname (master)) == NULL) + return -1; +#endif + + slave = open(name, O_RDWR|O_NOCTTY); + + if (termp) tcsetattr(slave, TCSAFLUSH, termp); + if (winsize) ioctl(slave, TIOCSWINSZ, winsize); + + pid = fork(); + if (pid < 0) + { + return pid; + } else if (pid == 0) + { + close (master); + setsid (); + dup2 (slave, STDIN_FILENO); + dup2 (slave, STDOUT_FILENO); + dup2 (slave, STDERR_FILENO); + + close (slave); + return 0; + } + ioctl (slave, TIOCSCTTY, NULL); + close (slave); + *amaster = master; + return pid; +} + +static void vt_run_command (VT *vt, const char *command, const char *term) +{ + struct winsize ws; + //signal (SIGCHLD,signal_child); +#if 0 + int was_pidone = (getpid () == 1); +#else + int was_pidone = 0; // do no special treatment, all child processes belong + // to root +#endif + signal (SIGINT,SIG_DFL); + ws.ws_row = vt->rows; + ws.ws_col = vt->cols; + ws.ws_xpixel = ws.ws_col * vt->cw; + ws.ws_ypixel = ws.ws_row * vt->ch; + vt->vtpty.pid = vt_forkpty (&vt->vtpty.pty, NULL, NULL, &ws); + if (vt->vtpty.pid == 0) + { + int i; + if (was_pidone) + { + if (setuid(1000)) fprintf (stderr, "setuid failed\n"); + } + else + { + for (i = 3; i<768; i++) { close (i); } /*hack, trying to close xcb */ + } + unsetenv ("TERM"); + unsetenv ("COLUMNS"); + unsetenv ("LINES"); + unsetenv ("TERMCAP"); + unsetenv ("COLOR_TERM"); + unsetenv ("COLORTERM"); + unsetenv ("VTE_VERSION"); + unsetenv ("CTX_BACKEND"); + //setenv ("TERM", "ansi", 1); + //setenv ("TERM", "vt102", 1); + //setenv ("TERM", "vt100", 1); + // setenv ("TERM", term?term:"xterm", 1); + setenv ("TERM", term?term:"xterm-256color", 1); + setenv ("COLORTERM", "truecolor", 1); + //setenv ("CTX_VERSION", "0", 1); + setenv ("CTX_BACKEND", "ctx", 1); // speeds up launching of clients + + { + char *cargv[32]; + int cargc; + char *rest, *copy; + copy = calloc (strlen (command)+2, 1); + strcpy (copy, command); + rest = copy; + cargc = 0; + while (rest && cargc < 30 && rest[0] != ';') + { + cargv[cargc++] = rest; + rest = string_chop_head (rest); + } + cargv[cargc] = NULL; + execvp (cargv[0], cargv); + } + exit (0); + } + else if (vt->vtpty.pid < 0) + { + VT_error ("forkpty failed (%s)", command); + return; + } + fcntl(vt->vtpty.pty, F_SETFL, O_NONBLOCK|O_NOCTTY); + _ctx_add_listen_fd (vt->vtpty.pty); +} + +void vt_destroy (VT *vt) +{ + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + vt->line_count--; + } + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + //if (vt->ctx) + // { ctx_free (vt->ctx); } + free (vt->argument_buf); + ctx_list_remove (&vts, vt); + kill (vt->vtpty.pid, 9); + _ctx_remove_listen_fd (vt->vtpty.pty); + close (vt->vtpty.pty); +#if 1 + if (vt->title) + free (vt->title); +#endif + free (vt); +} + +int vt_get_line_count (VT *vt) +{ + return vt->line_count; +} + +const char *vt_get_line (VT *vt, int no) +{ + if (no >= vt->rows) + { + CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows); + if (!l) + { + return ""; + } + CtxString *str = l->data; + return str->str; + } + else + { + CtxList *l = ctx_list_nth (vt->lines, no); + if (!l) + { + return "-"; + } + CtxString *str = l->data; + return str->str; + } +} + +int vt_line_is_continuation (VT *vt, int no) +{ + if (no >= vt->rows) + { + CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows); + if (!l) + { + return 1; + } + VtLine *line = l->data; + return line->wrapped; + } + else + { + CtxList *l = ctx_list_nth (vt->lines, no); + if (!l) + { + return 1; + } + VtLine *line = l->data; + return line->wrapped; + } +} + +int vt_get_cols (VT *vt) +{ + return vt->cols; +} + +int vt_get_rows (VT *vt) +{ + return vt->rows; +} + +int vt_get_cursor_x (VT *vt) +{ + return vt->cursor_x; +} + +int vt_get_cursor_y (VT *vt) +{ + return vt->cursor_y; +} + +static void draw_braille_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v) +{ + ctx_rectangle (ctx, 0.167 * cw + x + u * cw * 0.5, + y - ch + 0.080 * ch + v * ch * 0.25, + 0.33 *cw, 0.33 * cw); +} + +static void draw_sextant_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v) +{ + ctx_rectangle (ctx, x + u * cw * 0.5, + y - ch + v * ch * 0.3333, + 0.5 *cw, 0.34 * ch); +} + +int vt_special_glyph (Ctx *ctx, VT *vt, float x, float y, int cw, int ch, int unichar) +{ + switch (unichar) + { + case 0x2594: // UPPER_ONE_EIGHT_BLOCK + ctx_begin_path (ctx); + { + float factor = 1.0f/8.0f; + ctx_rectangle (ctx, x, y - ch, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2581: // LOWER_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + { + float factor = 1.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2582: // LOWER_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + { + float factor = 1.0f/4.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2583: // LOWER_THREE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 3.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2585: // LOWER_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 5.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2586: // LOWER_THREE_QUARTERS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 3.0f/4.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2587: // LOWER_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 7.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2589: // LEFT_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*7/8, ch); + ctx_fill (ctx); + return 0; + case 0x258A: // LEFT_THREE_QUARTERS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*3/4, ch); + ctx_fill (ctx); + return 0; + case 0x258B: // LEFT_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*5/8, ch); + ctx_fill (ctx); + return 0; + case 0x258D: // LEFT_THREE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*3/8, ch); + ctx_fill (ctx); + return 0; + case 0x258E: // LEFT_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/4, ch); + ctx_fill (ctx); + return 0; + case 0x258F: // LEFT_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/8, ch); + ctx_fill (ctx); + return 0; + case 0x258C: // HALF_LEFT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/2, ch); + ctx_fill (ctx); + return 0; + case 0x2590: // HALF_RIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8f: // VT_RIGHT_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*1/8, y - ch, cw*7/8, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8d: // VT_RIGHT_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*3/8, y - ch, cw*5/8, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8b: // VT_RIGHT_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*3/4, y - ch, cw/4, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8e: // VT_RIGHT_THREE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*1/4, y - ch, cw*3/4, ch); + ctx_fill (ctx); + return 0; + case 0x2595: // VT_RIGHT_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*7/8, y - ch, cw/8, ch); + ctx_fill (ctx); + return 0; + case 0x2580: // HALF_UP_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0x2584: // _HALF_DOWN_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0x2596: // _QUADRANT LOWER LEFT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2597: // _QUADRANT LOWER RIGHT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x+cw/2, y - ch/2, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2598: // _QUADRANT UPPER LEFT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x259D: // _QUADRANT UPPER RIGHT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2599: // _QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259A: // _QUADRANT UPPER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259B: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + return 0; + case 0x259C: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259E: // _QUADRANT UPPER RIGHT AND LOWER LEFT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + return 0; + case 0x259F: // _QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x2588: // FULL_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_fill (ctx); + return 0; + case 0x2591: // LIGHT_SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.25); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x2592: // MEDIUM_SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.5); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x2593: // DARK SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.75); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x23BA: //HORIZONTAL_SCANLINE-1 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.1 - ch * 0.1, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BB: //HORIZONTAL_SCANLINE-3 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.3 - ch * 0.075, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BC: //HORIZONTAL_SCANLINE-7 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.7 - ch * 0.025, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BD: //HORIZONTAL_SCANLINE-9 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.9 + ch * 0.0, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2500: //VT_BOX_DRAWINGS_LIGHT_HORIZONTAL // and scanline 5 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2212: // minus -sign + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw * 0.1, y - ch/2 - ch * 0.1 / 2, cw * 0.8, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2502: // VT_BOX_DRAWINGS_LIGHT_VERTICAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch + 1); + ctx_fill (ctx); + return 0; + case 0x250c: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2510: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2514: //VT_BOX_DRAWINGS_LIGHT_UP_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ch*0.1/2); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2 + ch * 0.1, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2518: //VT_BOX_DRAWINGS_LIGHT_UP_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ ch*0.1/2); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x251C: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, cw/2+ch * 0.1, ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + return 0; + case 0x2524: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x252C: // VT_BOX_DRAWINGS_LIGHT_DOWN_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, ch * 0.1, ch/2+ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2534: // VT_BOX_DRAWINGS_LIGHT_UP_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch /2+ch*0.1/2); + ctx_fill (ctx); + return 0; + case 0x253C: // VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + return 0; + case 0xe0a0: // PowerLine branch + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x+cw/2, y - 0.15 * ch); + ctx_rel_line_to (ctx, -cw/3, -ch * 0.7); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/3, ch * 0.7); + ctx_line_width (ctx, cw * 0.25); + ctx_stroke (ctx); + ctx_restore (ctx); + break; + // case 0xe0a1: // PowerLine LN + // case 0xe0a2: // PowerLine Lock + case 0xe0b0: // PowerLine left solid + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0xe0b1: // PowerLine left line + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch * 0.1); + ctx_rel_line_to (ctx, cw * 0.9, -ch/2 * 0.8); + ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8); + ctx_line_width (ctx, cw * 0.2); + ctx_stroke (ctx); + ctx_restore (ctx); + return 0; + case 0xe0b2: // PowerLine Right solid + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, -ch/2); + ctx_rel_line_to (ctx, cw, -ch/2); + ctx_fill (ctx); + return 0; + case 0xe0b3: // PowerLine right line + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch * 0.1); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8); + ctx_rel_line_to (ctx, cw * 0.9, ch/2 * 0.8); + ctx_line_width (ctx, cw * 0.2); + ctx_stroke (ctx); + ctx_restore (ctx); + return 0; + /* + case 0x1fb70: // left triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_fill (ctx); + return 0; + case 0x1fb72: // right triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x1fb73: // lower triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x1fb71: // upper triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw, 0); + ctx_fill (ctx); + return 0; + */ + case 0x25E2: // VT_BLACK_LOWER_RIGHT_TRIANGLE: + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x25E3: // VT_BLACK_LOWER_LEFT_TRIANGLE: + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch); + ctx_fill (ctx); + return 0; + case 0x25E4: // tri + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_fill (ctx); + return 0; + case 0x25E5: // tri + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x2800: + case 0x2801: + case 0x2802: + case 0x2803: + case 0x2804: + case 0x2805: + case 0x2806: + case 0x2807: + case 0x2808: + case 0x2809: + case 0x280A: + case 0x280B: + case 0x280C: + case 0x280D: + case 0x280E: + case 0x280F: + case 0x2810: + case 0x2811: + case 0x2812: + case 0x2813: + case 0x2814: + case 0x2815: + case 0x2816: + case 0x2817: + case 0x2818: + case 0x2819: + case 0x281A: + case 0x281B: + case 0x281C: + case 0x281D: + case 0x281E: + case 0x281F: + case 0x2820: + case 0x2821: + case 0x2822: + case 0x2823: + case 0x2824: + case 0x2825: + case 0x2826: + case 0x2827: + case 0x2828: + case 0x2829: + case 0x282A: + case 0x282B: + case 0x282C: + case 0x282D: + case 0x282E: + case 0x282F: + case 0x2830: + case 0x2831: + case 0x2832: + case 0x2833: + case 0x2834: + case 0x2835: + case 0x2836: + case 0x2837: + case 0x2838: + case 0x2839: + case 0x283A: + case 0x283B: + case 0x283C: + case 0x283D: + case 0x283E: + case 0x283F: + ctx_begin_path (ctx); + { + int bit_pattern = unichar - 0x2800; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x2840: + case 0x2841: + case 0x2842: + case 0x2843: + case 0x2844: + case 0x2845: + case 0x2846: + case 0x2847: + case 0x2848: + case 0x2849: + case 0x284A: + case 0x284B: + case 0x284C: + case 0x284D: + case 0x284E: + case 0x284F: + case 0x2850: + case 0x2851: + case 0x2852: + case 0x2853: + case 0x2854: + case 0x2855: + case 0x2856: + case 0x2857: + case 0x2858: + case 0x2859: + case 0x285A: + case 0x285B: + case 0x285C: + case 0x285D: + case 0x285E: + case 0x285F: + case 0x2860: + case 0x2861: + case 0x2862: + case 0x2863: + case 0x2864: + case 0x2865: + case 0x2866: + case 0x2867: + case 0x2868: + case 0x2869: + case 0x286A: + case 0x286B: + case 0x286C: + case 0x286D: + case 0x286E: + case 0x286F: + case 0x2870: + case 0x2871: + case 0x2872: + case 0x2873: + case 0x2874: + case 0x2875: + case 0x2876: + case 0x2877: + case 0x2878: + case 0x2879: + case 0x287A: + case 0x287B: + case 0x287C: + case 0x287D: + case 0x287E: + case 0x287F: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 0, 3); + { + int bit_pattern = unichar - 0x2840; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x2880: + case 0x2881: + case 0x2882: + case 0x2883: + case 0x2884: + case 0x2885: + case 0x2886: + case 0x2887: + case 0x2888: + case 0x2889: + case 0x288A: + case 0x288B: + case 0x288C: + case 0x288D: + case 0x288E: + case 0x288F: + case 0x2890: + case 0x2891: + case 0x2892: + case 0x2893: + case 0x2894: + case 0x2895: + case 0x2896: + case 0x2897: + case 0x2898: + case 0x2899: + case 0x289A: + case 0x289B: + case 0x289C: + case 0x289D: + case 0x289E: + case 0x289F: + case 0x28A0: + case 0x28A1: + case 0x28A2: + case 0x28A3: + case 0x28A4: + case 0x28A5: + case 0x28A6: + case 0x28A7: + case 0x28A8: + case 0x28A9: + case 0x28AA: + case 0x28AB: + case 0x28AC: + case 0x28AD: + case 0x28AE: + case 0x28AF: + case 0x28B0: + case 0x28B1: + case 0x28B2: + case 0x28B3: + case 0x28B4: + case 0x28B5: + case 0x28B6: + case 0x28B7: + case 0x28B8: + case 0x28B9: + case 0x28BA: + case 0x28BB: + case 0x28BC: + case 0x28BD: + case 0x28BE: + case 0x28BF: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 1, 3); + { + int bit_pattern = unichar - 0x2880; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x28C0: + case 0x28C1: + case 0x28C2: + case 0x28C3: + case 0x28C4: + case 0x28C5: + case 0x28C6: + case 0x28C7: + case 0x28C8: + case 0x28C9: + case 0x28CA: + case 0x28CB: + case 0x28CC: + case 0x28CD: + case 0x28CE: + case 0x28CF: + case 0x28D0: + case 0x28D1: + case 0x28D2: + case 0x28D3: + case 0x28D4: + case 0x28D5: + case 0x28D6: + case 0x28D7: + case 0x28D8: + case 0x28D9: + case 0x28DA: + case 0x28DB: + case 0x28DC: + case 0x28DD: + case 0x28DE: + case 0x28DF: + case 0x28E0: + case 0x28E1: + case 0x28E2: + case 0x28E3: + case 0x28E4: + case 0x28E5: + case 0x28E6: + case 0x28E7: + case 0x28E8: + case 0x28E9: + case 0x28EA: + case 0x28EB: + case 0x28EC: + case 0x28ED: + case 0x28EE: + case 0x28EF: + case 0x28F0: + case 0x28F1: + case 0x28F2: + case 0x28F3: + case 0x28F4: + case 0x28F5: + case 0x28F6: + case 0x28F7: + case 0x28F8: + case 0x28F9: + case 0x28FA: + case 0x28FB: + case 0x28FC: + case 0x28FD: + case 0x28FE: + case 0x28FF: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 0, 3); + draw_braille_bit (ctx, x, y, cw, ch, 1, 3); + { + int bit_pattern = unichar - 0x28C0; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x1fb00: + case 0x1fb01: + case 0x1fb02: + case 0x1fb03: + case 0x1fb04: + case 0x1fb05: + case 0x1fb06: + case 0x1fb07: + case 0x1fb08: + case 0x1fb09: + case 0x1fb0a: + case 0x1fb0b: + case 0x1fb0c: + case 0x1fb0d: + case 0x1fb0e: + case 0x1fb0f: + case 0x1fb10: + case 0x1fb11: + case 0x1fb12: + case 0x1fb13: + case 0x1fb14: + case 0x1fb15: + case 0x1fb16: + case 0x1fb17: + case 0x1fb18: + case 0x1fb19: + case 0x1fb1a: + case 0x1fb1b: + case 0x1fb1c: + case 0x1fb1d: + case 0x1fb1e: + case 0x1fb1f: + case 0x1fb20: + case 0x1fb21: + case 0x1fb22: + case 0x1fb23: + case 0x1fb24: + case 0x1fb25: + case 0x1fb26: + case 0x1fb27: + case 0x1fb28: + case 0x1fb29: + case 0x1fb2a: + case 0x1fb2b: + case 0x1fb2c: + case 0x1fb2d: + case 0x1fb2e: + case 0x1fb2f: + case 0x1fb30: + case 0x1fb31: + case 0x1fb32: + case 0x1fb33: + case 0x1fb34: + case 0x1fb35: + case 0x1fb36: + case 0x1fb37: + case 0x1fb38: + case 0x1fb39: + case 0x1fb3a: + case 0x1fb3b: + + { + ctx_begin_path (ctx); + uint32_t bitmask = (unichar - 0x1fb00) + 1; + if (bitmask > 20) bitmask ++; + if (bitmask > 41) bitmask ++; + int bit = 0; + for (int v = 0; v < 3; v ++) + for (int u = 0; u < 2; u ++, bit ++) + { + if (bitmask & (1<<bit)) + { + draw_sextant_bit (ctx, x, y, cw, ch, u, v); + } + } + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch / 3.0f); + ctx_rel_line_to (ctx, cw/2, ch/3.0f); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f); + ctx_rel_line_to (ctx, cw, ch/3.0f); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f * 2); + ctx_rel_line_to (ctx, cw/2, ch/3.0f * 2); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f * 2); + ctx_rel_line_to (ctx, cw, ch/3.0f * 2); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb40: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb41: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw/2, -ch/3.0); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb42: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb43: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f); + ctx_rel_line_to (ctx, cw/2, -ch/3.0f*2.0f); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb44: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, -ch/3.0 * 2); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb45: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb46: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb47: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb48: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb49: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, -ch/3.0*2); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch/3.0*2); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch/3 * 2); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3 * 2); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb50: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb51: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, ch/3.0); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb52: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb53: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, -ch/3.0); + ctx_fill (ctx); + return 0; + } + case 0x1fb54: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb55: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, -ch/3.0*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb56: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, -ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb57: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb58: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb59: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch/3 * 2); + ctx_fill (ctx); + return 0; + } + case 0x1fb5a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, ch/3 * 2); + ctx_fill (ctx); + return 0; + } + case 0x1fb5b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb5c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3); + + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb5d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3 * 2); + ctx_rel_line_to (ctx, -cw/2, ch/3); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb5e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3 * 2); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb5f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw/2, ch/3*2); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb60: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb61: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb62: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw/2, -ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb63: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, -ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb64: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch/3*2); + ctx_rel_line_to (ctx, -cw/2, -ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb65: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3*2); + ctx_rel_line_to (ctx, -cw, -ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb66: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb67: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, -ch/3.0); + ctx_fill (ctx); + return 0; + } + case 0x1fb68: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb69: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb6a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb6b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw, -ch); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb82: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb83: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 3); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb84: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 5); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 5); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb85: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 6); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 6); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb86: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 7); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 7); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb87: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*6, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*2, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb88: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*5, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*3, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*3, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb89: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*3, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*5, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*5, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb8a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*2, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*6, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*6, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb97: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/4); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/4); + ctx_rel_line_to (ctx, -cw, 0); + ctx_close_path (ctx); + ctx_move_to (ctx, 0, -ch/2); + ctx_rel_line_to (ctx, 0, -ch/4); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/4); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb9a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb9b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + + } + return -1; +} + +void vt_ctx_glyph (Ctx *ctx, VT *vt, float x, float y, int unichar, int bold, float scale_x, float scale_y, float offset_y) +{ + int did_save = 0; + if (unichar <= ' ') + return; + scale_x *= vt->scale_x; + scale_y *= vt->scale_y; + + if (!ctx_renderer_is_term (ctx)) + { + // TODO : use our own special glyphs when glyphs are not passed through + if (!vt_special_glyph (ctx, vt, x, y + offset_y * vt->ch, vt->cw * scale_x, vt->ch * scale_y, unichar) ) + return; + } + + if (scale_x != 1.0 || scale_y != 1.0) + { + if (!did_save) + { + ctx_save (ctx); + did_save = 1; + } + + ctx_translate (ctx, x, y); + ctx_scale (ctx, scale_x, scale_y); + ctx_translate (ctx, -x, -y); + } + if (offset_y != 0.0f) + { + if (!did_save) + { + ctx_save (ctx); + did_save = 1; + } + ctx_translate (ctx, 0, vt->font_size * offset_y); + } + y -= vt->font_size * 0.22; + if (bold + && !ctx_renderer_is_term (ctx) + ) + { + ctx_move_to (ctx, x - vt->font_size/30.0, y); + //ctx_line_width (ctx, vt->font_size/30.0); + ctx_glyph (ctx, unichar, 0); + } + ctx_move_to (ctx, x, y); + ctx_glyph (ctx, unichar, 0); + if (did_save) + ctx_restore (ctx); +} + +//static uint8_t palette[256][3]; + +/* optimized for ANSI ART - and avoidance of human metamers + * among color deficient vision - by distributing and pertubating + * until all 64 combinations - sans self application, have + * likely to be discernable by humans. + */ + + +void vt_ctx_get_color (VT *vt, int no, int intensity, uint8_t *rgba) +{ + uint8_t r = 0, g = 0, b = 0; + if (no < 16 && no >= 0) + { + switch (intensity) + { + case 0: + no = 0; + break; + case 1: + // 15 becomes 7 + if (no == 15) { no = 8; } + else if (no > 8) { no -= 8; } + break; + case 2: + /* give the normal color special treatment, and in really normal + * cirumstances it is the dim variant of foreground that is used + */ + if (no == 15) { no = 7; } + break; + case 3: + case 4: + if (no < 8) + { no += 8; } + break; + default: + break; + } + r = palettes[vt->palette_no][no][0]; + g = palettes[vt->palette_no][no][1]; + b = palettes[vt->palette_no][no][2]; + } + else if (no < 16 + 6*6*6) + { + no = no-16; + b = (no % 6) * 255 / 5; + no /= 6; + g = (no % 6) * 255 / 5; + no /= 6; + r = (no % 6) * 255 / 5; + } + else + { + int gray = no - (16 + 6*6*6); + float val = gray * 255 / 24; + r = g = b = val; + } + rgba[0]=r; + rgba[1]=g; + rgba[2]=b; + rgba[3]=255; +} + +int vt_keyrepeat (VT *vt) +{ + return vt->keyrepeat; +} + +static void vt_flush_bg (VT *vt, Ctx *ctx) +{ + if (vt->bg_active) + { + ctx_rgba8 (ctx, vt->bg_rgba[0], vt->bg_rgba[1], vt->bg_rgba[2], vt->bg_rgba[3]); + ctx_rectangle (ctx, vt->bg_x0, vt->bg_y0, vt->bg_width, vt->bg_height); + ctx_fill (ctx); + vt->bg_active = 0; + } +} + +static void vt_draw_bg (VT *vt, Ctx *ctx, + float x0, float y0, + float width, float height, + uint8_t *rgba) +{ + int same_color = !memcmp(rgba, vt->bg_rgba, 4); + if (vt->bg_active && !same_color) + { + vt_flush_bg (vt, ctx); + } + + if (vt->bg_active && same_color) + { + vt->bg_width += width; + } + else + { + memcpy (vt->bg_rgba, rgba, 4); + vt->bg_active = 1; + vt->bg_x0 = x0; + vt->bg_y0 = y0; + vt->bg_width = width; + vt->bg_height = height; + } +} + +float vt_draw_cell (VT *vt, Ctx *ctx, + int row, int col, // pass 0 to force draw - like + float x0, float y0, // for scrollback visible + uint64_t style, + uint32_t unichar, + int dw, int dh, + int in_smooth_scroll, + int in_select, + int is_fg) +// dw is 0 or 1 +// dh is 0 1 or -1 1 is upper -1 is lower +{ + int on_white = vt->reverse_video; + int color = 0; + int bold = (style & STYLE_BOLD) != 0; + int dim = (style & STYLE_DIM) != 0; + int is_hidden = (style & STYLE_HIDDEN) != 0; + int proportional = (style & STYLE_PROPORTIONAL) != 0; + int fg_set = (style & STYLE_FG_COLOR_SET) != 0; + int bg_intensity = 0; + int fg_intensity = 2; + int reverse = ( (style & STYLE_REVERSE) != 0) ^ in_select; + int blink = ( (style & STYLE_BLINK) != 0); + int blink_fast = ( (style & STYLE_BLINK_FAST) != 0); + int cw = vt->cw; + int ch = vt->ch; + if (proportional) + { + if (vt->font_is_mono) + { + ctx_font (ctx, "regular"); + vt->font_is_mono = 0; + } + cw = ctx_glyph_width (ctx, unichar); + } + else + { + if (vt->font_is_mono == 0) + { + ctx_font (ctx, "mono"); + vt->font_is_mono = 1; + if (col > 1) + { + int x = x0; + int new_cw = cw - ( (x % cw) ); + if (new_cw < cw*3/2) + { new_cw += cw; } + cw = new_cw; + } + } + } + float scale_x = 1.0f; + float scale_y = 1.0f; + float offset_y = 0.0f; + if (dw) + { + scale_x = 2.0f; + } + if (dh) + { + scale_y = 2.0f; + } + if (dh == 1) + { + offset_y = 0.5f; + } + else if (dh == -1) + { + offset_y = 0.0f; + } + if (in_smooth_scroll) + { + offset_y -= vt->scroll_offset / (dh?2:1); + } + cw *= scale_x; + if (blink_fast) + { + if ( (vt->blink_state % 2) == 0) + { blink = 1; } + else + { blink = 0; } + } + else if (blink) + { + if ( (vt->blink_state % 10) < 5) + { blink = 1; } + else + { blink = 0; } + } + /* + from the vt100 technical-manual: + + "Reverse characters [..] normally have dim backgrounds with + black characters so that large white spaces have the same impact + on the viewer's eye as the smaller brighter white areas of + normal characters. Bold and reverse asserted together give a + background of normal intensity. Blink applied to nonreverse + characters causes them to alternate between their usual + intensity and the next lower intensity. (Normal characters vary + between normal and dim intensity. Bold characters vary between + bright and normal intensity.) Blink applied to a reverse + character causes that character to alternate between normal and + reverse video representations of that character." + + This is in contrast with how the truth table appears to be + meant used, since it uses a reverse computed as the xor of + the global screen reverse and the reverse attribute of the + cell. + + To fulfil the more asthethic resulting from implementing the + text, and would be useful to show how the on_bright background + mode of the vt100 actually displays the vttest. + + */ + if (on_white) + { + if (bold) + { + bg_intensity = 2; + fg_intensity = blink?1: 0; + } + else if (dim) + { + bg_intensity = 2; + fg_intensity = blink?3: 1; + } + else + { + bg_intensity = 2; + fg_intensity = blink?1: 0; + } + if (fg_set) + { + fg_intensity = blink?2:3; + } + } + else /* bright on dark */ + { + if (bold) + { + bg_intensity = 0; + fg_intensity = blink?2: 3; + } + else if (dim) + { + bg_intensity = 0; + fg_intensity = blink?0: 1; + } + else + { + bg_intensity = 0; + fg_intensity = blink?1: 2; + } + } + uint8_t bg_rgb[4]= {0,0,0,255}; + uint8_t fg_rgb[4]= {255,255,255,255}; + { + //ctx_begin_path (ctx); + if (style & STYLE_BG24_COLOR_SET) + { + uint64_t temp = style >> 40; + bg_rgb[0] = temp & 0xff; + temp >>= 8; + bg_rgb[1] = temp & 0xff; + temp >>= 8; + bg_rgb[2] = temp & 0xff; +#if 0 + if (dh) + { + bg_rgb[0] = + bg_rgb[1] = + bg_rgb[2] = 30; + } +#endif + } + else + { + if (style & STYLE_BG_COLOR_SET) + { + color = (style >> 40) & 255; + bg_intensity = -1; + vt_ctx_get_color (vt, color, bg_intensity, bg_rgb); + } + else + { + switch (bg_intensity) + { + case 0: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i]; } + break; + case 1: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; } + break; + case 2: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i] * 0.05 + vt->fg_color[i] * 0.95; } + break; + case 3: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->fg_color[i]; } + break; + } + } + } + } + if (style & STYLE_FG24_COLOR_SET) + { + uint64_t temp = style >> 16; + fg_rgb[0] = temp & 0xff; + temp >>= 8; + fg_rgb[1] = temp & 0xff; + temp >>= 8; + fg_rgb[2] = temp & 0xff; + } + else + { + if ( (style & STYLE_FG_COLOR_SET) == 0) + { + switch (fg_intensity) + { + case 0: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.7 + vt->fg_color[i] * 0.3; } + break; + case 1: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; } + break; + case 2: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.20 + vt->fg_color[i] * 0.80; } + break; + case 3: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->fg_color[i]; } + } + } + else + { + color = (style >> 16) & 255; + vt_ctx_get_color (vt, color, fg_intensity, fg_rgb); + } + } + + if (reverse) + { + for (int c = 0; c < 3; c ++) + { + int t = bg_rgb[c]; + bg_rgb[c] = fg_rgb[c]; + fg_rgb[c] = t; + } + } + + if (is_fg || + ((!on_white) && bg_rgb[0]==0 && bg_rgb[1]==0 && bg_rgb[2]==0) || + ((on_white) && bg_rgb[0]==255 && bg_rgb[1]==255 && bg_rgb[2]==255)) + /* these comparisons are not entirely correct, when on dark background we assume black to + * be default and non-set, even when theme might differ + */ + { + /* skipping draw of background */ + } + else + { + if (dh) + { + vt_draw_bg (vt, ctx, ctx_floorf(x0), + ctx_floorf(y0 - ch - ch * (vt->scroll_offset)), cw, ch, bg_rgb); + } + else + { + vt_draw_bg (vt, ctx, x0, y0 - ch + ch * offset_y, cw, ch, bg_rgb); + } + } + + if (!is_fg) + return cw; + + int italic = (style & STYLE_ITALIC) != 0; + int strikethrough = (style & STYLE_STRIKETHROUGH) != 0; + int overline = (style & STYLE_OVERLINE) != 0; + int underline = (style & STYLE_UNDERLINE) != 0; + int underline_var = (style & STYLE_UNDERLINE_VAR) != 0; + if (dh == 1) + { + underline = underline_var = 0; + } + int double_underline = 0; + int curved_underline = 0; + if (underline_var) + { + if (underline) + { + double_underline = 1; + } + else + { + curved_underline = 1; + } + } + + int has_underline = (underline || double_underline || curved_underline); + + if (unichar == ' ' && !has_underline) + is_hidden = 1; + + if (!is_hidden) + { + + ctx_rgba8 (ctx, fg_rgb[0], fg_rgb[1], fg_rgb[2], 255); + + + if (italic) + { + ctx_save (ctx); + ctx_translate (ctx, (x0 + cw/3), (y0 + vt->ch/2) ); + ctx_scale (ctx, 0.9, 0.9); + ctx_rotate (ctx, 0.15); + ctx_translate (ctx, - (x0 + cw/3), - (y0 + vt->ch/2) ); + } + vt_ctx_glyph (ctx, vt, x0, y0, unichar, bold, scale_x, scale_y, offset_y); + if (italic) + { + ctx_restore (ctx); + } + if (curved_underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05); + ctx_rel_line_to (ctx, (cw+2) /3, vt->ch * 0.1); + ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05); + //ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.050:0.04) ); + ctx_stroke (ctx); + } + else if (double_underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.130 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.030 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.050:0.04) ); + ctx_stroke (ctx); + } + else if (underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + if (overline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.94 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + if (strikethrough) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.43 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + } + return cw; +} + +int vt_has_blink (VT *vt) +{ + if (!vt) return 0; + return (vt->in_smooth_scroll ? 10 : 0); + //return vt->has_blink + (vt->in_smooth_scroll ? 10 : 0); +} + +//extern int enable_terminal_menu; +// + +//void ctx_set_popup (Ctx *ctx, void (*popup)(Ctx *ctx, void *data), void *popup_data); + +static char *primary = NULL; +static void scrollbar_drag (CtxEvent *event, void *data, void *data2); +static int scrollbar_down = 0; + +void vt_mouse_event (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + CtxClient *client = vt_get_client (vt); + float x = event->x; + float y = event->y; + int device_no = event->device_no; + char buf[128]=""; + if ((!vt->in_alt_screen) && + (event->x > vt->width - vt->cw * 1.5 || scrollbar_down) && + (event->type == CTX_DRAG_MOTION || + event->type == CTX_DRAG_PRESS || + event->type == CTX_DRAG_RELEASE)) + return scrollbar_drag (event, data, data2); + switch (event->type) + { + case CTX_MOTION: + case CTX_DRAG_MOTION: + //if (event->device_no==1) + { + sprintf (buf, "mouse-motion %.0f %.0f %i", x, y, device_no); +// ctx_set_dirty (event->ctx, 1); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); +// vt->rev++; + } + break; + case CTX_DRAG_PRESS: + if (event->device_no==2) + { + if (primary) + { + if (vt) + vt_paste (vt, primary); + } + } + else if (event->device_no==3 && !vt->in_alt_screen) + { + vt->popped = 1; + } + else + { + sprintf (buf, "mouse-press %.0f %.0f %i", x, y, device_no); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); +// ctx_set_dirty (event->ctx, 1); +// vt->rev++; + } + break; + case CTX_DRAG_RELEASE: + if (event->device_no==3 && !vt->in_alt_screen) + { + vt->popped = 0; + } + ctx_set_dirty (event->ctx, 1); + sprintf (buf, "mouse-release %.0f %.0f %i", x, y, device_no); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); + break; + default: + // we should not stop propagation + return; + break; + } + event->stop_propagate = 1; +//vt->rev++; +} +static int scrollbar_focused = 0; +#if 0 +static void scrollbar_enter (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + vt->rev++; + scrollbar_focused = 1; +} + +static void scrollbar_leave (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + vt->rev++; + scrollbar_focused = 0; +} +#endif + +static void scrollbar_drag (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + float disp_lines = vt->rows; + float tot_lines = vt->line_count + vt->scrollback_count; + + vt->scroll = tot_lines - disp_lines - (event->y*1.0/ ctx_client_height (vt->id)) * tot_lines + disp_lines/2; + if (vt->scroll < 0) { vt->scroll = 0.0; } + if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; } + vt->rev++; + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; + + switch (event->type) + { + case CTX_DRAG_PRESS: + scrollbar_down = 1; + break; + case CTX_DRAG_RELEASE: + scrollbar_down = 0; + break; + default: + break; + } +} + +#if 0 +static void scroll_handle_drag (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + float tot_lines = vt->line_count + vt->scrollback_count; + if (event->type == CTX_DRAG_MOTION) + { + vt->scroll -= (event->delta_y * tot_lines) / (vt->rows * vt->ch); + } + if (vt->scroll < 0) { vt->scroll = 0.0; } + if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; } + vt->rev++; + event->stop_propagate = 1; +} +#endif + +#if 0 +static void test_popup (Ctx *ctx, void *data) +{ + VT *vt = data; + + float x = ctx_client_x (vt->id); + float y = ctx_client_y (vt->id); + ctx_rectangle (ctx, x, y, 100, 100); + ctx_rgb (ctx, 1,0,0); + ctx_fill (ctx); +} +#endif + +void itk_style_color (Ctx *ctx, const char *name); // only itk fun used in vt + +void vt_use_images (VT *vt, Ctx *ctx) +{ + /* this is a call intended for minimized/shaded fully obscured + * clients to make sure their textures are kept alive + * in the server + */ + //float x0=0; + float y0=0; + //vt->has_blink = 0; + //vt->blink_state++; + + ctx_save (ctx); + + { + /* draw graphics */ + for (int row = ((vt->scroll!=0.0f)?vt->scroll:0); + row < (vt->scroll) + vt->rows * 4; + row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + + if (row >= vt->rows && !vt->in_alt_screen) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + if (line->ctx_copy) + { + ctx_render_ctx_textures (line->ctx_copy, ctx); + } + } + } + } + ctx_restore (ctx); +} + + +void vt_register_events (VT *vt, Ctx *ctx, double x0, double y0) +{ + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_translate (ctx, x0, y0); + ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch); + ctx_listen (ctx, CTX_DRAG, vt_mouse_event, vt, NULL); + ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL); + ctx_begin_path (ctx); + ctx_restore (ctx); +} + +void vt_draw (VT *vt, Ctx *ctx, double x0, double y0) +{ + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_translate (ctx, x0, y0); + x0 = 0; + y0 = 0; + ctx_font (ctx, "mono"); + vt->font_is_mono = 0; + ctx_font_size (ctx, vt->font_size * vt->font_to_cell_scale); + vt->has_blink = 0; + vt->blink_state++; +#if 0 + int cursor_x_px = 0; + int cursor_y_px = 0; + int cursor_w = vt->cw; + int cursor_h = vt->ch; + cursor_x_px = x0 + (vt->cursor_x - 1) * vt->cw; + cursor_y_px = y0 + (vt->cursor_y - 1) * vt->ch; + cursor_w = vt->cw; + cursor_h = vt->ch; +#endif + ctx_save (ctx); + //if (vt->scroll || full) + { + ctx_begin_path (ctx); +#if 1 + ctx_rectangle (ctx, 0, 0, vt->width, //(vt->cols) * vt->cw, + (vt->rows) * vt->ch); + if (vt->reverse_video) + { + itk_style_color (ctx, "terminal-bg-reverse"); + ctx_fill (ctx); + } + else + { + itk_style_color (ctx, "terminal-bg"); + ctx_fill (ctx); + } +#endif + if (vt->scroll != 0.0f) + ctx_translate (ctx, 0.0, vt->ch * vt->scroll); + } + /* draw terminal lines */ + { + for (int row = (vt->scroll!=0.0f)?vt->scroll:0; row < (vt->scroll) + vt->rows; row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + if (row >= vt->rows) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + int r = vt->rows - row; + const char *data = line->string.str; + + vt->bg_active = 0; + for (int is_fg = 0; is_fg < 2; is_fg++) + { + const char *d = data; + float x = x0; + uint64_t style = 0; + uint32_t unichar = 0; + int in_scrolling_region = vt->in_smooth_scroll && + ((r >= vt->margin_top && r <= vt->margin_bottom) || r <= 0); + if (is_fg) + vt_flush_bg (vt, ctx); + + for (int col = 1; col <= vt->cols * 1.33 && x < vt->cols * vt->cw; col++) + { + int c = col; + int real_cw; + int in_selected_region = 0; + //if (vt->in_alt_screen == 0) + { + if (r > vt->select_start_row && r < vt->select_end_row) + { + in_selected_region = 1; + } + else if (r == vt->select_start_row) + { + if (col >= vt->select_start_col) { in_selected_region = 1; } + if (r == vt->select_end_row) + { + if (col > vt->select_end_col) { in_selected_region = 0; } + } + } + else if (r == vt->select_end_row) + { + in_selected_region = 1; + if (col > vt->select_end_col) { in_selected_region = 0; } + } + } + if (vt->select_active == 0) in_selected_region = 0; + style = vt_line_get_style (line, col-1); + unichar = d?ctx_utf8_to_unichar (d) :' '; + + int is_cursor = 0; + if (vt->cursor_x == col && vt->cursor_y == vt->rows - row && vt->cursor_visible) + is_cursor = 1; + + real_cw=vt_draw_cell (vt, ctx, r, c, x, y, style, unichar, + line->double_width, + line->double_height_top?1: + line->double_height_bottom?-1:0, + in_scrolling_region, + in_selected_region ^ is_cursor, is_fg); + if (r == vt->cursor_y && col == vt->cursor_x) + { +#if 0 + cursor_x_px = x; +#endif + } + x+=real_cw; + if (style & STYLE_BLINK || + style & STYLE_BLINK_FAST) + { + vt->has_blink = 1; + } + if (d) + { + d = mrg_utf8_skip (d, 1); + if (!*d) { d = NULL; } + } + } + } +#if 0 + if (line->wrapped) + { + ctx_rectangle (ctx, x0, y, 10, 10); + ctx_rgb (ctx, 1,0,0); + ctx_fill (ctx); + } +#endif + } + } + } + +#if 0 + /* draw cursor (done inline with fg/bg reversing, some cursor styles might need + * additional drawing though + */ + if (vt->cursor_visible) + { + // ctx_rgba (ctx, 0.9, 0.8, 0.0, 0.5333); + ctx_rgba (ctx, 1.0,1.0,1.0,1.0); + ctx_begin_path (ctx); + ctx_rectangle (ctx, + cursor_x_px, cursor_y_px, + cursor_w, cursor_h); + ctx_fill (ctx); + } +#endif + + + { + /* draw graphics */ + for (int row = ((vt->scroll!=0.0f)?vt->scroll:0); row < (vt->scroll) + vt->rows * 4; row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + + if (row >= vt->rows && !vt->in_alt_screen) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + { + for (int i = 0; i < 4; i++) + { + Image *image = line->images[i]; + if (image) + { + int u = (line->image_col[i]-1) * vt->cw + (line->image_X[i] * vt->cw); + int v = y - vt->ch + (line->image_Y[i] * vt->ch); + // int rows = (image->height + (vt->ch-1) ) /vt->ch; + // + // + if (v + image->height +vt->scroll * vt->ch > 0.0 && + image->width && image->height /* some ghost images appear with these */ + ) + { + ctx_save (ctx); + ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols, + vt->ch * vt->rows); + ctx_clip (ctx); + char texture_n[65]; + + sprintf (texture_n, "vtimg%i", image->eid_no); + ctx_rectangle (ctx, u, v, image->width, image->height); + ctx_translate (ctx, u, v); + + //replace this texture_n with NULL to + // be content addressed - but bit slower + ctx_define_texture (ctx, texture_n, image->width, + image->height, + 0, + image->kitty_format == 32 ? + CTX_FORMAT_RGBA8 : + CTX_FORMAT_RGB8, + image->data, texture_n); + ctx_fill (ctx); + + ctx_restore (ctx); + } + } + } + + if (line->ctx_copy) + { + //fprintf (stderr, " [%i]\n", _ctx_frame (ctx)); + //ctx_render_stream (line->ctx_copy, stderr, 1); + + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols, + vt->ch * vt->rows); + ctx_clip (ctx); + ctx_translate (ctx, 0.0, y - vt->ch); + + //(vt->rows-row-1) * (vt->ch) ); + //float factor = vt->cols * vt->cw / 1000.0; + //ctx_scale (ctx, factor, factor); + // + ctx_render_ctx (line->ctx_copy, ctx); + ctx_restore (ctx); + } + } + } + // y -= vt->ch; + } + } + + + for (int i = 0; i < 4; i++) + { + if (vt->leds[i]) + { + ctx_rgba (ctx, .5,1,.5,0.8); + ctx_rectangle (ctx, vt->cw * i + vt->cw * 0.25, vt->ch * 0.25, vt->cw/2, vt->ch/2); + ctx_fill (ctx); + } + } + ctx_restore (ctx); +//#define SCROLL_SPEED 0.25; +#define SCROLL_SPEED 0.001; + if (vt->in_smooth_scroll) + { + if (vt->in_smooth_scroll<0) + { + vt->scroll_offset += SCROLL_SPEED; + if (vt->scroll_offset >= 0.0) + { + vt->scroll_offset = 0; + vt->in_smooth_scroll = 0; + vt->rev++; + } + } + else + { + vt->scroll_offset -= SCROLL_SPEED; + if (vt->scroll_offset <= 0.0) + { + vt->scroll_offset = 0; + vt->in_smooth_scroll = 0; + vt->rev++; + } + } + } + + /* scrollbar */ + if (!vt->in_alt_screen) + { + float disp_lines = vt->rows; + float tot_lines = vt->line_count + vt->scrollback_count; + float offset = (tot_lines - disp_lines - vt->scroll) / tot_lines; + float win_len = disp_lines / tot_lines; + +#if 0 + ctx_rectangle (ctx, (vt->cols *vt->cw), 0, + (vt->width) - (vt->cols * vt->cw), + vt->rows * vt->ch); + ctx_rgb (ctx,1,0,0); + ctx_fill (ctx); +#endif + + ctx_rectangle (ctx, (vt->width) - vt->cw * 1.5, + 0, 1.5 * vt->cw, + vt->rows * vt->ch); + //ctx_listen (ctx, CTX_DRAG, scrollbar_drag, vt, NULL); + //ctx_listen (ctx, CTX_ENTER, scrollbar_enter, vt, NULL); + //ctx_listen (ctx, CTX_LEAVE, scrollbar_leave, vt, NULL); + if (vt->scroll != 0 || scrollbar_focused) + ctx_rgba (ctx, 0.5, 0.5, 0.5, .25); + else + ctx_rgba (ctx, 0.5, 0.5, 0.5, .10); + ctx_fill (ctx); + ctx_round_rectangle (ctx, (vt->width) - vt->cw * 1.5, + offset * vt->rows * vt->ch, (1.5-0.2) * vt->cw, + win_len * vt->rows * vt->ch, + vt->cw * 1.5 /2); + //ctx_listen (ctx, CTX_DRAG, scroll_handle_drag, vt, NULL); + if (vt->scroll != 0 || scrollbar_focused) + ctx_rgba (ctx, 1, 1, 1, .25); + else + ctx_rgba (ctx, 1, 1, 1, .10); + ctx_fill (ctx); + } + + ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch); + ctx_listen (ctx, CTX_DRAG, vt_mouse_event, vt, NULL); + ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL); + ctx_begin_path (ctx); + + ctx_restore (ctx); + + if (vt->popped) + { + //ctx_set_popup (ctx, test_popup, vt); + } +} + + +int vt_is_done (VT *vt) +{ + return vt->vtpty.done; +} + +int vt_get_result (VT *vt) +{ + /* we could block - at least for a while, here..? */ + return vt->result; +} + +void vt_set_scrollback_lines (VT *vt, int scrollback_lines) +{ + vt->scrollback_limit = scrollback_lines; +} + +int vt_get_scrollback_lines (VT *vt) +{ + return vt->scrollback_limit; +} + +void vt_set_scroll (VT *vt, int scroll) +{ + if (vt->scroll == scroll) + return; + vt->scroll = scroll; + if (vt->scroll > ctx_list_length (vt->scrollback) ) + { vt->scroll = ctx_list_length (vt->scrollback); } + if (vt->scroll < 0) + { vt->scroll = 0; } +} + +int vt_get_scroll (VT *vt) +{ + return vt->scroll; +} + +char * +vt_get_selection (VT *vt) +{ + CtxString *str = ctx_string_new (""); + char *ret; + for (int row = vt->select_start_row; row <= vt->select_end_row; row++) + { + const char *line_str = vt_get_line (vt, vt->rows - row); + int col = 1; + for (const char *c = line_str; *c; c = mrg_utf8_skip (c, 1), col ++) + { + if (row == vt->select_end_row && col > vt->select_end_col) + { continue; } + if (row == vt->select_start_row && col < vt->select_start_col) + { continue; } + ctx_string_append_utf8char (str, c); + } + if (row < vt->select_end_row && !vt_line_is_continuation (vt, vt->rows-row-1)) + { + _ctx_string_append_byte (str, '\n'); + } + } + ret = str->str; + ctx_string_free (str, 0); + return ret; +} + +int vt_get_local (VT *vt) +{ + return vt->local_editing; +} + +void vt_set_local (VT *vt, int local) +{ + vt->local_editing = local; +} + +static unsigned long prev_press_time = 0; +static int short_count = 0; + + +void terminal_set_primary (const char *text) +{ + if (primary) free (primary); + primary = NULL; + if (text) primary = strdup (text); +} + +void terminal_long_tap (Ctx *ctx, VT *vt); +static int long_tap_cb_id = 0; +static int single_tap (Ctx *ctx, void *data) +{ +#if 0 // XXX + VT *vt = data; + if (short_count == 0 && !vt->select_active) + terminal_long_tap (ctx, vt); +#endif + return 0; +} + +void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y) +{ + char buf[64]=""; + int button_state = 0; + vt->rev++; + ctx_ticks(); + if ((! (vt->mouse | vt->mouse_all | vt->mouse_drag)) || + (event && (event->state & CTX_MODIFIER_STATE_SHIFT))) + { + // regular mouse select, this is incomplete + // fully ignorant of scrollback for now + // + if (type == VT_MOUSE_PRESS) + { + vt->cursor_down = 1; + vt->select_begin_col = x; + vt->select_begin_row = y - (int)vt->scroll; + vt->select_start_col = x; + vt->select_start_row = y - (int)vt->scroll; + vt->select_end_col = x; + vt->select_end_row = y - (int)vt->scroll; + vt->select_active = 0; + if (long_tap_cb_id) + { + ctx_remove_idle (vt->root_ctx, long_tap_cb_id); + long_tap_cb_id = 0; + } + + if ((ctx_ticks () - prev_press_time) < 1000*300 && + abs(px_x - vt->select_begin_x) + + abs(px_y - vt->select_begin_y) < 8) + { + short_count++; + switch (short_count) + { + case 1: + { + /* extend selection until space, XXX should handle utf8 instead of ascii here! */ + + int hit_space = 0; + + while (vt->select_start_col > 1 && !hit_space) + { + vt->select_start_col --; + char *sel = vt_get_selection (vt); + if (sel[0] == ' ' || sel[0] == '\0') + hit_space = 1; + free (sel); + } + if (hit_space) + vt->select_start_col++; + + hit_space = 0; + while ((hit_space == 0) && + (vt->select_end_col < vt->cols)) + { + vt->select_end_col ++; + char *sel = vt_get_selection (vt); + int len = strlen(sel); + if (sel[len-1]==' ') + hit_space = 1; + free (sel); + } + if (hit_space) + vt->select_end_col--; + + vt->select_active = 1; + + { char *sel = vt_get_selection (vt); + if (sel) + { + terminal_set_primary (sel); + free (sel); + } + } + } + break; + case 2: + vt->select_start_col = 1; + vt->select_end_col = vt->cols; + vt->select_active = 1; + { + char *sel = vt_get_selection (vt); + if (sel){ + terminal_set_primary (sel); + free (sel); + } + } + break; + case 3: + short_count = 0; + vt->select_start_col = + vt->select_end_col = vt->select_begin_col; + vt->select_active = 0; + terminal_set_primary (""); + break; + } + } + else + { + if (vt->root_ctx && short_count == 0) + long_tap_cb_id = ctx_add_timeout (vt->root_ctx, 1000, single_tap, vt); + short_count = 0; + //vt->select_start_col = + //vt->select_end_col = vt->select_begin_col; + } + vt->select_begin_x = px_x; + vt->select_begin_y = px_y; + prev_press_time = ctx_ticks (); + vt->rev++; + } + else if (type == VT_MOUSE_RELEASE) + { + if (long_tap_cb_id) + { + ctx_remove_idle (vt->root_ctx, long_tap_cb_id); + long_tap_cb_id = 0; + } + vt->cursor_down = 0; + } + else if (type == VT_MOUSE_MOTION && vt->cursor_down) + { + int row = y - (int)vt->scroll; + int col = x; + if ((row > vt->select_begin_row) || + ((row == vt->select_begin_row) && (col >= vt->select_begin_col))) + { + vt->select_start_col = vt->select_begin_col; + vt->select_start_row = vt->select_begin_row; + vt->select_end_col = col; + vt->select_end_row = row; + } + else + { + vt->select_start_col = col; + vt->select_start_row = row; + vt->select_end_col = vt->select_begin_col; + vt->select_end_row = vt->select_begin_row; + } + if (vt->select_end_row == vt->select_start_row && + abs (vt->select_begin_x - px_x) < vt->cw/2) + { + vt->select_active = 0; + } + else + { + vt->select_active = 1; + char *selection = vt_get_selection (vt); + if (selection) + { + terminal_set_primary (selection); + free (selection); + } + } + + if (y < 1) + { + vt->scroll += 1.0f; + if (vt->scroll > vt->scrollback_count) + vt->scroll = vt->scrollback_count; + } + else if (y > vt->rows) + { + vt->scroll -= 1.0f; + if (vt->scroll < 0) + vt->scroll = 0.0f; + } + + vt->rev++; + } + return; + } + if (type == VT_MOUSE_MOTION) + { button_state = 3; } + + if (vt->unit_pixels && vt->mouse_decimal) + { + x = px_x; + y = px_y; + } + switch (type) + { + case VT_MOUSE_MOTION: + if (!vt->mouse_all) + return; + if (x==vt->lastx && y==vt->lasty) + return; + vt->lastx = x; + vt->lasty = y; + // sprintf (buf, "\033[<35;%i;%iM", x, y); + break; + case VT_MOUSE_RELEASE: + if (vt->mouse_decimal == 0) + button_state = 3; + break; + case VT_MOUSE_PRESS: + button_state = 0; + break; + case VT_MOUSE_DRAG: // XXX not really used - remove + if (! (vt->mouse_all || vt->mouse_drag) ) + return; + button_state = 32; + break; + } + // todo : mix in ctrl/meta state + if (vt->mouse_decimal) + { + sprintf (buf, "\033[<%i;%i;%i%c", button_state, x, y, type == VT_MOUSE_RELEASE?'m':'M'); + } + else + { + sprintf (buf, "\033[M%c%c%c", button_state + 32, x + 32, y + 32); + } + if (buf[0]) + { + vt_write (vt, buf, strlen (buf) ); + fflush (NULL); + } +} + +pid_t vt_get_pid (VT *vt) +{ + return vt->vtpty.pid; +} + +void vt_set_ctx (VT *vt, Ctx *ctx) +{ + vt->root_ctx = ctx; +} +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#if !__COSMOPOLITAN__ +#include <unistd.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <pty.h> +#include <math.h> +#include <malloc.h> +#include <sys/time.h> +#include <time.h> +#endif + +#define VT_RECORD 0 +extern Ctx *ctx; +#define flag_is_set(a, f) (((a) & (f))!=0) +#define flag_set(a, f) ((a) |= (f)); +#define flag_unset(a, f) ((a) &= ~(f)); + +//#define CTX_x CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0) +//#define CTX_y CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0) +#define CTX_lower CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0) +#define CTX_raise CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0) +#define CTX_raise_top CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0) +#define CTX_terminate CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0) +#define CTX_maximize CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0) +#define CTX_unmaximize CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0) +//#define CTX_width CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0) +//#define CTX_title CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0) +#define CTX_title 15643372 +#define CTX_action CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0) +//#define CTX_height CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0) + +void terminal_update_title (const char *title); +int ctx_renderer_is_sdl (Ctx *ctx); +int ctx_renderer_is_fb (Ctx *ctx); +int ctx_renderer_is_term (Ctx *ctx); +void ctx_sdl_set_fullscreen (Ctx *ctx, int val); +int ctx_sdl_get_fullscreen (Ctx *ctx); +float ctx_target_fps = 25.0; +static int ctx_fetched_bytes = 1; + +CtxClient *vt_get_client (VT *vt); + +CtxList *vts = NULL; + +void ctx_clients_signal_child (int signum) +{ + pid_t pid; + int status; + if ( (pid = waitpid (-1, &status, WNOHANG) ) != -1) + { + if (pid) + { + for (CtxList *l = vts; l; l=l->next) + { + VtPty *vt = l->data; + if (vt->pid == pid) + { + vt->done = 1; + //vt->result = status; + } + } + } + } +} + + + +int vt_set_prop (VT *vt, uint32_t key_hash, const char *val) +{ +#if 1 + switch (key_hash) + { + case CTX_title: + { + CtxClient *client = vt_get_client (vt); + if (client) + { + if (client->title) free (client->title); + client->title = strdup (val); + } + } + + break; + } +#else + float fval = strtod (val, NULL); + CtxClient *client = ctx_client_by_id (ct->id); + uint32_t val_hash = ctx_strhash (val, 0); + if (!client) + return 0; + + if (key_hash == ctx_strhash("start_move", 0)) + { + start_moving (client); + moving_client = 1; + return 0; + } + +// set "pcm-hz" "8000" +// set "pcm-bits" "8" +// set "pcm-encoding" "ulaw" +// set "play-pcm" "d41ata312313" +// set "play-pcm-ref" "foo.wav" + +// get "free" +// storage of blobs for referencing when drawing or for playback +// set "foo.wav" "\3\1\1\4\" +// set "fnord.png" "PNG12.4a312" + + switch (key_hash) + { + case CTX_title: ctx_client_set_title (ct->id, val); break; + case CTX_x: client->x = fval; break; + case CTX_y: client->y = fval; break; + case CTX_width: ctx_client_resize (ct->id, fval, client->height); break; + case CTX_height: ctx_client_resize (ct->id, client->width, fval); break; + case CTX_action: + switch (val_hash) + { + case CTX_maximize: ctx_client_maximize (client); break; + case CTX_unmaximize: ctx_client_unmaximize (client); break; + case CTX_lower: ctx_client_lower (client); break; + case CTX_lower_bottom: ctx_client_lower_bottom (client); break; + case CTX_raise: ctx_client_raise (client); break; + case CTX_raise_top: ctx_client_raise_top (client); break; + } + break; + } + ct->rev++; +#endif + return 0; +} + +static float _ctx_font_size = 10.0; + +CtxList *clients = NULL; +CtxClient *active = NULL; +CtxClient *active_tab = NULL; + +static CtxClient *ctx_client_by_id (int id); + +int ctx_client_resize (int id, int width, int height); +void ctx_client_maximize (int id); + +CtxClient *vt_get_client (VT *vt) +{ + for (CtxList *l = clients; l; l =l->next) + { + CtxClient *client = l->data; + if (client->vt == vt) + return client; + } + return NULL; +} + +CtxClient *ctx_client_new (Ctx *ctx, + const char *commandline, + int x, int y, int width, int height, + CtxClientFlags flags) +{ + static int global_id = 0; + float font_size = ctx_get_font_size (ctx); + CtxClient *client = calloc (sizeof (CtxClient), 1); + ctx_list_append (&clients, client); + client->id = global_id++; + client->x = x; + client->y = y; + client->flags = flags; + client->ctx = ctx; + client->width = width; + client->height = height; + + if (ctx_renderer_is_term (ctx)) + { + font_size = 3; + } + + //fprintf (stderr, "client new:%f\n", font_size); +#if CTX_THREADS + mtx_init (&client->mtx, mtx_plain); +#endif + float line_spacing = 2.0f; + client->vt = vt_new (commandline, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0); + vt_set_ctx (client->vt, ctx); + return client; +} + +CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags) +{ + CtxString *string = ctx_string_new (""); + for (int i = 0; argv[i]; i++) + { + char space = ' '; + if (i > 0) + ctx_string_append_data (string, &space, 1); + for (int c = 0; argv[i][c]; c++) + { + switch (argv[i][c]) + { + case '"':ctx_string_append_str (string, "\\\"");break; + case '\'':ctx_string_append_str (string, "\\\'");break; + default:ctx_string_append_data (string, &argv[i][c], 1);break; + } + } + } + CtxClient *ret = ctx_client_new (ctx, string->str, x, y, width, height, flags); + ctx_string_free (string, 1); + return ret; +} + +extern float ctx_shape_cache_rate; +extern int _ctx_max_threads; + +static int focus_follows_mouse = 0; + +static CtxClient *find_active (int x, int y) +{ + CtxClient *ret = NULL; + float titlebar_height = _ctx_font_size; + int resize_border = titlebar_height/2; + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *c = l->data; + if ((c->flags & ITK_CLIENT_MAXIMIZED) && c == active_tab) + if (x > c->x - resize_border && x < c->x+c->width + resize_border && + y > c->y - titlebar_height && y < c->y+c->height + resize_border) + { + ret = c; + } + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *c = l->data; + if (!(c->flags & ITK_CLIENT_MAXIMIZED)) + if (x > c->x - resize_border && x < c->x+c->width + resize_border && + y > c->y - titlebar_height && y < c->y+c->height + resize_border) + { + ret = c; + } + } + return ret; +} + +int id_to_no (int id) +{ + CtxList *l; + int no = 0; + + for (l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->id == id) + return no; + no++; + } + return -1; +} + +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int w, int h); +void ctx_client_shade_toggle (int id); +float ctx_client_min_y_pos (Ctx *ctx); +float ctx_client_max_y_pos (Ctx *ctx); + +#if 0 +void ensure_layout () +{ + int n_clients = ctx_list_length (clients); + if (n_clients == 1) + { + CtxClient *client = clients->data; + if (client->flags & ITK_CLIENT_MAXIMIZED) + { + ctx_client_move (client->id, 0, 0); + ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx)); + if (active_tab == NULL) + active_tab = client; + } + } + else + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->flags & ITK_CLIENT_MAXIMIZED) + { + ctx_client_move (client->id, 0, client_min_y_pos (ctx)); + ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx) - + ctx_client_min_y_pos (ctx) / 2); // /2 to counter the double titlebar of non-maximized + if (active_tab == NULL) + active_tab = client; + } + } +} +#endif + +static CtxClient *ctx_client_by_id (int id) +{ + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->id == id) + return client; + } + return NULL; +} + +void ctx_client_remove (Ctx *ctx, CtxClient *client) +{ + ctx_client_lock (client); + if (!client->internal) + { + + if (client->vt) + vt_destroy (client->vt); + } + + if (client->title) + free (client->title); + +#if VT_RECORD + if (client->recording) + ctx_free (client->recording); +#endif + + ctx_list_remove (&clients, client); + + if (client == active_tab) + { + active_tab = NULL; + } + + if (ctx) + if (client == active) + { + active = find_active (ctx_pointer_x (ctx), ctx_pointer_y (ctx)); + if (!active) active = clients?clients->data:NULL; + } + + ctx_client_unlock (client); + free (client); + //ensure_layout(); +} + +#if 0 +void ctx_client_remove_by_id (int id) +{ + int no = id_to_no (id); + if (no>=0) + ctx_client_remove (no); +} +#endif + +int ctx_client_height (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->height; +} + +int ctx_client_x (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->x; +} + +int ctx_client_y (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->y; +} + +void ctx_client_raise_top (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + ctx_list_remove (&clients, client); + ctx_list_append (&clients, client); +} + +void ctx_client_lower_bottom (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + ctx_list_remove (&clients, client); + ctx_list_prepend (&clients, client); +} + + +void ctx_client_iconify (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags |= ITK_CLIENT_ICONIFIED; +} + +int ctx_client_is_iconified (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_ICONIFIED) != 0; +} + +void ctx_client_uniconify (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags &= ~ITK_CLIENT_ICONIFIED; +} + +void ctx_client_maximize (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (client->flags & ITK_CLIENT_MAXIMIZED) + return; + client->flags |= ITK_CLIENT_MAXIMIZED; + client->unmaximized_x = client->x; + client->unmaximized_y = client->y; + client->unmaximized_width = client->width; + client->unmaximized_height = client->height; + + // enforce_layout does the size + //client_resize (id, ctx_width (ctx), ctx_height(ctx) - ctx_client_min_y_pos (ctx)); + + ctx_client_move (id, 0, ctx_client_min_y_pos (client->ctx)); + active_tab = client; +} + +int ctx_client_is_maximized (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_MAXIMIZED) != 0; +} + +void ctx_client_unmaximize (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if ((client->flags & ITK_CLIENT_MAXIMIZED) == 0) + return; + client->flags &= ~ITK_CLIENT_MAXIMIZED; + ctx_client_resize (id, client->unmaximized_width, client->unmaximized_height); + ctx_client_move (id, client->unmaximized_x, client->unmaximized_y); + active_tab = NULL; +} + +void ctx_client_maximized_toggle (int id) +{ + if (ctx_client_is_maximized (id)) + ctx_client_unmaximize (id); + else + ctx_client_maximize (id); +} + + +void ctx_client_shade (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags |= ITK_CLIENT_SHADED; +} + +int ctx_client_is_shaded (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_SHADED) != 0; +} + +void ctx_client_unshade (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags &= ~ITK_CLIENT_SHADED; +} + +void ctx_client_toggle_maximized (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (ctx_client_is_maximized (id)) + ctx_client_unmaximize (id); + else + ctx_client_maximize (id); +} + +void ctx_client_shade_toggle (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (ctx_client_is_shaded (id)) + ctx_client_shade (id); + else + ctx_client_unshade (id); +} + +void ctx_client_move (int id, int x, int y) +{ + CtxClient *client = ctx_client_by_id (id); + if (client && (client->x != x || client->y != y)) + { + client->x = x; + client->y = y; + vt_rev_inc (client->vt); + } +} + +int ctx_client_resize (int id, int width, int height) +{ + CtxClient *client = ctx_client_by_id (id); + + if (client && ((height != client->height) || (width != client->width) )) + { + client->width = width; + client->height = height; + if (client->vt) + vt_set_px_size (client->vt, width, height); + return 1; + } + return 0; +} + +static void ctx_client_titlebar_drag (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + if (event->type == CTX_DRAG_RELEASE) + { + static int prev_drag_end_time = 0; + if (event->time - prev_drag_end_time < 500) + { + //client_shade_toggle (client->id); + ctx_client_maximized_toggle (client->id); + } + prev_drag_end_time = event->time; + } + + float new_x = client->x + event->delta_x; + float new_y = client->y + event->delta_y; + + float snap_threshold = 8; + + if (ctx_renderer_is_term (event->ctx)) + snap_threshold = 1; + + if (new_y < ctx_client_min_y_pos (event->ctx)) new_y = ctx_client_min_y_pos (event->ctx); + if (new_y > ctx_client_max_y_pos (event->ctx)) new_y = ctx_client_max_y_pos (event->ctx); + + if (fabs (new_x - 0) < snap_threshold) new_x = 0.0; + if (fabs (ctx_width (event->ctx) - (new_x + client->width)) < snap_threshold) new_x = ctx_width (event->ctx) - client->width; + + ctx_client_move (client->id, new_x, new_y); + + //vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + + event->stop_propagate = 1; +} + +static float min_win_dim = 32; + +static void ctx_client_resize_se (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_w = client->width + event->delta_x; + int new_h = client->height + event->delta_y; + if (new_w <= min_win_dim) new_w = min_win_dim; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_e (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_w = client->width + event->delta_x; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, client->height); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_s (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_h = client->height + event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, client->width, new_h); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_n (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_y = client->y + event->delta_y; + int new_h = client->height - event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, client->width, new_h); + ctx_client_move (client->id, client->x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_ne (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_y = client->y + event->delta_y; + int new_h = client->height - event->delta_y; + int new_w = client->width + event->delta_x; + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, client->x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_sw (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + float new_x = client->x + event->delta_x; + int new_w = client->width - event->delta_x; + int new_h = client->height + event->delta_y; + + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, new_x, client->y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_nw (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_x = client->x + event->delta_x; + float new_y = client->y + event->delta_y; + int new_w = client->width - event->delta_x; + int new_h = client->height - event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, new_x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_w (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + float new_x = client->x + event->delta_x; + int new_w = client->width - event->delta_x; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, client->height); + ctx_client_move (client->id, new_x, client->y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + + event->stop_propagate = 1; +} + +static void ctx_client_close (CtxEvent *event, void *data, void *data2) +{ + //Ctx *ctx = event->ctx; + CtxClient *client = data; + + // client->do_quit = 1; + + ctx_client_remove (event->ctx, client); + + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +/********************/ +void vt_use_images (VT *vt, Ctx *ctx); +float _ctx_green = 0.5; + +static void ctx_client_draw (Ctx *ctx, CtxClient *client, float x, float y) +{ + if (client->internal) + { +#if 0 + ctx_save (ctx); + + ctx_translate (ctx, x, y); + int width = client->width; + int height = client->height; + + itk_panel_start (itk, "", 0, 0, width, height); + //itk_seperator (itk); +#if 0 + if (itk_button (itk, "add tab")) + { + add_tab (vt_find_shell_command(), 1); + } +#endif + //itk_sameline (itk); + //itk_toggle (itk, "on screen keyboard", &on_screen_keyboard); + //itk_toggle (itk, "focus follows mouse", &focus_follows_mouse); + itk_slider_float (itk, "CTX_GREEN", &_ctx_green, 0.0, 1.0, 0.5); + itk_ctx_settings (itk); + itk_itk_settings (itk); + + itk_panel_end (itk); + itk_done (itk); + //itk_key_bindings (itk); + + ctx_restore (ctx); +#endif + } + else + { + ctx_client_lock (client); + + int found = 0; + for (CtxList *l2 = clients; l2; l2 = l2->next) + if (l2->data == client) found = 1; + if (found) + { + + int rev = vt_rev (client->vt); +#if VT_RECORD + if (client->drawn_rev != rev) + { + if (!client->recording) + client->recording = ctx_new (); + else + ctx_reset (client->recording); + vt_draw (client->vt, client->recording, 0.0, 0.0); + } + + if (client->recording) + { + ctx_save (ctx); + ctx_translate (ctx, x, y); + ctx_render_ctx (client->recording, ctx); + vt_register_events (client->vt, ctx, 0.0, 0.0); + ctx_restore (ctx); + } +#else + + vt_draw (client->vt, ctx, x, y); + vt_register_events (client->vt, ctx, x, y); +#endif + client->drawn_rev = rev; + ctx_client_unlock (client); + } + } +} + +static void ctx_client_use_images (Ctx *ctx, CtxClient *client) +{ + if (!client->internal) + { + uint32_t rev = vt_rev (client->vt); +#if VT_RECORD + if (client->drawn_rev != rev) + { + if (!client->recording) + client->recording = ctx_new (); + else + ctx_reset (client->recording); + vt_draw (client->vt, client->recording, 0.0, 0.0); + } + + if (client->recording) + { + ctx_save (ctx); + ctx_render_ctx_textures (client->recording, ctx); + ctx_restore (ctx); + } +#else + vt_use_images (client->vt, ctx); +#endif + client->drawn_rev = rev; + } +} + +void ctx_client_lock (CtxClient *client) +{ +#if CTX_THREADS + mtx_lock (&client->mtx); +#endif +} + +void ctx_client_unlock (CtxClient *client) +{ +#if CTX_THREADS + mtx_unlock (&client->mtx); +#endif +} + +#if 0 +void ctx_client_handle_event (Ctx *ctx, CtxEvent *ctx_event, const char *event) +{ + if (!active) + return; + if (active->internal) + return; + VT *vt = active->vt; + CtxClient *client = vt_get_client (vt); + + ctx_client_lock (client); + + if (!strcmp (event, "F11")) + { +#ifndef NO_SDL + if (ctx_renderer_is_sdl (ctx)) + { + ctx_sdl_set_fullscreen (ctx, !ctx_sdl_get_fullscreen (ctx)); + } +#endif + } + else if (!strcmp (event, "shift-return")) + { + vt_feed_keystring (vt, ctx_event, "return"); + } + else if (!strcmp (event, "shift-control-v") ) + { + char *text = ctx_get_clipboard (ctx); + if (text) + { + if (vt) + vt_paste (vt, text); + free (text); + } + } + else if (!strcmp (event, "shift-control-c") && vt) + { + char *text = vt_get_selection (vt); + if (text) + { + ctx_set_clipboard (ctx, text); + free (text); + } + } + else if (!strcmp (event, "shift-control-t") || + ((ctx_renderer_is_fb (ctx) || ctx_renderer_is_term (ctx)) + && !strcmp (event, "control-t") )) + { + //XXX add_tab (vt_find_shell_command(), 1); + } + else if (!strcmp (event, "shift-control-n") ) + { + pid_t pid; + if ( (pid=fork() ) ==0) + { + unsetenv ("CTX_VERSION"); + // execlp (execute_self, execute_self, NULL); + exit (0); + } + } + +#if 0 + else if (!strcmp (event, "alt-1")) switch_to_tab(0); + else if (!strcmp (event, "alt-2")) switch_to_tab(1); + else if (!strcmp (event, "alt-3")) switch_to_tab(2); + else if (!strcmp (event, "alt-4")) switch_to_tab(3); + else if (!strcmp (event, "alt-5")) switch_to_tab(4); + else if (!strcmp (event, "alt-6")) switch_to_tab(5); + else if (!strcmp (event, "alt-7")) switch_to_tab(6); + else if (!strcmp (event, "alt-8")) switch_to_tab(7); + else if (!strcmp (event, "alt-9")) switch_to_tab(8); + else if (!strcmp (event, "alt-0")) switch_to_tab(9); +#endif + else if (!strcmp (event, "shift-control-q") ) + { + ctx_quit (ctx); + } + else if (!strcmp (event, "shift-control-w") ) + { + active->do_quit = 1; + } + else if (!strcmp (event, "shift-control-s") ) + { + if (vt) + { + char *sel = vt_get_selection (vt); + if (sel) + { + vt_feed_keystring (vt, ctx_event, sel); + free (sel); + } + } + } + else + { + if (vt) + vt_feed_keystring (vt, ctx_event, event); + } + ctx_client_unlock (client); +} +#endif + +static int ctx_clients_dirty_count (void) +{ + int changes = 0; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if ((client->drawn_rev != vt_rev (client->vt) ) || + vt_has_blink (client->vt)) + changes++; + } + return changes; +} + +static void ctx_client_titlebar_drag_maximized (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + active = active_tab = client; + if (event->type == CTX_DRAG_RELEASE) + { + static int prev_drag_end_time = 0; + if (event->time - prev_drag_end_time < 500) + { + //client_shade_toggle (client->id); + ctx_client_unmaximize (client->id); + ctx_client_raise_top (client->id); + active_tab = NULL; + } + prev_drag_end_time = event->time; + } + ctx_set_dirty (event->ctx, 1); + vt_rev_inc (client->vt); + event->stop_propagate = 1; +} + +float ctx_client_min_y_pos (Ctx *ctx) +{ + return _ctx_font_size * 2; // a titlebar and a panel +} + +float ctx_client_max_y_pos (Ctx *ctx) +{ + return ctx_height (ctx); +} + +void ctx_client_titlebar_draw (Ctx *ctx, CtxClient *client, + float x, float y, float width, float titlebar_height) +{ +#if 0 + ctx_move_to (ctx, x, y + height * 0.8); + if (client == active) + ctx_rgba (ctx, 1, 1,0.4, 1.0); + else + ctx_rgba (ctx, 1, 1,1, 0.8); + ctx_text (ctx, client->title); +#else + ctx_rectangle (ctx, x, y - titlebar_height, + width, titlebar_height); + if (client == active) + itk_style_color (ctx, "titlebar-focused-bg"); + else + itk_style_color (ctx, "titlebar-bg"); + + if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y == titlebar_height) + { + ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag_maximized, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL); + } + else + { + ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL); + } + ctx_fill (ctx); + //ctx_font_size (ctx, itk->font_size);//titlebar_height);// * 0.85); + + if (client == active && + (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y != titlebar_height)) +#if 1 + ctx_rectangle (ctx, x + width - titlebar_height, + y - titlebar_height, titlebar_height, + titlebar_height); +#endif + ctx_rgb (ctx, 1, 0,0); + ctx_listen (ctx, CTX_PRESS, ctx_client_close, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_ARROW); + //ctx_fill (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x + width - titlebar_height * 0.8, y - titlebar_height * 0.22); + if (client == active) + itk_style_color (ctx, "titlebar-focused-close"); + else + itk_style_color (ctx, "titlebar-close"); + ctx_text (ctx, "X"); + + ctx_move_to (ctx, x + width/2, y - titlebar_height * 0.22); + if (client == active) + itk_style_color (ctx, "titlebar-focused-fg"); + else + itk_style_color (ctx, "titlebar-fg"); + + ctx_save (ctx); + ctx_text_align (ctx, CTX_TEXT_ALIGN_CENTER); + if (client->title) + ctx_text (ctx, client->title); + else + ctx_text (ctx, "untitled"); + ctx_restore (ctx); +#endif +} + +#if 0 +static void key_down (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "down %i %s\n", event->unicode, event->string); +} +static void key_up (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "up %i %s\n", event->unicode, event->string); +} +static void key_press (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "press %i %s\n", event->unicode, event->string); +} +#endif + +int ctx_clients_draw (Ctx *ctx) +{ + _ctx_font_size = ctx_get_font_size (ctx); + float titlebar_height = _ctx_font_size; + int n_clients = ctx_list_length (clients); + + if (active && flag_is_set(active->flags, ITK_CLIENT_MAXIMIZED) && n_clients == 1) + { + ctx_client_draw (ctx, active, 0, 0); + return 0; + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED)) + { + if (client == active_tab) + { + ctx_client_draw (ctx, client, 0, titlebar_height); + } + else + { + ctx_client_use_images (ctx, client); + } + } + } + + { + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + VT *vt = client->vt; + if (vt && !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED)) + { + if (flag_is_set(client->flags, ITK_CLIENT_SHADED)) + { + ctx_client_use_images (ctx, client); + } + else + { + ctx_client_draw (ctx, client, client->x, client->y); + } + + // resize regions + if (client == active && + !flag_is_set(client->flags, ITK_CLIENT_SHADED) && + !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) && + flag_is_set(client->flags, ITK_CLIENT_UI_RESIZABLE)) + { + itk_style_color (ctx, "titlebar-focused-bg"); + + ctx_rectangle (ctx, + client->x, + client->y - titlebar_height * 2, + client->width, titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_n, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_N); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x, + client->y + client->height - titlebar_height, + client->width, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_s, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_S); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width, + client->y - titlebar_height, + titlebar_height, client->height + titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_e, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_E); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y - titlebar_height, + titlebar_height, client->height + titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_w, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_W); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width - titlebar_height, + client->y - titlebar_height * 2, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_ne, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NE); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y - titlebar_height * 2, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_nw, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NW); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y + client->height - titlebar_height, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_sw, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SW); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width - titlebar_height, + client->y + client->height - titlebar_height, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_se, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SE); + ctx_begin_path (ctx); + + } + + if (client->flags & ITK_CLIENT_TITLEBAR) + ctx_client_titlebar_draw (ctx, client, client->x, client->y, client->width, titlebar_height); + } + } + } + return 0; +} + +extern int _ctx_enable_hash_cache; + +void vt_audio_task (VT *vt, int click); + +int ctx_input_pending (Ctx *ctx, int timeout); + +int ctx_clients_need_redraw (Ctx *ctx) +{ + int changes = 0; + int follow_mouse = focus_follows_mouse; + CtxList *to_remove = NULL; + //ensure_layout (); + +// if (print_shape_cache_rate) +// fprintf (stderr, "\r%f ", ctx_shape_cache_rate); + + CtxClient *client = find_active (ctx_pointer_x (ctx), + ctx_pointer_y (ctx)); + + if (follow_mouse || ctx_pointer_is_down (ctx, 0) || + ctx_pointer_is_down (ctx, 1) || (active==NULL)) + { + if (client) + { + if (active != client) + { + active = client; + if (follow_mouse == 0 || + (ctx_pointer_is_down (ctx, 0) || + ctx_pointer_is_down (ctx, 1))) + { + //if (client != clients->data) + #if 1 + if ((client->flags & ITK_CLIENT_MAXIMIZED)==0) + { + ctx_list_remove (&clients, client); + ctx_list_append (&clients, client); + } +#endif + } + changes ++; + } + } + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->vt) + { + if (vt_is_done (client->vt)) + ctx_list_prepend (&to_remove, client); + } + } + for (CtxList *l = to_remove; l; l = l->next) + { + ctx_client_remove (ctx, l->data); + changes++; + } + while (to_remove) + { + ctx_list_remove (&to_remove, to_remove->data); + } + + changes += ctx_clients_dirty_count (); + return changes != 0; +} + +float ctx_avg_bytespeed = 0.0; + +static void ctx_client_handle_events_iteration (Ctx *ctx) +{ + static int fail_safe = 0; + //int n_clients = ctx_list_length (clients); + int pending_data = 0; + long time_start = ctx_ticks (); + int sleep_time = 1000000/ctx_target_fps; + + pending_data = ctx_input_pending (ctx, sleep_time); + + ctx_fetched_bytes = 0; + if (pending_data || fail_safe>100) + { + if (!pending_data)pending_data = 1; + /* record amount of time spent - and adjust time of reading for + * vts? + */ + long int fractional_sleep = sleep_time / pending_data; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + ctx_client_lock (client); + int found = 0; + for (CtxList *l2 = clients; l2; l2 = l2->next) + if (l2->data == client) found = 1; + if (!found) + goto done; + + ctx_fetched_bytes += vt_poll (client->vt, fractional_sleep); + //ctx_fetched_bytes += vt_poll (client->vt, sleep_time); //fractional_sleep); + ctx_client_unlock (client); + } +done: + fail_safe = 0; + } + else + { + fail_safe ++; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + vt_audio_task (client->vt, 0); + } + } + + //int got_events = 0; + + //while (ctx_get_event (ctx)) { } +#if 0 + if (changes /*|| pending_data */) + { + ctx_target_fps *= 1.6; + if (ctx_target_fps > 60) ctx_target_fps = 60; + } + else + { + ctx_target_fps = ctx_target_fps * 0.95 + 30.0 * 0.05; + + // 20fps is the lowest where sun 8bit ulaw 8khz works reliably + } + + if (ctx_avg_bytespeed > 1024 * 1024) ctx_target_fps = 10.0; + + if (_ctx_green < 0.4) + ctx_target_fps = 120.0; + else if (_ctx_green > 0.6) + ctx_target_fps = 25.0; + + //ctx_target_fps = 30.0; +#else + ctx_target_fps = 30.0; +#endif + + long time_end = ctx_ticks (); + + int timed = (time_end-time_start); + float bytespeed = ctx_fetched_bytes / ((timed)/ (1000.0f * 1000.0f)); + + ctx_avg_bytespeed = bytespeed * 0.2 + ctx_avg_bytespeed * 0.8; +#if 0 + fprintf (stderr, "%.2fmb/s %i/%i %.2f \r", ctx_avg_bytespeed/1024/1024, ctx_fetched_bytes, timed, ctx_target_fps); +#endif +} + + +static int ctx_clients_handle_events_fun (void *data) +{ + Ctx *ctx = data; + while (!ctx->quit) + { + int n_clients = ctx_list_length (clients); + ctx_client_handle_events_iteration (data); + switch (n_clients) + { + case 0: + usleep (1000 * 10); + break; + case 1: + usleep (1); // letting quit work - and also makes framerate for dump + break; + default: + usleep (0); // the switching between clients should be enough + break; + } + } + return 0; +} + +void ctx_clients_handle_events (Ctx *ctx) +{ +#if CTX_THREADS==0 + ctx_client_handle_events_iteration (ctx); +#else + static thrd_t tid = 0; + if (tid == 0) + { + thrd_create (&tid, (void*)ctx_clients_handle_events_fun, ctx); + } +#endif +} + +#endif /* CTX_VT */ +#endif // __CTX_H__ diff --git a/lib/ctx/ctx.c b/lib/ctx/ctx.c new file mode 100644 index 0000000000000000000000000000000000000000..c3dc0557928417fb8b71c26a41b1c37d8de7be00 --- /dev/null +++ b/lib/ctx/ctx.c @@ -0,0 +1,44 @@ +#include <math.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> + +/* clang-format off */ +#define CTX_MIN_EDGE_LIST_SIZE 256 +#define CTX_MAX_EDGE_LIST_SIZE 512 +#define CTX_MIN_JOURNAL_SIZE 512 +#define CTX_MAX_JOURNAL_SIZE 512 + +#define CTX_LIMIT_FORMATS 1 +#define CTX_DITHER 1 +#define CTX_ENABLE_RGB565_BYTESWAPPED 1 +#define CTX_BITPACK_PACKER 0 +#define CTX_COMPOSITING_GROUPS 0 +#define CTX_RENDERSTREAM_STATIC 0 +#define CTX_GRADIENT_CACHE 1 +#define CTX_ENABLE_CLIP 1 +#define CTX_BLOATY_FAST_PATHS 0 +#define CTX_1BIT_CLIP 1 +#define CTX_RASTERIZER_AA 15 +#define CTX_RASTERIZER_FORCE_AA 0 +#define CTX_SHAPE_CACHE 0 +#define CTX_SHAPE_CACHE_DIM 16*18 +#define CTX_SHAPE_CACHE_ENTRIES 128 +#define CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS 36 +#define CTX_RASTERIZER 1 +#define CTX_EVENTS 0 +#define CTX_STRINGPOOL_SIZE 512 +#define CTX_ENABLE_SHADOW_BLUR 0 +#define CTX_FORMATTER 0 +#define CTX_PARSER 0 +#define CTX_FONTS_FROM_FILE 0 +/* clang-format on */ + +#define CTX_IMPLEMENTATION + +/* Defining this causes the internal font to be dropped. */ +#define _CTX_INTERNAL_FONT_ + +#include "font-fira-mono.h" + +#include "ctx.h" diff --git a/lib/ctx/ctx.h b/lib/ctx/ctx.h new file mode 100644 index 0000000000000000000000000000000000000000..c62ab8c2d4cab18c409365541cfd339546d9a1b8 --- /dev/null +++ b/lib/ctx/ctx.h @@ -0,0 +1,46980 @@ +/* ctx git commit: 35100785e2c4 */ +/* + * ctx.h is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * ctx.h is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with ctx; if not, see <https://www.gnu.org/licenses/>. + * + * 2012, 2015, 2019, 2020 Øyvind Kolås <pippin@gimp.org> + * + * ctx is a single header 2d vector graphics processing framework. + * + * To use ctx in a project, do the following: + * + * #define CTX_IMPLEMENTATION + * #include "ctx.h" + * + * Ctx contains a minimal default fallback font with only ascii, so + * you probably want to also include a font, and perhaps enable + * the cairo or SDL2 optional renderers, a more complete example + * could be: + * + * #include <cairo.h> + * #include <SDL.h> + * #include "ctx-font-regular.h" + * #define CTX_IMPLEMENTATION + * #include "ctx.h" + * + * The behavior of ctx can be tweaked, and features can be configured, enabled + * or disabled with other #defines, see further down in the start of this file + * for details. + */ + +#ifndef CTX_H +#define CTX_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if !__COSMOPOLITAN__ +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#endif + +typedef struct _Ctx Ctx; + +/* The pixel formats supported as render targets + */ +enum _CtxPixelFormat +{ + CTX_FORMAT_NONE=0, + CTX_FORMAT_GRAY8, // 1 - these enum values are not coincidence + CTX_FORMAT_GRAYA8, // 2 - + CTX_FORMAT_RGB8, // 3 - + CTX_FORMAT_RGBA8, // 4 - + CTX_FORMAT_BGRA8, // 5 + CTX_FORMAT_RGB565, // 6 + CTX_FORMAT_RGB565_BYTESWAPPED, // 7 + CTX_FORMAT_RGB332, // 8 + CTX_FORMAT_RGBAF, // 9 + CTX_FORMAT_GRAYF, // 10 + CTX_FORMAT_GRAYAF, // 11 + CTX_FORMAT_GRAY1, //12 MONO + CTX_FORMAT_GRAY2, //13 DUO + CTX_FORMAT_GRAY4, //14 + CTX_FORMAT_CMYK8, //15 + CTX_FORMAT_CMYKA8, //16 + CTX_FORMAT_CMYKAF, //17 + CTX_FORMAT_YUV420, //18 +}; +typedef enum _CtxPixelFormat CtxPixelFormat; + +typedef struct _CtxGlyph CtxGlyph; + +/** + * ctx_new: + * + * Create a new drawing context, this context has no pixels but + * accumulates commands and can be played back on other ctx + * render contexts. + */ +Ctx *ctx_new (void); + + + + + +/** + * ctx_new_for_framebuffer: + * + * Create a new drawing context for a framebuffer, rendering happens + * immediately. + */ +Ctx *ctx_new_for_framebuffer (void *data, + int width, + int height, + int stride, + CtxPixelFormat pixel_format); +/** + * ctx_new_ui: + * + * Create a new interactive ctx context, might depend on additional + * integration. + */ +Ctx *ctx_new_ui (int width, int height); + +/** + * ctx_new_for_drawlist: + * + * Create a new drawing context for a pre-existing drawlist. + */ +Ctx *ctx_new_for_drawlist (void *data, size_t length); + + +/** + * ctx_dirty_rect: + * + * Query the dirtied bounding box of drawing commands thus far. + */ +void ctx_dirty_rect (Ctx *ctx, int *x, int *y, int *width, int *height); + +/** + * ctx_free: + * @ctx: a ctx context + */ +void ctx_free (Ctx *ctx); + +/* clears and resets a context */ +void ctx_reset (Ctx *ctx); +void ctx_begin_path (Ctx *ctx); +void ctx_save (Ctx *ctx); +void ctx_restore (Ctx *ctx); +void ctx_start_group (Ctx *ctx); +void ctx_end_group (Ctx *ctx); +void ctx_clip (Ctx *ctx); +void ctx_identity (Ctx *ctx); +void ctx_rotate (Ctx *ctx, float x); + +void ctx_image_smoothing (Ctx *ctx, int enabled); +int ctx_get_image_smoothing (Ctx *ctx); + +#define CTX_LINE_WIDTH_HAIRLINE -1000.0 +#define CTX_LINE_WIDTH_ALIASED -1.0 +#define CTX_LINE_WIDTH_FAST -1.0 /* aliased 1px wide line */ +void ctx_miter_limit (Ctx *ctx, float limit); +float ctx_get_miter_limit (Ctx *ctx); +void ctx_line_width (Ctx *ctx, float x); +void ctx_line_dash_offset (Ctx *ctx, float line_dash); +float ctx_get_line_dash_offset (Ctx *ctx); +void ctx_apply_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f); // htran, vtran +void ctx_set_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f); +void ctx_line_dash (Ctx *ctx, float *dashes, int count); +void ctx_font_size (Ctx *ctx, float x); +void ctx_font (Ctx *ctx, const char *font); +void ctx_font_family (Ctx *ctx, const char *font_family); +void ctx_scale (Ctx *ctx, float x, float y); +void ctx_translate (Ctx *ctx, float x, float y); +void ctx_line_to (Ctx *ctx, float x, float y); +void ctx_move_to (Ctx *ctx, float x, float y); +void ctx_curve_to (Ctx *ctx, float cx0, float cy0, + float cx1, float cy1, + float x, float y); +void ctx_quad_to (Ctx *ctx, float cx, float cy, + float x, float y); +void ctx_arc (Ctx *ctx, + float x, float y, + float radius, + float angle1, float angle2, + int direction); +void ctx_arc_to (Ctx *ctx, float x1, float y1, + float x2, float y2, float radius); +void ctx_rel_arc_to (Ctx *ctx, float x1, float y1, + float x2, float y2, float radius); +void ctx_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h); +void ctx_round_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h, + float radius); +void ctx_rel_line_to (Ctx *ctx, + float x, float y); +void ctx_rel_move_to (Ctx *ctx, + float x, float y); +void ctx_rel_curve_to (Ctx *ctx, + float x0, float y0, + float x1, float y1, + float x2, float y2); +void ctx_rel_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y); +void ctx_close_path (Ctx *ctx); +float ctx_get_font_size (Ctx *ctx); +const char *ctx_get_font (Ctx *ctx); +float ctx_get_line_width (Ctx *ctx); +int ctx_width (Ctx *ctx); +int ctx_height (Ctx *ctx); +int ctx_rev (Ctx *ctx); +float ctx_x (Ctx *ctx); +float ctx_y (Ctx *ctx); +void ctx_current_point (Ctx *ctx, float *x, float *y); +void ctx_get_transform (Ctx *ctx, float *a, float *b, + float *c, float *d, + float *e, float *f); + +CtxGlyph *ctx_glyph_allocate (int n_glyphs); + +void gtx_glyph_free (CtxGlyph *glyphs); + +int ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke); + +void ctx_preserve (Ctx *ctx); +void ctx_fill (Ctx *ctx); +void ctx_stroke (Ctx *ctx); + +void ctx_parse (Ctx *ctx, const char *string); + +void ctx_shadow_rgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_shadow_blur (Ctx *ctx, float x); +void ctx_shadow_offset_x (Ctx *ctx, float x); +void ctx_shadow_offset_y (Ctx *ctx, float y); +void ctx_view_box (Ctx *ctx, + float x0, float y0, + float w, float h); +void +ctx_set_pixel_u8 (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_global_alpha (Ctx *ctx, float global_alpha); +float ctx_get_global_alpha (Ctx *ctx); + +void ctx_named_source (Ctx *ctx, const char *name); +// followed by a color, gradient or pattern definition + +void ctx_stroke_source (Ctx *ctx); // next source definition is for stroking + +void ctx_rgba_stroke (Ctx *ctx, float r, float g, float b, float a); +void ctx_rgb_stroke (Ctx *ctx, float r, float g, float b); +void ctx_rgba8_stroke (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_gray_stroke (Ctx *ctx, float gray); +void ctx_drgba_stroke (Ctx *ctx, float r, float g, float b, float a); +void ctx_cmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_cmyk_stroke (Ctx *ctx, float c, float m, float y, float k); +void ctx_dcmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_dcmyk_stroke (Ctx *ctx, float c, float m, float y, float k); + + + +void ctx_rgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_rgb (Ctx *ctx, float r, float g, float b); +void ctx_rgba8 (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void ctx_gray (Ctx *ctx, float gray); +void ctx_drgba (Ctx *ctx, float r, float g, float b, float a); +void ctx_cmyka (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_cmyk (Ctx *ctx, float c, float m, float y, float k); +void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a); +void ctx_dcmyk (Ctx *ctx, float c, float m, float y, float k); + +/* there is also getters for colors, by first setting a color in one format and getting + * it with another color conversions can be done + */ + +void ctx_get_rgba (Ctx *ctx, float *rgba); +void ctx_get_graya (Ctx *ctx, float *ya); +void ctx_get_drgba (Ctx *ctx, float *drgba); +void ctx_get_cmyka (Ctx *ctx, float *cmyka); +void ctx_get_dcmyka (Ctx *ctx, float *dcmyka); +int ctx_in_fill (Ctx *ctx, float x, float y); +int ctx_in_stroke (Ctx *ctx, float x, float y); + +void ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1); +void ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0, + float x1, float y1, float r1); +/* XXX should be ctx_gradient_add_stop_rgba */ +void ctx_gradient_add_stop (Ctx *ctx, float pos, float r, float g, float b, float a); + +void ctx_gradient_add_stop_u8 (Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + +/* + * + */ +void ctx_define_texture (Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + int format, + void *data, + char *ret_eid); + +void +ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh, + CtxPixelFormat format, int dst_stride, + uint8_t *dst_data); + +void +ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format, + uint8_t *data, + int ox, int oy, + int dirtyX, int dirtyY, + int dirtyWidth, int dirtyHeight); + + +/* loads an image file from disk into texture, returning pixel width, height + * and eid, the eid is based on the path; not the contents - avoiding doing + * sha1 checksum of contents. The width and height of the image is returned + * along with the used eid, width height or eid can be NULL if we + * do not care about their values. + */ +void ctx_texture_load (Ctx *ctx, + const char *path, + int *width, + int *height, + char *eid); + +/* sets the paint source to be a texture by eid + */ +void ctx_texture (Ctx *ctx, const char *eid, float x, float y); + +void ctx_draw_texture (Ctx *ctx, const char *eid, float x, float y, float w, float h); + +void ctx_draw_texture_clipped (Ctx *ctx, const char *eid, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight); + +void ctx_draw_image (Ctx *ctx, const char *path, float x, float y, float w, float h); + +void ctx_draw_image_clipped (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight); + +/* used by the render threads of fb and sdl backends. + */ +void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source); +/* used when sharing cache state of eids between clients + */ +void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache); + +typedef struct _CtxDrawlist CtxDrawlist; +typedef void (*CtxFullCb) (CtxDrawlist *drawlist, void *data); + +int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format); // bits per pixel +int ctx_pixel_format_get_stride (CtxPixelFormat format, int width); +int ctx_pixel_format_components (CtxPixelFormat format); + +void _ctx_set_store_clear (Ctx *ctx); +void _ctx_set_transformation (Ctx *ctx, int transformation); + +Ctx *ctx_hasher_new (int width, int height, int cols, int rows); +uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row); + +int ctx_utf8_strlen (const char *s); + +#ifdef _BABL_H +#define CTX_BABL 1 +#else +#define CTX_BABL 0 +#endif + +/* If cairo.h is included before ctx.h add cairo integration code + */ +#ifdef CAIRO_H +#define CTX_CAIRO 1 +#else +#define CTX_CAIRO 0 +#endif + +#ifdef SDL_h_ +#define CTX_SDL 1 +#else +#define CTX_SDL 0 +#endif + +#ifndef CTX_FB +#if CTX_SDL +#define CTX_FB 1 +#else +#define CTX_FB 0 +#endif +#endif + +#if CTX_SDL +#define ctx_mutex_t SDL_mutex +#define ctx_create_mutex() SDL_CreateMutex() +#define ctx_lock_mutex(a) SDL_LockMutex(a) +#define ctx_unlock_mutex(a) SDL_UnlockMutex(a) +#else +#define ctx_mutex_t int +#define ctx_create_mutex() NULL +#define ctx_lock_mutex(a) +#define ctx_unlock_mutex(a) +#endif + +#if CTX_CAIRO + +/* render the deferred commands of a ctx context to a cairo + * context + */ +void ctx_render_cairo (Ctx *ctx, cairo_t *cr); + +/* create a ctx context that directly renders to the specified + * cairo context + */ +Ctx * ctx_new_for_cairo (cairo_t *cr); +#endif + +/* free with free() */ +char *ctx_render_string (Ctx *ctx, int longform, int *retlen); + +void ctx_render_stream (Ctx *ctx, FILE *stream, int formatter); + +void ctx_render_ctx (Ctx *ctx, Ctx *d_ctx); +void ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx); /* cycles through all + used texture eids + */ + +void ctx_start_move (Ctx *ctx); + + +int ctx_add_single (Ctx *ctx, void *entry); + +uint32_t ctx_utf8_to_unichar (const char *input); +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); + + +typedef enum +{ + CTX_FILL_RULE_WINDING, + CTX_FILL_RULE_EVEN_ODD +} CtxFillRule; + +typedef enum +{ +#if 0 + CTX_COMPOSITE_SOURCE_OVER = 0, + CTX_COMPOSITE_COPY = 32, + CTX_COMPOSITE_SOURCE_IN = 64, + CTX_COMPOSITE_SOURCE_OUT = 96, + CTX_COMPOSITE_SOURCE_ATOP = 128, + CTX_COMPOSITE_CLEAR = 160, + + CTX_COMPOSITE_DESTINATION_OVER = 192, + CTX_COMPOSITE_DESTINATION = 224, + CTX_COMPOSITE_DESTINATION_IN = 256, + CTX_COMPOSITE_DESTINATION_OUT = 288, + CTX_COMPOSITE_DESTINATION_ATOP = 320, + CTX_COMPOSITE_XOR = 352, + + CTX_COMPOSITE_ALL = (32+64+128+256) +#else + CTX_COMPOSITE_SOURCE_OVER =0, + CTX_COMPOSITE_COPY , + CTX_COMPOSITE_SOURCE_IN , + CTX_COMPOSITE_SOURCE_OUT , + CTX_COMPOSITE_SOURCE_ATOP , + CTX_COMPOSITE_CLEAR , + + CTX_COMPOSITE_DESTINATION_OVER , + CTX_COMPOSITE_DESTINATION , + CTX_COMPOSITE_DESTINATION_IN , + CTX_COMPOSITE_DESTINATION_OUT , + CTX_COMPOSITE_DESTINATION_ATOP , + CTX_COMPOSITE_XOR , +#endif +} CtxCompositingMode; + +typedef enum +{ + CTX_BLEND_NORMAL, + CTX_BLEND_MULTIPLY, + CTX_BLEND_SCREEN, + CTX_BLEND_OVERLAY, + CTX_BLEND_DARKEN, + CTX_BLEND_LIGHTEN, + CTX_BLEND_COLOR_DODGE, + CTX_BLEND_COLOR_BURN, + CTX_BLEND_HARD_LIGHT, + CTX_BLEND_SOFT_LIGHT, + CTX_BLEND_DIFFERENCE, + CTX_BLEND_EXCLUSION, + CTX_BLEND_HUE, + CTX_BLEND_SATURATION, + CTX_BLEND_COLOR, + CTX_BLEND_LUMINOSITY, // 15 + CTX_BLEND_DIVIDE, + CTX_BLEND_ADDITION, + CTX_BLEND_SUBTRACT, // 18 +} CtxBlend; + +void ctx_blend_mode (Ctx *ctx, CtxBlend mode); + +typedef enum +{ + CTX_JOIN_BEVEL = 0, + CTX_JOIN_ROUND = 1, + CTX_JOIN_MITER = 2 +} CtxLineJoin; + +typedef enum +{ + CTX_CAP_NONE = 0, + CTX_CAP_ROUND = 1, + CTX_CAP_SQUARE = 2 +} CtxLineCap; + +typedef enum +{ + CTX_TEXT_BASELINE_ALPHABETIC = 0, + CTX_TEXT_BASELINE_TOP, + CTX_TEXT_BASELINE_HANGING, + CTX_TEXT_BASELINE_MIDDLE, + CTX_TEXT_BASELINE_IDEOGRAPHIC, + CTX_TEXT_BASELINE_BOTTOM +} CtxTextBaseline; + +typedef enum +{ + CTX_TEXT_ALIGN_START = 0, + CTX_TEXT_ALIGN_END, + CTX_TEXT_ALIGN_CENTER, + CTX_TEXT_ALIGN_LEFT, + CTX_TEXT_ALIGN_RIGHT +} CtxTextAlign; + +typedef enum +{ + CTX_TEXT_DIRECTION_INHERIT = 0, + CTX_TEXT_DIRECTION_LTR, + CTX_TEXT_DIRECTION_RTL +} CtxTextDirection; + +struct +_CtxGlyph +{ + uint32_t index; + float x; + float y; +}; + +CtxTextAlign ctx_get_text_align (Ctx *ctx); +CtxTextBaseline ctx_get_text_baseline (Ctx *ctx); +CtxTextDirection ctx_get_text_direction (Ctx *ctx); +CtxFillRule ctx_get_fill_rule (Ctx *ctx); +CtxLineCap ctx_get_line_cap (Ctx *ctx); +CtxLineJoin ctx_get_line_join (Ctx *ctx); +CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx); +CtxBlend ctx_get_blend_mode (Ctx *ctx); + +void ctx_gradient_add_stop_string (Ctx *ctx, float pos, const char *color); + +void ctx_text_align (Ctx *ctx, CtxTextAlign align); +void ctx_text_baseline (Ctx *ctx, CtxTextBaseline baseline); +void ctx_text_direction (Ctx *ctx, CtxTextDirection direction); +void ctx_fill_rule (Ctx *ctx, CtxFillRule fill_rule); +void ctx_line_cap (Ctx *ctx, CtxLineCap cap); +void ctx_line_join (Ctx *ctx, CtxLineJoin join); +void ctx_compositing_mode (Ctx *ctx, CtxCompositingMode mode); +int ctx_set_drawlist (Ctx *ctx, void *data, int length); +typedef struct _CtxEntry CtxEntry; +/* we only care about the tight packing for this specific + * struct as we do indexing across members in arrays of it, + * to make sure its size becomes 9bytes - + * the pack pragma is also sufficient on recent gcc versions + */ +#pragma pack(push,1) +struct + _CtxEntry +{ + uint8_t code; + union + { + float f[2]; + uint8_t u8[8]; + int8_t s8[8]; + uint16_t u16[4]; + int16_t s16[4]; + uint32_t u32[2]; + int32_t s32[2]; + uint64_t u64[1]; // unused + } data; // 9bytes long, we're favoring compactness and correctness + // over performance. By sacrificing float precision, zeroing + // first 8bit of f[0] would permit 8bytes long and better + // aglinment and cacheline behavior. +}; +#pragma pack(pop) +const CtxEntry *ctx_get_drawlist (Ctx *ctx); +int ctx_append_drawlist (Ctx *ctx, void *data, int length); + +/* these are only needed for clients rendering text, as all text gets + * converted to paths. + */ +void ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs); + +void ctx_glyphs_stroke (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs); + +void ctx_text (Ctx *ctx, + const char *string); +void ctx_text_stroke (Ctx *ctx, + const char *string); + +void ctx_fill_text (Ctx *ctx, + const char *string, + float x, + float y); + +void ctx_stroke_text (Ctx *ctx, + const char *string, + float x, + float y); + +/* returns the total horizontal advance if string had been rendered */ +float ctx_text_width (Ctx *ctx, + const char *string); + +float ctx_glyph_width (Ctx *ctx, int unichar); + +int ctx_load_font_ttf (const char *name, const void *ttf_contents, int length); + + + +enum _CtxModifierState +{ + CTX_MODIFIER_STATE_SHIFT = (1<<0), + CTX_MODIFIER_STATE_CONTROL = (1<<1), + CTX_MODIFIER_STATE_ALT = (1<<2), + CTX_MODIFIER_STATE_BUTTON1 = (1<<3), + CTX_MODIFIER_STATE_BUTTON2 = (1<<4), + CTX_MODIFIER_STATE_BUTTON3 = (1<<5), + CTX_MODIFIER_STATE_DRAG = (1<<6), // pointer button is down (0 or any) +}; +typedef enum _CtxModifierState CtxModifierState; + +enum _CtxScrollDirection +{ + CTX_SCROLL_DIRECTION_UP, + CTX_SCROLL_DIRECTION_DOWN, + CTX_SCROLL_DIRECTION_LEFT, + CTX_SCROLL_DIRECTION_RIGHT +}; +typedef enum _CtxScrollDirection CtxScrollDirection; + +typedef struct _CtxEvent CtxEvent; + +void ctx_set_renderer (Ctx *ctx, + void *renderer); +void *ctx_get_renderer (Ctx *ctx); + +int ctx_renderer_is_sdl (Ctx *ctx); +int ctx_renderer_is_fb (Ctx *ctx); +int ctx_renderer_is_ctx (Ctx *ctx); +int ctx_renderer_is_term (Ctx *ctx); + +/* the following API is only available when CTX_EVENTS is defined to 1 + * + * it provides the ability to register callbacks with the current path + * that get delivered with transformed coordinates. + */ +int ctx_is_dirty (Ctx *ctx); +void ctx_set_dirty (Ctx *ctx, int dirty); +float ctx_get_float (Ctx *ctx, uint64_t hash); +void ctx_set_float (Ctx *ctx, uint64_t hash, float value); + +unsigned long ctx_ticks (void); +void ctx_flush (Ctx *ctx); + +void ctx_set_clipboard (Ctx *ctx, const char *text); +char *ctx_get_clipboard (Ctx *ctx); + +void _ctx_events_init (Ctx *ctx); +typedef struct _CtxIntRectangle CtxIntRectangle; +struct _CtxIntRectangle { + int x; + int y; + int width; + int height; +}; + +void ctx_quit (Ctx *ctx); +int ctx_has_quit (Ctx *ctx); + +typedef void (*CtxCb) (CtxEvent *event, + void *data, + void *data2); +typedef void (*CtxDestroyNotify) (void *data); + +enum _CtxEventType { + CTX_PRESS = 1 << 0, + CTX_MOTION = 1 << 1, + CTX_RELEASE = 1 << 2, + CTX_ENTER = 1 << 3, + CTX_LEAVE = 1 << 4, + CTX_TAP = 1 << 5, + CTX_TAP_AND_HOLD = 1 << 6, + + /* NYI: SWIPE, ZOOM ROT_ZOOM, */ + + CTX_DRAG_PRESS = 1 << 7, + CTX_DRAG_MOTION = 1 << 8, + CTX_DRAG_RELEASE = 1 << 9, + CTX_KEY_PRESS = 1 << 10, + CTX_KEY_DOWN = 1 << 11, + CTX_KEY_UP = 1 << 12, + CTX_SCROLL = 1 << 13, + CTX_MESSAGE = 1 << 14, + CTX_DROP = 1 << 15, + + CTX_SET_CURSOR = 1 << 16, // used internally + + /* client should store state - preparing + * for restart + */ + CTX_POINTER = (CTX_PRESS | CTX_MOTION | CTX_RELEASE | CTX_DROP), + CTX_TAPS = (CTX_TAP | CTX_TAP_AND_HOLD), + CTX_CROSSING = (CTX_ENTER | CTX_LEAVE), + CTX_DRAG = (CTX_DRAG_PRESS | CTX_DRAG_MOTION | CTX_DRAG_RELEASE), + CTX_KEY = (CTX_KEY_DOWN | CTX_KEY_UP | CTX_KEY_PRESS), + CTX_MISC = (CTX_MESSAGE), + CTX_ANY = (CTX_POINTER | CTX_DRAG | CTX_CROSSING | CTX_KEY | CTX_MISC | CTX_TAPS), +}; +typedef enum _CtxEventType CtxEventType; + +#define CTX_CLICK CTX_PRESS // SHOULD HAVE MORE LOGIC + +struct _CtxEvent { + CtxEventType type; + uint32_t time; + Ctx *ctx; + int stop_propagate; /* when set - propagation is stopped */ + + CtxModifierState state; + + int device_no; /* 0 = left mouse button / virtual focus */ + /* 1 = middle mouse button */ + /* 2 = right mouse button */ + /* 3 = first multi-touch .. (NYI) */ + + float device_x; /* untransformed (device) coordinates */ + float device_y; + + /* coordinates; and deltas for motion/drag events in user-coordinates: */ + float x; + float y; + float start_x; /* start-coordinates (press) event for drag, */ + float start_y; /* untransformed coordinates */ + float prev_x; /* previous events coordinates */ + float prev_y; + float delta_x; /* x - prev_x, redundant - but often useful */ + float delta_y; /* y - prev_y, redundant - .. */ + + + unsigned int unicode; /* only valid for key-events, re-use as keycode? */ + const char *string; /* as key can be "up" "down" "space" "backspace" "a" "b" "ø" etc .. */ + /* this is also where the message is delivered for + * MESSAGE events + * + * and the data for drop events are delivered + */ + CtxScrollDirection scroll_direction; + + + // would be nice to add the bounding box of the hit-area causing + // the event, making for instance scissored enter/leave repaint easier. +}; + +// layer-event "layer" motion x y device_no + +void ctx_add_key_binding_full (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data, + CtxDestroyNotify destroy_notify, + void *destroy_data); +void ctx_add_key_binding (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data); +typedef struct CtxBinding { + char *nick; + char *command; + char *label; + CtxCb cb; + void *cb_data; + CtxDestroyNotify destroy_notify; + void *destroy_data; +} CtxBinding; +CtxBinding *ctx_get_bindings (Ctx *ctx); +void ctx_clear_bindings (Ctx *ctx); +void ctx_remove_idle (Ctx *ctx, int handle); +int ctx_add_timeout_full (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data); +int ctx_add_timeout (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data); +int ctx_add_idle_full (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data); +int ctx_add_idle (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data); + + +void ctx_add_hit_region (Ctx *ctx, const char *id); + +void ctx_set_title (Ctx *ctx, const char *title); + +void ctx_listen_full (Ctx *ctx, + float x, + float y, + float width, + float height, + CtxEventType types, + CtxCb cb, + void *data1, + void *data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data); +void ctx_event_stop_propagate (CtxEvent *event); +void ctx_listen (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2); +void ctx_listen_with_finalize (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data); + +void ctx_init (int *argc, char ***argv); // is a no-op but could launch + // terminal +CtxEvent *ctx_get_event (Ctx *ctx); +int ctx_has_event (Ctx *ctx, int timeout); +void ctx_get_event_fds (Ctx *ctx, int *fd, int *count); + +int ctx_pointer_is_down (Ctx *ctx, int no); +float ctx_pointer_x (Ctx *ctx); +float ctx_pointer_y (Ctx *ctx); +void ctx_freeze (Ctx *ctx); +void ctx_thaw (Ctx *ctx); +int ctx_events_frozen (Ctx *ctx); +void ctx_events_clear_items (Ctx *ctx); +int ctx_events_width (Ctx *ctx); +int ctx_events_height (Ctx *ctx); + +/* The following functions drive the event delivery, registered callbacks + * are called in response to these being called. + */ + +int ctx_key_down (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); +int ctx_key_up (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); +int ctx_key_press (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time); + + +int ctx_scrolled (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time); +void ctx_incoming_message (Ctx *ctx, const char *message, long time); +int ctx_pointer_motion (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_release (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_press (Ctx *ctx, float x, float y, int device_no, uint32_t time); +int ctx_pointer_drop (Ctx *ctx, float x, float y, int device_no, uint32_t time, + char *string); + +typedef enum +{ + CTX_CONT = '\0', // - contains args from preceding entry + CTX_NOP = ' ', // + CTX_DATA = '(', // size size-in-entries - u32 + CTX_DATA_REV = ')', // reverse traversal data marker + CTX_SET_RGBA_U8 = '*', // r g b a - u8 + CTX_NEW_EDGE = '+', // x0 y0 x1 y1 - s16 + // set pixel might want a shorter ascii form? or keep it an embedded + // only option? + CTX_SET_PIXEL = '-', // 8bit "fast-path" r g b a x y - u8 for rgba, and u16 for x,y + /* optimizations that reduce the number of entries used, + * not visible outside the drawlist compression, thus + * using entries that cannot be used directly as commands + * since they would be interpreted as numbers - if values>127 + * then the embedded font data is harder to escape. + */ + CTX_REL_LINE_TO_X4 = '0', // x1 y1 x2 y2 x3 y3 x4 y4 -- s8 + CTX_REL_LINE_TO_REL_CURVE_TO = '1', // x1 y1 cx1 cy1 cx2 cy2 x y -- s8 + CTX_REL_CURVE_TO_REL_LINE_TO = '2', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8 + CTX_REL_CURVE_TO_REL_MOVE_TO = '3', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8 + CTX_REL_LINE_TO_X2 = '4', // x1 y1 x2 y2 -- s16 + CTX_MOVE_TO_REL_LINE_TO = '5', // x1 y1 x2 y2 -- s16 + CTX_REL_LINE_TO_REL_MOVE_TO = '6', // x1 y1 x2 y2 -- s16 + CTX_FILL_MOVE_TO = '7', // x y + CTX_REL_QUAD_TO_REL_QUAD_TO = '8', // cx1 x1 cy1 y1 cx1 x2 cy1 y1 -- s8 + CTX_REL_QUAD_TO_S16 = '9', // cx1 cy1 x y - s16 + // expand with: . : + CTX_FLUSH = ';', + + CTX_DEFINE_GLYPH = '@', // unichar width - u32 + CTX_ARC_TO = 'A', // x1 y1 x2 y2 radius + CTX_ARC = 'B', // x y radius angle1 angle2 direction + CTX_CURVE_TO = 'C', // cx1 cy1 cx2 cy2 x y + CTX_STROKE = 'E', // + CTX_FILL = 'F', // + CTX_RESTORE = 'G', // + CTX_HOR_LINE_TO = 'H', // x + CTX_DEFINE_TEXTURE = 'I', // "eid" width height format "data" + CTX_ROTATE = 'J', // radians + CTX_COLOR = 'K', // model, c1 c2 c3 ca - has a variable set of + // arguments. + CTX_LINE_TO = 'L', // x y + CTX_MOVE_TO = 'M', // x y + CTX_BEGIN_PATH = 'N', // + CTX_SCALE = 'O', // xscale yscale + CTX_NEW_PAGE = 'P', // - NYI - optional page-size + CTX_QUAD_TO = 'Q', // cx cy x y + CTX_VIEW_BOX = 'R', // x y width height + CTX_SMOOTH_TO = 'S', // cx cy x y + CTX_SMOOTHQ_TO = 'T', // x y + CTX_RESET = 'U', // + CTX_VER_LINE_TO = 'V', // y + CTX_APPLY_TRANSFORM = 'W', // a b c d e f - for set_transform combine with identity + CTX_EXIT = 'X', // + CTX_ROUND_RECTANGLE = 'Y', // x y width height radius + + CTX_CLOSE_PATH2 = 'Z', // + CTX_STROKE_SOURCE = '_', // next source definition applies to strokes + CTX_KERNING_PAIR = '[', // glA glB kerning, glA and glB in u16 kerning in s32 + CTX_COLOR_SPACE = ']', // IccSlot data data_len, + // data can be a string with a name, + // icc data or perhaps our own serialization + // of profile data + CTX_REL_ARC_TO = 'a', // x1 y1 x2 y2 radius + CTX_CLIP = 'b', + CTX_REL_CURVE_TO = 'c', // cx1 cy1 cx2 cy2 x y + CTX_LINE_DASH = 'd', // dashlen0 [dashlen1 ...] + CTX_TRANSLATE = 'e', // x y + CTX_LINEAR_GRADIENT = 'f', // x1 y1 x2 y2 + CTX_SAVE = 'g', + CTX_REL_HOR_LINE_TO = 'h', // x + CTX_TEXTURE = 'i', + CTX_PRESERVE = 'j', // + CTX_SET_KEY = 'k', // - used together with another char to identify + // a key to set + CTX_REL_LINE_TO = 'l', // x y + CTX_REL_MOVE_TO = 'm', // x y + CTX_FONT = 'n', // as used by text parser + CTX_RADIAL_GRADIENT = 'o', // x1 y1 radius1 x2 y2 radius2 + CTX_GRADIENT_STOP = 'p', // argument count depends on current color model + CTX_REL_QUAD_TO = 'q', // cx cy x y + CTX_RECTANGLE = 'r', // x y width height + CTX_REL_SMOOTH_TO = 's', // cx cy x y + CTX_REL_SMOOTHQ_TO = 't', // x y + CTX_STROKE_TEXT = 'u', // string - utf8 string + CTX_REL_VER_LINE_TO = 'v', // y + CTX_GLYPH = 'w', // unichar fontsize + CTX_TEXT = 'x', // string | kern - utf8 data to shape or horizontal kerning amount + CTX_IDENTITY = 'y', // + CTX_CLOSE_PATH = 'z', // + CTX_START_GROUP = '{', + CTX_END_GROUP = '}', + CTX_SOURCE_TRANSFORM = '`', + + CTX_EDGE = '&', // will not occur in commandstream + CTX_EDGE_FLIPPED = '^', // x0 y0 x1 y1 - s16 // thus these use reserved entries as code + + /* though expressed as two chars in serialization we have + * dedicated byte commands for the setters to keep the dispatch + * simpler. There is no need for these to be human readable thus we go >128 + * + * unused: !&<=>?:.=/\`, + * reserved: '"& # %^@ + */ + + + CTX_FILL_RULE = 128, // kr rule - u8, default = CTX_FILLE_RULE_EVEN_ODD + CTX_BLEND_MODE = 129, // kB mode - u8 , default=0 + + CTX_MITER_LIMIT = 130, // km limit - float, default = 0.0 + + CTX_LINE_JOIN = 131, // kj join - u8 , default=0 + CTX_LINE_CAP = 132, // kc cap - u8, default = 0 + CTX_LINE_WIDTH = 133, // kw width, default = 2.0 + CTX_GLOBAL_ALPHA = 134, // ka alpha - default=1.0 + CTX_COMPOSITING_MODE = 135, // kc mode - u8 , default=0 + + CTX_FONT_SIZE = 136, // kf size - float, default=? + CTX_TEXT_ALIGN = 137, // kt align - u8, default = CTX_TEXT_ALIGN_START + CTX_TEXT_BASELINE = 138, // kb baseline - u8, default = CTX_TEXT_ALIGN_ALPHABETIC + CTX_TEXT_DIRECTION = 139, // kd + + CTX_SHADOW_BLUR = 140, // ks + CTX_SHADOW_COLOR = 141, // kC + CTX_SHADOW_OFFSET_X = 142, // kx + CTX_SHADOW_OFFSET_Y = 143, // ky + CTX_IMAGE_SMOOTHING = 144, // kS + CTX_LINE_DASH_OFFSET = 145, // kD lineDashOffset + + // items marked with % are currently only for the parser + // for instance for svg compatibility or simulated/converted color spaces + // not the serialization/internal render stream + // + CTX_STROKE_RECT = 200, // strokeRect - only exist in long form + CTX_FILL_RECT = 201, // fillRect - only exist in long form +} CtxCode; + + +#pragma pack(push,1) + +typedef struct _CtxCommand CtxCommand; +typedef struct _CtxIterator CtxIterator; + +CtxIterator * +ctx_current_path (Ctx *ctx); +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2); + +#define CTX_ASSERT 0 + +#if CTX_ASSERT==1 +#define ctx_assert(a) if(!(a)){fprintf(stderr,"%s:%i assertion failed\n", __FUNCTION__, __LINE__); } +#else +#define ctx_assert(a) +#endif + +int ctx_get_drawlist_count (Ctx *ctx); + +struct + _CtxCommand +{ + union + { + uint8_t code; + CtxEntry entry; + struct + { + uint8_t code; + float scalex; + float scaley; + } scale; + struct + { + uint8_t code; + uint32_t stringlen; + uint32_t blocklen; + uint8_t cont; + uint8_t data[8]; /* ... and continues */ + } data; + struct + { + uint8_t code; + uint32_t stringlen; + uint32_t blocklen; + } data_rev; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } text; + struct + { + uint8_t code; + uint32_t key_hash; + float pad; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } set; + struct + { + uint8_t code; + uint32_t pad0; + float pad1; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } get; + struct { + uint8_t code; + uint32_t count; /* better than byte_len in code, but needs to then be set */ + float pad1; + uint8_t code_data; + uint32_t byte_len; + uint32_t blocklen; + uint8_t code_cont; + float data[2]; /* .. and - possibly continues */ + } line_dash; + struct { + uint8_t code; + uint32_t space_slot; + float pad1; + uint8_t code_data; + uint32_t data_len; + uint32_t blocklen; + uint8_t code_cont; + uint8_t data[8]; /* .. and continues */ + } colorspace; + struct + { + uint8_t code; + float x; + float y; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + char eid[8]; /* .. and continues */ + } texture; + struct + { + uint8_t code; + uint32_t width; + uint32_t height; + uint8_t code_cont0; + uint16_t format; + uint16_t pad0; + uint32_t pad1; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont1; + char eid[8]; /* .. and continues */ + // followed by - in variable offset code_Data, data_len, datablock_len, cont, pixeldata + } define_texture; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } text_stroke; + struct + { + uint8_t code; + float pad; + float pad2; + uint8_t code_data; + uint32_t stringlen; + uint32_t blocklen; + uint8_t code_cont; + uint8_t utf8[8]; /* .. and continues */ + } set_font; + struct + { + uint8_t code; + float model; + float r; + uint8_t pad1; + float g; + float b; + uint8_t pad2; + float a; + } rgba; + struct + { + uint8_t code; + float model; + float c; + uint8_t pad1; + float m; + float y; + uint8_t pad2; + float k; + float a; + } cmyka; + struct + { + uint8_t code; + float model; + float g; + uint8_t pad1; + float a; + } graya; + + struct + { + uint8_t code; + float model; + float c0; + uint8_t pad1; + float c1; + float c2; + uint8_t pad2; + float c3; + float c4; + uint8_t pad3; + float c5; + float c6; + uint8_t pad4; + float c7; + float c8; + uint8_t pad5; + float c9; + float c10; + } set_color; + struct + { + uint8_t code; + float x; + float y; + } rel_move_to; + struct + { + uint8_t code; + float x; + float y; + } rel_line_to; + struct + { + uint8_t code; + float x; + float y; + } line_to; + struct + { + uint8_t code; + float cx1; + float cy1; + uint8_t pad0; + float cx2; + float cy2; + uint8_t pad1; + float x; + float y; + } rel_curve_to; + struct + { + uint8_t code; + float x; + float y; + } move_to; + struct + { + uint8_t code; + float cx1; + float cy1; + uint8_t pad0; + float cx2; + float cy2; + uint8_t pad1; + float x; + float y; + } curve_to; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float r1; + float x2; + uint8_t pad1; + float y2; + float r2; + } radial_gradient; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float x2; + float y2; + } linear_gradient; + struct + { + uint8_t code; + float x; + float y; + uint8_t pad0; + float width; + float height; + uint8_t pad1; + float radius; + } rectangle; + struct { + uint8_t code; + float x; + float y; + uint8_t pad0; + float width; + float height; + } view_box; + + struct + { + uint8_t code; + uint16_t glyph_before; + uint16_t glyph_after; + int32_t amount; + } kern; + + struct + { + uint8_t code; + uint32_t glyph; + uint32_t advance; // * 256 + } define_glyph; + + struct + { + uint8_t code; + uint8_t rgba[4]; + uint16_t x; + uint16_t y; + } set_pixel; + struct + { + uint8_t code; + float cx; + float cy; + uint8_t pad0; + float x; + float y; + } quad_to; + struct + { + uint8_t code; + float cx; + float cy; + uint8_t pad0; + float x; + float y; + } rel_quad_to; + struct + { + uint8_t code; + float x; + float y; + uint8_t pad0; + float radius; + float angle1; + uint8_t pad1; + float angle2; + float direction; + } + arc; + struct + { + uint8_t code; + float x1; + float y1; + uint8_t pad0; + float x2; + float y2; + uint8_t pad1; + float radius; + } + arc_to; + /* some format specific generic accesors: */ + struct + { + uint8_t code; + float x0; + float y0; + uint8_t pad0; + float x1; + float y1; + uint8_t pad1; + float x2; + float y2; + uint8_t pad2; + float x3; + float y3; + uint8_t pad3; + float x4; + float y4; + } c; + struct + { + uint8_t code; + float a0; + float a1; + uint8_t pad0; + float a2; + float a3; + uint8_t pad1; + float a4; + float a5; + uint8_t pad2; + float a6; + float a7; + uint8_t pad3; + float a8; + float a9; + } f; + struct + { + uint8_t code; + uint32_t a0; + uint32_t a1; + uint8_t pad0; + uint32_t a2; + uint32_t a3; + uint8_t pad1; + uint32_t a4; + uint32_t a5; + uint8_t pad2; + uint32_t a6; + uint32_t a7; + uint8_t pad3; + uint32_t a8; + uint32_t a9; + } u32; + struct + { + uint8_t code; + uint64_t a0; + uint8_t pad0; + uint64_t a1; + uint8_t pad1; + uint64_t a2; + uint8_t pad2; + uint64_t a3; + uint8_t pad3; + uint64_t a4; + } u64; + struct + { + uint8_t code; + int32_t a0; + int32_t a1; + uint8_t pad0; + int32_t a2; + int32_t a3; + uint8_t pad1; + int32_t a4; + int32_t a5; + uint8_t pad2; + int32_t a6; + int32_t a7; + uint8_t pad3; + int32_t a8; + int32_t a9; + } s32; + struct + { + uint8_t code; + int16_t a0; + int16_t a1; + int16_t a2; + int16_t a3; + uint8_t pad0; + int16_t a4; + int16_t a5; + int16_t a6; + int16_t a7; + uint8_t pad1; + int16_t a8; + int16_t a9; + int16_t a10; + int16_t a11; + uint8_t pad2; + int16_t a12; + int16_t a13; + int16_t a14; + int16_t a15; + uint8_t pad3; + int16_t a16; + int16_t a17; + int16_t a18; + int16_t a19; + } s16; + struct + { + uint8_t code; + uint16_t a0; + uint16_t a1; + uint16_t a2; + uint16_t a3; + uint8_t pad0; + uint16_t a4; + uint16_t a5; + uint16_t a6; + uint16_t a7; + uint8_t pad1; + uint16_t a8; + uint16_t a9; + uint16_t a10; + uint16_t a11; + uint8_t pad2; + uint16_t a12; + uint16_t a13; + uint16_t a14; + uint16_t a15; + uint8_t pad3; + uint16_t a16; + uint16_t a17; + uint16_t a18; + uint16_t a19; + } u16; + struct + { + uint8_t code; + uint8_t a0; + uint8_t a1; + uint8_t a2; + uint8_t a3; + uint8_t a4; + uint8_t a5; + uint8_t a6; + uint8_t a7; + uint8_t pad0; + uint8_t a8; + uint8_t a9; + uint8_t a10; + uint8_t a11; + uint8_t a12; + uint8_t a13; + uint8_t a14; + uint8_t a15; + uint8_t pad1; + uint8_t a16; + uint8_t a17; + uint8_t a18; + uint8_t a19; + uint8_t a20; + uint8_t a21; + uint8_t a22; + uint8_t a23; + } u8; + struct + { + uint8_t code; + int8_t a0; + int8_t a1; + int8_t a2; + int8_t a3; + int8_t a4; + int8_t a5; + int8_t a6; + int8_t a7; + uint8_t pad0; + int8_t a8; + int8_t a9; + int8_t a10; + int8_t a11; + int8_t a12; + int8_t a13; + int8_t a14; + int8_t a15; + uint8_t pad1; + int8_t a16; + int8_t a17; + int8_t a18; + int8_t a19; + int8_t a20; + int8_t a21; + int8_t a22; + int8_t a23; + } s8; + }; + CtxEntry next_entry; // also pads size of CtxCommand slightly. +}; + +typedef struct _CtxImplementation CtxImplementation; +struct _CtxImplementation +{ + void (*process) (void *renderer, CtxCommand *entry); + void (*reset) (void *renderer); + void (*flush) (void *renderer); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *renderer); +}; + +CtxCommand *ctx_iterator_next (CtxIterator *iterator); + +#define ctx_arg_string() ((char*)&entry[2].data.u8[0]) + + +/* The above should be public API + */ + +#pragma pack(pop) + +/* access macros for nth argument of a given type when packed into + * an CtxEntry pointer in current code context + */ +#define ctx_arg_float(no) entry[(no)>>1].data.f[(no)&1] +#define ctx_arg_u64(no) entry[(no)].data.u64[0] +#define ctx_arg_u32(no) entry[(no)>>1].data.u32[(no)&1] +#define ctx_arg_s32(no) entry[(no)>>1].data.s32[(no)&1] +#define ctx_arg_u16(no) entry[(no)>>2].data.u16[(no)&3] +#define ctx_arg_s16(no) entry[(no)>>2].data.s16[(no)&3] +#define ctx_arg_u8(no) entry[(no)>>3].data.u8[(no)&7] +#define ctx_arg_s8(no) entry[(no)>>3].data.s8[(no)&7] +#define ctx_arg_string() ((char*)&entry[2].data.u8[0]) + +typedef enum +{ + CTX_GRAY = 1, + CTX_RGB = 3, + CTX_DRGB = 4, + CTX_CMYK = 5, + CTX_DCMYK = 6, + CTX_LAB = 7, + CTX_LCH = 8, + CTX_GRAYA = 101, + CTX_RGBA = 103, + CTX_DRGBA = 104, + CTX_CMYKA = 105, + CTX_DCMYKA = 106, + CTX_LABA = 107, + CTX_LCHA = 108, + CTX_GRAYA_A = 201, + CTX_RGBA_A = 203, + CTX_RGBA_A_DEVICE = 204, + CTX_CMYKA_A = 205, + CTX_DCMYKA_A = 206, + // RGB device and RGB ? +} CtxColorModel; + +enum _CtxAntialias +{ + CTX_ANTIALIAS_DEFAULT, // fast - suitable for realtime UI + CTX_ANTIALIAS_NONE, // non-antialiased + CTX_ANTIALIAS_FAST, // aa 3 // deprected or is default equal to this now? + CTX_ANTIALIAS_GOOD, // aa 5 // this should perhaps still be 5? + CTX_ANTIALIAS_BEST // aa 17 // accurate-suitable for saved assets +}; +typedef enum _CtxAntialias CtxAntialias; + +enum _CtxCursor +{ + CTX_CURSOR_UNSET, + CTX_CURSOR_NONE, + CTX_CURSOR_ARROW, + CTX_CURSOR_IBEAM, + CTX_CURSOR_WAIT, + CTX_CURSOR_HAND, + CTX_CURSOR_CROSSHAIR, + CTX_CURSOR_RESIZE_ALL, + CTX_CURSOR_RESIZE_N, + CTX_CURSOR_RESIZE_S, + CTX_CURSOR_RESIZE_E, + CTX_CURSOR_RESIZE_NE, + CTX_CURSOR_RESIZE_SE, + CTX_CURSOR_RESIZE_W, + CTX_CURSOR_RESIZE_NW, + CTX_CURSOR_RESIZE_SW, + CTX_CURSOR_MOVE +}; +typedef enum _CtxCursor CtxCursor; + +/* to be used immediately after a ctx_listen or ctx_listen_full causing the + * cursor to change when hovering the listen area. + */ +void ctx_listen_set_cursor (Ctx *ctx, + CtxCursor cursor); + +/* lower level cursor setting that is independent of ctx event handling + */ +void ctx_set_cursor (Ctx *ctx, CtxCursor cursor); +CtxCursor ctx_get_cursor (Ctx *ctx); +void ctx_set_antialias (Ctx *ctx, CtxAntialias antialias); +CtxAntialias ctx_get_antialias (Ctx *ctx); +void ctx_set_render_threads (Ctx *ctx, int n_threads); +int ctx_get_render_threads (Ctx *ctx); + +void ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache); +int ctx_get_hash_cache (Ctx *ctx); + + +typedef struct _CtxParser CtxParser; + CtxParser *ctx_parser_new ( + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data); + + +enum _CtxColorSpace +{ + CTX_COLOR_SPACE_DEVICE_RGB, + CTX_COLOR_SPACE_DEVICE_CMYK, + CTX_COLOR_SPACE_USER_RGB, + CTX_COLOR_SPACE_USER_CMYK, + CTX_COLOR_SPACE_TEXTURE +}; +typedef enum _CtxColorSpace CtxColorSpace; + +/* sets the color space for a slot, the space is either a string of + * "sRGB" "rec2020" .. etc or an icc profile. + * + * The slots device_rgb and device_cmyk is mostly to be handled outside drawing + * code, and user_rgb and user_cmyk is to be used. With no user_cmyk set + * user_cmyk == device_cmyk. + * + * The set profiles follows the graphics state. + */ +void ctx_colorspace (Ctx *ctx, + CtxColorSpace space_slot, + unsigned char *data, + int data_length); + +void +ctx_parser_set_size (CtxParser *parser, + int width, + int height, + float cell_width, + float cell_height); + +void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count); + +int +ctx_get_contents (const char *path, + unsigned char **contents, + long *length); + +void ctx_parser_free (CtxParser *parser); +typedef struct _CtxSHA1 CtxSHA1; + +void +ctx_bin2base64 (const void *bin, + int bin_length, + char *ascii); +int +ctx_base642bin (const char *ascii, + int *length, + unsigned char *bin); +float ctx_term_get_cell_width (Ctx *ctx); +float ctx_term_get_cell_height (Ctx *ctx); + + +#if 1 // CTX_VT + +typedef struct _VT VT; +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str); +void vt_paste (VT *vt, const char *str); +char *vt_get_selection (VT *vt); +long vt_rev (VT *vt); +int vt_has_blink (VT *vt); + +int ctx_clients_need_redraw (Ctx *ctx); +void ctx_clients_handle_events (Ctx *ctx); + + +typedef struct _CtxBuffer CtxBuffer; +CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data); + + +#endif + +#ifndef CTX_CODEC_CHAR +//#define CTX_CODEC_CHAR '\035' +//#define CTX_CODEC_CHAR 'a' +#define CTX_CODEC_CHAR '\020' // datalink escape +//#define CTX_CODEC_CHAR '^' +#endif + +#ifndef assert +#define assert(a) +#endif + +#ifdef __cplusplus +} +#endif +#endif +#ifndef __CTX_H__ +#define __CTX_H__ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#ifndef CTX_STRING_H +#define CTX_STRING_H + +typedef struct _CtxString CtxString; +struct _CtxString +{ + char *str; + int length; + int utf8_length; + int allocated_length; + int is_line; +}; + +CtxString *ctx_string_new_with_size (const char *initial, int initial_size); +CtxString *ctx_string_new (const char *initial); +void ctx_string_free (CtxString *string, int freealloc); +const char *ctx_string_get (CtxString *string); +uint32_t ctx_string_get_unichar (CtxString *string, int pos); +int ctx_string_get_length (CtxString *string); +int ctx_string_get_utf8length (CtxString *string); +void ctx_string_set (CtxString *string, const char *new_string); +void ctx_string_clear (CtxString *string); +void ctx_string_append_str (CtxString *string, const char *str); +void ctx_string_append_byte (CtxString *string, char val); +void ctx_string_append_string (CtxString *string, CtxString *string2); +void ctx_string_append_unichar (CtxString *string, unsigned int unichar); +void ctx_string_append_data (CtxString *string, const char *data, int len); + +void ctx_string_append_utf8char (CtxString *string, const char *str); +void ctx_string_append_printf (CtxString *string, const char *format, ...); +void ctx_string_replace_utf8 (CtxString *string, int pos, const char *new_glyph); +void ctx_string_insert_utf8 (CtxString *string, int pos, const char *new_glyph); + +void ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar); +void ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar); +void ctx_string_remove (CtxString *string, int pos); +char *ctx_strdup_printf (const char *format, ...); + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#endif +#ifndef _CTX_INTERNAL_FONT_ +#define _CTX_INTERNAL_FONT_ + +#ifndef CTX_FONT_ascii +/* this is a ctx encoded font based on DejaVuSans.ttf */ +/* CTX_SUBDIV:8 CTX_BAKE_FONT_SIZE:160 */ +/* glyphs covered: + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi + jklmnopqrstuvwxyz{|}~ */ +static const struct __attribute__ ((packed)) {uint8_t code; uint32_t a; uint32_t b;} +ctx_font_ascii[]={ +{'@', 0x00000020, 0x00002bb0},/* x-advance: 43.687500 */ +{'@', 0x00000021, 0x00003719},/* ! x-advance: 55.097656 */ +{'M', 0x41a5e7f2, 0xc1886037}, +{'l', 0x4159fc90, 0x00000000}, +{'4', 0x00880000, 0x0000ff94}, +{'6', 0xff780000, 0xfd670000}, +{'l', 0x4159fc90, 0x00000000}, +{'l', 0x00000000, 0x422fd6c4}, +{'l', 0xbfabcfe0, 0x41bfad86}, +{'l', 0xc12df5b2, 0x00000000}, +{'l', 0xbfb46710, 0xc1bfad86}, +{'l', 0x00000000, 0xc22fd6c4}, +{'@', 0x00000022, 0x00003f38},/* " x-advance: 63.218750 */ +{'M', 0x41c50c07, 0xc2c86716}, +{'l', 0x00000000, 0x4214fe48}, +{'4', 0x0000ffa5, 0xfed70000}, +{'6', 0x0000005b, 0x000000ca}, +{'l', 0x00000000, 0x4214fe48}, +{'l', 0xc1368ce4, 0x00000000}, +{'l', 0x00000000, 0xc214fe48}, +{'l', 0x41368ce4, 0x00000000}, +{'@', 0x00000023, 0x0000732a},/* # x-advance: 115.164062 */ +{'M', 0x428c8973, 0xc271e113}, +{'l', 0xc19c3dda, 0x00000000}, +{'4', 0x00b3ffd3, 0x0000009d}, +{'6', 0xff4d002c, 0xfecfffb0}, +{'l', 0xc0df5b10, 0x41ded19c}, +{'l', 0x419cc74c, 0x00000000}, +{'l', 0x40e180e0, 0xc1ded19c}, +{'l', 0x412bcfe8, 0x00000000}, +{'l', 0xc0dd3540, 0x41ded19c}, +{'l', 0x41a78448, 0x00000000}, +{'l', 0x00000000, 0x41255e7c}, +{'l', 0xc1bc74d4, 0x00000000}, +{'l', 0xc0b01b80, 0x41b35430}, +{'l', 0x41aabd00, 0x00000000}, +{'l', 0x00000000, 0x41244b9a}, +{'l', 0xc1bfad88, 0x00000000}, +{'l', 0xc0df5b10, 0x41de4829}, +{'l', 0xc12bcfe4, 0x00000000}, +{'l', 0x40dd3540, 0xc1de4829}, +{'l', 0xc19d50c0, 0x00000000}, +{'l', 0xc0dd3540, 0x41de4829}, +{'l', 0xc12ce2ca, 0x00000000}, +{'l', 0x40df5b10, 0xc1de4829}, +{'l', 0xc1a920a5, 0x00000000}, +{'l', 0x00000000, 0xc1244b9a}, +{'l', 0x41bcfe48, 0x00000000}, +{'l', 0x40b46718, 0xc1b35430}, +{'l', 0xc1ace2cb, 0x00000000}, +{'l', 0x00000000, 0xc1255e7c}, +{'l', 0x41c1d353, 0x00000000}, +{'l', 0x40db0f78, 0xc1ded19c}, +{'l', 0x412df5b0, 0x00000000}, +{'@', 0x00000024, 0x00005773},/* $ x-advance: 87.449219 */ +{'M', 0x4239c595, 0x41a19c59}, +{'4', 0x0000ffcb, 0xff5f0000}, +{'q', 0xc0e180d8, 0xbe09731d}, +{0, 0xc16180dc, 0xbfce2cac}, +{'9', 0xfff4ffc8, 0xffdcff8f}, +{'l', 0x00000000, 0xc14149e1}, +{'q', 0x40db0f76, 0x4089731e}, +{0, 0x415d3543, 0x40d05278}, +{'9', 0x00110038, 0x00110073}, +{'l', 0x00000000, 0xc1f4d50c}, +{'q', 0xc16d50c2, 0xc01aa180}, +{0, 0xc1ace2cb, 0xc10301b8}, +{'q', 0xc0d6c3de, 0xc0b8b2b0}, +{0, 0xc0d6c3de, 0xc17d6c3c}, +{'q', 0x00000000, 0xc12f0898}, +{0, 0x40ea180e, 0xc189fc90}, +{'9', 0xffce003a, 0xffc700a8}, +{'4', 0xff820000, 0x00000035}, +{'l', 0x00000000, 0x417920a8}, +{'8', 0x0a600231, 0x165b082e}, +{'l', 0x00000000, 0x413beb60}, +{'8', 0xdea5ead4, 0xf2a0f4d2}, +{'l', 0x00000000, 0x41e54302}, +{'q', 0x4173c228, 0x401655f0}, +{0, 0x41b35432, 0x41063a6c}, +{'q', 0x40e5cc70, 0x40c149e0}, +{0, 0x40e5cc70, 0x4184149e}, +{'q', 0x00000000, 0x413579fc}, +{0, 0xc0f4d510, 0x418f5b0f}, +{'9', 0x0034ffc4, 0x003cff51}, +{'6', 0x00a20000, 0xfdc1ffcb}, +{'l', 0x00000000, 0xc1dc2258}, +{'8', 0x23a106c2, 0x4be01ce0}, +{'8', 0x471e2e00, 0x2561191e}, +{'m', 0x40d6c3d8, 0x414e2cac}, +{'l', 0x00000000, 0x41e87bb4}, +{'8', 0xda66f744, 0xb322e322}, +{'8', 0xb5dfd100, 0xd898e5e0}, +{'@', 0x00000025, 0x0000829a},/* % x-advance: 130.601562 */ +{'M', 0x42c7dda3, 0xc2306037}, +{'8', 0x27b700d2, 0x6ee627e6}, +{'8', 0x6e1a4500, 0x2749271a}, +{'8', 0xd947002d, 0x921ad81a}, +{'8', 0x92e6ba00, 0xd8b9d8e6}, +{'m', 0x00000000, 0xc1086034}, +{'q', 0x4129aa18, 0x00000000}, +{0, 0x4186c3dc, 0x40ec3dd8}, +{'q', 0x40c7bb50, 0x40ec3dd8}, +{0, 0x40c7bb50, 0x419f768c}, +{'q', 0x00000000, 0x4148ce2d}, +{0, 0xc0c9e120, 0x419f768d}, +{'q', 0xc0c7bb40, 0x40ea180d}, +{0, 0xc1863a68, 0x40ea180d}, +{'q', 0xc12bcfe8, 0x34000000}, +{0, 0xc187d6c4, 0xc0ea180d}, +{'q', 0xc0c7bb40, 0xc0ec3dda}, +{0, 0xc0c7bb40, 0xc19f768d}, +{'q', 0x00000000, 0xc149e114}, +{0, 0x40c7bb40, 0xc19f768c}, +{'9', 0xffc50032, 0xffc50087}, +{'m', 0xc28a8603, 0xc2237d6c}, +{'8', 0x28b700d2, 0x6de627e6}, +{'8', 0x6e1a4600, 0x2749271a}, +{'8', 0xd949002e, 0x921ad91a}, +{'8', 0x93e6bb00, 0xd8b7d8e6}, +{'m', 0x42726a86, 0xc1086038}, +{'l', 0x412bcfe0, 0x00000000}, +{'4', 0x033ffe0b, 0x0000ffab}, +{'6', 0xfcc101f5, 0x0000fe1c}, +{'q', 0x4129aa14, 0x00000000}, +{0, 0x41874d50, 0x40ec3de0}, +{'q', 0x40c9e110, 0x40ea1800}, +{0, 0x40c9e110, 0x419eed18}, +{'q', 0x00000000, 0x414af3f8}, +{0, 0xc0c9e110, 0x41a00000}, +{'q', 0xc0c7bb48, 0x40ea1808}, +{0, 0xc1874d51, 0x40ea1808}, +{'q', 0xc12abcfe, 0x00000000}, +{0, 0xc1874d51, 0xc0ea1808}, +{'q', 0xc0c59579, 0xc0ec3dd8}, +{0, 0xc0c59579, 0xc1a00000}, +{'q', 0x00000000, 0xc147bb48}, +{0, 0x40c7bb47, 0xc19eed18}, +{'q', 0x40c7bb46, 0xc0ec3de0}, +{0, 0x4186c3de, 0xc0ec3de0}, +{'@', 0x00000026, 0x00006b2e},/* & x-advance: 107.179688 */ +{'M', 0x4205b0f7, 0xc257920a}, +{'8', 0x56b92bd0, 0x5aea2aea}, +{'q', 0x00000000, 0x411cc74e}, +{0, 0x40e3a6a8, 0x41827845}, +{'q', 0x40e3a6a8, 0x40d05278}, +{0, 0x418ed19c, 0x40d05278}, +{'8', 0xf05f0033, 0xcd53ef2c}, +{'6', 0xfeddfee4, 0xffc4004b}, +{'l', 0x42086037, 0x420b98e9}, +{'q', 0x407d6c40, 0xc0bf2410}, +{0, 0x40c59570, 0xc14c06e0}, +{'9', 0xffca0011, 0xff8d0014}, +{'l', 0x4147bb40, 0x00000000}, +{'q', 0xbf4e2c80, 0x410dbeb8}, +{0, 0xc0897310, 0x418c225c}, +{'9', 0x0045ffe5, 0x0088ffb3}, +{'4', 0x00990095, 0x0000ff79}, +{'l', 0xc1198e98, 0xc11dda33}, +{'q', 0xc0df5b10, 0x40bf2414}, +{0, 0xc16a1810, 0x410ed19c}, +{'q', 0xc0f4d510, 0x4038b2ae}, +{0, 0xc1838b2c, 0x4038b2ae}, +{'q', 0xc181655e, 0x34000000}, +{0, 0xc1d38b2a, 0xc1131d35}, +{'q', 0xc1244b99, 0xc114301c}, +{0, 0xc1244b99, 0xc1bd87bb}, +{'q', 0x00000000, 0xc109731e}, +{0, 0x408fe482, 0xc180dbeb}, +{'q', 0x408fe484, 0xc0f2af40}, +{0, 0x4157d6c4, 0xc163a6a8}, +{'8', 0xbdd9dfe7, 0xbef3dff3}, +{'q', 0x00000000, 0xc12df5b0}, +{0, 0x40ee63a4, 0xc18b98e8}, +{'q', 0x40ee63a8, 0xc0d49e10}, +{0, 0x419e63a6, 0xc0d49e10}, +{'8', 0x0958002c, 0x1c5a092c}, +{'l', 0x00000000, 0x41436fb0}, +{'8', 0xdaa7e7d2, 0xf3b2f3d6}, +{'8', 0x1ea500c8, 0x4cde1dde}, +{'8', 0x370f1b00, 0x4d401b10}, +{'@', 0x00000027, 0x000025c9},/* ' x-advance: 37.785156 */ +{'M', 0x41c50c07, 0xc2c86716}, +{'l', 0x00000000, 0x4214fe48}, +{'l', 0xc1368ce3, 0x00000000}, +{'l', 0x00000000, 0xc214fe48}, +{'l', 0x41368ce3, 0x00000000}, +{'@', 0x00000028, 0x0000359f},/* ( x-advance: 53.621094 */ +{'M', 0x422a7844, 0xc2d09732}, +{'q', 0xc10fe480, 0x4176fae0}, +{0, 0xc155b0f6, 0x41f44b9c}, +{'q', 0xc08b98e8, 0x41719c54}, +{0, 0xc08b98e8, 0x41f4d50a}, +{'q', 0x00000000, 0x41780dbe}, +{0, 0x408b98e8, 0x41f5e7f2}, +{'9', 0x00790023, 0x00f4006a}, +{'l', 0xc12bcfe2, 0x00000000}, +{'q', 0xc12112e6, 0xc17c5958}, +{0, 0xc1719c5a, 0xc1f80dbf}, +{'q', 0xc09eed18, 0xc173c224}, +{0, 0xc09eed18, 0xc1f225cc}, +{'q', 0x00000000, 0xc16f768c}, +{0, 0x409eed18, 0xc1f112e6}, +{'q', 0x409eed1c, 0xc172af40}, +{0, 0x41719c5a, 0xc1f80dc0}, +{'l', 0x412bcfe2, 0x00000000}, +{'@', 0x00000029, 0x0000359f},/* ) x-advance: 53.621094 */ +{'M', 0x41301b7d, 0xc2d09732}, +{'l', 0x412bcfe5, 0x00000000}, +{'q', 0x412112e6, 0x417d6c40}, +{0, 0x41708972, 0x41f80dc0}, +{'q', 0x40a112e8, 0x4172af40}, +{0, 0x40a112e8, 0x41f112e6}, +{'q', 0x00000000, 0x41708974}, +{0, 0xc0a112e8, 0x41f225cc}, +{'9', 0x0079ffd9, 0x00f8ff88}, +{'l', 0xc12bcfe5, 0x00000000}, +{'q', 0x410ed19d, 0xc175e7f3}, +{0, 0x41549e11, 0xc1f44b99}, +{'q', 0x408dbeb4, 0xc173c226}, +{0, 0x408dbeb4, 0xc1f5e7f2}, +{'q', 0x00000000, 0xc1780dc0}, +{0, 0xc08dbeb4, 0xc1f4d50a}, +{'q', 0xc08b98e8, 0xc1719c58}, +{0, 0xc1549e11, 0xc1f44b9c}, +{'@', 0x0000002a, 0x000044b9},/* * x-advance: 68.722656 */ +{'M', 0x42814302, 0xc2a761ef}, +{'l', 0xc1c0c070, 0x41505278}, +{'l', 0x41c0c070, 0x41516560}, +{'l', 0xc07920b0, 0x40d27848}, +{'l', 0xc1b46716, 0xc159fc94}, +{'l', 0x00000000, 0x41ca6a88}, +{'l', 0xc0f4d50c, 0x00000000}, +{'l', 0x00000000, 0xc1ca6a88}, +{'l', 0xc1b46716, 0x4159fc94}, +{'l', 0xc07920a8, 0xc0d27848}, +{'l', 0x41c0c06e, 0xc1516560}, +{'l', 0xc1c0c06e, 0xc1505278}, +{'l', 0x407920a4, 0xc0d49e10}, +{'l', 0x41b46716, 0x4159fc90}, +{'l', 0x36000000, 0xc1ca6a84}, +{'l', 0x40f4d50c, 0x00000000}, +{'l', 0x00000000, 0x41ca6a84}, +{'l', 0x41b46716, 0xc159fc90}, +{'l', 0x407920b0, 0x40d49e10}, +{'@', 0x0000002b, 0x0000732a},/* + x-advance: 115.164062 */ +{'M', 0x427ce2ca, 0xc2ac5957}, +{'l', 0x00000000, 0x421587ba}, +{'l', 0x421587bc, 0x00000000}, +{'l', 0x00000000, 0x41368ce4}, +{'l', 0xc21587bc, 0x00000000}, +{'l', 0x00000000, 0x421587bb}, +{'l', 0xc1346714, 0x00000000}, +{'l', 0x00000000, 0xc21587bb}, +{'l', 0xc21587bb, 0x00000000}, +{'l', 0xb5800000, 0xc1368ce4}, +{'l', 0x421587bb, 0x00000000}, +{'l', 0x00000000, 0xc21587ba}, +{'l', 0x41346714, 0x00000000}, +{'@', 0x0000002c, 0x00002bb0},/* , x-advance: 43.687500 */ +{'M', 0x4180dbeb, 0xc1886037}, +{'l', 0x416293c2, 0x00000000}, +{'l', 0x00000000, 0x4138b2b0}, +{'l', 0xc1301b7c, 0x41abcfe4}, +{'l', 0xc10a8604, 0x00000000}, +{'l', 0x40b01b7c, 0xc1abcfe4}, +{'l', 0x00000000, 0xc138b2b0}, +{'@', 0x0000002d, 0x00003198},/* - x-advance: 49.593750 */ +{'M', 0x40d6c3dd, 0xc22c9e11}, +{'l', 0x4210b2af, 0x00000000}, +{'l', 0x00000000, 0x41301b7c}, +{'l', 0xc210b2af, 0x00000000}, +{'l', 0xb5c00000, 0xc1301b7c}, +{'[', 0x0047002d, 0x00000508}, +{'[', 0x004a002d, 0x000007a6}, +{'[', 0x004f002d, 0x000003d3}, +{'[', 0x0051002d, 0x00000508}, +{'[', 0x006f002d, 0x0000028c}, +{'@', 0x0000002e, 0x00002bb0},/* . x-advance: 43.687500 */ +{'M', 0x416b2af4, 0xc1886037}, +{'l', 0x416293c2, 0x00000000}, +{'l', 0x00000000, 0x41886037}, +{'l', 0xc16293c2, 0x00000000}, +{'l', 0x00000000, 0xc1886037}, +{'@', 0x0000002f, 0x00002e4f},/* / x-advance: 46.308594 */ +{'M', 0x420b98e9, 0xc2c86716}, +{'l', 0x41368ce4, 0x00000000}, +{'l', 0xc20b98e9, 0x42e1e7f2}, +{'l', 0xc1368ce4, 0xb5800000}, +{'l', 0x420b98e9, 0xc2e1e7f2}, +{'@', 0x00000030, 0x00005773},/* 0 x-advance: 87.449219 */ +{'M', 0x422ec3dd, 0xc2b68ce3}, +{'q', 0xc1278448, 0x00000000}, +{0, 0xc17c5956, 0x41255e80}, +{'q', 0xc0a7844c, 0x41244b98}, +{0, 0xc0a7844c, 0x41f7844c}, +{'q', 0x00000000, 0x41a4d50c}, +{0, 0x40a7844c, 0x41f7844c}, +{'q', 0x40a9aa1c, 0x41244b98}, +{0, 0x417c5956, 0x41244b98}, +{'q', 0x41289734, 0x00000000}, +{0, 0x417c5958, 0xc1244b98}, +{'q', 0x40a9aa18, 0xc1255e80}, +{0, 0x40a9aa18, 0xc1f7844c}, +{'q', 0x00000000, 0xc1a55e80}, +{0, 0xc0a9aa18, 0xc1f7844c}, +{'9', 0xffaeffd7, 0xffaeff82}, +{'m', 0x00000000, 0xc12bcfe0}, +{'q', 0x4186c3de, 0x00000000}, +{0, 0x41cda33a, 0x4155b0f8}, +{'q', 0x410ed198, 0x41549e10}, +{0, 0x410ed198, 0x421aa180}, +{'q', 0x00000000, 0x41ca6a86}, +{0, 0xc10ed198, 0x421aa181}, +{'q', 0xc10dbeb8, 0x41549e11}, +{0, 0xc1cda33a, 0x41549e11}, +{'q', 0xc186c3dd, 0xb4c00000}, +{0, 0xc1ce2cab, 0xc1549e11}, +{'q', 0xc10dbeb5, 0xc155b0f8}, +{0, 0xc10dbeb5, 0xc21aa181}, +{'q', 0x00000000, 0xc1caf3f8}, +{0, 0x410dbeb5, 0xc21aa180}, +{'q', 0x410ed19c, 0xc155b0f8}, +{0, 0x41ce2cab, 0xc155b0f8}, +{'@', 0x00000031, 0x00005773},/* 1 x-advance: 87.449219 */ +{'M', 0x41886037, 0xc1368ce3}, +{'l', 0x41b12e63, 0x00000000}, +{'l', 0x00000000, 0xc298e2cb}, +{'l', 0xc1c0c06e, 0x409aa180}, +{'l', 0x35800000, 0xc1459578}, +{'l', 0x41bfad88, 0xc09aa180}, +{'l', 0x4158e9a8, 0x00000000}, +{'l', 0x00000000, 0x42b1957a}, +{'l', 0x41b12e64, 0xb6400000}, +{'l', 0x00000000, 0x41368ce3}, +{'l', 0xc266df5a, 0x00000000}, +{'l', 0xb6000000, 0xc1368ce3}, +{'@', 0x00000032, 0x00005773},/* 2 x-advance: 87.449219 */ +{'M', 0x41d301b8, 0xc1368ce3}, +{'l', 0x423d4302, 0x00000000}, +{'4', 0x005b0000, 0x0000fe04}, +{'l', 0xb6000000, 0xc1368ce3}, +{'q', 0x40f6fad8, 0xc0ff920a}, +{0, 0x41a80dbe, 0xc1ab4670}, +{'q', 0x4155b0f6, 0xc157d6c4}, +{0, 0x41863a6b, 0xc18b0f76}, +{'8', 0x9e48c634, 0xb114d814}, +{'q', 0x00000000, 0xc0ff9210}, +{0, 0xc0b46718, 0xc1505278}, +{'q', 0xc0b24148, 0xc0a112f0}, +{0, 0xc1690528, 0xc0a112f0}, +{'q', 0xc0cc06e0, 0x00000000}, +{0, 0xc157d6c2, 0x400dbec0}, +{'9', 0x0011ffc8, 0x0035ff88}, +{'l', 0x00000000, 0xc15b0f78}, +{'q', 0x410301b8, 0xc0527840}, +{0, 0x4174d50c, 0xc09eed20}, +{'q', 0x40e3a6a8, 0xbfd6c3c0}, +{0, 0x41505278, 0xbfd6c3c0}, +{'q', 0x417920a4, 0x00000000}, +{0, 0x41c6a860, 0x40f920a0}, +{'q', 0x41143018, 0x40f920b0}, +{0, 0x41143018, 0x41a67168}, +{'8', 0x5dee3100, 0x68bd2cee}, +{'q', 0xbfd6c3e0, 0x3ff920c0}, +{0, 0xc12abd00, 0x41346718}, +{'q', 0xc10fe480, 0x4114301c}, +{0, 0xc1caf3f8, 0x41cfc904}, +{'@', 0x00000033, 0x00005773},/* 3 x-advance: 87.449219 */ +{'M', 0x425f1656, 0xc2581b7d}, +{'q', 0x411bb468, 0x40052780}, +{0, 0x4172af40, 0x410a8604}, +{'q', 0x40b01b80, 0x40d27840}, +{0, 0x40b01b80, 0x4181eed1}, +{'q', 0x00000000, 0x416d50c0}, +{0, 0xc12338b8, 0x41b79fc8}, +{'q', 0xc12338b0, 0x4101eed3}, +{0, 0xc1e7f240, 0x4101eed3}, +{'8', 0xf79800ce, 0xe292f6cb}, +{'l', 0x00000000, 0xc151655e}, +{'q', 0x40b46716, 0x40527844}, +{0, 0x4145957b, 0x409eed18}, +{'q', 0x40d6c3dc, 0x3fd6c3e0}, +{0, 0x41606df6, 0x3fd6c3e0}, +{'q', 0x414c06dc, 0x00000000}, +{0, 0x419b2af2, 0xc0a112e6}, +{'q', 0x40d6c3e0, 0xc0a112e6}, +{0, 0x40d6c3e0, 0xc16a180d}, +{'q', 0x00000000, 0xc10dbeb6}, +{0, 0xc0c7bb48, 0xc15d3542}, +{'9', 0xffd8ffcf, 0xffd8ff77}, +{'4', 0x0000ffa3, 0xffa70000}, +{'l', 0x41436fae, 0x00000000}, +{'q', 0x41200000, 0x00000000}, +{0, 0x4174d50c, 0xc07d6c40}, +{'8', 0xa42ae02a, 0xa2d4c300}, +{'q', 0xc0adf5b0, 0xc0852790}, +{0, 0xc17a338c, 0xc0852790}, +{'q', 0xc0b24148, 0x00000000}, +{0, 0xc13f2414, 0x3f9aa180}, +{'9', 0x0009ffcd, 0x001eff90}, +{'l', 0x00000000, 0xc14149e0}, +{'q', 0x40f6fad8, 0xc0097320}, +{0, 0x4166df5a, 0xc04e2cc0}, +{'q', 0x40d8e9ac, 0xbf897300}, +{0, 0x414c06de, 0xbf897300}, +{'q', 0x4176fad8, 0x00000000}, +{0, 0x41c36fae, 0x40e180e0}, +{'q', 0x410fe480, 0x40df5b10}, +{0, 0x410fe480, 0x419768cc}, +{'q', 0x00000000, 0x41052788}, +{0, 0xc0987bb0, 0x416180dc}, +{'q', 0xc0987bb0, 0x40b68ce8}, +{0, 0xc158e9a8, 0x40fd6c40}, +{'@', 0x00000034, 0x00005773},/* 4 x-advance: 87.449219 */ +{'M', 0x424fc905, 0xc2b0c74d}, +{'4', 0x01abfeef, 0x00000111}, +{'6', 0xfe550000, 0xffa2ffe4}, +{'l', 0x41886038, 0x00000000}, +{'l', 0x00000000, 0x42829aa2}, +{'l', 0x4164b990, 0xb6800000}, +{'l', 0x00000000, 0x41346714}, +{'l', 0xc164b990, 0x00000000}, +{'l', 0x00000000, 0x41bcfe48}, +{'l', 0xc157d6c4, 0x00000000}, +{'l', 0x00000000, 0xc1bcfe48}, +{'l', 0xc234f089, 0x00000000}, +{'l', 0xb5c00000, 0xc151655c}, +{'l', 0x4226b61e, 0xc27df5b1}, +{'@', 0x00000035, 0x00005773},/* 5 x-advance: 87.449219 */ +{'M', 0x416d50c0, 0xc2c86716}, +{'l', 0x4254e2ca, 0x00000000}, +{'4', 0x005b0000, 0x0000feba}, +{'l', 0x00000000, 0x41c48294}, +{'8', 0xf52ff817, 0xfc2ffc17}, +{'q', 0x41863a6a, 0x00000000}, +{0, 0x41d49e10, 0x41131d34}, +{'q', 0x411cc750, 0x41131d34}, +{0, 0x411cc750, 0x41c731d2}, +{'q', 0x00000000, 0x4181655f}, +{0, 0xc12112e8, 0x41c957a0}, +{'q', 0xc12112e4, 0x410ed19d}, +{0, 0xc1e31d34, 0x410ed19d}, +{'8', 0xf89900ce, 0xe795f8cc}, +{'l', 0x00000000, 0xc159fc90}, +{'8', 0x27631a30, 0x0c6c0c33}, +{'q', 0x4139c598, 0x00000000}, +{0, 0x41931d36, 0xc0c36fae}, +{'q', 0x40d8e9a8, 0xc0c36fae}, +{0, 0x40d8e9a8, 0xc1849e12}, +{'q', 0x00000000, 0xc1278448}, +{0, 0xc0d8e9a8, 0xc1849e10}, +{'q', 0xc0d8e9a8, 0xc0c36fb0}, +{0, 0xc1931d36, 0xc0c36fb0}, +{'8', 0x09aa00d5, 0x1ea809d6}, +{'l', 0x00000000, 0xc249579f}, +{'@', 0x00000036, 0x00005773},/* 6 x-advance: 87.449219 */ +{'M', 0x423579fc, 0xc25e036f}, +{'q', 0xc1120a4c, 0x00000000}, +{0, 0xc167f240, 0x40c7bb40}, +{'q', 0xc0a9aa18, 0x40c7bb48}, +{0, 0xc0a9aa18, 0x4188e9aa}, +{'q', 0x00000000, 0x412ce2cc}, +{0, 0x40a9aa18, 0x4188e9aa}, +{'q', 0x40abcfe8, 0x40c7bb48}, +{0, 0x4167f240, 0x40c7bb48}, +{'q', 0x41120a50, 0x00000000}, +{0, 0x4166df5c, 0xc0c7bb46}, +{'q', 0x40abcfe8, 0xc0c9e112}, +{0, 0x40abcfe8, 0xc188e9aa}, +{'q', 0x00000000, 0xc12df5b0}, +{0, 0xc0abcfe8, 0xc188e9aa}, +{'9', 0xffcfffd6, 0xffcfff8d}, +{'m', 0x41d74d50, 0xc229eed1}, +{'l', 0x00000000, 0x41459578}, +{'8', 0xe3aeedd8, 0xf6aef6d7}, +{'q', 0xc156c3e0, 0x00000000}, +{0, 0xc1a44b99, 0x4110f768}, +{'q', 0xc0e180dc, 0x4110f768}, +{0, 0xc100dbec, 0x41db0f78}, +{'8', 0xb94fd21f, 0xe769e72f}, +{'q', 0x41719c58, 0x00000000}, +{0, 0x41be9aa2, 0x41131d34}, +{'q', 0x410cabd0, 0x41120a50}, +{0, 0x410cabd0, 0x41c731d2}, +{'q', 0x00000000, 0x4176fada}, +{0, 0xc1120a50, 0x41c61eee}, +{'q', 0xc1120a50, 0x41154301}, +{0, 0xc1c25cc8, 0x41154301}, +{'q', 0xc18b0f76, 0xb4c00000}, +{0, 0xc1d49e10, 0xc1549e11}, +{'q', 0xc1131d36, 0xc155b0f8}, +{0, 0xc1131d36, 0xc21aa181}, +{'q', 0x00000000, 0xc1be112c}, +{0, 0x41346716, 0xc21768ce}, +{'q', 0x41346718, 0xc16293c0}, +{0, 0x41f225cc, 0xc16293c0}, +{'8', 0x08520028, 0x18560829}, +{'@', 0x00000037, 0x00005773},/* 7 x-advance: 87.449219 */ +{'M', 0x41346716, 0xc2c86716}, +{'l', 0x4280dbeb, 0x00000000}, +{'l', 0x00000000, 0x40b8b2b0}, +{'l', 0xc21180dc, 0x42bcdbeb}, +{'l', 0xc16293c2, 0x00000000}, +{'l', 0x4208e9aa, 0xc2b1957a}, +{'l', 0xc2407bb4, 0x00000000}, +{'l', 0xb6000000, 0xc1368ce0}, +{'@', 0x00000038, 0x00005773},/* 8 x-advance: 87.449219 */ +{'M', 0x422ec3dd, 0xc23e55e8}, +{'q', 0xc11aa180, 0x00000000}, +{0, 0xc173c224, 0x40a55e80}, +{'q', 0xc0b01b7c, 0x40a55e80}, +{0, 0xc0b01b7c, 0x4163a6a8}, +{'q', 0x00000000, 0x4110f76a}, +{0, 0x40b01b7c, 0x4163a6a9}, +{'q', 0x40b24148, 0x40a55e7e}, +{0, 0x4173c224, 0x40a55e7e}, +{'q', 0x411aa184, 0x00000000}, +{0, 0x4173c228, 0xc0a55e7e}, +{'q', 0x40b24148, 0xc0a7844a}, +{0, 0x40b24148, 0xc163a6a9}, +{'q', 0x00000000, 0xc110f768}, +{0, 0xc0b24148, 0xc163a6a8}, +{'9', 0xffd7ffd4, 0xffd7ff87}, +{'m', 0xc158e9a8, 0xc0b8b2b0}, +{'q', 0xc10b98ea, 0xc0097310}, +{0, 0xc159fc90, 0xc101eed0}, +{'q', 0xc09aa182, 0xc0bf2410}, +{0, 0xc09aa182, 0xc1690528}, +{'q', 0x00000000, 0xc14036f8}, +{0, 0x41086037, 0xc197f240}, +{'q', 0x4109731e, 0xc0df5b10}, +{0, 0x41bbeb61, 0xc0df5b10}, +{'q', 0x416f7690, 0x00000000}, +{0, 0x41bbeb62, 0x40df5b10}, +{'q', 0x41086038, 0x40df5b10}, +{0, 0x41086038, 0x4197f240}, +{'q', 0x00000000, 0x41097320}, +{0, 0xc09cc750, 0x41690528}, +{'q', 0xc09aa180, 0x40bf2418}, +{0, 0xc157d6c4, 0x4101eed0}, +{'q', 0x411cc74c, 0x40120a50}, +{0, 0x4173c224, 0x410ed1a0}, +{'q', 0x40b01b80, 0x40d49e10}, +{0, 0x40b01b80, 0x4181eed0}, +{'q', 0x00000000, 0x41690528}, +{0, 0xc10ed198, 0x41b2cabd}, +{'q', 0xc10dbeb8, 0x40f920a5}, +{0, 0xc1cb7d6e, 0x40f920a5}, +{'q', 0xc1849e10, 0x34000000}, +{0, 0xc1cc06de, 0xc0f920a4}, +{'q', 0xc10dbeb7, 0xc0f920a5}, +{0, 0xc10dbeb7, 0xc1b2cabd}, +{'q', 0x00000000, 0xc1198e98}, +{0, 0x40b01b7e, 0xc181eed0}, +{'9', 0xffcb002c, 0xffb9007a}, +{'m', 0xc09eed1c, 0xc1ab4670}, +{'8', 0x61263e00, 0x226d2227}, +{'8', 0xde6c0045, 0x9f27de27}, +{'8', 0x9fd9c200, 0xde94ded9}, +{'8', 0x229300ba, 0x61da22da}, +{'@', 0x00000039, 0x00005773},/* 9 x-advance: 87.449219 */ +{'M', 0x41719c59, 0xc0052784}, +{'l', 0x00000000, 0xc145957a}, +{'8', 0x1d521328, 0x0a520a29}, +{'q', 0x4156c3dc, 0x00000000}, +{0, 0x41a3c226, 0xc10fe482}, +{'q', 0x40e3a6a8, 0xc110f768}, +{0, 0x4101eed4, 0xc1db98ea}, +{'8', 0x46b22ee1, 0x189718d1}, +{'q', 0xc1708974, 0x00000000}, +{0, 0xc1be9aa2, 0xc110f768}, +{'q', 0xc10b98e9, 0xc1120a50}, +{0, 0xc10b98e9, 0xc1c731d4}, +{'q', 0x00000000, 0xc176fad8}, +{0, 0x41120a4f, 0xc1c61eec}, +{'q', 0x41120a4e, 0xc1154300}, +{0, 0x41c25cc7, 0xc1154300}, +{'q', 0x418b0f76, 0x00000000}, +{0, 0x41d4149c, 0x4155b0f8}, +{'q', 0x41131d38, 0x41549e10}, +{0, 0x41131d38, 0x421aa180}, +{'q', 0x00000000, 0x41bd87bc}, +{0, 0xc1346718, 0x421768ce}, +{'q', 0xc133542c, 0x416180dd}, +{0, 0xc1f19c58, 0x416180dd}, +{'8', 0xf8ae00d8, 0xe8aaf8d7}, +{'m', 0x41d7d6c4, 0xc229eed2}, +{'q', 0x41120a50, 0x00000000}, +{0, 0x4166df5c, 0xc0c7bb40}, +{'q', 0x40abcfe0, 0xc0c7bb48}, +{0, 0x40abcfe0, 0xc188e9ac}, +{'q', 0x00000000, 0xc12ce2c8}, +{0, 0xc0abcfe0, 0xc1886034}, +{'q', 0xc0a9aa18, 0xc0c9e120}, +{0, 0xc166df5c, 0xc0c9e120}, +{'q', 0xc1120a50, 0x00000000}, +{0, 0xc167f240, 0x40c9e120}, +{'q', 0xc0a9aa18, 0x40c7bb40}, +{0, 0xc0a9aa18, 0x41886034}, +{'q', 0x00000000, 0x412df5b4}, +{0, 0x40a9aa18, 0x4188e9ac}, +{'q', 0x40abcfe0, 0x40c7bb40}, +{0, 0x4167f240, 0x40c7bb40}, +{'@', 0x0000003a, 0x00002e4f},/* : x-advance: 46.308594 */ +{'M', 0x4180dbeb, 0xc1886037}, +{'l', 0x416293c2, 0x00000000}, +{'4', 0x00880000, 0x0000ff8f}, +{'6', 0xff780000, 0xfe500000}, +{'l', 0x416293c2, 0x00000000}, +{'l', 0x00000000, 0x41886036}, +{'l', 0xc16293c2, 0x00000000}, +{'l', 0x00000000, 0xc1886036}, +{'@', 0x0000003b, 0x00002e4f},/* ; x-advance: 46.308594 */ +{'M', 0x4180dbeb, 0xc28e25cc}, +{'l', 0x416293c2, 0x00000000}, +{'4', 0x00880000, 0x0000ff8f}, +{'6', 0xff780000, 0x01b00000}, +{'l', 0x416293c2, 0x36000000}, +{'l', 0x00000000, 0x4138b2b0}, +{'l', 0xc1301b7c, 0x41abcfe4}, +{'l', 0xc10a8604, 0x00000000}, +{'l', 0x40b01b7c, 0xc1abcfe4}, +{'l', 0x00000000, 0xc138b2b0}, +{'@', 0x0000003c, 0x0000732a},/* < x-advance: 115.164062 */ +{'M', 0x42c93543, 0xc2874d51}, +{'l', 0xc28a8604, 0x41c50c08}, +{'l', 0x428a8604, 0x41c3f921}, +{'l', 0x00000000, 0x41436fac}, +{'l', 0xc2ac149e, 0xc1f9aa17}, +{'l', 0xb5800000, 0xc132414c}, +{'l', 0x42ac149e, 0xc1f9aa16}, +{'l', 0x00000000, 0x41436fa8}, +{'@', 0x0000003d, 0x0000732a},/* = x-advance: 115.164062 */ +{'M', 0x41690527, 0xc279aa18}, +{'l', 0x42ac149e, 0x00000000}, +{'4', 0x005a0000, 0x0000fd50}, +{'6', 0xffa60000, 0x00db0000}, +{'l', 0x42ac149e, 0x00000000}, +{'l', 0x00000000, 0x41368ce4}, +{'l', 0xc2ac149e, 0x00000000}, +{'l', 0xb5800000, 0xc1368ce4}, +{'@', 0x0000003e, 0x0000732a},/* > x-advance: 115.164062 */ +{'M', 0x41690527, 0xc2874d51}, +{'l', 0x00000000, 0xc1436fa8}, +{'l', 0x42ac149e, 0x41f9aa16}, +{'l', 0x00000000, 0x4132414c}, +{'l', 0xc2ac149e, 0x41f9aa17}, +{'l', 0xb5800000, 0xc1436fac}, +{'l', 0x428a414a, 0xc1c3f921}, +{'l', 0xc28a414a, 0xc1c50c08}, +{'@', 0x0000003f, 0x000048f3},/* ? x-advance: 72.949219 */ +{'M', 0x41d1eed1, 0xc1886037}, +{'l', 0x4159fc92, 0x00000000}, +{'4', 0x00880000, 0x0000ff94}, +{'6', 0xff780000, 0xffb20069}, +{'4', 0x0000ff9a, 0xffae0000}, +{'8', 0xa70fca00, 0xaf3fde0f}, +{'l', 0x40c149e0, 0xc0bf2418}, +{'8', 0xcb2ce41e, 0xcd0de70d}, +{'8', 0xb3ddd100, 0xe3a4e3de}, +{'8', 0x12a600d6, 0x369d12d1}, +{'l', 0x00000000, 0xc149e110}, +{'8', 0xd366e232, 0xf16bf134}, +{'q', 0x41459578, 0x00000000}, +{0, 0x419e63a6, 0x40d05270}, +{'q', 0x40f08970, 0x40d05280}, +{0, 0x40f08970, 0x41897320}, +{'8', 0x4ded2800, 0x52bd24ed}, +{'l', 0xc0bcfe48, 0x40b8b2b0}, +{'8', 0x27dd19e7, 0x1bf20df6}, +{'8', 0x1bfc0bfd, 0x2cff10ff}, +{'l', 0x00000000, 0x410414a0}, +{'@', 0x00000040, 0x00008973},/* @ x-advance: 137.449219 */ +{'M', 0x424c9052, 0xc210293c}, +{'q', 0x00000000, 0x41198e9a}, +{0, 0x40987bb8, 0x41719c5a}, +{'8', 0x2b682b26, 0xd4670042}, +{'q', 0x40987bc0, 0xc0b01b80}, +{0, 0x40987bc0, 0xc1708974}, +{'q', 0x00000000, 0xc11655e8}, +{0, 0xc09aa180, 0xc16e63a4}, +{'8', 0xd498d4da, 0x2c9900c0}, +{'9', 0x002cffda, 0x0077ffda}, +{'m', 0x42124f08, 0x41a08973}, +{'8', 0x3db629e0, 0x13a013d7}, +{'q', 0xc138b2b0, 0x00000000}, +{0, 0xc19655e8, 0xc1052784}, +{'q', 0xc0e5cc78, 0xc1063a6a}, +{0, 0xc0e5cc78, 0xc1ae7f24}, +{'q', 0x00000000, 0xc156c3dc}, +{0, 0x40e7f240, 0xc1ae7f24}, +{'q', 0x40e7f240, 0xc1063a68}, +{0, 0x4195cc76, 0xc1063a68}, +{'8', 0x14610037, 0x3c491329}, +{'4', 0xffba0000, 0x0000004c}, +{'l', 0x00000000, 0x4245957a}, +{'q', 0x411cc748, 0xbfbcfe40}, +{0, 0x4174d508, 0xc10ed19a}, +{'q', 0x40b24150, 0xc0f08974}, +{0, 0x40b24150, 0xc19b2af3}, +{'8', 0x95efc700, 0xa3cdcef0}, +{'q', 0xc0df5b10, 0xc10cabd0}, +{0, 0xc1886038, 0xc156c3e0}, +{'q', 0xc1200000, 0xc09655e0}, +{0, 0xc1ae7f24, 0xc09655e0}, +{'q', 0xc104149c, 0x00000000}, +{0, 0xc17d6c3c, 0x400dbea0}, +{'q', 0xc0f2af40, 0x40097320}, +{0, 0xc1606df4, 0x40ce2cb0}, +{'q', 0xc1289734, 0x40db0f80}, +{0, 0xc184149f, 0x418fe482}, +{'q', 0xc0bcfe48, 0x41312e64}, +{0, 0xc0bcfe48, 0x41c036fc}, +{'q', 0x00000000, 0x412abcfc}, +{0, 0x4074d510, 0x419fffff}, +{'q', 0x407920a0, 0x41154302}, +{0, 0x4133542e, 0x41838b2b}, +{'q', 0x40e180e0, 0x40df5b0f}, +{0, 0x41827846, 0x412abcfe}, +{'q', 0x41143018, 0x4067f240}, +{0, 0x419e63a6, 0x4067f240}, +{'q', 0x410a8600, 0x00000000}, +{0, 0x4187d6c4, 0xc038b2ac}, +{'9', 0xffe90043, 0xffbd007a}, +{'l', 0x40c149e0, 0x40ee63a6}, +{'q', 0xc1063a68, 0x40d0527a}, +{0, 0xc19293c0, 0x41200001}, +{'q', 0xc11dda38, 0x405b0f70}, +{0, 0xc1a08974, 0x405b0f70}, +{'q', 0xc146a860, 0x00000000}, +{0, 0xc1bb61ee, 0xc08b98e8}, +{'q', 0xc1301b80, 0xc08dbeb6}, +{0, 0xc19cc74e, 0xc14d19c6}, +{'q', 0xc109731e, 0xc1063a6a}, +{0, 0xc151655e, 0xc19b2af4}, +{'q', 0xc08fe482, 0xc1312e62}, +{0, 0xc08fe482, 0xc1be112d}, +{'q', 0x00000000, 0xc1436fb0}, +{0, 0x40920a4e, 0xc1ba4f08}, +{'q', 0x40920a50, 0xc1312e64}, +{0, 0x41505278, 0xc19bb466}, +{'q', 0x410a8604, 0xc1086038}, +{0, 0x41a00000, 0xc1505278}, +{'q', 0x413579fc, 0xc0920a50}, +{0, 0x41c036fc, 0xc0920a50}, +{'q', 0x4163a6a8, 0x00000000}, +{0, 0x41d301b4, 0x40bad870}, +{'q', 0x41436fb0, 0x40bad880}, +{0, 0x41a3c228, 0x41849e14}, +{'q', 0x40a112e0, 0x40d27840}, +{0, 0x40f4d510, 0x4164b98c}, +{'q', 0x402bcfe0, 0x40f6fad8}, +{0, 0x402bcfe0, 0x417f9208}, +{'q', 0x00000000, 0x418d3543}, +{0, 0xc12abd00, 0x41ded19d}, +{'q', 0xc12abd00, 0x412338b2}, +{0, 0xc1ebb468, 0x4129aa17}, +{'l', 0x00000000, 0xc1255e7f}, +{'@', 0x00000041, 0x00005e06},/* A x-advance: 94.023438 */ +{'M', 0x423beb62, 0xc2adb0f7}, +{'4', 0x018eff6d, 0x00000126}, +{'6', 0xfe72ff6d, 0xff96ffc3}, +{'l', 0x4175e7f4, 0x00000000}, +{'l', 0x4218c06d, 0x42c86716}, +{'l', 0xc16180d8, 0x00000000}, +{'l', 0xc1120a50, 0xc1cda338}, +{'l', 0xc234abd0, 0x00000000}, +{'l', 0xc1120a4e, 0x41cda338}, +{'l', 0xc164b98e, 0x00000000}, +{'l', 0x42190527, 0xc2c86716}, +{'[', 0x00410041, 0x000003d3}, +{'@', 0x00000042, 0x00005e4b},/* B x-advance: 94.292969 */ +{'M', 0x41d86037, 0xc23f68ce}, +{'4', 0x01250000, 0x000000ad}, +{'q', 0x412f0894, 0x00000000}, +{0, 0x4181655c, 0xc08fe482}, +{'8', 0x912adc2a, 0x92d6b500}, +{'9', 0xffddffd7, 0xffddff7f}, +{'6', 0x0000ff53, 0xfeb70000}, +{'4', 0x00f10000, 0x000000a0}, +{'q', 0x411eed18, 0x00000000}, +{0, 0x416c3dd8, 0xc06c3de0}, +{'8', 0xa527e227, 0xa6d9c400}, +{'9', 0xffe2ffda, 0xffe2ff8a}, +{'6', 0x0000ff60, 0xffa7ff94}, +{'l', 0x420a8603, 0x00000000}, +{'q', 0x41780dc0, 0x00000000}, +{0, 0x41bf2414, 0x40ce2cb0}, +{'q', 0x41063a68, 0x40ce2ca0}, +{0, 0x41063a68, 0x419293c0}, +{'q', 0x00000000, 0x41131d38}, +{0, 0xc0897310, 0x416a1810}, +{'q', 0xc0897320, 0x40adf5b0}, +{0, 0xc149e114, 0x40d8e9a8}, +{'q', 0x411ffffc, 0x40097320}, +{0, 0x41780dbc, 0x410fe480}, +{'q', 0x40b24150, 0x40d8e9b0}, +{0, 0x40b24150, 0x4187d6c5}, +{'q', 0x00000000, 0x4156c3dd}, +{0, 0xc1120a50, 0x41a5e7f2}, +{'9', 0x003affb7, 0x003aff31}, +{'l', 0xc20fe482, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x00000043, 0x00005ff9},/* C x-advance: 95.972656 */ +{'M', 0x42b10c07, 0xc2b8f769}, +{'l', 0x00000000, 0x4164b990}, +{'q', 0xc0db0f80, 0xc0cc06e0}, +{0, 0xc16a1810, 0xc1187bb0}, +{'q', 0xc0f6fae0, 0xc049e120}, +{0, 0xc1838b2c, 0xc049e120}, +{'q', 0xc189731c, 0x00000000}, +{0, 0xc1d27843, 0x41289730}, +{'q', 0xc1120a50, 0x41278450}, +{0, 0xc1120a50, 0x41f2af40}, +{'q', 0x00000000, 0x419e63a7}, +{0, 0x41120a50, 0x41f2af40}, +{'q', 0x41120a4e, 0x4127844b}, +{0, 0x41d27843, 0x4127844b}, +{'q', 0x410b98e8, 0x00000000}, +{0, 0x41838b2c, 0xc049e114}, +{'9', 0xffe7003e, 0xffb40075}, +{'l', 0x00000000, 0x416293c2}, +{'q', 0xc0e3a6b0, 0x409aa180}, +{0, 0xc1719c60, 0x40e7f241}, +{'q', 0xc0fd6c30, 0x401aa180}, +{0, 0xc1863a68, 0x401aa180}, +{'q', 0xc1b60370, 0x34000000}, +{0, 0xc20f5b10, 0xc15e4828}, +{'q', 0xc151655c, 0xc15f5b10}, +{0, 0xc151655c, 0xc21836fb}, +{'q', 0x00000000, 0xc1c149e0}, +{0, 0x4151655e, 0xc21836fa}, +{'q', 0x4151655e, 0xc15f5b10}, +{0, 0x420f5b10, 0xc15f5b10}, +{'q', 0x410fe480, 0x00000000}, +{0, 0x41874d50, 0x401aa180}, +{'q', 0x40ff9210, 0x401655e0}, +{0, 0x416f7690, 0x40e3a6a0}, +{'@', 0x00000044, 0x000069d6},/* D x-advance: 105.835938 */ +{'M', 0x41d86037, 0xc2b21eed}, +{'4', 0x026f0000, 0x00000083}, +{'q', 0x41a5e7f2, 0x00000000}, +{0, 0x41f2af3e, 0xc11655e8}, +{'q', 0x411aa180, 0xc11655e8}, +{0, 0x411aa180, 0xc1ed50bf}, +{'q', 0x00000000, 0xc1a112e8}, +{0, 0xc11aa180, 0xc1ebb468}, +{'9', 0xffb5ffb4, 0xffb5ff0e}, +{'6', 0x0000ff7d, 0xffa7ff94}, +{'l', 0x41ded19c, 0x00000000}, +{'q', 0x41e90526, 0x00000000}, +{0, 0x422b01b7, 0x41425cc8}, +{'q', 0x4159fc90, 0x414149e0}, +{0, 0x4159fc90, 0x421768ce}, +{'q', 0x00000000, 0x41cf3f91}, +{0, 0xc15b0f78, 0x421836fa}, +{'9', 0x0061ff93, 0x0061feab}, +{'l', 0xc1ded19c, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x00000045, 0x000056d8},/* E x-advance: 86.843750 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x427d6c3d, 0x00000000}, +{'l', 0x00000000, 0x41368ce0}, +{'l', 0xc24731d2, 0x00000000}, +{'l', 0xb6000000, 0x41ed50c2}, +{'l', 0x423edf5a, 0x00000000}, +{'l', 0x00000000, 0x41368ce0}, +{'l', 0xc23edf5a, 0x00000000}, +{'l', 0xb6000000, 0x42113c22}, +{'l', 0x424c06de, 0x35800000}, +{'l', 0x00000000, 0x41368ce3}, +{'l', 0xc28120a4, 0x00000000}, +{'l', 0xb6800000, 0xc2c86716}, +{'@', 0x00000046, 0x00004f0f},/* F x-advance: 79.058594 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x426655e7, 0x00000000}, +{'l', 0x00000000, 0x41368ce0}, +{'l', 0xc2301b7c, 0x00000000}, +{'l', 0xb6000000, 0x41ec3dda}, +{'l', 0x421eed18, 0x00000000}, +{'l', 0x00000000, 0x41368ce4}, +{'l', 0xc21eed18, 0x00000000}, +{'l', 0xb6000000, 0x423f68ce}, +{'l', 0xc158e9aa, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x00000047, 0x00006a82},/* G x-advance: 106.507812 */ +{'M', 0x42a39fc9, 0xc164b98e}, +{'l', 0x00000000, 0xc1d74d51}, +{'l', 0xc1b12e64, 0x00000000}, +{'4', 0xffa70000, 0x0000011c}, +{'l', 0x00000000, 0x422c149e}, +{'q', 0xc0fb4670, 0x40b24146}, +{0, 0xc18a8600, 0x41074d4f}, +{'q', 0xc11768d0, 0x40346716}, +{0, 0xc1a19c5a, 0x40346716}, +{'q', 0xc1bbeb62, 0x34000000}, +{0, 0xc2131d36, 0xc15b0f76}, +{'q', 0xc1538b28, 0xc15c225c}, +{0, 0xc1538b28, 0xc2190528}, +{'q', 0x00000000, 0xc1c48294}, +{0, 0x41538b2a, 0xc2190526}, +{'q', 0x41549e12, 0xc15c2260}, +{0, 0x42131d36, 0xc15c2260}, +{'q', 0x411cc748, 0x00000000}, +{0, 0x4194b98c, 0x401aa180}, +{'9', 0x00130046, 0x00380082}, +{'l', 0x00000000, 0x4166df60}, +{'q', 0xc0f08970, 0xc0cc06e0}, +{0, 0xc17f9208, 0xc1198e98}, +{'q', 0xc1074d50, 0xc04e2cc0}, +{0, 0xc18e482a, 0xc04e2cc0}, +{'q', 0xc1931d34, 0x00000000}, +{0, 0xc1dd3542, 0x41244b98}, +{'q', 0xc1131d36, 0x41244b98}, +{0, 0xc1131d36, 0x41f4d50c}, +{'q', 0x00000000, 0x41a225cd}, +{0, 0x41131d36, 0x41f44b99}, +{'q', 0x4114301c, 0x41244b99}, +{0, 0x41dd3542, 0x41244b99}, +{'8', 0xf7660039, 0xe151f62d}, +{'@', 0x00000048, 0x0000675b},/* H x-advance: 103.355469 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x4158e9aa, 0x00000000}, +{'l', 0x00000000, 0x42244b99}, +{'l', 0x42450c06, 0x00000000}, +{'l', 0x00000000, 0xc2244b99}, +{'l', 0x4158e9a8, 0x00000000}, +{'l', 0x00000000, 0x42c86716}, +{'l', 0xc158e9a8, 0x00000000}, +{'l', 0x00000000, 0xc23edf5b}, +{'l', 0xc2450c06, 0x00000000}, +{'l', 0xb6000000, 0x423edf5b}, +{'l', 0xc158e9aa, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x00000049, 0x00002889},/* I x-advance: 40.535156 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x4158e9aa, 0x00000000}, +{'l', 0x00000000, 0x42c86716}, +{'l', 0xc158e9aa, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x0000004a, 0x00002889},/* J x-advance: 40.535156 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'4', 0x0000006c, 0x02e90000}, +{'q', 0x00000000, 0x4190f769}, +{0, 0xc0dd3544, 0x41d27845}, +{'9', 0x0041ffca, 0x0041ff50}, +{'4', 0x0000ffd7, 0xffa50000}, +{'l', 0x40874d50, 0x00000000}, +{'q', 0x410fe482, 0x00000000}, +{0, 0x414af3f9, 0xc0a112e6}, +{'q', 0x406c3ddc, 0xc0a112e5}, +{0, 0x406c3ddc, 0xc1906df5}, +{'l', 0x00000000, 0xc2ba7165}, +{'@', 0x0000004b, 0x00005a22},/* K x-advance: 90.132812 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x4158e9aa, 0x00000000}, +{'l', 0x00000000, 0x4229655e}, +{'l', 0x4233dda2, 0xc229655e}, +{'l', 0x418b98ec, 0x00000000}, +{'l', 0xc246ed1a, 0x423ad87b}, +{'l', 0x42552784, 0x4255f5b1}, +{'l', 0xc18ed19c, 0x00000000}, +{'l', 0xc2407bb4, 0xc2410527}, +{'l', 0xb6000000, 0x42410527}, +{'l', 0xc158e9aa, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x0000004c, 0x00004c93},/* L x-advance: 76.574219 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x4158e9aa, 0x00000000}, +{'l', 0x00000000, 0x42b1957a}, +{'l', 0x42432af4, 0xb6400000}, +{'l', 0x00000000, 0x41368ce3}, +{'l', 0xc279655f, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'[', 0x0041004c, 0x00000327}, +{'@', 0x0000004d, 0x00007697},/* M x-advance: 118.589844 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x41a19c58, 0x00000000}, +{'l', 0x41cc9054, 0x42886036}, +{'l', 0x41cda336, 0xc2886036}, +{'l', 0x41a19c5c, 0x00000000}, +{'l', 0x00000000, 0x42c86716}, +{'l', 0xc1538b30, 0x00000000}, +{'l', 0x00000000, 0xc2aff920}, +{'l', 0xc1ceb61c, 0x4289731c}, +{'l', 0xc159fc94, 0x36800000}, +{'l', 0xc1ceb61e, 0xc289731c}, +{'l', 0x00000000, 0x42aff920}, +{'l', 0xc1527844, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x0000004e, 0x000066d1},/* N x-advance: 102.816406 */ +{'M', 0x4157d6c4, 0xc2c86716}, +{'l', 0x41920a4f, 0x00000000}, +{'l', 0x4231b7d6, 0x42a7a6a8}, +{'l', 0x00000000, 0xc2a7a6a8}, +{'l', 0x41527848, 0x00000000}, +{'l', 0x00000000, 0x42c86716}, +{'l', 0xc1920a50, 0x00000000}, +{'l', 0xc231b7d6, 0xc2a7a6a8}, +{'l', 0x00000000, 0x42a7a6a8}, +{'l', 0xc1527844, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x0000004f, 0x00006c30},/* O x-advance: 108.187500 */ +{'M', 0x4258a4f0, 0xc2b6036f}, +{'q', 0xc16c3dd8, 0x00000000}, +{0, 0xc1bbeb61, 0x41301b78}, +{'q', 0xc10a8604, 0x41301b80}, +{0, 0xc10a8604, 0x41f00000}, +{'q', 0x00000000, 0x419768cf}, +{0, 0x410a8604, 0x41ef768d}, +{'q', 0x410b98ea, 0x41301b7d}, +{0, 0x41bbeb61, 0x41301b7d}, +{'q', 0x416c3dd8, 0x00000000}, +{0, 0x41bad87c, 0xc1301b7d}, +{'q', 0x410a8600, 0xc1301b7c}, +{0, 0x410a8600, 0xc1ef768d}, +{'q', 0x00000000, 0xc197f240}, +{0, 0xc10a8600, 0xc1f00000}, +{'9', 0xffa8ffbc, 0xffa8ff46}, +{'m', 0x00000000, 0xc1301b80}, +{'q', 0x41a89730, 0x00000000}, +{0, 0x4206c3de, 0x416293c0}, +{'q', 0x4149e110, 0x416180e0}, +{0, 0x4149e110, 0x421768ce}, +{'q', 0x00000000, 0x41bd87bc}, +{0, 0xc149e110, 0x421768ce}, +{'q', 0xc149e118, 0x416180dd}, +{0, 0xc206c3de, 0x416180dd}, +{'q', 0xc1a920a4, 0xb4c00000}, +{0, 0xc2074d50, 0xc16180dc}, +{'q', 0xc149e114, 0xc16180db}, +{0, 0xc149e114, 0xc21768ce}, +{'q', 0x00000000, 0xc1be112c}, +{0, 0x4149e112, 0xc21768ce}, +{'q', 0x414af3fa, 0xc16293c0}, +{0, 0x42074d50, 0xc16293c0}, +{'[', 0x002d004f, 0x000003d3}, +{'@', 0x00000050, 0x000052e2},/* P x-advance: 82.882812 */ +{'M', 0x41d86037, 0xc2b21eed}, +{'4', 0x012d0000, 0x00000088}, +{'q', 0x411768cc, 0x00000000}, +{0, 0x416a180c, 0xc09cc750}, +{'8', 0x9129d929, 0x91d7b900}, +{'9', 0xffd9ffd7, 0xffd9ff8b}, +{'6', 0x0000ff78, 0xffa7ff94}, +{'l', 0x41f4d50c, 0x00000000}, +{'q', 0x4186c3dc, 0x00000000}, +{0, 0x41cb7d6a, 0x40f4d510}, +{'q', 0x410a8608, 0x40f2af40}, +{0, 0x410a8608, 0x41b24148}, +{'q', 0x00000000, 0x416d50c0}, +{0, 0xc10a8608, 0x41b35430}, +{'9', 0x003cffbc, 0x003cff35}, +{'l', 0xc1886037, 0x00000000}, +{'l', 0x00000000, 0x422112e6}, +{'l', 0xc158e9aa, 0x00000000}, +{'l', 0x00000000, 0xc2c86716}, +{'@', 0x00000051, 0x00006c30},/* Q x-advance: 108.187500 */ +{'M', 0x4258a4f0, 0xc2b6036f}, +{'q', 0xc16c3dd8, 0x00000000}, +{0, 0xc1bbeb61, 0x41301b78}, +{'q', 0xc10a8604, 0x41301b80}, +{0, 0xc10a8604, 0x41f00000}, +{'q', 0x00000000, 0x419768cf}, +{0, 0x410a8604, 0x41ef768d}, +{'q', 0x410b98ea, 0x41301b7d}, +{0, 0x41bbeb61, 0x41301b7d}, +{'q', 0x416c3dd8, 0x00000000}, +{0, 0x41bad87c, 0xc1301b7d}, +{'q', 0x410a8600, 0xc1301b7c}, +{0, 0x410a8600, 0xc1ef768d}, +{'q', 0x00000000, 0xc197f240}, +{0, 0xc10a8600, 0xc1f00000}, +{'9', 0xffa8ffbc, 0xffa8ff46}, +{'m', 0x4197f240, 0x42b263a6}, +{'4', 0x009c008e, 0x0000ff7d}, +{'l', 0xc16d50bc, 0xc1805278}, +{'8', 0x01e501ef, 0x00ef00f7}, +{'q', 0xc1a920a4, 0x00000000}, +{0, 0xc2074d50, 0xc16180dc}, +{'q', 0xc149e114, 0xc16293c1}, +{0, 0xc149e114, 0xc21768ce}, +{'q', 0x00000000, 0xc1be112c}, +{0, 0x4149e112, 0xc21768ce}, +{'q', 0x414af3fa, 0xc16293c0}, +{0, 0x42074d50, 0xc16293c0}, +{'q', 0x41a89730, 0x00000000}, +{0, 0x4206c3de, 0x416293c0}, +{'q', 0x4149e110, 0x416180e0}, +{0, 0x4149e110, 0x421768ce}, +{'q', 0x00000000, 0x418b98ea}, +{0, 0xc0e180e0, 0x41eeed1a}, +{'q', 0xc0df5b10, 0x4146a860}, +{0, 0xc1a225cc, 0x419293c2}, +{'[', 0x002d0051, 0x000003d3}, +{'@', 0x00000052, 0x00005f80},/* R x-advance: 95.500000 */ +{'M', 0x427406df, 0xc23beb62}, +{'8', 0x32430b22, 0x6a422621}, +{'4', 0x00db006e, 0x0000ff8c}, +{'l', 0xc14d19c8, 0xc1cda338}, +{'8', 0x96b3b0d9, 0xe69be6db}, +{'l', 0xc16c3dda, 0x00000000}, +{'l', 0x00000000, 0x4229655e}, +{'4', 0x0000ff94, 0xfcdf0000}, +{'l', 0x41f4d50c, 0x00000000}, +{'q', 0x4189731c, 0x00000000}, +{0, 0x41cd19c6, 0x40e5cc70}, +{'q', 0x41074d50, 0x40e5cc80}, +{0, 0x41074d50, 0x41ad6c40}, +{'q', 0x00000000, 0x411768cc}, +{0, 0xc08dbec0, 0x417b4670}, +{'9', 0x0031ffde, 0x0045ff9a}, +{'m', 0xc207d6c4, 0xc2285278}, +{'4', 0x011c0000, 0x00000088}, +{'q', 0x411cc74c, 0x00000000}, +{0, 0x416c3dd8, 0xc08fe480}, +{'8', 0x9628dc28, 0x97d8ba00}, +{'q', 0xc09eed18, 0xc08fe480}, +{0, 0xc16c3dd8, 0xc08fe480}, +{'l', 0xc1886037, 0x00000000}, +{'@', 0x00000053, 0x0000573f},/* S x-advance: 87.246094 */ +{'M', 0x42931d35, 0xc2c1d354}, +{'l', 0x00000000, 0x41538b28}, +{'q', 0xc0f6fad0, 0xc06c3dc0}, +{0, 0xc1690528, 0xc0b01b70}, +{'q', 0xc0db0f70, 0xbfe7f240}, +{0, 0xc1538b28, 0xbfe7f240}, +{'q', 0xc1312e64, 0x00000000}, +{0, 0xc188e9ab, 0x40897310}, +{'8', 0x61d122d1, 0x501f3500}, +{'9', 0x001a0020, 0x002b0079}, +{'l', 0x410301b8, 0x3fd6c3e0}, +{'q', 0x4172af40, 0x4038b2b0}, +{0, 0x41b2cabc, 0x412338b0}, +{'q', 0x40e7f240, 0x40e7f240}, +{0, 0x40e7f240, 0x419bb467}, +{'q', 0x00000000, 0x41690528}, +{0, 0xc11cc750, 0x41b0a4f0}, +{'q', 0xc11bb464, 0x40f08975}, +{0, 0xc1e4b98c, 0x40f08975}, +{'q', 0xc0e3a6a8, 0x34000000}, +{0, 0xc172af40, 0xbfce2cab}, +{'9', 0xfff4ffc1, 0xffdaff7c}, +{'l', 0x00000000, 0xc15f5b0f}, +{'q', 0x4104149e, 0x4094301c}, +{0, 0x4181655e, 0x40df5b0e}, +{'q', 0x40fd6c3c, 0x401655e8}, +{0, 0x417920a6, 0x401655e8}, +{'q', 0x4139c594, 0x00000000}, +{0, 0x418f5b0e, 0xc0920a4e}, +{'8', 0x9832dc32, 0xa4dcc500}, +{'9', 0xffdfffdd, 0xffcfff8a}, +{'l', 0xc10414a0, 0xbfce2cc0}, +{'q', 0xc172af3e, 0xc04149e0}, +{0, 0xc1af920a, 0xc11768cc}, +{'q', 0xc0d8e9a6, 0xc0ce2cb0}, +{0, 0xc0d8e9a6, 0xc18f5b0e}, +{'q', 0x00000000, 0xc1549e18}, +{0, 0x41154301, 0xc1a7844c}, +{'q', 0x411655e8, 0xc0f4d510}, +{0, 0x41ceb61f, 0xc0f4d510}, +{'q', 0x40e180d8, 0x00000000}, +{0, 0x4165cc74, 0x3fa338c0}, +{'q', 0x40ea1808, 0x3fa338c0}, +{0, 0x416f768c, 0x4074d500}, +{'[', 0x00410053, 0x0000028c}, +{'@', 0x00000054, 0x000053f5},/* T x-advance: 83.957031 */ +{'M', 0xbece2cac, 0xc2c86716}, +{'l', 0x42a987bb, 0x00000000}, +{'l', 0x00000000, 0x41368ce0}, +{'l', 0xc20e4828, 0x00000000}, +{'l', 0x00000000, 0x42b1957a}, +{'l', 0xc159fc90, 0x00000000}, +{'l', 0x00000000, 0xc2b1957a}, +{'l', 0xc20e4829, 0x00000000}, +{'l', 0xb5b00000, 0xc1368ce0}, +{'@', 0x00000055, 0x0000649a},/* U x-advance: 100.601562 */ +{'M', 0x413f2414, 0xc2c86716}, +{'4', 0x0000006c, 0x01e60000}, +{'q', 0x00000000, 0x4180dbeb}, +{0, 0x40bad87c, 0x41b9c595}, +{'q', 0x40bad87c, 0x40e180da}, +{0, 0x419768cd, 0x40e180da}, +{'q', 0x41505278, 0x00000000}, +{0, 0x4196df5a, 0xc0e180da}, +{'9', 0xffc8002e, 0xff47002e}, +{'4', 0xfe1a0000, 0x0000006c}, +{'l', 0x00000000, 0x427a338b}, +{'q', 0x00000000, 0x419cc74d}, +{0, 0xc11bb468, 0x41ecc74c}, +{'q', 0xc11aa180, 0x41200001}, +{0, 0xc1e4b98e, 0x41200001}, +{'q', 0xc197f240, 0xb4c00000}, +{0, 0xc1e5cc74, 0xc1200000}, +{'q', 0xc11aa180, 0xc11fffff}, +{0, 0xc11aa180, 0xc1ecc74c}, +{'l', 0x00000000, 0xc27a338b}, +{'@', 0x00000056, 0x00005e06},/* V x-advance: 94.023438 */ +{'M', 0x421d50c0, 0x00000000}, +{'l', 0xc2190527, 0xc2c86716}, +{'l', 0x416293c1, 0x00000000}, +{'l', 0x41fdf5b2, 0x42a8b98e}, +{'l', 0x41fe7f24, 0xc2a8b98e}, +{'l', 0x416180d8, 0x00000000}, +{'l', 0xc218c06d, 0x42c86716}, +{'l', 0xc175e7f4, 0x00000000}, +{'@', 0x00000057, 0x000087e7},/* W x-advance: 135.902344 */ +{'M', 0x40920a4f, 0xc2c86716}, +{'l', 0x415b0f76, 0x00000000}, +{'l', 0x41a89731, 0x42a9655e}, +{'l', 0x41a80dbe, 0xc2a9655e}, +{'l', 0x4173c224, 0x00000000}, +{'l', 0x41a89734, 0x42a9655e}, +{'l', 0x41a80dbc, 0xc2a9655e}, +{'l', 0x415c2260, 0x00000000}, +{'l', 0xc1c957a0, 0x42c86716}, +{'l', 0xc1886038, 0x00000000}, +{'l', 0xc1a920a4, 0xc2adf5b1}, +{'l', 0xc1aabcfe, 0x42adf5b1}, +{'l', 0xc1886036, 0x00000000}, +{'l', 0xc1c8ce2c, 0xc2c86716}, +{'@', 0x00000058, 0x00005e29},/* X x-advance: 94.160156 */ +{'M', 0x410a8603, 0xc2c86716}, +{'l', 0x41690527, 0x00000000}, +{'l', 0x41c731d3, 0x4214fe48}, +{'l', 0x41c844b8, 0xc214fe48}, +{'l', 0x41690528, 0x00000000}, +{'l', 0xc200dbeb, 0x42407bb4}, +{'l', 0x4209731d, 0x42505278}, +{'l', 0xc1690528, 0x00000000}, +{'l', 0xc1e180da, 0xc22a7844}, +{'l', 0xc1e31d35, 0x422a7844}, +{'l', 0xc16a180e, 0x00000000}, +{'l', 0x420f1656, 0xc255f5b1}, +{'l', 0xc1f9aa18, 0xc23ad87b}, +{'@', 0x00000059, 0x000053f5},/* Y x-advance: 83.957031 */ +{'M', 0xbe89731d, 0xc2c86716}, +{'l', 0x41690527, 0x00000000}, +{'l', 0x41de4829, 0x4224d50c}, +{'l', 0x41dcabd0, 0xc224d50c}, +{'l', 0x41690528, 0x00000000}, +{'l', 0xc20dbeb6, 0x4251eed1}, +{'l', 0x00000000, 0x423edf5b}, +{'l', 0xc159fc90, 0x00000000}, +{'l', 0x00000000, 0xc23edf5b}, +{'l', 0xc20dbeb6, 0xc251eed1}, +{'@', 0x0000005a, 0x00005e29},/* Z x-advance: 94.160156 */ +{'M', 0x40f6fad8, 0xc2c86716}, +{'l', 0x429d731c, 0x00000000}, +{'l', 0x00000000, 0x41255e80}, +{'l', 0xc27d6c3c, 0x429ce9aa}, +{'l', 0x4281cc74, 0xb6400000}, +{'l', 0x00000000, 0x41368ce3}, +{'l', 0xc2a39fc8, 0x00000000}, +{'l', 0xb6400000, 0xc1255e7f}, +{'l', 0x427d6c3d, 0xc29ce9aa}, +{'l', 0xc2773f91, 0x00000000}, +{'l', 0x00000000, 0xc1368ce0}, +{'@', 0x0000005b, 0x0000359f},/* [ x-advance: 53.621094 */ +{'M', 0x413cfe48, 0xc2d0dbeb}, +{'l', 0x41e3a6a8, 0x00000000}, +{'l', 0x00000000, 0x41198e98}, +{'l', 0xc180dbeb, 0x00000000}, +{'l', 0x00000000, 0x42ceb61f}, +{'l', 0x4180dbeb, 0xb5800000}, +{'l', 0x00000000, 0x41198e9b}, +{'l', 0xc1e3a6a8, 0x00000000}, +{'l', 0x00000000, 0xc2f519c5}, +{'@', 0x0000005c, 0x00002e4f},/* \ x-advance: 46.308594 */ +{'M', 0x41368ce3, 0xc2c86716}, +{'l', 0x420b98e9, 0x42e1e7f2}, +{'l', 0xc1368ce4, 0xb5800000}, +{'l', 0xc20b98e9, 0xc2e1e7f2}, +{'l', 0x41368ce3, 0x00000000}, +{'@', 0x0000005d, 0x0000359f},/* ] x-advance: 53.621094 */ +{'M', 0x42273f92, 0xc2d0dbeb}, +{'l', 0x00000000, 0x42f519c5}, +{'l', 0xc1e3a6a8, 0x36000000}, +{'l', 0xb5800000, 0xc1198e9b}, +{'l', 0x41805278, 0x00000000}, +{'l', 0x00000000, 0xc2ceb61f}, +{'l', 0xc1805278, 0x00000000}, +{'l', 0xb5800000, 0xc1198e98}, +{'l', 0x41e3a6a8, 0x00000000}, +{'@', 0x0000005e, 0x0000732a},/* ^ x-advance: 115.164062 */ +{'M', 0x42805278, 0xc2c86716}, +{'l', 0x4211c596, 0x421587bb}, +{'l', 0xc157d6c8, 0x00000000}, +{'l', 0xc1ec3dd8, 0xc1d4149e}, +{'l', 0xc1ec3ddb, 0x41d4149e}, +{'l', 0xc157d6c3, 0x00000000}, +{'l', 0x4211c595, 0xc21587bb}, +{'l', 0x41527844, 0x00000000}, +{'@', 0x0000005f, 0x000044b9},/* _ x-advance: 68.722656 */ +{'M', 0x428c225d, 0x41b68ce3}, +{'l', 0x00000000, 0x41198e9a}, +{'l', 0xc28ed19d, 0x00000000}, +{'l', 0x36600000, 0xc1198e9a}, +{'l', 0x428ed19d, 0x00000000}, +{'@', 0x00000060, 0x000044b9},/* ` x-advance: 68.722656 */ +{'M', 0x41c50c07, 0xc2dbdda3}, +{'l', 0x419768cd, 0x41c8ce2c}, +{'l', 0xc1244b98, 0x00000000}, +{'l', 0xc1af0896, 0xc1c8ce2c}, +{'l', 0x41538b2a, 0x00000000}, +{'@', 0x00000061, 0x0000543a},/* a x-advance: 84.226562 */ +{'M', 0x423c74d5, 0xc2172414}, +{'q', 0xc16f768c, 0x00000000}, +{0, 0xc1a5e7f2, 0x405b0f70}, +{'8', 0x5dd21bd2, 0x53223400}, +{'q', 0x408b98e8, 0x4074d50c}, +{0, 0x413cfe46, 0x4074d50c}, +{'q', 0x41244b9c, 0x00000000}, +{0, 0x41838b2c, 0xc0e7f242}, +{'9', 0xffc60031, 0xff650031}, +{'4', 0xffea0000, 0x0000ff9e}, +{'m', 0x41c50c06, 0xc0a338b8}, +{'4', 0x01570000, 0x0000ff9e}, +{'l', 0x00000000, 0xc1368ce3}, +{'q', 0xc0874d50, 0x40db0f77}, +{0, 0xc1289734, 0x412225cd}, +{'q', 0xc0c9e110, 0x404e2caa}, +{0, 0xc176fad8, 0x404e2caa}, +{'q', 0xc138b2ae, 0x34000000}, +{0, 0xc1931d34, 0xc0ce2cab}, +{'q', 0xc0d8e9ac, 0xc0d05278}, +{0, 0xc0d8e9ac, 0xc18b0f76}, +{'q', 0x00000000, 0xc14af3fc}, +{0, 0x41074d50, 0xc1990528}, +{'9', 0xffcd0044, 0xffcd00ca}, +{'4', 0x0000008a, 0xfff70000}, +{'q', 0x00000000, 0xc1086034}, +{0, 0xc0b46718, 0xc1527844}, +{'q', 0xc0b24148, 0xc09655e0}, +{0, 0xc17b4670, 0xc09655e0}, +{'8', 0x0c9c00cd, 0x25a30cd0}, +{'l', 0x00000000, 0xc1368ce4}, +{'8', 0xe169ec36, 0xf663f633}, +{'q', 0x41827844, 0x00000000}, +{0, 0x41c2e63a, 0x41074d50}, +{'q', 0x4100dbec, 0x41074d54}, +{0, 0x4100dbec, 0x41cd19c6}, +{'@', 0x00000062, 0x0000573f},/* b x-advance: 87.246094 */ +{'M', 0x4285d354, 0xc216112e}, +{'q', 0x00000000, 0xc159fc90}, +{0, 0xc0b46718, 0xc1aabcfe}, +{'q', 0xc0b24148, 0xc0f920a8}, +{0, 0xc175e7f0, 0xc0f920a8}, +{'q', 0xc11cc750, 0x00000000}, +{0, 0xc176fada, 0x40f920a8}, +{'q', 0xc0b24148, 0x40f6fad8}, +{0, 0xc0b24148, 0x41aabcfe}, +{'q', 0x00000000, 0x4159fc90}, +{0, 0x40b24148, 0x41ab4671}, +{'q', 0x40b46714, 0x40f6fad8}, +{0, 0x4176fada, 0x40f6fad8}, +{'q', 0x411cc74c, 0x00000000}, +{0, 0x4175e7f0, 0xc0f6fad8}, +{'9', 0xffc2002d, 0xff55002d}, +{'m', 0xc2280dbe, 0xc1d1eed2}, +{'q', 0x407920a0, 0xc0d6c3d8}, +{0, 0x411cc74c, 0xc11eed1c}, +{'q', 0x40bf2410, 0xc0527840}, +{0, 0x4163a6a8, 0xc0527840}, +{'q', 0x415b0f74, 0x00000000}, +{0, 0x41b1b7d6, 0x412df5b0}, +{'q', 0x41097320, 0x412df5b4}, +{0, 0x41097320, 0x41e4b990}, +{'q', 0x00000000, 0x418dbeb6}, +{0, 0xc1097320, 0x41e4b98e}, +{'q', 0xc1086038, 0x412df5b1}, +{0, 0xc1b1b7d6, 0x412df5b1}, +{'q', 0xc10414a0, 0xb4c00000}, +{0, 0xc163a6a8, 0xc04e2cad}, +{'9', 0xffe6ffd1, 0xffb0ffb2}, +{'l', 0x00000000, 0x41346716}, +{'l', 0xc146a860, 0x00000000}, +{'l', 0x00000000, 0xc2d0dbeb}, +{'l', 0x4146a860, 0x00000000}, +{'l', 0x00000000, 0x4222af3f}, +{'@', 0x00000063, 0x00004b92},/* c x-advance: 75.570312 */ +{'M', 0x4286180e, 0xc2909052}, +{'l', 0x00000000, 0x4138b2ac}, +{'8', 0xdeace9d7, 0xf5acf5d7}, +{'q', 0xc14036fc, 0x00000000}, +{0, 0xc1954302, 0x40f4d508}, +{'q', 0xc0d49e10, 0x40f2af40}, +{0, 0xc0d49e10, 0x41aabcfe}, +{'q', 0x00000000, 0x415c225c}, +{0, 0x40d49e10, 0x41ab4671}, +{'q', 0x40d49e10, 0x40f2af3e}, +{0, 0x41954302, 0x40f2af3e}, +{'8', 0xf554002a, 0xde54f52a}, +{'l', 0x00000000, 0x41368ce2}, +{'8', 0x1cab13d7, 0x09a309d4}, +{'q', 0xc187d6c4, 0x00000000}, +{0, 0xc1d7d6c4, 0xc12abcfe}, +{'q', 0xc1200000, 0xc12abcff}, +{0, 0xc1200000, 0xc1e655e8}, +{'q', 0xb5000000, 0xc1931d36}, +{0, 0x412112e6, 0xc1e768d0}, +{'q', 0x412225cc, 0xc1289730}, +{0, 0x41ddbeb5, 0xc1289730}, +{'8', 0x0959002d, 0x1b54092b}, +{'@', 0x00000064, 0x0000573f},/* d x-advance: 87.246094 */ +{'M', 0x4279aa18, 0xc27f0897}, +{'l', 0x00000000, 0xc222af3f}, +{'l', 0x41459578, 0x00000000}, +{'4', 0x03430000, 0x0000ff9e}, +{'l', 0x00000000, 0xc1346716}, +{'q', 0xc07920b0, 0x40d6c3dd}, +{0, 0xc11dda34, 0x41200000}, +{'q', 0xc0bcfe48, 0x404e2caa}, +{0, 0xc163a6a8, 0x404e2caa}, +{'q', 0xc159fc90, 0x34000000}, +{0, 0xc1b1b7d7, 0xc12df5b0}, +{'q', 0xc1086036, 0xc12df5b0}, +{0, 0xc1086036, 0xc1e4b98e}, +{'q', 0xb5000000, 0xc18dbeb6}, +{0, 0x41086036, 0xc1e4b990}, +{'q', 0x4109731e, 0xc12df5b0}, +{0, 0x41b1b7d7, 0xc12df5b0}, +{'q', 0x41052784, 0x00000000}, +{0, 0x4163a6a8, 0x40527840}, +{'9', 0x0019002f, 0x004f004e}, +{'m', 0xc2285278, 0x41d1eed2}, +{'q', 0xb6000000, 0x4159fc90}, +{0, 0x40b24148, 0x41ab4671}, +{'q', 0x40b46714, 0x40f6fad8}, +{0, 0x4176fad8, 0x40f6fad8}, +{'q', 0x411cc74c, 0x00000000}, +{0, 0x4176fad8, 0xc0f6fad8}, +{'q', 0x40b46718, 0xc0f920a4}, +{0, 0x40b46718, 0xc1ab4671}, +{'q', 0x00000000, 0xc159fc90}, +{0, 0xc0b46718, 0xc1aabcfe}, +{'q', 0xc0b46718, 0xc0f920a8}, +{0, 0xc176fad8, 0xc0f920a8}, +{'q', 0xc11cc74e, 0x00000000}, +{0, 0xc176fad8, 0x40f920a8}, +{'q', 0xc0b2414c, 0x40f6fad8}, +{0, 0xc0b2414c, 0x41aabcfe}, +{'@', 0x00000065, 0x00005490},/* e x-advance: 84.562500 */ +{'M', 0x429a7f24, 0xc222af3f}, +{'4', 0x00300000, 0x0000fe3a}, +{'q', 0x3f4e2ca0, 0x414c06de}, +{0, 0x40f4d508, 0x419bb466}, +{'q', 0x40dd3548, 0x40d49e12}, +{0, 0x41998e9a, 0x40d49e12}, +{'8', 0xf36e0038, 0xd76af335}, +{'l', 0x00000000, 0x413ad87b}, +{'q', 0xc0d49e10, 0x40346716}, +{0, 0xc159fc8c, 0x4089731d}, +{'q', 0xc0df5b10, 0x3fbcfe49}, +{0, 0xc16293c4, 0x3fbcfe49}, +{'q', 0xc18fe482, 0x00000000}, +{0, 0xc1e4301b, 0xc127844c}, +{'q', 0xc127844a, 0xc127844b}, +{0, 0xc127844a, 0xc1e293c2}, +{'q', 0xb5000000, 0xc193a6a8}, +{0, 0x411eed1a, 0xc1ea180e}, +{'q', 0x411ffffe, 0xc12df5b0}, +{0, 0x41d74d4f, 0xc12df5b0}, +{'q', 0x4172af40, 0x00000000}, +{0, 0x41bfad88, 0x411cc750}, +{'9', 0x004d0046, 0x00d40046}, +{'m', 0xc1459578, 0xc067f240}, +{'q', 0xbe097400, 0xc12225cc}, +{0, 0xc0b68ce8, 0xc1816560}, +{'q', 0xc0b01b80, 0xc0c149d8}, +{0, 0xc16a180c, 0xc0c149d8}, +{'q', 0xc1255e80, 0x00000000}, +{0, 0xc1849e12, 0x40bad878}, +{'q', 0xc0c59578, 0x40bad878}, +{0, 0xc0e3a6a8, 0x41838b2a}, +{'l', 0x42301b7e, 0xbd897200}, +{'@', 0x00000066, 0x00003063},/* f x-advance: 48.386719 */ +{'M', 0x424c06df, 0xc2d0dbeb}, +{'4', 0x00520000, 0x0000ffa2}, +{'8', 0x15b600cb, 0x4dec15ec}, +{'l', 0x00000000, 0x40d49e10}, +{'l', 0x41a2af40, 0x00000000}, +{'l', 0x00000000, 0x41198ea0}, +{'l', 0xc1a2af40, 0x00000000}, +{'l', 0x00000000, 0x42832414}, +{'l', 0xc146a85f, 0x00000000}, +{'l', 0x00000000, 0xc2832414}, +{'0', 0xb40000a2, 0xd700005e}, +{'q', 0x00000000, 0xc148ce30}, +{0, 0x40bad87a, 0xc1920a50}, +{'q', 0x40bad87c, 0xc0b8b2b0}, +{0, 0x4194301b, 0xc0b8b2b0}, +{'l', 0x413ad87c, 0x00000000}, +{'@', 0x00000067, 0x0000573f},/* g x-advance: 87.246094 */ +{'M', 0x4279aa18, 0xc219d354}, +{'q', 0x00000000, 0xc156c3dc}, +{0, 0xc0b24150, 0xc1a67166}, +{'q', 0xc0b01b78, 0xc0ec3dd8}, +{0, 0xc1780dbc, 0xc0ec3dd8}, +{'q', 0xc11eed1a, 0x00000000}, +{0, 0xc1780dbe, 0x40ec3dd8}, +{'q', 0xc0b01b80, 0x40ec3de0}, +{0, 0xc0b01b80, 0x41a67166}, +{'q', 0x00000000, 0x4155b0f8}, +{0, 0x40b01b80, 0x41a5e7f2}, +{'q', 0x40b24148, 0x40ec3dda}, +{0, 0x41780dbe, 0x40ec3dda}, +{'q', 0x41200000, 0x00000000}, +{0, 0x41780dbc, 0xc0ec3dda}, +{'9', 0xffc5002c, 0xff5b002c}, +{'m', 0x41459578, 0x41e90528}, +{'q', 0x00000000, 0x41998e9a}, +{0, 0xc1086038, 0x41e4b98e}, +{'q', 0xc1086034, 0x41154300}, +{0, 0xc1d0dbea, 0x41154300}, +{'8', 0xf99e00cc, 0xe8a7f8d2}, +{'l', 0x00000000, 0xc14036fb}, +{'8', 0x2255172b, 0x0b560b2a}, +{'q', 0x41425cc4, 0x00000000}, +{0, 0x419180dc, 0xc0c9e112}, +{'9', 0xffcd0030, 0xff670030}, +{'l', 0x00000000, 0xc0c36fb0}, +{'q', 0xc074d510, 0x40d49e12}, +{0, 0xc11cc750, 0x411eed1a}, +{'q', 0xc0bf2410, 0x40527844}, +{0, 0xc164b98c, 0x40527844}, +{'q', 0xc15d3544, 0x00000000}, +{0, 0xc1b2414a, 0xc1289732}, +{'q', 0xc1074d50, 0xc1289732}, +{0, 0xc1074d50, 0xc1df5b0f}, +{'q', 0xb5000000, 0xc18b98ea}, +{0, 0x41074d50, 0xc1dfe484}, +{'q', 0x41074d50, 0xc1289730}, +{0, 0x41b2414a, 0xc1289730}, +{'q', 0x41052784, 0x00000000}, +{0, 0x4164b98c, 0x40527840}, +{'9', 0x001a002f, 0x004f004e}, +{'l', 0x00000000, 0xc1368ce4}, +{'l', 0x41459578, 0x00000000}, +{'l', 0x00000000, 0x4283ad88}, +{'@', 0x00000068, 0x0000571d},/* h x-advance: 87.113281 */ +{'M', 0x4296df5b, 0xc23579fc}, +{'4', 0x016a0000, 0x0000ff9e}, +{'l', 0x00000000, 0xc233dda3}, +{'q', 0x00000000, 0xc12abcfc}, +{0, 0xc0852780, 0xc17f9208}, +{'q', 0xc0852788, 0xc0a9aa18}, +{0, 0xc147bb48, 0xc0a9aa18}, +{'q', 0xc1200000, 0x00000000}, +{0, 0xc17c5956, 0x40cc06d8}, +{'9', 0x0033ffd2, 0x008bffd2}, +{'l', 0x00000000, 0x4229eed1}, +{'l', 0xc146a860, 0x00000000}, +{'4', 0xfcbd0000, 0x00000063}, +{'l', 0x00000000, 0x4223c225}, +{'8', 0xaf53ca23, 0xe66fe630}, +{'q', 0x414f3f90, 0x00000000}, +{0, 0x419cc74e, 0x4100dbf0}, +{'q', 0x40d49e10, 0x40ff9208}, +{0, 0x40d49e10, 0x41bc74d4}, +{'@', 0x00000069, 0x00002630},/* i x-advance: 38.187500 */ +{'M', 0x414f3f92, 0xc29655e8}, +{'l', 0x4145957a, 0x00000000}, +{'4', 0x02590000, 0x0000ff9e}, +{'6', 0xfda70000, 0xff160000}, +{'l', 0x4145957a, 0x00000000}, +{'l', 0x00000000, 0x417a3388}, +{'l', 0xc145957a, 0x00000000}, +{'l', 0x00000000, 0xc17a3388}, +{'@', 0x0000006a, 0x00002630},/* j x-advance: 38.187500 */ +{'M', 0x414f3f92, 0xc29655e8}, +{'4', 0x00000062, 0x02640000}, +{'q', 0x00000000, 0x4165cc71}, +{0, 0xc0b01b80, 0x41a67163}, +{'9', 0x0033ffd5, 0x0033ff74}, +{'4', 0x0000ffdb, 0xffad0000}, +{'l', 0x40527845, 0x00000000}, +{'8', 0xe74c0038, 0x9414e614}, +{'6', 0xfd9c0000, 0xff160000}, +{'l', 0x4145957a, 0x00000000}, +{'l', 0x00000000, 0x417a3388}, +{'l', 0xc145957a, 0x00000000}, +{'l', 0x00000000, 0xc17a3388}, +{'@', 0x0000006b, 0x00004f98},/* k x-advance: 79.593750 */ +{'M', 0x4147bb46, 0xc2d0dbeb}, +{'l', 0x4146a860, 0x00000000}, +{'l', 0x00000000, 0x4276b61e}, +{'l', 0x421361ee, 0xc201aa18}, +{'l', 0x417c5958, 0x00000000}, +{'l', 0xc21f768d, 0x420cabd0}, +{'l', 0x42262cab, 0x42200000}, +{'l', 0xc180dbea, 0x00000000}, +{'l', 0xc218c06e, 0xc212d87b}, +{'l', 0x36000000, 0x4212d87b}, +{'l', 0xc146a860, 0x00000000}, +{'l', 0x00000000, 0xc2d0dbeb}, +{'@', 0x0000006c, 0x00002630},/* l x-advance: 38.187500 */ +{'M', 0x414f3f92, 0xc2d0dbeb}, +{'l', 0x4145957a, 0x00000000}, +{'l', 0x00000000, 0x42d0dbeb}, +{'l', 0xc145957a, 0x00000000}, +{'l', 0x00000000, 0xc2d0dbeb}, +{'@', 0x0000006d, 0x000085e4},/* m x-advance: 133.890625 */ +{'M', 0x428ef3f9, 0xc272f3f9}, +{'q', 0x40943020, 0xc1052784}, +{0, 0x41312e60, 0xc1448294}, +{'q', 0x40ce2cb0, 0xc07d6c40}, +{0, 0x4172af40, 0xc07d6c40}, +{'q', 0x413beb60, 0x00000000}, +{0, 0x4190f768, 0x410414a0}, +{'9', 0x00410033, 0x00ba0033}, +{'4', 0x016a0000, 0x0000ff9d}, +{'l', 0x00000000, 0xc233dda3}, +{'q', 0x00000000, 0xc12ce2cc}, +{0, 0xc074d500, 0xc1805278}, +{'q', 0xc074d500, 0xc0a78448}, +{0, 0xc13ad878, 0xc0a78448}, +{'q', 0xc1198ea0, 0x00000000}, +{0, 0xc172af40, 0x40cc06d8}, +{'9', 0x0033ffd4, 0x008bffd4}, +{'4', 0x01530000, 0x0000ff9d}, +{'l', 0x00000000, 0xc233dda3}, +{'q', 0x00000000, 0xc12df5b0}, +{0, 0xc074d510, 0xc1805278}, +{'q', 0xc074d500, 0xc0a78448}, +{0, 0xc13cfe48, 0xc0a78448}, +{'q', 0xc11768cc, 0x00000000}, +{0, 0xc1708972, 0x40ce2ca8}, +{'9', 0x0033ffd4, 0x008affd4}, +{'l', 0x00000000, 0x4229eed1}, +{'l', 0xc146a860, 0x00000000}, +{'4', 0xfda70000, 0x00000063}, +{'l', 0x00000000, 0x413ad87c}, +{'q', 0x40874d50, 0xc0dd3538}, +{0, 0x412225ce, 0xc12338b4}, +{'q', 0x40bcfe48, 0xc0527840}, +{0, 0x41606df4, 0xc0527840}, +{'8', 0x216f0041, 0x6044212e}, +{'@', 0x0000006e, 0x0000571d},/* n x-advance: 87.113281 */ +{'M', 0x4296df5b, 0xc23579fc}, +{'4', 0x016a0000, 0x0000ff9e}, +{'l', 0x00000000, 0xc233dda3}, +{'q', 0x00000000, 0xc12abcfc}, +{0, 0xc0852780, 0xc17f9208}, +{'q', 0xc0852788, 0xc0a9aa18}, +{0, 0xc147bb48, 0xc0a9aa18}, +{'q', 0xc1200000, 0x00000000}, +{0, 0xc17c5956, 0x40cc06d8}, +{'9', 0x0033ffd2, 0x008bffd2}, +{'l', 0x00000000, 0x4229eed1}, +{'l', 0xc146a860, 0x00000000}, +{'4', 0xfda70000, 0x00000063}, +{'l', 0x00000000, 0x413ad87c}, +{'8', 0xaf53ca23, 0xe66fe630}, +{'q', 0x414f3f90, 0x00000000}, +{0, 0x419cc74e, 0x4100dbf0}, +{'q', 0x40d49e10, 0x40ff9208}, +{0, 0x40d49e10, 0x41bc74d4}, +{'@', 0x0000006f, 0x00005418},/* o x-advance: 84.093750 */ +{'M', 0x42285278, 0xc2850527}, +{'q', 0xc11eed18, 0x00000000}, +{0, 0xc17b4670, 0x40f920a0}, +{'q', 0xc0b8b2b0, 0x40f6fad8}, +{0, 0xc0b8b2b0, 0x41a9aa18}, +{'q', 0x00000000, 0x4157d6c2}, +{0, 0x40b68ce0, 0x41aa338b}, +{'q', 0x40b8b2b0, 0x40f6fad6}, +{0, 0x417c5958, 0x40f6fad6}, +{'q', 0x411dda34, 0x00000000}, +{0, 0x417a338c, 0xc0f920a6}, +{'q', 0x40b8b2b0, 0xc0f920a4}, +{0, 0x40b8b2b0, 0xc1a9aa17}, +{'q', 0x00000000, 0xc155b0f8}, +{0, 0xc0b8b2b0, 0xc1a920a6}, +{'9', 0xffc2ffd2, 0xffc2ff83}, +{'m', 0x00000000, 0xc1278450}, +{'q', 0x4180dbec, 0x00000000}, +{0, 0x41ca6a84, 0x41278450}, +{'q', 0x41131d38, 0x41278448}, +{0, 0x41131d38, 0x41e7f240}, +{'q', 0x00000000, 0x4193a6a8}, +{0, 0xc1131d38, 0x41e7f240}, +{'q', 0xc1131d30, 0x4127844d}, +{0, 0xc1ca6a84, 0x4127844d}, +{'q', 0xc181655e, 0xb4c00000}, +{0, 0xc1caf3f9, 0xc127844c}, +{'q', 0xc1120a4e, 0xc1289731}, +{0, 0xc1120a4e, 0xc1e7f240}, +{'q', 0xb5000000, 0xc194301c}, +{0, 0x41120a4e, 0xc1e7f240}, +{'q', 0x41131d36, 0xc1278450}, +{0, 0x41caf3f9, 0xc1278450}, +{'[', 0x002d006f, 0x0000028c}, +{'@', 0x00000070, 0x0000573f},/* p x-advance: 87.246094 */ +{'M', 0x41c731d3, 0xc1346716}, +{'l', 0x00000000, 0x421f768c}, +{'l', 0xc146a860, 0x36000000}, +{'4', 0xfcc20000, 0x00000063}, +{'l', 0x00000000, 0x41368ce4}, +{'q', 0x407920a8, 0xc0d6c3d8}, +{0, 0x411cc74e, 0xc11eed1c}, +{'q', 0x40bf2410, 0xc0527840}, +{0, 0x4163a6a8, 0xc0527840}, +{'q', 0x415b0f74, 0x00000000}, +{0, 0x41b1b7d6, 0x412df5b0}, +{'q', 0x41097320, 0x412df5b4}, +{0, 0x41097320, 0x41e4b990}, +{'q', 0x00000000, 0x418dbeb6}, +{0, 0xc1097320, 0x41e4b98e}, +{'q', 0xc1086038, 0x412df5b1}, +{0, 0xc1b1b7d6, 0x412df5b1}, +{'q', 0xc10414a0, 0xb4c00000}, +{0, 0xc163a6a8, 0xc04e2cad}, +{'9', 0xffe6ffd1, 0xffb0ffb2}, +{'m', 0x42280dbe, 0xc1d1eed1}, +{'q', 0x00000000, 0xc159fc90}, +{0, 0xc0b46718, 0xc1aabcfe}, +{'q', 0xc0b24148, 0xc0f920a8}, +{0, 0xc175e7f0, 0xc0f920a8}, +{'q', 0xc11cc750, 0x00000000}, +{0, 0xc176fada, 0x40f920a8}, +{'q', 0xc0b24148, 0x40f6fad8}, +{0, 0xc0b24148, 0x41aabcfe}, +{'q', 0x00000000, 0x4159fc90}, +{0, 0x40b24148, 0x41ab4671}, +{'q', 0x40b46714, 0x40f6fad8}, +{0, 0x4176fada, 0x40f6fad8}, +{'q', 0x411cc74c, 0x00000000}, +{0, 0x4175e7f0, 0xc0f6fad8}, +{'q', 0x40b46718, 0xc0f920a4}, +{0, 0x40b46718, 0xc1ab4671}, +{'@', 0x00000071, 0x0000573f},/* q x-advance: 87.246094 */ +{'M', 0x41a2af3f, 0xc216112e}, +{'q', 0x00000000, 0x4159fc90}, +{0, 0x40b2414c, 0x41ab4671}, +{'q', 0x40b46714, 0x40f6fad8}, +{0, 0x4176fad8, 0x40f6fad8}, +{'q', 0x411cc74c, 0x00000000}, +{0, 0x4176fad8, 0xc0f6fad8}, +{'q', 0x40b46718, 0xc0f920a4}, +{0, 0x40b46718, 0xc1ab4671}, +{'q', 0x00000000, 0xc159fc90}, +{0, 0xc0b46718, 0xc1aabcfe}, +{'q', 0xc0b46718, 0xc0f920a8}, +{0, 0xc176fad8, 0xc0f920a8}, +{'q', 0xc11cc74e, 0x00000000}, +{0, 0xc176fad8, 0x40f920a8}, +{'9', 0x003dffd4, 0x00aaffd4}, +{'m', 0x42285278, 0x41d1eed1}, +{'q', 0xc07920b0, 0x40d6c3dd}, +{0, 0xc11dda34, 0x41200000}, +{'q', 0xc0bcfe48, 0x404e2caa}, +{0, 0xc163a6a8, 0x404e2caa}, +{'q', 0xc159fc90, 0x34000000}, +{0, 0xc1b1b7d7, 0xc12df5b0}, +{'q', 0xc1086036, 0xc12df5b0}, +{0, 0xc1086036, 0xc1e4b98e}, +{'q', 0xb5000000, 0xc18dbeb6}, +{0, 0x41086036, 0xc1e4b990}, +{'q', 0x4109731e, 0xc12df5b0}, +{0, 0x41b1b7d7, 0xc12df5b0}, +{'q', 0x41052784, 0x00000000}, +{0, 0x4163a6a8, 0x40527840}, +{'9', 0x0019002f, 0x004f004e}, +{'l', 0x00000000, 0xc1368ce4}, +{'l', 0x41459578, 0x00000000}, +{'l', 0x00000000, 0x42cf844c}, +{'l', 0xc1459578, 0xb6800000}, +{'l', 0x00000000, 0xc21f768c}, +{'@', 0x00000072, 0x00003882},/* r x-advance: 56.507812 */ +{'M', 0x42620a4f, 0xc27e7f24}, +{'8', 0xf3dcf7f0, 0xfcd6fced}, +{'q', 0xc127844c, 0x00000000}, +{0, 0xc180dbeb, 0x40db0f78}, +{'9', 0x0036ffd4, 0x009cffd4}, +{'l', 0x00000000, 0x421e63a6}, +{'l', 0xc146a860, 0x00000000}, +{'4', 0xfda70000, 0x00000063}, +{'l', 0x00000000, 0x413ad87c}, +{'q', 0x407920a8, 0xc0db0f78}, +{0, 0x412225ce, 0xc12225cc}, +{'q', 0x40c7bb40, 0xc056c3e0}, +{0, 0x4172af3c, 0xc056c3e0}, +{'8', 0x0116000a, 0x031b010c}, +{'l', 0x3d897400, 0x414af3f8}, +{'@', 0x00000073, 0x0000479c},/* s x-advance: 71.609375 */ +{'M', 0x42737d6c, 0xc291e7f2}, +{'l', 0x00000000, 0x413ad87c}, +{'8', 0xe0aaebd7, 0xf6a3f6d3}, +{'8', 0x169200b7, 0x43dc16dc}, +{'8', 0x361a2200, 0x2569131a}, +{'l', 0x40874d50, 0x3f708980}, +{'q', 0x41527844, 0x40346720}, +{0, 0x41954302, 0x40ff9208}, +{'q', 0x40b24150, 0x40a338b4}, +{0, 0x40b24150, 0x4164b990}, +{'q', 0x00000000, 0x4127844b}, +{0, 0xc1052788, 0x41849e11}, +{'q', 0xc104149c, 0x40c36fad}, +{0, 0xc1b6036e, 0x40c36fad}, +{'8', 0xf79c00d0, 0xe492f7cc}, +{'l', 0x00000000, 0xc14c06df}, +{'8', 0x2a6b1c36, 0x0d690d35}, +{'8', 0xe96b0045, 0xbd25e825}, +{'q', 0x00000000, 0xc0a112e8}, +{0, 0xc05b0f70, 0xc0f6fad8}, +{'9', 0xffebffe6, 0xffd7ff8a}, +{'l', 0xc0897320, 0xbf80dbe0}, +{'q', 0xc1379fc8, 0xc01aa180}, +{0, 0xc1849e11, 0xc0ec3de0}, +{'q', 0xc0a338b2, 0xc0a112e0}, +{0, 0xc0a338b2, 0xc15c225c}, +{'q', 0x00000000, 0xc129aa18}, +{0, 0x40f08972, 0xc18301b8}, +{'q', 0x40f08974, 0xc0b8b2b0}, +{0, 0x41aabcff, 0xc0b8b2b0}, +{'8', 0x08670036, 0x18590830}, +{'@', 0x00000074, 0x000035e4},/* t x-advance: 53.890625 */ +{'M', 0x41c9579f, 0xc2c10527}, +{'l', 0x00000000, 0x41aabcfc}, +{'l', 0x41cb7d6d, 0x00000000}, +{'4', 0x004c0000, 0x0000ff35}, +{'l', 0x00000000, 0x422338b2}, +{'8', 0x5e134900, 0x14521414}, +{'4', 0x00000065, 0x00520000}, +{'l', 0xc14af3f8, 0x00000000}, +{'q', 0xc164b990, 0x00000000}, +{0, 0xc19dda34, 0xc0a9aa18}, +{'9', 0xffd6ffd5, 0xff65ffd5}, +{'l', 0x00000000, 0xc22338b2}, +{'l', 0xc110f768, 0x00000000}, +{'l', 0xb5000000, 0xc1198ea0}, +{'l', 0x4110f768, 0x00000000}, +{'l', 0x35800000, 0xc1aabcfc}, +{'l', 0x4146a85f, 0x00000000}, +{'@', 0x00000075, 0x0000571d},/* u x-advance: 87.113281 */ +{'M', 0x413ad87b, 0xc1ed50c0}, +{'4', 0xfe940000, 0x00000062}, +{'l', 0x00000000, 0x4234225d}, +{'q', 0x00000000, 0x412abcfe}, +{0, 0x40852784, 0x41805278}, +{'q', 0x40852780, 0x40a9aa18}, +{0, 0x4147bb44, 0x40a9aa18}, +{'q', 0x41200000, 0x00000000}, +{0, 0x417c5958, 0xc0cc06de}, +{'9', 0xffcd002e, 0xff75002e}, +{'l', 0x00000000, 0xc22a7845}, +{'l', 0x41459574, 0x00000000}, +{'4', 0x02590000, 0x0000ff9e}, +{'l', 0x00000000, 0xc138b2af}, +{'8', 0x51ad36dd, 0x1a921ad1}, +{'q', 0xc14f3f94, 0x34000000}, +{0, 0xc19d50c1, 0xc100dbeb}, +{'9', 0xffc0ffcb, 0xff44ffcb}, +{'m', 0x41f89732, 0xc23d4302}, +{'l', 0x00000000, 0x00000000}, +{'@', 0x00000076, 0x00005157},/* v x-advance: 81.339844 */ +{'M', 0x408301b8, 0xc29655e8}, +{'l', 0x4151655e, 0x00000000}, +{'l', 0x41bbeb61, 0x427c5958}, +{'l', 0x41bbeb62, 0xc27c5958}, +{'l', 0x41516560, 0x00000000}, +{'l', 0xc1e180dc, 0x429655e8}, +{'l', 0xc1863a6a, 0x00000000}, +{'l', 0xc1e180dc, 0xc29655e8}, +{'@', 0x00000077, 0x0000706a},/* w x-advance: 112.414062 */ +{'M', 0x40b8b2af, 0xc29655e8}, +{'l', 0x4145957a, 0x00000000}, +{'l', 0x4176fad6, 0x426aa181}, +{'l', 0x4175e7f4, 0xc26aa181}, +{'l', 0x41690528, 0x00000000}, +{'l', 0x4176fad4, 0x426aa181}, +{'l', 0x4175e7f8, 0xc26aa181}, +{'l', 0x41459578, 0x00000000}, +{'l', 0xc19d50c0, 0x429655e8}, +{'l', 0xc1690528, 0x00000000}, +{'l', 0xc1816560, 0xc2767165}, +{'l', 0xc181eed0, 0x42767165}, +{'l', 0xc1690528, 0x00000000}, +{'l', 0xc19d50c0, 0xc29655e8}, +{'@', 0x00000078, 0x00005157},/* x x-advance: 81.339844 */ +{'M', 0x4296df5b, 0xc29655e8}, +{'l', 0xc1d9731e, 0x42124f09}, +{'l', 0x41e4b98e, 0x421a5cc7}, +{'l', 0xc1690524, 0x00000000}, +{'l', 0xc1af0898, 0xc1ec3dda}, +{'l', 0xc1af0897, 0x41ec3dda}, +{'l', 0xc1690527, 0x00000000}, +{'l', 0x41e98e9a, 0xc21d50c0}, +{'l', 0xc1d5b0f7, 0xc20f5b10}, +{'l', 0x41690526, 0x00000000}, +{'l', 0x419f768e, 0x41d63a6c}, +{'l', 0x419f768c, 0xc1d63a6c}, +{'l', 0x41690528, 0x00000000}, +{'@', 0x00000079, 0x00005157},/* y x-advance: 81.339844 */ +{'M', 0x4230e9aa, 0x40df5b0f}, +{'q', 0xc0a78450, 0x4156c3dc}, +{0, 0xc12338b4, 0x418c225c}, +{'9', 0x0020ffd9, 0x0020ff96}, +{'4', 0x0000ffb2, 0xffae0000}, +{'l', 0x40e7f242, 0x00000000}, +{'8', 0xed3f0028, 0xa531ed16}, +{'l', 0x400dbeb0, 0xc0b46716}, +{'l', 0xc1f338b2, 0xc293eb62}, +{'l', 0x4151655e, 0x00000000}, +{'l', 0x41bbeb61, 0x426b2af4}, +{'l', 0x41bbeb62, 0xc26b2af4}, +{'l', 0x41516560, 0x00000000}, +{'l', 0xc204149e, 0x42a44b99}, +{'@', 0x0000007a, 0x00004825},/* z x-advance: 72.144531 */ +{'M', 0x40f2af3f, 0xc29655e8}, +{'l', 0x426aa180, 0x00000000}, +{'l', 0x00000000, 0x41346718}, +{'l', 0xc239c595, 0x42581b7d}, +{'l', 0x4239c595, 0x35800000}, +{'l', 0x00000000, 0x411dda33}, +{'l', 0xc271579f, 0x00000000}, +{'l', 0x00000000, 0xc1346716}, +{'l', 0x4239c595, 0xc2581b7c}, +{'l', 0xc2330f76, 0x00000000}, +{'l', 0xb5000000, 0xc11dda38}, +{'@', 0x0000007b, 0x00005773},/* { x-advance: 87.449219 */ +{'M', 0x428c8973, 0x414c06df}, +{'4', 0x004d0000, 0x0000ffdf}, +{'q', 0xc185b0f8, 0x00000000}, +{0, 0xc1b35432, 0xc09eed1c}, +{'9', 0xffd9ffd3, 0xff62ffd3}, +{'l', 0x00000000, 0xc1805278}, +{'q', 0x00000000, 0xc12225cc}, +{0, 0xc067f240, 0xc1606df6}, +{'9', 0xffe1ffe4, 0xffe1ff97}, +{'4', 0x0000ffe0, 0xffb40000}, +{'l', 0x408301b8, 0x00000000}, +{'8', 0xe269004c, 0x911ce11c}, +{'l', 0x00000000, 0xc180dbec}, +{'q', 0x00000000, 0xc16d50c0}, +{0, 0x40b46710, 0xc19dda30}, +{'9', 0xffd9002d, 0xffd900b3}, +{'4', 0x00000021, 0x004c0000}, +{'l', 0xc0920a50, 0x00000000}, +{'8', 0x179e00b5, 0x63e917e9}, +{'l', 0x00000000, 0x41852786}, +{'q', 0x00000000, 0x41289730}, +{0, 0xc0459580, 0x4174d50c}, +{'8', 0x33ad26e8, 0x34530e3b}, +{'9', 0x00260018, 0x00790018}, +{'l', 0x00000000, 0x41852785}, +{'8', 0x63174b00, 0x17621717}, +{'l', 0x40920a50, 0x00000000}, +{'@', 0x0000007c, 0x00002e4f},/* | x-advance: 46.308594 */ +{'M', 0x41e6df5b, 0xc2d2112e}, +{'l', 0x00000000, 0x4309731d}, +{'l', 0xc1368ce4, 0x00000000}, +{'l', 0x00000000, 0xc309731d}, +{'l', 0x41368ce4, 0x00000000}, +{'@', 0x0000007d, 0x00005773},/* } x-advance: 87.449219 */ +{'M', 0x4189731d, 0x414c06df}, +{'l', 0x409655e8, 0x00000000}, +{'8', 0xe961004b, 0x9d17e917}, +{'l', 0x00000000, 0xc1852784}, +{'q', 0x00000000, 0xc127844a}, +{0, 0x404149e0, 0xc173c224}, +{'8', 0xcc53da18, 0xcdadf3c5}, +{'9', 0xffdaffe8, 0xff86ffe8}, +{'l', 0x00000000, 0xc1852786}, +{'8', 0x9de9b400, 0xe99fe9ea}, +{'4', 0x0000ffdb, 0xffb40000}, +{'l', 0x40874d50, 0x00000000}, +{'q', 0x4185b0f7, 0x00000000}, +{0, 0x41b24149, 0x409eed20}, +{'9', 0x0027002d, 0x009d002d}, +{'l', 0x00000000, 0x4180dbec}, +{'8', 0x6f1c5000, 0x1e691e1c}, +{'4', 0x00000021, 0x004c0000}, +{'l', 0xc0852780, 0x00000000}, +{'q', 0xc1187bb8, 0x00000000}, +{0, 0xc1527848, 0x407920a0}, +{'9', 0x001fffe4, 0x0070ffe4}, +{'l', 0x00000000, 0x41805278}, +{'q', 0x00000000, 0x416d50c0}, +{0, 0xc0b46718, 0x419e63a6}, +{'9', 0x0027ffd4, 0x0027ff4e}, +{'l', 0xc0874d50, 0x00000000}, +{'l', 0x00000000, 0xc11aa181}, +{'@', 0x0000007e, 0x0000732a},/* ~ x-advance: 115.164062 */ +{'M', 0x42c93543, 0xc25b5430}, +{'l', 0x00000000, 0x413f2414}, +{'8', 0x3c982ac8, 0x129d12d1}, +{'q', 0xc0ec3dd0, 0x00000000}, +{0, 0xc189731c, 0xc07d6c40}, +{'8', 0xfdf8fefb, 0xfcf5fffd}, +{'q', 0xc1267168, 0xc0852780}, +{0, 0xc185b0f8, 0xc0852780}, +{'8', 0x14a300d1, 0x409e14d2}, +{'l', 0x00000000, 0xc13f2414}, +{'8', 0xc468d638, 0xee64ee30}, +{'q', 0x40ec3dd8, 0x00000000}, +{0, 0x4189fc90, 0x4080dbe8}, +{'8', 0x03080205, 0x040b0104}, +{'q', 0x41267164, 0x40852780}, +{0, 0x4185b0f6, 0x40852780}, +{'8', 0xec5b002e, 0xbf64ec2d}, +}; +#define CTX_FONT_ascii 1 +#endif +#endif //_CTX_INTERNAL_FONT_ +#ifndef __CTX_LIST__ +#define __CTX_LIST__ + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#endif + +/* The whole ctx_list implementation is in the header and will be inlined + * wherever it is used. + */ + +static inline void *ctx_calloc (size_t size, size_t count) +{ + size_t byte_size = size * count; + char *ret = (char*)malloc (byte_size); + for (size_t i = 0; i < byte_size; i++) + ret[i] = 0; + return ret; +} + +typedef struct _CtxList CtxList; +struct _CtxList { + void *data; + CtxList *next; + void (*freefunc)(void *data, void *freefunc_data); + void *freefunc_data; +}; + +static inline void ctx_list_prepend_full (CtxList **list, void *data, + void (*freefunc)(void *data, void *freefunc_data), + void *freefunc_data) +{ + CtxList *new_= (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = *list; + new_->data=data; + new_->freefunc=freefunc; + new_->freefunc_data = freefunc_data; + *list = new_; +} + +static inline int ctx_list_length (CtxList *list) +{ + int length = 0; + CtxList *l; + for (l = list; l; l = l->next, length++); + return length; +} + +static inline void ctx_list_prepend (CtxList **list, void *data) +{ + CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1); + new_->next= *list; + new_->data=data; + *list = new_; +} + +static inline CtxList *ctx_list_nth (CtxList *list, int no) +{ + while (no-- && list) + { list = list->next; } + return list; +} + +static inline void *ctx_list_nth_data (CtxList *list, int no) +{ + CtxList *l = ctx_list_nth (list, no); + if (l) + return l->data; + return NULL; +} + + +static inline void +ctx_list_insert_before (CtxList **list, CtxList *sibling, + void *data) +{ + if (*list == NULL || *list == sibling) + { + ctx_list_prepend (list, data); + } + else + { + CtxList *prev = NULL; + for (CtxList *l = *list; l; l=l->next) + { + if (l == sibling) + { break; } + prev = l; + } + if (prev) + { + CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + } + } +} + +static inline void ctx_list_remove_link (CtxList **list, CtxList *link) +{ + CtxList *iter, *prev = NULL; + if ((*list) == link) + { + prev = (*list)->next; + *list = prev; + link->next = NULL; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter == link) + { + if (prev) + prev->next = iter->next; + link->next = NULL; + return; + } + else + prev = iter; +} + +static inline void ctx_list_remove (CtxList **list, void *data) +{ + CtxList *iter, *prev = NULL; + if ((*list)->data == data) + { + if ((*list)->freefunc) + (*list)->freefunc ((*list)->data, (*list)->freefunc_data); + prev = (*list)->next; + free (*list); + *list = prev; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter->data == data) + { + if (iter->freefunc) + iter->freefunc (iter->data, iter->freefunc_data); + prev->next = iter->next; + free (iter); + break; + } + else + prev = iter; +} + +static inline void ctx_list_free (CtxList **list) +{ + while (*list) + ctx_list_remove (list, (*list)->data); +} + +static inline void +ctx_list_reverse (CtxList **list) +{ + CtxList *new_ = NULL; + CtxList *l; + for (l = *list; l; l=l->next) + ctx_list_prepend (&new_, l->data); + ctx_list_free (list); + *list = new_; +} + +static inline void *ctx_list_last (CtxList *list) +{ + if (list) + { + CtxList *last; + for (last = list; last->next; last=last->next); + return last->data; + } + return NULL; +} + +static inline void ctx_list_concat (CtxList **list, CtxList *list_b) +{ + if (*list) + { + CtxList *last; + for (last = *list; last->next; last=last->next); + last->next = list_b; + return; + } + *list = list_b; +} + +static inline void ctx_list_append_full (CtxList **list, void *data, + void (*freefunc)(void *data, void *freefunc_data), + void *freefunc_data) +{ + CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1); + new_->data=data; + new_->freefunc = freefunc; + new_->freefunc_data = freefunc_data; + ctx_list_concat (list, new_); +} + +static inline void ctx_list_append (CtxList **list, void *data) +{ + ctx_list_append_full (list, data, NULL, NULL); +} + +static inline void +ctx_list_insert_at (CtxList **list, + int no, + void *data) +{ + if (*list == NULL || no == 0) + { + ctx_list_prepend (list, data); + } + else + { + int pos = 0; + CtxList *prev = NULL; + CtxList *sibling = NULL; + for (CtxList *l = *list; l && pos < no; l=l->next) + { + prev = sibling; + sibling = l; + pos ++; + } + if (prev) + { + CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + return; + } + ctx_list_append (list, data); + } +} + +static CtxList* +ctx_list_merge_sorted (CtxList* list1, + CtxList* list2, + int(*compare)(const void *a, const void *b, void *userdata), void *userdata +) +{ + if (list1 == NULL) + return(list2); + else if (list2==NULL) + return(list1); + + if (compare (list1->data, list2->data, userdata) >= 0) + { + list1->next = ctx_list_merge_sorted (list1->next,list2, compare, userdata); + /*list1->next->prev = list1; + list1->prev = NULL;*/ + return list1; + } + else + { + list2->next = ctx_list_merge_sorted (list1,list2->next, compare, userdata); + /*list2->next->prev = list2; + list2->prev = NULL;*/ + return list2; + } +} + +static void +ctx_list_split_half (CtxList* head, + CtxList** list1, + CtxList** list2) +{ + CtxList* fast; + CtxList* slow; + if (head==NULL || head->next==NULL) + { + *list1 = head; + *list2 = NULL; + } + else + { + slow = head; + fast = head->next; + + while (fast != NULL) + { + fast = fast->next; + if (fast != NULL) + { + slow = slow->next; + fast = fast->next; + } + } + + *list1 = head; + *list2 = slow->next; + slow->next = NULL; + } +} + +static inline void ctx_list_sort (CtxList **head, + int(*compare)(const void *a, const void *b, void *userdata), + void *userdata) +{ + CtxList* list1; + CtxList* list2; + + /* Base case -- length 0 or 1 */ + if ((*head == NULL) || ((*head)->next == NULL)) + { + return; + } + + ctx_list_split_half (*head, &list1, &list2); + ctx_list_sort (&list1, compare, userdata); + ctx_list_sort (&list2, compare, userdata); + *head = ctx_list_merge_sorted (list1, list2, compare, userdata); +} + +static inline void ctx_list_insert_sorted (CtxList **list, + void *item, + int(*compare)(const void *a, const void *b, void *userdata), + void *userdata) +{ + ctx_list_prepend (list, item); + ctx_list_sort (list, compare, userdata); +} + + +static inline CtxList *ctx_list_find_custom (CtxList *list, + void *needle, + int(*compare)(const void *a, const void *b), + void *userdata) +{ + CtxList *l; + for (l = list; l; l = l->next) + { + if (compare (l->data, needle) == 0) + return l; + } + return NULL; +} + +#endif + +/* definitions that determine which features are included and their settings, + * for particular platforms - in particular microcontrollers ctx might need + * tuning for different quality/performance/resource constraints. + * + * the way to configure ctx is to set these defines, before both including it + * as a header and in the file where CTX_IMPLEMENTATION is set to include the + * implementation for different featureset and runtime settings. + * + */ + +/* whether the font rendering happens in backend or front-end of API, the + * option is used set to 0 by the tool that converts ttf fonts to ctx internal + * representation - both should be possible so that this tool can be made + * into a TTF/OTF font import at runtime (perhaps even with live subsetting). + */ +#ifndef CTX_BACKEND_TEXT +#define CTX_BACKEND_TEXT 1 +#endif + + +#define CTX_RASTERIZER_AA_SLOPE_LIMIT3 (65536/CTX_SUBDIV/15) +//#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (120536/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (100000/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT5 (140425/CTX_SUBDIV/15) +#define CTX_RASTERIZER_AA_SLOPE_LIMIT15 (260425/CTX_SUBDIV/15) + + +#ifndef CTX_ONLY_FAST_AA +#define CTX_ONLY_FAST_AA 0 +#endif + +/* subpixel-aa coordinates used in BITPACKing of drawlist + * + * powers of 2 is faster + */ +#ifndef CTX_SUBDIV +#define CTX_SUBDIV 8 // max framebufer width 4095 +//#define CTX_SUBDIV 10 // max framebufer width 3250 +//#define CTX_SUBDIV 16 // max framebufer width 2047 +//#define CTX_SUBDIV 24 // max framebufer width 1350 +//#define CTX_SUBDIV 32 // max framebufer width 1023 +#endif + + +// 8 12 68 40 24 +// 16 12 68 40 24 +/* scale-factor for font outlines prior to bit quantization by CTX_SUBDIV + * + * changing this also changes font file format - the value should be baked + * into the ctxf files making them less dependent on the ctx used to + * generate them + */ +#define CTX_BAKE_FONT_SIZE 160 + +/* pack some linetos/curvetos/movetos into denser drawlist instructions, + * permitting more vectors to be stored in the same space, experimental + * feature with added overhead. + */ +#ifndef CTX_BITPACK +#define CTX_BITPACK 1 +#endif + +/* whether we have a shape-cache where we keep pre-rasterized bitmaps of + * commonly occuring small shapes, disabled by default since it has some + * glitches (and potential hangs with multi threading). + */ +#ifndef CTX_SHAPE_CACHE +#define CTX_SHAPE_CACHE 0 +#endif + +/* size (in pixels, w*h) that we cache rasterization for + */ +#ifndef CTX_SHAPE_CACHE_DIM +#define CTX_SHAPE_CACHE_DIM (16*16) +#endif + +#ifndef CTX_SHAPE_CACHE_MAX_DIM +#define CTX_SHAPE_CACHE_MAX_DIM 32 +#endif + +/* maximum number of entries in shape cache + */ +#ifndef CTX_SHAPE_CACHE_ENTRIES +#define CTX_SHAPE_CACHE_ENTRIES 160 +#endif + + +#ifndef CTX_PARSER_FIXED_TEMP +#define CTX_PARSER_FIXED_TEMP 0 + // when 1 CTX_PARSER_MAXLEN is the fixed max stringlen +#endif // and no allocations happens beyond creating the parser, + // when 0 the scratchbuf for parsing is a separate dynamically + // growing buffer, that maxes out at CTX_PARSER_MAXLEN + // +#ifndef CTX_PARSER_MAXLEN +#if CTX_PARSER_FIXED_TEMP +#define CTX_PARSER_MAXLEN 1024*128 // This is the maximum texture/string size supported +#else +#define CTX_PARSER_MAXLEN 1024*1024*16 // 16mb +#endif +#endif + +#ifndef CTX_COMPOSITING_GROUPS +#define CTX_COMPOSITING_GROUPS 1 +#endif + +/* maximum nesting level of compositing groups + */ +#ifndef CTX_GROUP_MAX +#define CTX_GROUP_MAX 8 +#endif + +#ifndef CTX_ENABLE_CLIP +#define CTX_ENABLE_CLIP 1 +#endif + +/* use a 1bit clip buffer, saving RAM on microcontrollers, other rendering + * will still be antialiased. + */ +#ifndef CTX_1BIT_CLIP +#define CTX_1BIT_CLIP 0 +#endif + + +#ifndef CTX_ENABLE_SHADOW_BLUR +#define CTX_ENABLE_SHADOW_BLUR 1 +#endif + +#ifndef CTX_GRADIENTS +#define CTX_GRADIENTS 1 +#endif + +#ifndef CTX_ALIGNED_STRUCTS +#define CTX_ALIGNED_STRUCTS 1 +#endif + +#ifndef CTX_GRADIENT_CACHE +#define CTX_GRADIENT_CACHE 1 +#endif + +#ifndef CTX_FONTS_FROM_FILE +#define CTX_FONTS_FROM_FILE 0 +#endif + +#ifndef CTX_FORMATTER +#define CTX_FORMATTER 1 +#endif + +#ifndef CTX_PARSER +#define CTX_PARSER 1 +#endif + +#ifndef CTX_CURRENT_PATH +#define CTX_CURRENT_PATH 1 +#endif + +#ifndef CTX_XML +#define CTX_XML 1 +#endif + +#ifndef CTX_VT +#define CTX_VT 0 +#endif + +/* when ctx_math is defined, which it is by default, we use ctx' own + * implementations of math functions, instead of relying on math.h + * the possible inlining gives us a slight speed-gain, and on + * embedded platforms guarantees that we do not do double precision + * math. + */ +#ifndef CTX_MATH +#define CTX_MATH 1 // use internal fast math for sqrt,sin,cos,atan2f etc. +#endif + +#define ctx_log(fmt, ...) +//#define ctx_log(str, a...) fprintf(stderr, str, ##a) + +/* the initial journal size - for both rasterizer + * edgelist and drawlist. + */ +#ifndef CTX_MIN_JOURNAL_SIZE +#define CTX_MIN_JOURNAL_SIZE 512 +#endif + +/* The maximum size we permit the drawlist to grow to, + * the memory used is this number * 9, where 9 is sizeof(CtxEntry) + */ +#ifndef CTX_MAX_JOURNAL_SIZE +//#define CTX_MAX_JOURNAL_SIZE CTX_MIN_JOURNAL_SIZE +#define CTX_MAX_JOURNAL_SIZE 1024*1024*16 +#endif + +#ifndef CTX_DRAWLIST_STATIC +#define CTX_DRAWLIST_STATIC 0 +#endif + +#ifndef CTX_MIN_EDGE_LIST_SIZE +#define CTX_MIN_EDGE_LIST_SIZE 1024 +#endif + +#ifndef CTX_RASTERIZER_AA +#define CTX_RASTERIZER_AA 15 // vertical-AA of CTX_ANTIALIAS_DEFAULT +#endif + +/* The maximum complexity of a single path + */ +#ifndef CTX_MAX_EDGE_LIST_SIZE +#define CTX_MAX_EDGE_LIST_SIZE CTX_MIN_EDGE_LIST_SIZE +#endif + +#ifndef CTX_STRINGPOOL_SIZE + // XXX should be possible to make zero and disappear when codepaths not in use + // to save size, for card10 this is defined as a low number (some text + // properties still make use of it) + // + // for desktop-use this should be fully dynamic, possibly + // with chained pools, gradients are stored here. +#define CTX_STRINGPOOL_SIZE 1000 // +#endif + +/* whether we dither or not for gradients + */ +#ifndef CTX_DITHER +#define CTX_DITHER 0 +#endif + +/* only source-over clear and copy will work, the API still + * through - but the renderer is limited, for use to measure + * size and possibly in severely constrained ROMs. + */ +#ifndef CTX_BLENDING_AND_COMPOSITING +#define CTX_BLENDING_AND_COMPOSITING 1 +#endif + +/* this forces the inlining of some performance + * critical paths. + */ +#ifndef CTX_FORCE_INLINES +#define CTX_FORCE_INLINES 1 +#endif + +/* create one-off inlined inner loop for normal blend mode (for floating point, + * for RGBA8 manual loops overrrides + */ +#ifndef CTX_INLINED_NORMAL +#define CTX_INLINED_NORMAL 1 +#endif + +#ifndef CTX_INLINED_GRADIENTS +#define CTX_INLINED_GRADIENTS 1 +#endif + +#ifndef CTX_BRAILLE_TEXT +#define CTX_BRAILLE_TEXT 0 +#endif + +/* Build code paths for grayscale rasterization, this makes clipping + * faster. + */ +#ifndef CTX_NATIVE_GRAYA8 +#define CTX_NATIVE_GRAYA8 1 +#endif + +/* enable CMYK rasterization targets + */ +#ifndef CTX_ENABLE_CMYK +#define CTX_ENABLE_CMYK 1 +#endif + +/* enable color management, slightly increases CtxColor struct size, can + * be disabled for microcontrollers. + */ +#ifndef CTX_ENABLE_CM +#define CTX_ENABLE_CM 1 +#endif + +#ifndef CTX_EVENTS +#define CTX_EVENTS 1 +#endif + +#ifndef CTX_LIMIT_FORMATS +#define CTX_LIMIT_FORMATS 0 +#endif + +#ifndef CTX_ENABLE_FLOAT +#define CTX_ENABLE_FLOAT 0 +#endif + +/* by default ctx includes all pixel formats, on microcontrollers + * it can be useful to slim down code and runtime size by only + * defining the used formats, set CTX_LIMIT_FORMATS to 1, and + * manually add CTX_ENABLE_ flags for each of them. + */ +#if CTX_LIMIT_FORMATS +#if CTX_NATIVE_GRAYA8 +#define CTX_ENABLE_GRAYA8 1 +#define CTX_ENABLE_GRAY8 1 +#endif +#else + +#define CTX_ENABLE_GRAY1 1 +#define CTX_ENABLE_GRAY2 1 +#define CTX_ENABLE_GRAY4 1 +#define CTX_ENABLE_GRAY8 1 +#define CTX_ENABLE_GRAYA8 1 +#define CTX_ENABLE_GRAYF 1 +#define CTX_ENABLE_GRAYAF 1 + +#define CTX_ENABLE_RGB8 1 +#define CTX_ENABLE_RGBA8 1 +#define CTX_ENABLE_BGRA8 1 +#define CTX_ENABLE_RGB332 1 +#define CTX_ENABLE_RGB565 1 +#define CTX_ENABLE_RGB565_BYTESWAPPED 1 +#define CTX_ENABLE_RGBAF 1 +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#define CTX_ENABLE_YUV420 1 + +#if CTX_ENABLE_CMYK +#define CTX_ENABLE_CMYK8 1 +#define CTX_ENABLE_CMYKA8 1 +#define CTX_ENABLE_CMYKAF 1 +#endif +#endif + +#ifndef CTX_RGB565_ALPHA +#define CTX_RGB565_ALPHA 0 // when enabled pure purple is transparent, + // for a ~15% overall performance hit +#endif + +#ifndef CTX_RGB332_ALPHA +#define CTX_RGB332_ALPHA 0 // when enabled pure purple is transparent, + // for a ~15% overall performance hit +#endif + +/* by including ctx-font-regular.h, or ctx-font-mono.h the + * built-in fonts using ctx drawlist encoding is enabled + */ +#if CTX_FONT_regular || CTX_FONT_mono || CTX_FONT_bold \ + || CTX_FONT_italic || CTX_FONT_sans || CTX_FONT_serif \ + || CTX_FONT_ascii +#ifndef CTX_FONT_ENGINE_CTX +#define CTX_FONT_ENGINE_CTX 1 +#endif +#endif + +#ifndef CTX_FONT_ENGINE_CTX_FS +#define CTX_FONT_ENGINE_CTX_FS 0 +#endif + +/* If stb_strutype.h is included before ctx.h add integration code for runtime loading + * of opentype fonts. + */ +#ifdef __STB_INCLUDE_STB_TRUETYPE_H__ +#ifndef CTX_FONT_ENGINE_STB +#define CTX_FONT_ENGINE_STB 1 +#endif +#else +#define CTX_FONT_ENGINE_STB 0 +#endif + +#ifdef _BABL_H +#define CTX_BABL 1 +#else +#define CTX_BABL 0 +#endif + +#ifndef CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 +#define CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 1 +#endif + +/* force add format if we have shape cache */ +#if CTX_SHAPE_CACHE +#ifdef CTX_ENABLE_GRAY8 +#undef CTX_ENABLE_GRAY8 +#endif +#define CTX_ENABLE_GRAY8 1 +#endif + +/* include the bitpack packer, can be opted out of to decrease code size + */ +#ifndef CTX_BITPACK_PACKER +#define CTX_BITPACK_PACKER 0 +#endif + +/* enable RGBA8 intermediate format for + *the indirectly implemented pixel-formats. + */ +#if CTX_ENABLE_GRAY1 | CTX_ENABLE_GRAY2 | CTX_ENABLE_GRAY4 | CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED | CTX_ENABLE_RGB8 | CTX_ENABLE_RGB332 + + #ifdef CTX_ENABLE_RGBA8 + #undef CTX_ENABLE_RGBA8 + #endif + #define CTX_ENABLE_RGBA8 1 +#endif + +#ifdef CTX_ENABLE_CMYKF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_GRAYF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_GRAYAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_RGBAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_CMYKAF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + +#ifdef CTX_ENABLE_CMYKF +#ifdef CTX_ENABLE_FLOAT +#undef CTX_ENABLE_FLOAT +#endif +#define CTX_ENABLE_FLOAT 1 +#endif + + +/* enable cmykf which is cmyk intermediate format + */ +#ifdef CTX_ENABLE_CMYK8 +#ifdef CTX_ENABLE_CMYKF +#undef CTX_ENABLE_CMYKF +#endif +#define CTX_ENABLE_CMYKF 1 +#endif +#ifdef CTX_ENABLE_CMYKA8 +#ifdef CTX_ENABLE_CMYKF +#undef CTX_ENABLE_CMYKF +#endif +#define CTX_ENABLE_CMYKF 1 +#endif + +#ifdef CTX_ENABLE_CMYKF8 +#ifdef CTX_ENABLE_CMYK +#undef CTX_ENABLE_CMYK +#endif +#define CTX_ENABLE_CMYK 1 +#endif + +#define CTX_PI 3.141592653589793f +#ifndef CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS +#define CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS 400 +#endif + +#ifndef CTX_MAX_FRAMEBUFFER_WIDTH +#define CTX_MAX_FRAMEBUFFER_WIDTH 2560 +#endif + +#ifndef CTX_RASTERIZER_INLINED_FAST_COPY_OVER +#define CTX_RASTERIZER_INLINED_FAST_COPY_OVER 1 +#endif + +#ifndef CTX_MAX_FONTS +#define CTX_MAX_FONTS 3 +#endif + +#ifndef CTX_MAX_STATES +#define CTX_MAX_STATES 10 +#endif + +#ifndef CTX_MAX_EDGES +#define CTX_MAX_EDGES 257 +#endif + +#ifndef CTX_MAX_LINGERING_EDGES +#define CTX_MAX_LINGERING_EDGES 64 +#endif + + +#ifndef CTX_MAX_PENDING +#define CTX_MAX_PENDING 128 +#endif + +#ifndef CTX_MAX_TEXTURES +#define CTX_MAX_TEXTURES 16 +#endif + +#ifndef CTX_HASH_ROWS +#define CTX_HASH_ROWS 8 +#endif +#ifndef CTX_HASH_COLS +#define CTX_HASH_COLS 8 +#endif + +#ifndef CTX_MAX_THREADS +#define CTX_MAX_THREADS 8 // runtime is max of cores/2 and this +#endif + + + +#define CTX_RASTERIZER_EDGE_MULTIPLIER 1024 + +#ifndef CTX_IMPLEMENTATION +#define CTX_IMPLEMENTATION 0 +#else +#undef CTX_IMPLEMENTATION +#define CTX_IMPLEMENTATION 1 +#endif + + +#ifdef CTX_RASTERIZER +#if CTX_RASTERIZER==0 +#if CTX_SDL || CTX_FB +#undef CTX_RASTERIZER +#define CTX_RASTERIZER 1 +#endif +#else +#undef CTX_RASTERIZER +#define CTX_RASTERIZER 1 +#endif +#endif + +#if CTX_RASTERIZER +#ifndef CTX_COMPOSITE +#define CTX_COMPOSITE 1 +#endif +#else +#ifndef CTX_COMPOSITE +#define CTX_COMPOSITE 0 +#endif +#endif + +#ifndef CTX_GRADIENT_CACHE_ELEMENTS +#define CTX_GRADIENT_CACHE_ELEMENTS 256 +#endif + +#ifndef CTX_PARSER_MAX_ARGS +#define CTX_PARSER_MAX_ARGS 20 +#endif + + +#ifndef CTX_SCREENSHOT +#define CTX_SCREENSHOT 0 +#endif + +#ifndef CTX_ALSA_AUDIO +#define CTX_ALSA_AUDIO 0 +#endif + +#if NO_ALSA +#undef CTX_ALSA_AUDIO +#define CTX_ALSA_AUDIO 0 +#endif + +#ifndef CTX_AUDIO +#define CTX_AUDIO 0 +#endif + +#ifndef CTX_TILED +#if CTX_SDL || CTX_FB +#define CTX_TILED 1 +#else +#define CTX_TILED 0 +#endif +#endif + +#ifndef CTX_THREADS +#if CTX_FB +#define CTX_THREADS 1 +#else +#define CTX_THREADS 0 +#endif +#endif + +#if CTX_THREADS +#include <threads.h> +#endif + /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + */ + +#if CTX_FORMATTER + +/* returns the maximum string length including terminating \0 */ +int ctx_a85enc_len (int input_length); +int ctx_a85enc (const void *srcp, char *dst, int count); + +#if CTX_PARSER + +int ctx_a85dec (const char *src, char *dst, int count); +int ctx_a85len (const char *src, int count); +#endif + +#endif +#ifndef __CTX_EXTRA_H +#define __CTX_EXTRA_H + + +#define CTX_CLAMP(val,min,max) ((val)<(min)?(min):(val)>(max)?(max):(val)) +static inline int ctx_mini (int a, int b) { return (a < b) * a + (a >= b) * b; } +static inline float ctx_minf (float a, float b) { return (a < b) * a + (a >= b) * b; } +static inline int ctx_maxi (int a, int b) { return (a > b) * a + (a <= b) * b; } +static inline float ctx_maxf (float a, float b) { return (a > b) * a + (a <= b) * b; } + + +typedef enum CtxOutputmode +{ + CTX_OUTPUT_MODE_QUARTER, + CTX_OUTPUT_MODE_BRAILLE, + CTX_OUTPUT_MODE_SIXELS, + CTX_OUTPUT_MODE_GRAYS, + CTX_OUTPUT_MODE_CTX, + CTX_OUTPUT_MODE_CTX_COMPACT, + CTX_OUTPUT_MODE_UI +} CtxOutputmode; + + + + + +#if CTX_FORCE_INLINES +#define CTX_INLINE inline __attribute__((always_inline)) +#else +#define CTX_INLINE inline +#endif + +static inline float ctx_pow2 (float a) { return a * a; } +#if CTX_MATH + +static inline float +ctx_fabsf (float x) +{ + union + { + float f; + uint32_t i; + } u = { x }; + u.i &= 0x7fffffff; + return u.f; +} + +static inline float +ctx_invsqrtf (float x) +{ + void *foo = &x; + float xhalf = 0.5f * x; + int i=* (int *) foo; + void *bar = &i; + i = 0x5f3759df - (i >> 1); + x = * (float *) bar; + x *= (1.5f - xhalf * x * x); + x *= (1.5f - xhalf * x * x); //repeating Newton-Raphson step for higher precision + return x; +} + +static inline float +ctx_invsqrtf_fast (float x) +{ + void *foo = &x; +//float xhalf = 0.5f * x; + int i=* (int *) foo; + void *bar = &i; + i = 0x5f3759df - (i >> 1); + x = * (float *) bar; +//x *= (1.5f - xhalf * x * x); + return x; +} + +CTX_INLINE static float ctx_sqrtf (float a) +{ + return 1.0f/ctx_invsqrtf (a); +} + +CTX_INLINE static float ctx_sqrtf_fast (float a) +{ + return 1.0f/ctx_invsqrtf_fast (a); +} + +CTX_INLINE static float ctx_hypotf (float a, float b) +{ + return ctx_sqrtf (ctx_pow2 (a)+ctx_pow2 (b) ); +} + +CTX_INLINE static float ctx_hypotf_fast (float a, float b) +{ + return ctx_sqrtf_fast (ctx_pow2 (a)+ctx_pow2 (b) ); +} + +CTX_INLINE static float +ctx_sinf (float x) +{ + if (x < -CTX_PI * 2) + { + x = -x; + long ix = x / (CTX_PI * 2); + x = x - ix * CTX_PI * 2; + x = -x; + } + if (x < -CTX_PI * 1000) + { + x = -0.5; + } + if (x > CTX_PI * 1000) + { + // really large numbers tend to cause practically inifinite + // loops since the > CTX_PI * 2 seemingly fails + x = 0.5; + } + if (x > CTX_PI * 2) + { + long ix = x / (CTX_PI * 2); + x = x - (ix * CTX_PI * 2); + } + while (x < -CTX_PI) + { x += CTX_PI * 2; } + while (x > CTX_PI) + { x -= CTX_PI * 2; } + + /* source : http://mooooo.ooo/chebyshev-sine-approximation/ */ + float coeffs[]= + { + -0.10132118f, // x + 0.0066208798f, // x^3 + -0.00017350505f, // x^5 + 0.0000025222919f, // x^7 + -0.000000023317787f, // x^9 + 0.00000000013291342f + }; // x^11 + float x2 = x*x; + float p11 = coeffs[5]; + float p9 = p11*x2 + coeffs[4]; + float p7 = p9*x2 + coeffs[3]; + float p5 = p7*x2 + coeffs[2]; + float p3 = p5*x2 + coeffs[1]; + float p1 = p3*x2 + coeffs[0]; + return (x - CTX_PI + 0.00000008742278f) * + (x + CTX_PI - 0.00000008742278f) * p1 * x; +} + +static inline float ctx_atan2f (float y, float x) +{ + float atan, z; + if ( x == 0.0f ) + { + if ( y > 0.0f ) + { return CTX_PI/2; } + if ( y == 0.0f ) + { return 0.0f; } + return -CTX_PI/2; + } + z = y/x; + if ( ctx_fabsf ( z ) < 1.0f ) + { + atan = z/ (1.0f + 0.28f*z*z); + if (x < 0.0f) + { + if ( y < 0.0f ) + { return atan - CTX_PI; } + return atan + CTX_PI; + } + } + else + { + atan = CTX_PI/2 - z/ (z*z + 0.28f); + if ( y < 0.0f ) { return atan - CTX_PI; } + } + return atan; +} + + +static inline float ctx_atanf (float a) +{ + return ctx_atan2f ( (a), 1.0f); +} + +static inline float ctx_asinf (float x) +{ + return ctx_atanf ( (x) * (ctx_invsqrtf (1.0f-ctx_pow2 (x) ) ) ); +} + +static inline float ctx_acosf (float x) +{ + return ctx_atanf ( (ctx_sqrtf (1.0f-ctx_pow2 (x) ) / (x) ) ); +} + +CTX_INLINE static float ctx_cosf (float a) +{ + return ctx_sinf ( (a) + CTX_PI/2.0f); +} + +static inline float ctx_tanf (float a) +{ + return (ctx_cosf (a) /ctx_sinf (a) ); +} +static inline float +ctx_floorf (float x) +{ + return (int)x; // XXX +} +static inline float +ctx_expf (float x) +{ + union { uint32_t i; float f; } v = + { (uint32_t)( (1 << 23) * (x + 183.1395965f)) }; + return v.f; +} + +/* define more trig based on having sqrt, sin and atan2 */ + +#else +#if !__COSMOPOLITAN__ +#include <math.h> +#endif +static inline float ctx_fabsf (float x) { return fabsf (x); } +static inline float ctx_floorf (float x) { return floorf (x); } +static inline float ctx_sinf (float x) { return sinf (x); } +static inline float ctx_atan2f (float y, float x) { return atan2f (y, x); } +static inline float ctx_hypotf (float a, float b) { return hypotf (a, b); } +static inline float ctx_acosf (float a) { return acosf (a); } +static inline float ctx_cosf (float a) { return cosf (a); } +static inline float ctx_tanf (float a) { return tanf (a); } +static inline float ctx_expf (float p) { return expf (p); } +static inline float ctx_sqrtf (float a) { return sqrtf (a); } +#endif + +static inline float _ctx_parse_float (const char *str, char **endptr) +{ + return strtod (str, endptr); /* XXX: , vs . problem in some locales */ +} + +const char *ctx_get_string (Ctx *ctx, uint64_t hash); +void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value); +typedef struct _CtxColor CtxColor; + +typedef struct _CtxMatrix CtxMatrix; +struct + _CtxMatrix +{ + float m[3][2]; +}; +void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix); + +int ctx_color (Ctx *ctx, const char *string); +typedef struct _CtxState CtxState; +CtxColor *ctx_color_new (); +CtxState *ctx_get_state (Ctx *ctx); +void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out); +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +void ctx_color_free (CtxColor *color); +void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color); +int ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color); +int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string); + +int ctx_color_is_transparent (CtxColor *color); +int ctx_utf8_len (const unsigned char first_byte); + +void ctx_user_to_device (Ctx *ctx, float *x, float *y); +void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y); +const char *ctx_utf8_skip (const char *s, int utf8_length); +void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix); +void ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y); +void ctx_matrix_invert (CtxMatrix *m); +void ctx_matrix_identity (CtxMatrix *matrix); +void ctx_matrix_scale (CtxMatrix *matrix, float x, float y); +void ctx_matrix_rotate (CtxMatrix *matrix, float angle); +void ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s); +void +ctx_matrix_translate (CtxMatrix *matrix, float x, float y); +int ctx_is_set_now (Ctx *ctx, uint64_t hash); +void ctx_set_size (Ctx *ctx, int width, int height); + +static inline float ctx_matrix_get_scale (CtxMatrix *matrix) +{ + return ctx_maxf (ctx_maxf (ctx_fabsf (matrix->m[0][0]), + ctx_fabsf (matrix->m[0][1]) ), + ctx_maxf (ctx_fabsf (matrix->m[1][0]), + ctx_fabsf (matrix->m[1][1]) ) ); +} + +#if CTX_FONTS_FROM_FILE +int ctx_load_font_ttf_file (const char *name, const char *path); +int +_ctx_file_get_contents (const char *path, + unsigned char **contents, + long *length); +#endif + +#endif +#ifndef __CTX_CONSTANTS +#define __CTX_CONSTANTS + +#define TOKENHASH(a) ((uint64_t)a) + +#define CTX_strokeSource TOKENHASH(3061861651908008) +#define CTX_add_stop TOKENHASH(1274978316678) +#define CTX_addStop TOKENHASH(40799943078278) +#define CTX_alphabetic TOKENHASH(2629359926678406) +#define CTX_arc TOKENHASH(11526) +#define CTX_arc_to TOKENHASH(1187065094) +#define CTX_arcTo TOKENHASH(38558051590) +#define CTX_begin_path TOKENHASH(3004110681622984) +#define CTX_beginPath TOKENHASH(8437143659599196) +#define CTX_bevel TOKENHASH(29868488) +#define CTX_bottom TOKENHASH(1043772488) +#define CTX_cap TOKENHASH(37066) +#define CTX_center TOKENHASH(1358332362) +#define CTX_clear TOKENHASH(42154890) +#define CTX_color TOKENHASH(43086922) +#define CTX_copy TOKENHASH(1807434) +#define CTX_clip TOKENHASH(1203082) +#define CTX_close_path TOKENHASH(3004110663420810) +#define CTX_closePath TOKENHASH(8437144279135038) +#define CTX_cmyka TOKENHASH(7199690) +#define CTX_cmyk TOKENHASH(908234) +#define CTX_cmykaS TOKENHASH(36313095114) +#define CTX_cmykS TOKENHASH(1135467466) +#define CTX_color TOKENHASH(43086922) +#define CTX_blending TOKENHASH(653586873224) +#define CTX_blend TOKENHASH(13646728) +#define CTX_blending_mode TOKENHASH(8147360531130856) +#define CTX_blendingMode TOKENHASH(7483585768187540) +#define CTX_blend_mode TOKENHASH(2758775686577032) +#define CTX_blendMode TOKENHASH(7773213171090182) +#define CTX_composite TOKENHASH(16930059746378) +#define CTX_compositing_mode TOKENHASH(2417816728103524) +#define CTX_compositingMode TOKENHASH(2807194446992106) +#define CTX_curve_to TOKENHASH(1215559149002) +#define CTX_curveTo TOKENHASH(39483449320906) +#define CTX_darken TOKENHASH(1089315020) +#define CTX_defineGlyph TOKENHASH(2497926167421194) +#define CTX_defineTexture TOKENHASH(2623744577477404) +#define CTX_kerningPair TOKENHASH(6964644556489058) +#define CTX_destinationIn TOKENHASH(8153299143600102) +#define CTX_destination_in TOKENHASH(3824201982576824) +#define CTX_destinationAtop TOKENHASH(8185118415574560) +#define CTX_destination_atop TOKENHASH(7742210324901698) +#define CTX_destinationOver TOKENHASH(3261713333438500) +#define CTX_destination_over TOKENHASH(7742210324728474) +#define CTX_destinationOut TOKENHASH(7742210322269456) +#define CTX_destination_out TOKENHASH(8153299143489102) +#define CTX_difference TOKENHASH(2756492040618700) +#define CTX_done TOKENHASH(492620) +#define CTX_drgba TOKENHASH(6573324) +#define CTX_drgb TOKENHASH(281868) +#define CTX_drgbaS TOKENHASH(36312468748) +#define CTX_drgbS TOKENHASH(1134841100) +#define CTX_end TOKENHASH(13326) +#define CTX_endfun TOKENHASH(1122513934) +#define CTX_end_group TOKENHASH(41200834917390) +#define CTX_endGroup TOKENHASH(3570227948106766) +#define CTX_even_odd TOKENHASH(426345774606) +#define CTX_evenOdd TOKENHASH(13671748091406) +#define CTX_exit TOKENHASH(1465998) +#define CTX_fill TOKENHASH(946896) +#define CTX_fill_rule TOKENHASH(16405972808400) +#define CTX_fillRule TOKENHASH(2776813389378256) +#define CTX_flush TOKENHASH(22395792) +#define CTX_font TOKENHASH(1475664) +#define CTX_font_size TOKENHASH(17342343316560) +#define CTX_setFontSize TOKENHASH(8657699789799734) +#define CTX_fontSize TOKENHASH(2806775148872784) +#define CTX_function TOKENHASH(1136803546576) +#define CTX_getkey TOKENHASH(1827516882) +#define CTX_global_alpha TOKENHASH(6945103263242432) +#define CTX_globalAlpha TOKENHASH(2684560928159160) +#define CTX_glyph TOKENHASH(22207378) +#define CTX_gradient_add_stop TOKENHASH(7829524561074416) +#define CTX_gradientAddStop TOKENHASH(8126442749593072) +#define CTX_graya TOKENHASH(8068370) +#define CTX_gray TOKENHASH(1776914) +#define CTX_grayaS TOKENHASH(36313963794) +#define CTX_grayS TOKENHASH(1136336146) +#define CTX_hanging TOKENHASH(20424786132) +#define CTX_height TOKENHASH(1497979348) +#define CTX_hor_line_to TOKENHASH(8345271542735158) +#define CTX_horLineTo TOKENHASH(3629696407754856) +#define CTX_hue TOKENHASH(15828) +#define CTX_identity TOKENHASH(1903455910294) +#define CTX_ideographic TOKENHASH(4370819675496700) +#define CTX_imageSmoothing TOKENHASH(4268778175825416) +#define CTX_join TOKENHASH(1072216) +#define CTX_laba TOKENHASH(205020) +#define CTX_lab TOKENHASH(8412) +#define CTX_lcha TOKENHASH(217436) +#define CTX_lch TOKENHASH(20828) +#define CTX_labaS TOKENHASH(1134764252) +#define CTX_labS TOKENHASH(35463388) +#define CTX_lchaS TOKENHASH(1134776668) +#define CTX_lchS TOKENHASH(35475804) +#define CTX_left TOKENHASH(1458652) +#define CTX_lighter TOKENHASH(43466246876) +#define CTX_lighten TOKENHASH(34876312284) +#define CTX_linear_gradient TOKENHASH(7801595375834212) +#define CTX_linearGradient TOKENHASH(4439260636789186) +#define CTX_line_cap TOKENHASH(1243731165916) +#define CTX_lineCap TOKENHASH(3436510399409980) +#define CTX_setLineCap TOKENHASH(7897176123029482) +#define CTX_line_height TOKENHASH(3300223516389168) +#define CTX_line_join TOKENHASH(35977601450716) +#define CTX_lineJoin TOKENHASH(3403122163024604) +#define CTX_setLineJoin TOKENHASH(2768281536656332) +#define CTX_line_spacing TOKENHASH(2519451230887150) +#define CTX_line_to TOKENHASH(37986206428) +#define CTX_lineTo TOKENHASH(1233857774300) +#define CTX_lineDash TOKENHASH(3001937455186652) +#define CTX_lineDashOffset TOKENHASH(3704120356324362) +#define CTX_line_width TOKENHASH(3004303386575580) +#define CTX_lineWidth TOKENHASH(8241159254028040) +#define CTX_setLineWidth TOKENHASH(8037913618228476) +#define CTX_view_box TOKENHASH(1823485803248) +#define CTX_viewBox TOKENHASH(3915860941641152) +#define CTX_middle TOKENHASH(499528414) +#define CTX_miter TOKENHASH(42447582) +#define CTX_miter_limit TOKENHASH(4281255327472850) +#define CTX_miterLimit TOKENHASH(7937453649653124) +#define CTX_move_to TOKENHASH(37986223198) +#define CTX_moveTo TOKENHASH(1233857791070) +#define CTX_multiply TOKENHASH(1886723143134) +#define CTX_new_page TOKENHASH(500602882528) +#define CTX_newPage TOKENHASH(16020123011552) +#define CTX_new_path TOKENHASH(734678600160) +#define CTX_newPath TOKENHASH(23510545975776) +#define CTX_new_state TOKENHASH(16912954280416) +#define CTX_none TOKENHASH(492640) +#define CTX_nonzero TOKENHASH(37865948256) +#define CTX_non_zero TOKENHASH(1211709359200) +#define CTX_normal TOKENHASH(946840672) +#define CTX_quad_to TOKENHASH(37986115046) +#define CTX_quadTo TOKENHASH(1233857682918) +#define CTX_radial_gradient TOKENHASH(8267515704460560) +#define CTX_radialGradient TOKENHASH(4399889250822134) +#define CTX_rectangle TOKENHASH(16375644301800) +#define CTX_rect TOKENHASH(1452520) +#define CTX_rel_arc_to TOKENHASH(3496527781786088) +#define CTX_relArcTo TOKENHASH(3209152175601038) +#define CTX_rel_curve_to TOKENHASH(4439651822639910) +#define CTX_relCurveTo TOKENHASH(7294415873689320) +#define CTX_rel_hor_line_to TOKENHASH(7051067105640810) +#define CTX_relHorLineTo TOKENHASH(8737419863647946) +#define CTX_relVerLineTo TOKENHASH(8737441317512906) +#define CTX_rel_line_to TOKENHASH(8345271542378314) +#define CTX_relLineTo TOKENHASH(3629696197927444) +#define CTX_rel_move_to TOKENHASH(8344984486309706) +#define CTX_relMoveTo TOKENHASH(3571677202293268) +#define CTX_rel_quad_to TOKENHASH(8343627754794826) +#define CTX_relQuadTo TOKENHASH(7894357900599828) +#define CTX_rel_smoothq_to TOKENHASH(7340038162167138) +#define CTX_relSmoothqTo TOKENHASH(3188040406230844) +#define CTX_rel_smooth_to TOKENHASH(8144941131301668) +#define CTX_relSmoothTo TOKENHASH(8947422784198618) +#define CTX_rel_ver_line_to TOKENHASH(8148126344839530) +#define CTX_restore TOKENHASH(16411699688) +#define CTX_reset TOKENHASH(46639592) +#define CTX_rgba TOKENHASH(205416) +#define CTX_rgb TOKENHASH(8808) +#define CTX_rgbaS TOKENHASH(1134764648) +#define CTX_rgbS TOKENHASH(35463784) +#define CTX_right TOKENHASH(46811880) +#define CTX_rotate TOKENHASH(516142184) +#define CTX_round TOKENHASH(13679720) +#define CTX_round_rectangle TOKENHASH(4332080966833870) +#define CTX_roundRectangle TOKENHASH(8317255488676642) +#define CTX_save TOKENHASH(508138) +#define CTX_scale TOKENHASH(15604074) +#define CTX_screen TOKENHASH(1088921962) +#define CTX_setkey TOKENHASH(1827516906) +#define CTX_shadowBlur TOKENHASH(2924056626980284) +#define CTX_shadowColor TOKENHASH(3509599043947446) +#define CTX_shadowOffsetX TOKENHASH(8499312693589794) +#define CTX_shadowOffsetY TOKENHASH(8499312693589796) +#define CTX_smooth_quad_to TOKENHASH(6832232668547050) +#define CTX_smoothQuadTo TOKENHASH(8278352345012646) +#define CTX_smooth_to TOKENHASH(38898089692138) +#define CTX_smoothTo TOKENHASH(3515270388878314) +#define CTX_sourceIn TOKENHASH(3444145493687402) +#define CTX_source_in TOKENHASH(35942915423338) +#define CTX_sourceAtop TOKENHASH(2920281959978332) +#define CTX_source_atop TOKENHASH(3007410591464110) +#define CTX_sourceOut TOKENHASH(7371294932695718) +#define CTX_source_out TOKENHASH(3851660580666474) +#define CTX_sourceOver TOKENHASH(7584784067385004) +#define CTX_sourceTransform TOKENHASH(7515321363744130) +#define CTX_source_over TOKENHASH(8690648756484770) +#define CTX_square TOKENHASH(511950058) +#define CTX_start TOKENHASH(47455658) +#define CTX_start_move TOKENHASH(2798358138985898) +#define CTX_start_group TOKENHASH(7836274863228782) +#define CTX_startGroup TOKENHASH(3812645199786240) +#define CTX_stroke TOKENHASH(498181546) +#define CTX_text_align TOKENHASH(3398277113762284) +#define CTX_textAlign TOKENHASH(3063795447820748) +#define CTX_texture TOKENHASH(16424292844) +#define CTX_text_baseline TOKENHASH(2589194334827348) +#define CTX_text_baseline TOKENHASH(2589194334827348) +#define CTX_textBaseline TOKENHASH(8381669925369340) +#define CTX_fillRect TOKENHASH(3811453831115472) +#define CTX_text TOKENHASH(1495532) +#define CTX_text_direction TOKENHASH(3614589880641524) +#define CTX_textDirection TOKENHASH(6790122975782654) +#define CTX_text_indent TOKENHASH(3633795456290560) +#define CTX_text_stroke TOKENHASH(8259523149811490) +#define CTX_strokeText TOKENHASH(8131451867629426) +#define CTX_strokeRect TOKENHASH(8165399289138988) +#define CTX_top TOKENHASH(37996) +#define CTX_transform TOKENHASH(34396827557164) +#define CTX_translate TOKENHASH(16912418348332) +#define CTX_verLineTo TOKENHASH(3629696413166220) +#define CTX_ver_line_to TOKENHASH(8345271542726354) +#define CTX_width TOKENHASH(22426354) +#define CTX_winding TOKENHASH(20424590066) +#define CTX_x TOKENHASH(52) +#define CTX_xor TOKENHASH(42100) +#define CTX_y TOKENHASH(54) +#define CTX_colorSpace TOKENHASH(3674150843793134) +#define CTX_userRGB TOKENHASH(59177128181102) +#define CTX_userCMYK TOKENHASH(3354734206905240) +#define CTX_deviceRGB TOKENHASH(7818727413767480) +#define CTX_deviceCMYK TOKENHASH(8943291245184210) +#define CTX_silver TOKENHASH(1358459626) +#define CTX_fuchsia TOKENHASH(7225355728) +#define CTX_gray TOKENHASH(1776914) +#define CTX_yellow TOKENHASH(1714319862) +#define CTX_white TOKENHASH(16145074) +#define CTX_maroon TOKENHASH(1110548702) +#define CTX_magenta TOKENHASH(7952877790) +#define CTX_blue TOKENHASH(506760) +#define CTX_green TOKENHASH(34028818) +#define CTX_red TOKENHASH(12776) +#define CTX_purple TOKENHASH(500344292) +#define CTX_olive TOKENHASH(16276386) +#define CTX_teal TOKENHASH(924140) +#define CTX_black TOKENHASH(27597704) +#define CTX_cyan TOKENHASH(1056458) +#define CTX_navy TOKENHASH(1818848) +#define CTX_lime TOKENHASH(490204) +#define CTX_aqua TOKENHASH(244934) +#define CTX_transparent TOKENHASH(3654078210101184) +#define CTX_currentColor TOKENHASH(7501877057638746) + +#endif +#ifndef __CTX_LIBC_H +#define __CTX_LIBC_H + +#if !__COSMOPOLITAN__ +#include <stddef.h> +#endif + +#if 0 +static inline void +ctx_memset (void *ptr, uint8_t val, int length) +{ + uint8_t *p = (uint8_t *) ptr; + for (int i = 0; i < length; i ++) + { p[i] = val; } +} +#else +#define ctx_memset memset +#endif + + +static inline void ctx_strcpy (char *dst, const char *src) +{ + int i = 0; + for (i = 0; src[i]; i++) + { dst[i] = src[i]; } + dst[i] = 0; +} + +static inline char *_ctx_strchr (const char *haystack, char needle) +{ + const char *p = haystack; + while (*p && *p != needle) + { + p++; + } + if (*p == needle) + { return (char *) p; } + return NULL; +} +static inline char *ctx_strchr (const char *haystack, char needle) +{ + return _ctx_strchr (haystack, needle); +} + +static inline int ctx_strcmp (const char *a, const char *b) +{ + int i; + for (i = 0; a[i] && b[i]; a++, b++) + if (a[0] != b[0]) + { return 1; } + if (a[0] == 0 && b[0] == 0) { return 0; } + return 1; +} + +static inline int ctx_strncmp (const char *a, const char *b, size_t n) +{ + size_t i; + for (i = 0; a[i] && b[i] && i < n; a++, b++) + if (a[0] != b[0]) + { return 1; } + return 0; +} + +static inline int ctx_strlen (const char *s) +{ + int len = 0; + for (; *s; s++) { len++; } + return len; +} + +static inline char *ctx_strstr (const char *h, const char *n) +{ + int needle_len = ctx_strlen (n); + if (n[0]==0) + { return (char *) h; } + while (h) + { + h = ctx_strchr (h, n[0]); + if (!h) + { return NULL; } + if (!ctx_strncmp (h, n, needle_len) ) + { return (char *) h; } + h++; + } + return NULL; +} + +#endif + +#if CTX_IMPLEMENTATION|CTX_COMPOSITE + +#ifndef __CTX_INTERNAL_H +#define __CTX_INTERNAL_H + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <math.h> +#endif + +#define CTX_BRANCH_HINTS 1 + +#if CTX_BRANCH_HINTS +#define CTX_LIKELY(x) __builtin_expect(!!(x), 1) +#define CTX_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define CTX_LIKELY(x) (x) +#define CTX_UNLIKELY(x) (x) +#endif + +typedef struct _CtxRasterizer CtxRasterizer; +typedef struct _CtxGState CtxGState; +typedef struct _CtxState CtxState; + +typedef struct _CtxSource CtxSource; + + +#define CTX_VALID_RGBA_U8 (1<<0) +#define CTX_VALID_RGBA_DEVICE (1<<1) +#if CTX_ENABLE_CM +#define CTX_VALID_RGBA (1<<2) +#endif +#if CTX_ENABLE_CMYK +#define CTX_VALID_CMYKA (1<<3) +#define CTX_VALID_DCMYKA (1<<4) +#endif +#define CTX_VALID_GRAYA (1<<5) +#define CTX_VALID_GRAYA_U8 (1<<6) +#define CTX_VALID_LABA ((1<<7) | CTX_VALID_GRAYA) + +struct _CtxColor +{ + uint8_t magic; // for colors used in keydb, set to a non valid start of + // string value. + uint8_t rgba[4]; + uint8_t l_u8; + uint8_t original; // the bitmask of the originally set color + uint8_t valid; // bitmask of which members contain valid + // values, gets denser populated as more + // formats are requested from a set color. + float device_red; + float device_green; + float device_blue; + float alpha; + float l; // luminance and gray +#if CTX_ENABLE_LAB // NYI + float a; + float b; +#endif +#if CTX_ENABLE_CMYK + float device_cyan; + float device_magenta; + float device_yellow; + float device_key; + float cyan; + float magenta; + float yellow; + float key; +#endif + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *space; // gets copied from state when color is declared +#else + void *space; // gets copied from state when color is declared, +#endif + float red; + float green; + float blue; +#endif +}; + +typedef struct _CtxGradientStop CtxGradientStop; + +struct _CtxGradientStop +{ + float pos; + CtxColor color; +}; + + +enum _CtxSourceType +{ + CTX_SOURCE_COLOR = 0, + CTX_SOURCE_TEXTURE, + CTX_SOURCE_LINEAR_GRADIENT, + CTX_SOURCE_RADIAL_GRADIENT, + CTX_SOURCE_INHERIT_FILL +}; + +typedef enum _CtxSourceType CtxSourceType; + +typedef struct _CtxPixelFormatInfo CtxPixelFormatInfo; + + +struct _CtxBuffer +{ + void *data; + int width; + int height; + int stride; + char *eid; // might be NULL, when not - should be unique for pixel contents + int frame; // last frame used in, everything > 3 can be removed, + // as clients wont rely on it. + CtxPixelFormatInfo *format; + void (*free_func) (void *pixels, void *user_data); + void *user_data; + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *space; +#else + void *space; +#endif +#endif +#if 1 + CtxBuffer *color_managed; /* only valid for one render target, cache + for a specific space + */ +#endif +}; + +//void _ctx_user_to_device (CtxState *state, float *x, float *y); +//void _ctx_user_to_device_distance (CtxState *state, float *x, float *y); + +typedef struct _CtxGradient CtxGradient; +struct _CtxGradient +{ + CtxGradientStop stops[16]; + int n_stops; +}; + +struct _CtxSource +{ + int type; + CtxMatrix transform; + union + { + CtxColor color; + struct + { + uint8_t rgba[4]; // shares data with set color + uint8_t pad; + float x0; + float y0; + CtxBuffer *buffer; + } texture; + struct + { + float x0; + float y0; + float x1; + float y1; + float dx; + float dy; + float start; + float end; + float length; + float rdelta; + } linear_gradient; + struct + { + float x0; + float y0; + float r0; + float x1; + float y1; + float r1; + float rdelta; + } radial_gradient; + }; +}; + +struct _CtxGState +{ + int keydb_pos; + int stringpool_pos; + + CtxMatrix transform; + CtxSource source_stroke; + CtxSource source_fill; + float global_alpha_f; + uint8_t global_alpha_u8; + + float line_width; + float line_dash_offset; + float miter_limit; + float font_size; +#if CTX_ENABLE_SHADOW_BLUR + float shadow_blur; + float shadow_offset_x; + float shadow_offset_y; +#endif + int clipped:1; + + int16_t clip_min_x; + int16_t clip_min_y; + int16_t clip_max_x; + int16_t clip_max_y; + +#if CTX_ENABLE_CM +#if CTX_BABL + const Babl *device_space; + const Babl *texture_space; + const Babl *rgb_space; + const Babl *cmyk_space; + + const Babl *fish_rgbaf_user_to_device; + const Babl *fish_rgbaf_texture_to_device; + const Babl *fish_rgbaf_device_to_user; + +#else + void *device_space; + void *texture_space; + void *rgb_space; + void *cmyk_space; + void *fish_rgbaf_user_to_device; // dummy padding + void *fish_rgbaf_texture_to_device; // dummy padding + void *fish_rgbaf_device_to_user; // dummy padding +#endif +#endif + CtxCompositingMode compositing_mode; // bitfield refs lead to + CtxBlend blend_mode; // non-vectorization + + float dashes[CTX_PARSER_MAX_ARGS]; + int n_dashes; + + CtxColorModel color_model; + /* bitfield-pack small state-parts */ + CtxLineCap line_cap:2; + CtxLineJoin line_join:2; + CtxFillRule fill_rule:1; + unsigned int image_smoothing:1; + unsigned int font:6; + unsigned int bold:1; + unsigned int italic:1; +}; + +typedef enum +{ + CTX_TRANSFORMATION_NONE = 0, + CTX_TRANSFORMATION_SCREEN_SPACE = 1, + CTX_TRANSFORMATION_RELATIVE = 2, +#if CTX_BITPACK + CTX_TRANSFORMATION_BITPACK = 4, +#endif + CTX_TRANSFORMATION_STORE_CLEAR = 16, +} CtxTransformation; + +#define CTX_DRAWLIST_DOESNT_OWN_ENTRIES 64 +#define CTX_DRAWLIST_EDGE_LIST 128 +#define CTX_DRAWLIST_CURRENT_PATH 512 +// BITPACK + +struct _CtxDrawlist +{ + CtxEntry *entries; + int count; + int size; + uint32_t flags; + int bitpack_pos; // stream is bitpacked up to this offset +}; + +#define CTX_MAX_KEYDB 64 // number of entries in keydb + // entries are "copy-on-change" between states + +// the keydb consists of keys set to floating point values, +// that might also be interpreted as integers for enums. +// +// the hash +typedef struct _CtxKeyDbEntry CtxKeyDbEntry; +struct _CtxKeyDbEntry +{ + uint64_t key; + float value; + //union { float f[1]; uint8_t u8[4]; }value; +}; + +struct _CtxState +{ + int has_moved:1; + int has_clipped:1; + float x; + float y; + int min_x; + int min_y; + int max_x; + int max_y; + int16_t gstate_no; + CtxGState gstate; + CtxGState gstate_stack[CTX_MAX_STATES];//at end, so can be made dynamic +#if CTX_GRADIENTS + CtxGradient gradient; /* we keep only one gradient, + this goes icky with multiple + restores - it should really be part of + graphics state.. + XXX, with the stringpool gradients + can be stored there. + */ +#endif + CtxKeyDbEntry keydb[CTX_MAX_KEYDB]; + char stringpool[CTX_STRINGPOOL_SIZE]; + int8_t source; // used for the single-shifting to stroking + // 0 = fill + // 1 = start_stroke + // 2 = in_stroke + // + // if we're at in_stroke at start of a source definition + // we do filling +}; + + +typedef struct _CtxFont CtxFont; +typedef struct _CtxFontEngine CtxFontEngine; + +struct _CtxFontEngine +{ +#if CTX_FONTS_FROM_FILE + int (*load_file) (const char *name, const char *path); +#endif + int (*load_memory) (const char *name, const void *data, int length); + int (*glyph) (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke); + float (*glyph_width) (CtxFont *font, Ctx *ctx, uint32_t unichar); + float (*glyph_kern) (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB); +}; + +struct _CtxFont +{ + CtxFontEngine *engine; + const char *name; + int type; // 0 ctx 1 stb 2 monobitmap + union + { + struct + { + CtxEntry *data; + int length; + /* we've got ~110 bytes to fill to cover as + much data as stbtt_fontinfo */ + //int16_t glyph_pos[26]; // for a..z + int glyphs; // number of glyphs + uint32_t *index; + } ctx; + struct + { + char *path; + } ctx_fs; +#if CTX_FONT_ENGINE_STB + struct + { + stbtt_fontinfo ttf_info; + int cache_index; + uint32_t cache_unichar; + } stb; +#endif + struct { int start; int end; int gw; int gh; const uint8_t *data;} monobitmap; + }; +}; + + +enum _CtxIteratorFlag +{ + CTX_ITERATOR_FLAT = 0, + CTX_ITERATOR_EXPAND_BITPACK = 2, + CTX_ITERATOR_DEFAULTS = CTX_ITERATOR_EXPAND_BITPACK +}; +typedef enum _CtxIteratorFlag CtxIteratorFlag; + + +struct + _CtxIterator +{ + int pos; + int first_run; + CtxDrawlist *drawlist; + int end_pos; + int flags; + + int bitpack_pos; + int bitpack_length; // if non 0 bitpack is active + CtxEntry bitpack_command[6]; // the command returned to the + // user if unpacking is needed. +}; +#define CTX_MAX_DEVICES 16 +#define CTX_MAX_KEYBINDINGS 256 + +#if CTX_EVENTS + +// include list implementation - since it already is a header+inline online +// implementation? + +typedef struct CtxItemCb { + CtxEventType types; + CtxCb cb; + void* data1; + void* data2; + + void (*finalize) (void *data1, void *data2, void *finalize_data); + void *finalize_data; + +} CtxItemCb; + + +#define CTX_MAX_CBS 128 + +typedef struct CtxItem { + CtxMatrix inv_matrix; /* for event coordinate transforms */ + + /* bounding box */ + float x0; + float y0; + float x1; + float y1; + + void *path; + double path_hash; + + CtxCursor cursor; /* if 0 then UNSET and no cursor change is requested + */ + + CtxEventType types; /* all cb's ored together */ + CtxItemCb cb[CTX_MAX_CBS]; + int cb_count; + int ref_count; +} CtxItem; + + +typedef struct _CtxEvents CtxEvents; +struct _CtxEvents +{ + int frozen; + int fullscreen; + CtxList *grabs; /* could split the grabs per device in the same way, + to make dispatch overhead smaller,. probably + not much to win though. */ + CtxItem *prev[CTX_MAX_DEVICES]; + float pointer_x[CTX_MAX_DEVICES]; + float pointer_y[CTX_MAX_DEVICES]; + unsigned char pointer_down[CTX_MAX_DEVICES]; + CtxEvent drag_event[CTX_MAX_DEVICES]; + CtxList *idles; + CtxList *events; // for ctx_get_event + int ctx_get_event_enabled; + int idle_id; + CtxBinding bindings[CTX_MAX_KEYBINDINGS]; /*< better as list, uses no mem if unused */ + int n_bindings; + int width; + int height; + CtxList *items; + CtxItem *last_item; + CtxModifierState modifier_state; + int tap_delay_min; + int tap_delay_max; + int tap_delay_hold; + double tap_hysteresis; +}; + + +#endif + +typedef struct _CtxEidInfo +{ + char *eid; + int frame; + int width; + int height; +} CtxEidInfo; + +struct _Ctx +{ + CtxImplementation *renderer; + CtxDrawlist drawlist; + int transformation; + CtxBuffer texture[CTX_MAX_TEXTURES]; + Ctx *texture_cache; + CtxList *eid_db; + int rev; + void *backend; + CtxState state; /**/ + int frame; /* used for texture lifetime */ +#if CTX_EVENTS + CtxCursor cursor; + int quit; + int dirty; + CtxEvents events; + int mouse_fd; + int mouse_x; + int mouse_y; +#endif +#if CTX_CURRENT_PATH + CtxDrawlist current_path; // possibly transformed coordinates ! + CtxIterator current_path_iterator; +#endif +}; + + +static void ctx_process (Ctx *ctx, CtxEntry *entry); +CtxBuffer *ctx_buffer_new (int width, int height, + CtxPixelFormat pixel_format); +void ctx_buffer_free (CtxBuffer *buffer); + +void +ctx_state_gradient_clear_stops (CtxState *state); + +static inline void ctx_interpret_style (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_transforms (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_pos (CtxState *state, CtxEntry *entry, void *data); +static inline void ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data); + +struct _CtxInternalFsEntry +{ + char *path; + int length; + char *data; +}; + +struct _CtxPixelFormatInfo +{ + CtxPixelFormat pixel_format; + uint8_t components:4; /* number of components */ + uint8_t bpp; /* bits per pixel - for doing offset computations + along with rowstride found elsewhere, if 0 it indicates + 1/8 */ + uint8_t ebpp; /*effective bytes per pixel - for doing offset + computations, for formats that get converted, the + ebpp of the working space applied */ + uint8_t dither_red_blue; + uint8_t dither_green; + CtxPixelFormat composite_format; + + void (*to_comp) (CtxRasterizer *r, + int x, const void * __restrict__ src, uint8_t * __restrict__ comp, int count); + void (*from_comp) (CtxRasterizer *r, + int x, const uint8_t * __restrict__ comp, void *__restrict__ dst, int count); + void (*apply_coverage) (CtxRasterizer *r, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x, uint8_t *coverage, + int count); + void (*setup) (CtxRasterizer *r); +}; + + +static inline void +_ctx_user_to_device (CtxState *state, float *x, float *y); +static void +_ctx_user_to_device_distance (CtxState *state, float *x, float *y); +static void ctx_state_init (CtxState *state); +static inline void +ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data); +static inline void +ctx_drawlist_deinit (CtxDrawlist *drawlist); + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format); + + +int ctx_utf8_len (const unsigned char first_byte); +const char *ctx_utf8_skip (const char *s, int utf8_length); +int ctx_utf8_strlen (const char *s); +int +ctx_unichar_to_utf8 (uint32_t ch, + uint8_t *dest); + +uint32_t +ctx_utf8_to_unichar (const char *input); + + +typedef struct _CtxHasher CtxHasher; + +typedef struct CtxEdge +{ +#if CTX_ALIGNED_STRUCTS==1 + uint32_t index; +#else + uint16_t index; +#endif + int32_t delta; + int32_t val; /* the center-line intersection */ +} CtxEdge; + +typedef void (*CtxFragment) (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy); + +#define CTX_MAX_GAUSSIAN_KERNEL_DIM 512 + +struct _CtxShapeEntry +{ + uint32_t hash; + uint16_t width; + uint16_t height; + int last_frame; // xxx + uint32_t uses; // instrumented for longer keep-alive + uint8_t data[]; +}; + +typedef struct _CtxShapeEntry CtxShapeEntry; + + +struct _CtxShapeCache +{ + CtxShapeEntry *entries[CTX_SHAPE_CACHE_ENTRIES]; + long size; +}; + +typedef struct _CtxShapeCache CtxShapeCache; + + +struct _CtxRasterizer +{ + CtxImplementation vfuncs; + /* these should be initialized and used as the bounds for rendering into the + buffer as well XXX: not yet in use, and when in use will only be + correct for axis aligned clips - proper rasterization of a clipping path + would be yet another refinement on top. + */ + +#if CTX_ENABLE_SHADOW_BLUR + float kernel[CTX_MAX_GAUSSIAN_KERNEL_DIM]; +#endif + + unsigned int aa; // level of vertical aa + int fast_aa; + int prev_active_edges; + int active_edges; + int pending_edges; + int ending_edges; + int edge_pos; // where we're at in iterating all edges + CtxEdge edges[CTX_MAX_EDGES]; + + int scanline; + int scan_min; + int scan_max; + int col_min; + int col_max; + + CtxDrawlist edge_list; + + CtxState *state; + Ctx *ctx; + Ctx *texture_source; /* normally same as ctx */ + + void *buf; + + +#if CTX_COMPOSITING_GROUPS + void *saved_buf; // when group redirected + CtxBuffer *group[CTX_GROUP_MAX]; +#endif + + + float x; // < redundant? use state instead? + float y; + + float first_x; + float first_y; + unsigned int needs_aa3; // count of how many edges implies antialiasing + unsigned int needs_aa5; // count of how many edges implies antialiasing + unsigned int needs_aa15; // count of how many edges implies antialiasing + int horizontal_edges; + uint8_t *opaque; // fully opaque-scanline + int uses_transforms; + int has_shape:2; + int has_prev:2; + int preserve:1; + + int16_t blit_x; + int16_t blit_y; + int16_t blit_width; + int16_t blit_height; + int16_t blit_stride; + + CtxPixelFormatInfo *format; + +#if CTX_ENABLE_SHADOW_BLUR + int in_shadow; +#endif + int in_text; + int shadow_x; + int shadow_y; + + CtxFragment fragment; + int swap_red_green; + uint8_t color[4*5]; + +#define CTX_COMPOSITE_ARGUMENTS CtxRasterizer *rasterizer, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x0, uint8_t * __restrict__ coverage, int count + + void (*comp_op)(CTX_COMPOSITE_ARGUMENTS); +#if CTX_ENABLE_CLIP + CtxBuffer *clip_buffer; +#endif + + int clip_rectangle; + +#if CTX_SHAPE_CACHE + CtxShapeCache shape_cache; +#endif +#if CTX_BRAILLE_TEXT + int term_glyphs:1; // store appropriate glyphs for redisplay + CtxList *glyphs; +#endif +}; + +struct _CtxSHA1 { + uint64_t length; + uint32_t state[5], curlen; + unsigned char buf[64]; +}; + + +struct _CtxHasher +{ + CtxRasterizer rasterizer; + int cols; + int rows; + uint8_t *hashes; + CtxSHA1 sha1_fill; + CtxSHA1 sha1_stroke; +}; + +#if CTX_RASTERIZER +void ctx_rasterizer_deinit (CtxRasterizer *rasterizer); +#endif + +#if CTX_EVENTS +extern int ctx_native_events; + +#if CTX_SDL +extern int ctx_sdl_events; +int ctx_sdl_consume_events (Ctx *ctx); +#endif + +#if CTX_FB +extern int ctx_fb_events; +int ctx_fb_consume_events (Ctx *ctx); +#endif + + +int ctx_nct_consume_events (Ctx *ctx); +int ctx_ctx_consume_events (Ctx *ctx); + +#endif + +enum { + NC_MOUSE_NONE = 0, + NC_MOUSE_PRESS = 1, /* "mouse-pressed", "mouse-released" */ + NC_MOUSE_DRAG = 2, /* + "mouse-drag" (motion with pressed button) */ + NC_MOUSE_ALL = 3 /* + "mouse-motion" (also delivered for release) */ +}; +void _ctx_mouse (Ctx *term, int mode); +void nc_at_exit (void); + +int ctx_terminal_width (void); +int ctx_terminal_height (void); +int ctx_terminal_cols (void); +int ctx_terminal_rows (void); +extern int ctx_frame_ack; + +int ctx_nct_consume_events (Ctx *ctx); + +typedef struct _CtxCtx CtxCtx; +struct _CtxCtx +{ + void (*render) (void *ctxctx, CtxCommand *command); + void (*reset) (void *ctxvtx); + void (*flush) (void *ctxctx); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *ctxctx); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; +}; + + +extern int _ctx_max_threads; +extern int _ctx_enable_hash_cache; +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len); +const char * +ctx_get (Ctx *ctx, const char *key); + +int ctx_renderer_is_term (Ctx *ctx); +Ctx *ctx_new_ctx (int width, int height); +Ctx *ctx_new_fb (int width, int height, int drm); +Ctx *ctx_new_sdl (int width, int height); +Ctx *ctx_new_term (int width, int height); +Ctx *ctx_new_termimg (int width, int height); + +int ctx_resolve_font (const char *name); +extern float ctx_u8_float[256]; +#define ctx_u8_to_float(val_u8) ctx_u8_float[((uint8_t)(val_u8))] +//#define ctx_u8_to_float(val_u8) (val_u8/255.0f) +// +// + + +static uint8_t ctx_float_to_u8 (float val_f) +{ + return val_f < 0.0f ? 0 : val_f > 1.0f ? 0xff : 0xff * val_f + 0.5f; +#if 0 + int val_i = val_f * 255.999f; + if (val_i < 0) { return 0; } + else if (val_i > 255) { return 255; } + return val_i; +#endif +} + + +#define CTX_CSS_LUMINANCE_RED 0.3f +#define CTX_CSS_LUMINANCE_GREEN 0.59f +#define CTX_CSS_LUMINANCE_BLUE 0.11f + +/* works on both float and uint8_t */ +#define CTX_CSS_RGB_TO_LUMINANCE(rgb) (\ + (rgb[0]) * CTX_CSS_LUMINANCE_RED + \ + (rgb[1]) * CTX_CSS_LUMINANCE_GREEN +\ + (rgb[2]) * CTX_CSS_LUMINANCE_BLUE) + +const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y); +const char *ctx_native_get_event (Ctx *n, int timeoutms); +void +ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out); +void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out); +float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb); +void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out); +void ctx_rgb_to_cmyk (float r, float g, float b, + float *c_out, float *m_out, float *y_out, float *k_out); +uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb); +#if CTX_ENABLE_CMYK +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out); +#endif +static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a); +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out); +static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a); +static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a); +static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha); + +int ctx_color_model_get_components (CtxColorModel model); + +static void ctx_state_set (CtxState *state, uint64_t key, float value); +static void +ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f); +static void ctx_font_setup (); +static float ctx_state_get (CtxState *state, uint64_t hash); + +#if CTX_RASTERIZER + +static void +ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y); + +static void +ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y); +static void +ctx_rasterizer_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2); +static void +ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2); + +static void +ctx_rasterizer_reset (CtxRasterizer *rasterizer); +static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer); +static void +ctx_rasterizer_arc (CtxRasterizer *rasterizer, + float x, + float y, + float radius, + float start_angle, + float end_angle, + int anticlockwise); + +static void +ctx_rasterizer_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y); + +static void +ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y); + +static void +ctx_rasterizer_rectangle (CtxRasterizer *rasterizer, + float x, + float y, + float width, + float height); + +static void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer); +static void ctx_rasterizer_clip (CtxRasterizer *rasterizer); +static void +ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name); + +static void +ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba); +static void +ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer, + uint16_t x, + uint16_t y, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a); +static void +ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius); + +#endif + +#if CTX_ENABLE_CM // XXX to be moved to ctx.h +void +ctx_set_drgb_space (Ctx *ctx, int device_space); +void +ctx_set_dcmyk_space (Ctx *ctx, int device_space); +void +ctx_rgb_space (Ctx *ctx, int device_space); +void +ctx_set_cmyk_space (Ctx *ctx, int device_space); +#endif + +#endif + +CtxRasterizer * +ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias); + + +CTX_INLINE static uint8_t ctx_lerp_u8 (uint8_t v0, uint8_t v1, uint8_t dx) +{ +#if 0 + return v0 + ((v1-v0) * dx)/255; +#else + return ( ( ( ( (v0) <<8) + (dx) * ( (v1) - (v0) ) ) ) >>8); +#endif +} + +CTX_INLINE static uint32_t ctx_lerp_RGBA8 (const uint32_t v0, const uint32_t v1, const uint8_t dx) +{ +#if 0 + char bv0[4]; + char bv1[4]; + char res[4]; + memcpy (&bv0[0], &v0, 4); + memcpy (&bv1[0], &v1, 4); + for (int c = 0; c < 4; c++) + res [c] = ctx_lerp_u8 (bv0[c], bv1[c], dx); + return ((uint32_t*)(&res[0]))[0]; +#else + const uint32_t cov = dx; + const uint32_t si_ga = (v1 & 0xff00ff00) >> 8; + 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 - (di_ga>>8); + return + (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((0xff00ff + d_ga * cov) & 0xff00ff00))); + +#endif +} + +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; + uint32_t d_rb = si_rb - di_rb; + const uint32_t d_ga = si_ga - (di_ga>>8); + return + (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((0xff00ff + d_ga * cov) & 0xff00ff00))); +} + + + +CTX_INLINE static float +ctx_lerpf (float v0, float v1, float dx) +{ + return v0 + (v1-v0) * dx; +} + + +#ifndef CTX_MIN +#define CTX_MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef CTX_MAX +#define CTX_MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +static inline void *ctx_calloc (size_t size, size_t count); + +void ctx_screenshot (Ctx *ctx, const char *output_path); + + +CtxSHA1 *ctx_sha1_new (void); +void ctx_sha1_free (CtxSHA1 *sha1); +int ctx_sha1_process(CtxSHA1 *sha1, const unsigned char * msg, unsigned long len); +int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out); + +void _ctx_texture_lock (void); +void _ctx_texture_unlock (void); +uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry); +void ctx_buffer_pixels_free (void *pixels, void *userdata); + +/*ctx_texture_init: + * return value: eid, as passed in or if NULL generated by hashing pixels and width/height + * XXX this is low-level and not to be used directly use define_texture instead. XXX + */ +const char *ctx_texture_init ( + Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + CtxPixelFormat format, + void *space, + uint8_t *pixels, + void (*freefunc) (void *pixels, void *user_data), + void *user_data); + +#if CTX_TILED +#if !__COSMOPOLITAN__ +#include <threads.h> +#endif +#endif +typedef struct _CtxTiled CtxTiled; + +struct _CtxTiled +{ + void (*render) (void *term, CtxCommand *command); + void (*reset) (void *term); + void (*flush) (void *term); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *term); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + uint8_t *pixels; + Ctx *ctx_copy; + Ctx *host[CTX_MAX_THREADS]; + CtxAntialias antialias; + int quit; +#if CTX_TILED + _Atomic int thread_quit; +#endif + int shown_frame; + int render_frame; + int rendered_frame[CTX_MAX_THREADS]; + int frame; + int min_col; // hasher cols and rows + int min_row; + int max_col; + int max_row; + uint8_t hashes[CTX_HASH_ROWS * CTX_HASH_COLS * 20]; + int8_t tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is + // responsible for a tile + // + + int pointer_down[3]; + + CtxCursor shown_cursor; +#if CTX_TILED + cnd_t cond; + mtx_t mtx; +#endif +}; + +static void +_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer, + CtxBuffer *buffer); + +#endif + + +#if CTX_IMPLEMENTATION + +#define SHA1_IMPLEMENTATION +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + * + * The plain ANSIC sha1 functionality has been extracted from libtomcrypt, + * and is included directly in the sources. /Øyvind K. - since libtomcrypt + * is public domain the adaptations done here to make the sha1 self contained + * also is public domain. + */ +#ifndef __SHA1_H +#define __SHA1_H +#if !__COSMOPOLITAN__ +#include <inttypes.h> +#endif + + +int ctx_sha1_init(CtxSHA1 * sha1); +CtxSHA1 *ctx_sha1_new (void) +{ + CtxSHA1 *state = (CtxSHA1*)calloc (sizeof (CtxSHA1), 1); + ctx_sha1_init (state); + return state; +} +void ctx_sha1_free (CtxSHA1 *sha1) +{ + free (sha1); +} + +#if 0 + CtxSHA1 sha1; + ctx_sha1_init (&sha1); + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); +#endif + +#ifdef SHA1_FF0 +#undef SHA1_FF0 +#endif +#ifdef SHA1_FF1 +#undef SHA1_FF1 +#endif + +#ifdef SHA1_IMPLEMENTATION +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#include <string.h> +#endif + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define STORE32H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ + (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } + +#define LOAD32H(x, y) \ + { x = ((unsigned long)((y)[0] & 255)<<24) | \ + ((unsigned long)((y)[1] & 255)<<16) | \ + ((unsigned long)((y)[2] & 255)<<8) | \ + ((unsigned long)((y)[3] & 255)); } + +/* rotates the hard way */ +#define ROL(x, y) ((((unsigned long)(x)<<(unsigned long)((y)&31)) | (((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) +#define ROLc(x, y) ROL(x,y) + +#define CRYPT_OK 0 +#define CRYPT_ERROR 1 +#define CRYPT_NOP 2 + +#ifndef MAX + #define MAX(x, y) ( ((x)>(y))?(x):(y) ) +#endif +#ifndef MIN + #define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* a simple macro for making hash "process" functions */ +#define HASH_PROCESS(func_name, compress_name, state_var, block_size) \ +int func_name (CtxSHA1 *sha1, const unsigned char *in, unsigned long inlen) \ +{ \ + unsigned long n; \ + int err; \ + assert (sha1 != NULL); \ + assert (in != NULL); \ + if (sha1->curlen > sizeof(sha1->buf)) { \ + return -1; \ + } \ + while (inlen > 0) { \ + if (sha1->curlen == 0 && inlen >= block_size) { \ + if ((err = compress_name (sha1, (unsigned char *)in)) != CRYPT_OK) { \ + return err; \ + } \ + sha1->length += block_size * 8; \ + in += block_size; \ + inlen -= block_size; \ + } else { \ + n = MIN(inlen, (block_size - sha1->curlen)); \ + memcpy(sha1->buf + sha1->curlen, in, (size_t)n); \ + sha1->curlen += n; \ + in += n; \ + inlen -= n; \ + if (sha1->curlen == block_size) { \ + if ((err = compress_name (sha1, sha1->buf)) != CRYPT_OK) { \ + return err; \ + } \ + sha1->length += 8*block_size; \ + sha1->curlen = 0; \ + } \ + } \ + } \ + return CRYPT_OK; \ +} + +/**********************/ + +#define F0(x,y,z) (z ^ (x & (y ^ z))) +#define F1(x,y,z) (x ^ y ^ z) +#define F2(x,y,z) ((x & y) | (z & (x | y))) +#define F3(x,y,z) (x ^ y ^ z) + +static int ctx_sha1_compress(CtxSHA1 *sha1, unsigned char *buf) +{ + uint32_t a,b,c,d,e,W[80],i; + + /* copy the state into 512-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD32H(W[i], buf + (4*i)); + } + + /* copy state */ + a = sha1->state[0]; + b = sha1->state[1]; + c = sha1->state[2]; + d = sha1->state[3]; + e = sha1->state[4]; + + /* expand it */ + for (i = 16; i < 80; i++) { + W[i] = ROL(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); + } + + /* compress */ + /* round one */ + #define SHA1_FF0(a,b,c,d,e,i) e = (ROLc(a, 5) + F0(b,c,d) + e + W[i] + 0x5a827999UL); b = ROLc(b, 30); + #define SHA1_FF1(a,b,c,d,e,i) e = (ROLc(a, 5) + F1(b,c,d) + e + W[i] + 0x6ed9eba1UL); b = ROLc(b, 30); + #define SHA1_FF2(a,b,c,d,e,i) e = (ROLc(a, 5) + F2(b,c,d) + e + W[i] + 0x8f1bbcdcUL); b = ROLc(b, 30); + #define SHA1_FF3(a,b,c,d,e,i) e = (ROLc(a, 5) + F3(b,c,d) + e + W[i] + 0xca62c1d6UL); b = ROLc(b, 30); + + for (i = 0; i < 20; ) { + SHA1_FF0(a,b,c,d,e,i++); + SHA1_FF0(e,a,b,c,d,i++); + SHA1_FF0(d,e,a,b,c,i++); + SHA1_FF0(c,d,e,a,b,i++); + SHA1_FF0(b,c,d,e,a,i++); + } + + /* round two */ + for (; i < 40; ) { + SHA1_FF1(a,b,c,d,e,i++); + SHA1_FF1(e,a,b,c,d,i++); + SHA1_FF1(d,e,a,b,c,i++); + SHA1_FF1(c,d,e,a,b,i++); + SHA1_FF1(b,c,d,e,a,i++); + } + + /* round three */ + for (; i < 60; ) { + SHA1_FF2(a,b,c,d,e,i++); + SHA1_FF2(e,a,b,c,d,i++); + SHA1_FF2(d,e,a,b,c,i++); + SHA1_FF2(c,d,e,a,b,i++); + SHA1_FF2(b,c,d,e,a,i++); + } + + /* round four */ + for (; i < 80; ) { + SHA1_FF3(a,b,c,d,e,i++); + SHA1_FF3(e,a,b,c,d,i++); + SHA1_FF3(d,e,a,b,c,i++); + SHA1_FF3(c,d,e,a,b,i++); + SHA1_FF3(b,c,d,e,a,i++); + } + + #undef SHA1_FF0 + #undef SHA1_FF1 + #undef SHA1_FF2 + #undef SHA1_FF3 + + /* store */ + sha1->state[0] = sha1->state[0] + a; + sha1->state[1] = sha1->state[1] + b; + sha1->state[2] = sha1->state[2] + c; + sha1->state[3] = sha1->state[3] + d; + sha1->state[4] = sha1->state[4] + e; + + return CRYPT_OK; +} + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return CRYPT_OK if successful +*/ +int ctx_sha1_init(CtxSHA1 * sha1) +{ + assert(sha1 != NULL); + sha1->state[0] = 0x67452301UL; + sha1->state[1] = 0xefcdab89UL; + sha1->state[2] = 0x98badcfeUL; + sha1->state[3] = 0x10325476UL; + sha1->state[4] = 0xc3d2e1f0UL; + sha1->curlen = 0; + sha1->length = 0; + return CRYPT_OK; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return CRYPT_OK if successful +*/ +HASH_PROCESS(ctx_sha1_process, ctx_sha1_compress, sha1, 64) + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (20 bytes) + @return CRYPT_OK if successful +*/ +int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out) +{ + int i; + + assert(sha1 != NULL); + assert(out != NULL); + + if (sha1->curlen >= sizeof(sha1->buf)) { + return -1; + } + + /* increase the length of the message */ + sha1->length += sha1->curlen * 8; + + /* append the '1' bit */ + sha1->buf[sha1->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 56 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (sha1->curlen > 56) { + while (sha1->curlen < 64) { + sha1->buf[sha1->curlen++] = (unsigned char)0; + } + ctx_sha1_compress(sha1, sha1->buf); + sha1->curlen = 0; + } + + /* pad upto 56 bytes of zeroes */ + while (sha1->curlen < 56) { + sha1->buf[sha1->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(sha1->length, sha1->buf+56); + ctx_sha1_compress(sha1, sha1->buf); + + /* copy output */ + for (i = 0; i < 5; i++) { + STORE32H(sha1->state[i], out+(4*i)); + } + return CRYPT_OK; +} +#endif + +#endif +#endif +#ifndef CTX_AUDIO_H +#define CTX_AUDIO_H + +#if !__COSMOPOLITAN__ +#include <stdint.h> +#endif + +/* This enum should be kept in sync with the corresponding mmm enum. + */ +typedef enum { + CTX_f32, + CTX_f32S, + CTX_s16, + CTX_s16S +} CtxPCM; + +void ctx_pcm_set_format (Ctx *ctx, CtxPCM format); +CtxPCM ctx_pcm_get_format (Ctx *ctx); +int ctx_pcm_get_sample_rate (Ctx *ctx); +void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate); +int ctx_pcm_get_frame_chunk (Ctx *ctx); +int ctx_pcm_get_queued (Ctx *ctx); +float ctx_pcm_get_queued_length (Ctx *ctx); +int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames); + +#endif + +#if CTX_IMPLEMENTATION +#if CTX_AUDIO + +//#include <string.h> +//#include "ctx-internal.h" +//#include "mmm.h" + +#if !__COSMOPOLITAN__ + +#include <pthread.h> +#if CTX_ALSA_AUDIO +#include <alsa/asoundlib.h> +#endif +#include <alloca.h> + +#endif + +#define DESIRED_PERIOD_SIZE 1000 + +int ctx_pcm_bytes_per_frame (CtxPCM format) +{ + switch (format) + { + case CTX_f32: return 4; + case CTX_f32S: return 8; + case CTX_s16: return 2; + case CTX_s16S: return 4; + default: return 1; + } +} + +static float ctx_host_freq = 48000; +static CtxPCM ctx_host_format = CTX_s16S; +static float client_freq = 48000; +static CtxPCM ctx_client_format = CTX_s16S; +static int ctx_pcm_queued = 0; +static int ctx_pcm_cur_left = 0; +static CtxList *ctx_pcm_list; /* data is a blob a 32bit uint first, followed by pcm-data */ + + +//static long int ctx_pcm_queued_ticks = 0; /* the number of ticks into the future + // * we've queued audio for + + + +int +ctx_pcm_channels (CtxPCM format) +{ + switch (format) + { + case CTX_s16: + case CTX_f32: + return 1; + case CTX_s16S: + case CTX_f32S: + return 2; + } + return 0; +} + +/* todo: only start audio thread on first write - enabling dynamic choice + * of sample-rate? or is it better to keep to opening 48000 as a standard + * and do better internal resampling for others? + */ + +#if CTX_ALSA_AUDIO +static snd_pcm_t *alsa_open (char *dev, int rate, int channels) +{ + snd_pcm_hw_params_t *hwp; + snd_pcm_sw_params_t *swp; + snd_pcm_t *h; + int r; + int dir; + snd_pcm_uframes_t period_size_min; + snd_pcm_uframes_t period_size_max; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + + if ((r = snd_pcm_open(&h, dev, SND_PCM_STREAM_PLAYBACK, 0) < 0)) + return NULL; + + hwp = alloca(snd_pcm_hw_params_sizeof()); + memset(hwp, 0, snd_pcm_hw_params_sizeof()); + snd_pcm_hw_params_any(h, hwp); + + snd_pcm_hw_params_set_access(h, hwp, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(h, hwp, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(h, hwp, rate, 0); + snd_pcm_hw_params_set_channels(h, hwp, channels); + dir = 0; + snd_pcm_hw_params_get_period_size_min(hwp, &period_size_min, &dir); + dir = 0; + snd_pcm_hw_params_get_period_size_max(hwp, &period_size_max, &dir); + + period_size = DESIRED_PERIOD_SIZE; + + dir = 0; + r = snd_pcm_hw_params_set_period_size_near(h, hwp, &period_size, &dir); + r = snd_pcm_hw_params_get_period_size(hwp, &period_size, &dir); + buffer_size = period_size * 4; + r = snd_pcm_hw_params_set_buffer_size_near(h, hwp, &buffer_size); + r = snd_pcm_hw_params(h, hwp); + swp = alloca(snd_pcm_sw_params_sizeof()); + memset(hwp, 0, snd_pcm_sw_params_sizeof()); + snd_pcm_sw_params_current(h, swp); + r = snd_pcm_sw_params_set_avail_min(h, swp, period_size); + snd_pcm_sw_params_set_start_threshold(h, swp, 0); + r = snd_pcm_sw_params(h, swp); + r = snd_pcm_prepare(h); + + return h; +} + +static snd_pcm_t *h = NULL; +static void *ctx_alsa_audio_start(Ctx *ctx) +{ +// Lyd *lyd = aux; + int c; + + /* The audio handler is implemented as a mixer that adds data on top + * of 0s, XXX: it should be ensured that minimal work is there is + * no data available. + */ + for (;;) + { + int client_channels = ctx_pcm_channels (ctx_client_format); + int is_float = 0; + int16_t data[81920*8]={0,}; + + if (ctx_client_format == CTX_f32 || + ctx_client_format == CTX_f32S) + is_float = 1; + + c = snd_pcm_wait(h, 1000); + + if (c >= 0) + c = snd_pcm_avail_update(h); + + if (c > 1000) c = 1000; // should use max mmm buffer sizes + + if (c == -EPIPE) + snd_pcm_prepare(h); + + if (c > 0) + { + int i; + for (i = 0; i < c && ctx_pcm_cur_left; i ++) + { + if (ctx_pcm_cur_left) // XXX this line can be removed + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + uint16_t left = 0, right = 0; + + if (is_float) + { + float *packet = (ctx_pcm_list->data); + packet += 4; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + left = right = packet[0] * (1<<15); + if (client_channels > 1) + right = packet[0] * (1<<15); + } + else // s16 + { + uint16_t *packet = (ctx_pcm_list->data); + packet += 8; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + + left = right = packet[0]; + if (client_channels > 1) + right = packet[1]; + } + data[i * 2 + 0] = left; + data[i * 2 + 1] = right; + + ctx_pcm_cur_left--; + ctx_pcm_queued --; + if (ctx_pcm_cur_left == 0) + { + void *old = ctx_pcm_list->data; + ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data); + free (old); + ctx_pcm_cur_left = 0; + if (ctx_pcm_list) + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + ctx_pcm_cur_left = packet_size; + } + } + } + } + + c = snd_pcm_writei(h, data, c); + if (c < 0) + c = snd_pcm_recover (h, c, 0); + }else{ + if (getenv("LYD_FATAL_UNDERRUNS")) + { + printf ("dying XXxx need to add API for this debug\n"); + //printf ("%i", lyd->active); + exit(0); + } + fprintf (stderr, "ctx alsa underun\n"); + //exit(0); + } + } +} +#endif + +static char MuLawCompressTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +static unsigned char LinearToMuLawSample(int16_t sample) +{ + const int cBias = 0x84; + const int cClip = 32635; + int sign = (sample >> 8) & 0x80; + + if (sign) + sample = (int16_t)-sample; + + if (sample > cClip) + sample = cClip; + + sample = (int16_t)(sample + cBias); + + int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF]; + int mantissa = (sample >> (exponent+3)) & 0x0F; + + int compressedByte = ~ (sign | (exponent << 4) | mantissa); + + return (unsigned char)compressedByte; +} + +void ctx_ctx_pcm (Ctx *ctx) +{ + int client_channels = ctx_pcm_channels (ctx_client_format); + int is_float = 0; + uint8_t data[81920*8]={0,}; + int c; + + if (ctx_client_format == CTX_f32 || + ctx_client_format == CTX_f32S) + is_float = 1; + + c = 2000; + + if (c > 0) + { + int i; + for (i = 0; i < c && ctx_pcm_cur_left; i ++) + { + if (ctx_pcm_cur_left) // XXX this line can be removed + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + int left = 0, right = 0; + + if (is_float) + { + float *packet = (ctx_pcm_list->data); + packet += 4; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + left = right = packet[0] * (1<<15); + if (client_channels > 1) + right = packet[1] * (1<<15); + } + else // s16 + { + uint16_t *packet = (ctx_pcm_list->data); + packet += 8; + packet += (packet_size - ctx_pcm_cur_left) * client_channels; + + left = right = packet[0]; + if (client_channels > 1) + right = packet[1]; + } + data[i] = LinearToMuLawSample((left+right)/2); + + ctx_pcm_cur_left--; + ctx_pcm_queued --; + if (ctx_pcm_cur_left == 0) + { + void *old = ctx_pcm_list->data; + ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data); + free (old); + ctx_pcm_cur_left = 0; + if (ctx_pcm_list) + { + uint32_t *packet_sizep = (ctx_pcm_list->data); + uint32_t packet_size = *packet_sizep; + ctx_pcm_cur_left = packet_size; + } + } + } + } + + char encoded[81920*8]=""; + + int encoded_len = ctx_a85enc (data, encoded, i); + fprintf (stdout, "\033_Af=%i;", i); + fwrite (encoded, 1, encoded_len, stdout); + fwrite ("\e\\", 1, 2, stdout); + fflush (stdout); + } +} + +int ctx_pcm_init (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return 0; + } + else +#endif + if (ctx_renderer_is_ctx (ctx)) + { + ctx_host_freq = 8000; + ctx_host_format = CTX_s16; +#if 0 + pthread_t tid; + pthread_create(&tid, NULL, (void*)ctx_audio_start, ctx); +#endif + } + else + { +#if CTX_ALSA_AUDIO + pthread_t tid; + h = alsa_open("default", ctx_host_freq, ctx_pcm_channels (ctx_host_format)); + if (!h) { + fprintf(stderr, "ctx unable to open ALSA device (%d channels, %f Hz), dying\n", + ctx_pcm_channels (ctx_host_format), ctx_host_freq); + return -1; + } + pthread_create(&tid, NULL, (void*)ctx_alsa_audio_start, ctx); +#endif + } + return 0; +} + +int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames) +{ + static int inited = 0; +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_queue (ctx->backend_data, data, frames); + } + else +#endif + { + if (!inited) + { + ctx_pcm_init (ctx); + inited = 1; + } + float factor = client_freq * 1.0 / ctx_host_freq; + int scaled_frames = frames / factor; + int bpf = ctx_pcm_bytes_per_frame (ctx_client_format); + + uint8_t *packet = malloc (scaled_frames * ctx_pcm_bytes_per_frame (ctx_client_format) + 16); + *((uint32_t *)packet) = scaled_frames; + + if (factor > 0.999 && factor < 1.0001) + { + memcpy (packet + 16, data, frames * bpf); + } + else + { + /* a crude nearest / sample-and hold resampler */ + int i; + for (i = 0; i < scaled_frames; i++) + { + int source_frame = i * factor; + memcpy (packet + 16 + bpf * i, data + source_frame * bpf, bpf); + } + } + if (ctx_pcm_list == NULL) // otherwise it is another frame at front + ctx_pcm_cur_left = scaled_frames; // and current cur_left is valid + + ctx_list_append (&ctx_pcm_list, packet); + ctx_pcm_queued += scaled_frames; + + return frames; + } + return 0; +} + +static int ctx_pcm_get_queued_frames (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_queued_frames (ctx->backend_data); + } +#endif + return ctx_pcm_queued; +} + +int ctx_pcm_get_queued (Ctx *ctx) +{ + return ctx_pcm_get_queued_frames (ctx); +} + +float ctx_pcm_get_queued_length (Ctx *ctx) +{ + return 1.0 * ctx_pcm_get_queued_frames (ctx) / ctx_host_freq; +} + +int ctx_pcm_get_frame_chunk (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_frame_chunk (ctx->backend_data); + } +#endif + if (ctx_renderer_is_ctx (ctx)) + { + // 300 stuttering + // 350 nothing + // 380 slight buzz + // 390 buzzing + // 400 ok - but sometimes falling out + // 410 buzzing + // 420 ok - but odd latency + // 450 buzzing + + if (ctx_pcm_get_queued_frames (ctx) > 400) + return 0; + else + return 400 - ctx_pcm_get_queued_frames (ctx); + + } + + if (ctx_pcm_get_queued_frames (ctx) > 1000) + return 0; + else + return 1000 - ctx_pcm_get_queued_frames (ctx); +} + +void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + mmm_pcm_set_sample_rate (ctx->backend_data, sample_rate); + } + else +#endif + client_freq = sample_rate; +} + +void ctx_pcm_set_format (Ctx *ctx, CtxPCM format) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + mmm_pcm_set_format (ctx->backend_data, format); + } + else +#endif + ctx_client_format = format; +} + +CtxPCM ctx_pcm_get_format (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_format (ctx->backend_data); + } +#endif + return ctx_client_format; +} + +int ctx_pcm_get_sample_rate (Ctx *ctx) +{ +#if 0 + if (!strcmp (ctx->backend->name, "mmm") || + !strcmp (ctx->backend->name, "mmm-client")) + { + return mmm_pcm_get_sample_rate (ctx->backend_data); + } +#endif + return client_freq; +} + +#endif + /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + */ + +#if CTX_FORMATTER + +/* returns the maximum string length including terminating \0 */ +int ctx_a85enc_len (int input_length) +{ + return (input_length / 4 + 1) * 5; +} + +int ctx_a85enc (const void *srcp, char *dst, int count) +{ + const uint8_t *src = (uint8_t*)srcp; + int out_len = 0; + + int padding = 4-(count % 4); + if (padding == 4) padding = 0; + + for (int i = 0; i < (count+3)/4; i ++) + { + uint32_t input = 0; + for (int j = 0; j < 4; j++) + { + input = (input << 8); + if (i*4+j<=count) + input += src[i*4+j]; + } + + int divisor = 85 * 85 * 85 * 85; +#if 0 + if (input == 0) + { + dst[out_len++] = 'z'; + } + /* todo: encode 4 spaces as 'y' */ + else +#endif + { + for (int j = 0; j < 5; j++) + { + dst[out_len++] = ((input / divisor) % 85) + '!'; + divisor /= 85; + } + } + } + out_len -= padding; + dst[out_len]=0; + return out_len; +} +#endif + +#if CTX_PARSER + +int ctx_a85dec (const char *src, char *dst, int count) +{ + int out_len = 0; + uint32_t val = 0; + int k = 0; + int i = 0; + int p = 0; + for (i = 0; i < count; i ++) + { + p = src[i]; + val *= 85; + if (CTX_UNLIKELY(p == '~')) + { + break; + } +#if 0 + else if (p == 'z') + { + for (int j = 0; j < 4; j++) + dst[out_len++] = 0; + k = 0; + } + else if (p == 'y') /* lets support this extension */ + { + for (int j = 0; j < 4; j++) + dst[out_len++] = 32; + k = 0; + } +#endif + else if (CTX_LIKELY(p >= '!' && p <= 'u')) + { + val += p-'!'; + if (CTX_UNLIKELY (k % 5 == 4)) + { + for (int j = 0; j < 4; j++) + { + dst[out_len++] = (val & (0xff << 24)) >> 24; + val <<= 8; + } + val = 0; + } + k++; + } + // we treat all other chars as whitespace + } + if (CTX_LIKELY (p != '~')) + { + val *= 85; + } + k = k % 5; + if (k) + { + val += 84; + for (int j = k; j < 4; j++) + { + val *= 85; + val += 84; + } + + for (int j = 0; j < k-1; j++) + { + dst[out_len++] = (val & (0xff << 24)) >> 24; + val <<= 8; + } + val = 0; + } + dst[out_len] = 0; + return out_len; +} + +#if 1 +int ctx_a85len (const char *src, int count) +{ + int out_len = 0; + int k = 0; + for (int i = 0; i < count; i ++) + { + if (src[i] == '~') + break; + else if (src[i] == 'z') + { + for (int j = 0; j < 4; j++) + out_len++; + k = 0; + } + else if (src[i] >= '!' && src[i] <= 'u') + { + if (k % 5 == 4) + out_len += 4; + k++; + } + // we treat all other chars as whitespace + } + k = k % 5; + if (k) + out_len += k-1; + return out_len; +} +#endif + +#endif +#ifndef THASH_H +#define THASH_H + +#if !__COSMOPOLITAN__ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#endif + +#define THASH_NO_INTERNING // ctx doesn't make use of thash_decode + +#define THASH_ENTER_DIRECT 16 + +#define THASH_SPACE 0 +#define THASH_DEC_OFFSET 29 +#define THASH_INC_OFFSET 30 +#define THASH_ENTER_UTF5 31 +#define THASH_START_OFFSET 'l' +#define THASH_JUMP_OFFSET 27 +#define THASH_MAXLEN 10 + +// todo: better whitespace handling for double version + + +static inline int thash_new_offset (uint32_t unichar) +{ + int offset = unichar % 32; + return unichar - offset + 14; // this gives ~85% compression on test corpus + return unichar; // this gives 88% compression on test corpus +} + +static int thash_is_in_range (uint32_t offset, uint32_t unichar) +{ + if (unichar == 32) + return 1; + if (offset - unichar <= 13 || + unichar - offset <= 14) + return 1; + return 0; +} + +static int thash_is_in_jump_range_dec (uint32_t offset, uint32_t unichar) +{ + return thash_is_in_range (offset - THASH_JUMP_OFFSET, unichar); +} + +static int thash_is_in_jump_range_inc (uint32_t offset, uint32_t unichar) +{ + return thash_is_in_range (offset + THASH_JUMP_OFFSET, unichar); +} + +//uint32_t ctx_utf8_to_unichar (const char *input); +//int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +//int ctx_utf8_len (const unsigned char first_byte); + +static int thash_utf5_length (uint32_t unichar) +{ + int octets = 0; + if (unichar == 0) return 1; + while (unichar) + { octets ++; + unichar /= 16; + } + return octets; +} + +typedef struct EncodeUtf5 { + int is_utf5; + int offset; + int length; + void *write_data; + uint32_t current; +} EncodeUtf5; + +void thash_encode_utf5 (const char *input, int inlen, + char *output, int *r_outlen) +{ + uint32_t offset = THASH_START_OFFSET; + + int is_utf5 = 1; + int len = 0; + + for (int i = 0; i < inlen; i+= ctx_utf8_len (input[i])) + { + int val = ctx_utf8_to_unichar(&input[i]); + int next_val = ' '; // always in range + int next_next_val = ' '; + int first_len = ctx_utf8_len (input[i]); + if (i + first_len < inlen) + { + int next_len = ctx_utf8_to_unichar (&input[i + first_len]); + if (i + first_len + next_len < inlen) + { + next_next_val = ctx_utf8_to_unichar (&input[i + first_len + next_len]); + } + } + + if (is_utf5) + { + int in_range = + thash_is_in_range (offset, val) + + thash_is_in_range (offset, next_val) + + thash_is_in_range (offset, next_next_val); + int change_cost = 4; + int no_change_cost = thash_utf5_length (val) + thash_utf5_length (next_val) + + thash_utf5_length (next_next_val); + + if (in_range > 2 && change_cost < no_change_cost) + { + output[len++] = THASH_ENTER_DIRECT; + is_utf5 = 0; + } + } else + { + if (!thash_is_in_range(offset, val)) + { + if (thash_is_in_jump_range_dec (offset, val)) + { + output[len++] = THASH_DEC_OFFSET; + offset -= THASH_JUMP_OFFSET; + } + else if (thash_is_in_jump_range_inc (offset, val)) + { + output[len++] = THASH_INC_OFFSET; + offset += THASH_JUMP_OFFSET; + } + else + { + output[len++] = THASH_ENTER_UTF5; + is_utf5 = 1; + } + } + } + + if (is_utf5) + { + int octets = 0; + offset = thash_new_offset (val); + while (val) + { + int oval = val % 16; + int last = 0; + if (val / 32 == 0) last = 16; + output[len+ (octets++)] = oval + last; + val /= 16; + } + for (int j = 0; j < octets/2; j++) // mirror in-place + { + int tmp = output[len+j]; + output[len+j] = output[len+octets-1-j]; + output[len+octets-1-j] = tmp; + } + len += octets; + } + else + { + if (val == 32) + { + output[len++] = THASH_SPACE; + } + else + { + output[len++]= val-offset+14; + } + } + } + if (len && output[len-1]==0) + output[len++] = 16; + output[len]=0; + *r_outlen = len; +} + +uint64_t _thash (const char *utf8) +{ + char encoded[4096]=""; + int encoded_len=0; + int wordlen = 0; + thash_encode_utf5 (utf8, strlen (utf8), encoded, &encoded_len); +#if 0 + Word word = {0}; + word.utf5 = (encoded[0] != THASH_ENTER_DIRECT); + for (int i = !word.utf5; i < encoded_len; i++) + word_set_val (&word, wordlen++, encoded[i]); + return word.hash; +#else + uint64_t hash = 0; + int utf5 = (encoded[0] != THASH_ENTER_DIRECT); + for (int i = !utf5; i < encoded_len; i++) + { + uint64_t val = encoded[i]; + + if (wordlen < THASH_MAXLEN) + { + hash = hash | (val << (5*wordlen)); + hash &= (((uint64_t)1<<52)-1); + } + else + { + hash = hash ^ ((hash << 4) + val); + hash &= (((uint64_t)1<<52)-1); + } + wordlen++; + } + hash <<= 1; + if (wordlen >= THASH_MAXLEN) + hash |= ((uint64_t)1<<51); // overflowed + return hash | utf5; +#endif +} + +typedef struct _Interned Interned; + +struct _Interned { + uint64_t hash; + char *string; +}; + +static Interned *interned = NULL; +static int n_interned = 0; +static int s_interned = 0; +static int interned_sorted = 1; + +static int interned_compare (const void *a, const void *b) +{ + const Interned *ia = (Interned*)a; + const Interned *ib = (Interned*)b; + if (ia->hash < ib->hash ) return -1; + else if (ia->hash > ib->hash ) return 1; + return 0; +} + + +uint64_t thash (const char *utf8) +{ + uint64_t hash = _thash (utf8); +#ifdef THASH_NO_INTERNING + return hash; +#endif + if (hash & ((uint64_t)1<<51)) /* overflowed */ + { + int i; + for (i = 0; i < n_interned; i++) + { + Interned *entry = &interned[i]; + if (entry->hash == hash) + return hash; + } + + if (n_interned + 1 >= s_interned) + { + s_interned = (s_interned + 128)*1.5; + //fprintf (stderr, "\r%p %i ", interned, s_interned); + interned = (Interned*)realloc (interned, s_interned * sizeof (Interned)); + } + + { + Interned *entry = &interned[n_interned]; + entry->hash = hash; + entry->string = strdup (utf8); + } + n_interned++; + interned_sorted = 0; + } + return hash; +} +uint64_t ctx_strhash(const char *str, int ignored) { return thash (str);} + +typedef struct ThashUtf5Dec { + int is_utf5; + int offset; + void *write_data; + uint32_t current; + void (*append_unichar) (uint32_t unichar, void *write_data); +} ThashUtf5Dec; + +typedef struct ThashUtf5DecDefaultData { + uint8_t *buf; + int length; +} ThashUtf5DecDefaultData; + +static void thash_decode_utf5_append_unichar_as_utf8 (uint32_t unichar, void *write_data) +{ + ThashUtf5DecDefaultData *data = (ThashUtf5DecDefaultData*)write_data; + unsigned char utf8[8]=""; + utf8[ctx_unichar_to_utf8 (unichar, utf8)]=0; + for (int j = 0; utf8[j]; j++) + data->buf[data->length++]=utf8[j]; + data->buf[data->length]=0; +} + +void thash_decode_utf5 (ThashUtf5Dec *dec, uint8_t in) +{ + if (dec->is_utf5) + { + if (in > 16) + { + if (dec->current) + { + dec->offset = thash_new_offset (dec->current); + dec->append_unichar (dec->current, dec->write_data); + dec->current = 0; + } + } + if (in == THASH_ENTER_DIRECT) + { + if (dec->current) + { + dec->offset = thash_new_offset (dec->current); + dec->append_unichar (dec->current, dec->write_data); + dec->current = 0; + } + dec->is_utf5 = 0; + } + else + { + dec->current = dec->current * 16 + (in % 16); + } + } + else + { + switch (in) + { + case THASH_ENTER_UTF5: dec->is_utf5 = 1; break; + case THASH_SPACE: dec->append_unichar (' ', dec->write_data); break; + case THASH_DEC_OFFSET: dec->offset -= THASH_JUMP_OFFSET; break; + case THASH_INC_OFFSET: dec->offset += THASH_JUMP_OFFSET; break; + default: + dec->append_unichar (dec->offset + in - 14, dec->write_data); + } + } +} + +void thash_decode_utf5_bytes (int is_utf5, + const unsigned char *input, int inlen, + char *output, int *r_outlen) +{ + ThashUtf5DecDefaultData append_data= {(unsigned char*)output, }; + ThashUtf5Dec dec = {is_utf5, + THASH_START_OFFSET, + &append_data, + 0, thash_decode_utf5_append_unichar_as_utf8 + }; + for (int i = 0; i < inlen; i++) + { + thash_decode_utf5 (&dec, input[i]); + } + if (dec.current) + dec.append_unichar (dec.current, dec.write_data); + if (r_outlen)*r_outlen = append_data.length; +} + +const char *thash_decode (uint64_t hash) +{ + if (!interned_sorted && interned) + { + qsort (interned, n_interned, sizeof (Interned), interned_compare); + interned_sorted = 1; + } + if (hash & ((uint64_t)1<<51)) + { + + for (int i = 0; i < n_interned; i++) + { + Interned *entry = &interned[i]; + if (entry->hash == hash) + return entry->string; + } + return "[missing string]"; + } + + static char ret[4096]=""; + uint8_t utf5[40]=""; + uint64_t tmp = hash & (((uint64_t)1<<51)-1); + int len = 0; + int is_utf5 = tmp & 1; + tmp /= 2; + int in_utf5 = is_utf5; + while (tmp > 0) + { + uint64_t remnant = tmp % 32; + uint64_t val = remnant; + + if ( in_utf5 && val == THASH_ENTER_DIRECT) in_utf5 = 0; + else if (!in_utf5 && val == THASH_ENTER_UTF5) in_utf5 = 1; + + utf5[len++] = val; + tmp -= remnant; + tmp /= 32; + } + if (in_utf5 && len && utf5[len-1] > 'G') + { + utf5[len++] = 0;//utf5_alphabet[0]; + } + utf5[len]=0; + int retlen = sizeof (ret); + thash_decode_utf5_bytes (is_utf5, utf5, len, ret, &retlen); + ret[len]=0; + return ret; +} + +#if 0 + +#include <assert.h> +#pragma pack(push,1) +typedef union Word +{ + uint64_t hash; + struct { + unsigned int utf5:1; + unsigned int c0:5; + unsigned int c1:5; + unsigned int c2:5; + unsigned int c3:5; + unsigned int c4:5; + unsigned int c5:5; + unsigned int c6:5; + unsigned int c7:5; + unsigned int c8:5; + unsigned int c9:5; + unsigned int overflowed:1; + }; +} Word; + +static inline void word_set_val (Word *word, int no, int val) +{ +#if 0 + switch(no) + { + case 0: word->c0 = val; break; + case 1: word->c1 = val; break; + case 2: word->c2 = val; break; + case 3: word->c3 = val; break; + case 4: word->c4 = val; break; + case 5: word->c5 = val; break; + case 6: word->c6 = val; break; + case 7: word->c7 = val; break; + case 8: word->c8 = val; break; + case 9: word->c9 = val; break; + default: + // for overflow only works when setting all in sequence + word->hash = word->hash + ((uint64_t)(val) << (5*no+1)); + word->overflowed = 1; + break; + } +#else + word->hash = word->hash | ((uint64_t)(val) << (5*no+1)); + if (no >= 9) + word->hash |= ((uint64_t)1<<51); + word->hash &= (((uint64_t)1<<52)-1); +#endif +} + +static inline int word_get_val (Word *word, int no) +{ + switch(no) + { + case 0: return word->c0;break; + case 1: return word->c1;break; + case 2: return word->c2;break; + case 3: return word->c3;break; + case 4: return word->c4;break; + case 5: return word->c5;break; + case 6: return word->c6;break; + case 7: return word->c7;break; + case 8: return word->c8;break; + case 9: return word->c9;break; + } +} + +static inline int word_get_length (Word *word) +{ + int len = 0; + if (word->c0) len ++; else return len; + if (word->c1) len ++; else return len; + if (word->c2) len ++; else return len; + if (word->c3) len ++; else return len; + if (word->c4) len ++; else return len; + if (word->c5) len ++; else return len; + if (word->c6) len ++; else return len; + if (word->c7) len ++; else return len; + if (word->c8) len ++; else return len; + if (word->c9) len ++; else return len; + return len; +} + + +static Word *word_append_unichar (Word *word, uint32_t unichar) +{ + //word_set_char (word, word_get_length (word), unichar); + // append unichar - possibly advancing. + return word; +} +#endif + +#endif +/* atty - audio interface and driver for terminals + * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +static const char *base64_map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +static void bin2base64_group (const unsigned char *in, int remaining, char *out) +{ + unsigned char digit[4] = {0,0,64,64}; + int i; + digit[0] = in[0] >> 2; + digit[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4); + if (remaining > 1) + { + digit[2] = ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6); + if (remaining > 2) + digit[3] = ((in[2] & 0x3f)); + } + for (i = 0; i < 4; i++) + out[i] = base64_map[digit[i]]; +} + +void +ctx_bin2base64 (const void *bin, + int bin_length, + char *ascii) +{ + /* this allocation is a hack to ensure we always produce the same result, + * regardless of padding data accidentally taken into account. + */ + unsigned char *bin2 = (unsigned char*)calloc (bin_length + 4, 1); + unsigned const char *p = bin2; + int i; + memcpy (bin2, bin, bin_length); + for (i=0; i*3 < bin_length; i++) + { + int remaining = bin_length - i*3; + bin2base64_group (&p[i*3], remaining, &ascii[i*4]); + } + free (bin2); + ascii[i*4]=0; +} + +static unsigned char base64_revmap[255]; +static void base64_revmap_init (void) +{ + static int done = 0; + if (done) + return; + + for (int i = 0; i < 255; i ++) + base64_revmap[i]=255; + for (int i = 0; i < 64; i ++) + base64_revmap[((const unsigned char*)base64_map)[i]]=i; + /* include variants used in URI encodings for decoder, + * even if that is not how we encode + */ + base64_revmap['-']=62; + base64_revmap['_']=63; + base64_revmap['+']=62; + base64_revmap['/']=63; + + done = 1; +} + + +int +ctx_base642bin (const char *ascii, + int *length, + unsigned char *bin) +{ + int i; + int charno = 0; + int outputno = 0; + int carry = 0; + base64_revmap_init (); + for (i = 0; ascii[i]; i++) + { + int bits = base64_revmap[((const unsigned char*)ascii)[i]]; + if (length && outputno > *length) + { + *length = -1; + return -1; + } + if (bits != 255) + { + switch (charno % 4) + { + case 0: + carry = bits; + break; + case 1: + bin[outputno] = (carry << 2) | (bits >> 4); + outputno++; + carry = bits & 15; + break; + case 2: + bin[outputno] = (carry << 4) | (bits >> 2); + outputno++; + carry = bits & 3; + break; + case 3: + bin[outputno] = (carry << 6) | bits; + outputno++; + carry = 0; + break; + } + charno++; + } + } + bin[outputno]=0; + if (length) + *length= outputno; + return outputno; +} +#include <stdio.h> +#include <string.h> + +static int ctx_yenc (const char *src, char *dst, int count) +{ + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = (src[i] + 42) % 256; + switch (o) + { + case 0x00: //null + case 0x20: //space// but better safe + case 0x0A: //lf // than sorry + case 0x0D: //cr + case 0x09: //tab // not really needed + case 0x10: //datalink escape (used by ctx) + case 0x11: //xoff + case 0x13: //xon + case 0x1b: // + case 0xff: // + case 0x3D: //= + dst[out_len++] = '='; + o = (o + 64) % 256; + /* FALLTHROUGH */ + default: + dst[out_len++] = o; + break; + } + } + dst[out_len]=0; + return out_len; +} + +static int ctx_ydec (const char *tmp_src, char *dst, int count) +{ + const char *src = tmp_src; +#if 0 + if (tmp_src == dst) + { + src = malloc (count); + memcpy (src, tmp_src, count); + } +#endif + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = src[i]; + switch (o) + { + case '=': + i++; + o = src[i]; + if (o == 'y') + { + dst[out_len]=0; +#if 0 + if (tmp_src == dst) free (src); +#endif + return out_len; + } + o = (o-42-64) % 256; + dst[out_len++] = o; + break; + case '\n': + case '\e': + case '\r': + case '\0': + break; + default: + o = (o-42) % 256; + dst[out_len++] = o; + break; + } + } + dst[out_len]=0; +#if 0 + if (tmp_src == dst) free (src); +#endif + return out_len; +} + +#if 0 +int main (){ + char *input="this is a testæøåÅØ'''\"!:_asdac\n\r"; + char encoded[256]=""; + char decoded[256]=""; + int in_len = strlen (input); + int out_len; + int dec_len; + + printf ("input: %s\n", input); + + out_len = ctx_yenc (input, encoded, in_len); + printf ("encoded: %s\n", encoded); + + dec_len = ydec (encoded, encoded, out_len); + + printf ("decoded: %s\n", encoded); + + return 0; +} +#endif +#ifndef CTX_DRAWLIST_H +#define CTX_DRAWLIST_H + +static int +ctx_conts_for_entry (CtxEntry *entry); +static void +ctx_iterator_init (CtxIterator *iterator, + CtxDrawlist *drawlist, + int start_pos, + int flags); + +int ctx_iterator_pos (CtxIterator *iterator); + +static void +ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size); +static int +ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry); +static int ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry); +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry); +int +ctx_add_data (Ctx *ctx, void *data, int length); + +int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2]); +int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length); + +static CtxEntry +ctx_void (CtxCode code); +static inline CtxEntry +ctx_f (CtxCode code, float x, float y); +static CtxEntry +ctx_u32 (CtxCode code, uint32_t x, uint32_t y); +#if 0 +static CtxEntry +ctx_s32 (CtxCode code, int32_t x, int32_t y); +#endif + +static inline CtxEntry +ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1); +static CtxEntry +ctx_u8 (CtxCode code, + uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h); + +#define CTX_PROCESS_VOID(cmd) do {\ + CtxEntry command = {cmd};\ + ctx_process (ctx, &command);}while(0) \ + +#define CTX_PROCESS_F(cmd, x, y) do {\ + CtxEntry command = ctx_f(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_F1(cmd, x) do {\ + CtxEntry command = ctx_f(cmd, x, 0);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U32(cmd, x, y) do {\ + CtxEntry command = ctx_u32(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U8(cmd, x) do {\ + CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\ + ctx_process (ctx, &command);}while(0) + + +#if CTX_BITPACK_PACKER +static int +ctx_last_history (CtxDrawlist *drawlist); +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos); + +static void +ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos); +#endif + +static void +ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1); +static void +ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1); +static void +ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len); + +typedef struct /* it has the same structure as CtxEntry, but should be given better names, + now that it is refactored to be a multiple of 4 bytes + */ +CtxSegment { + uint32_t code; + union { + int16_t s16[4]; + uint32_t u32[2]; + } data; +} CtxSegment; + +#endif + +#ifndef __CTX_UTIL_H +#define __CTX_UTIL_H + +inline static float ctx_fast_hypotf (float x, float y) +{ + if (x < 0) { x = -x; } + if (y < 0) { y = -y; } + if (x < y) + { return 0.96f * y + 0.4f * x; } + else + { return 0.96f * x + 0.4f * y; } +} + +static int ctx_str_is_number (const char *str) +{ + int got_digit = 0; + for (int i = 0; str[i]; i++) + { + if (str[i] >= '0' && str[i] <= '9') + { + got_digit ++; + } + else if (str[i] == '.') + { + } + else + return 0; + } + if (got_digit) + return 1; + return 0; +} + +#if CTX_FONTS_FROM_FILE + +typedef struct CtxFileContent +{ + char *path; + unsigned char *contents; + long length; + int free_data; +} CtxFileContent; + +CtxList *registered_contents = NULL; + +void +ctx_register_contents (const char *path, + const unsigned char *contents, + long length, + int free_data) +{ + // if (path[0] != '/') && strchr(path, ':')) + // with this check regular use is faster, but we lose + // generic filesystem overrides.. + for (CtxList *l = registered_contents; l; l = l->next) + { + CtxFileContent *c = (CtxFileContent*)l->data; + if (!strcmp (c->path, path)) + { + if (c->free_data) + { + free (c->contents); + } + c->free_data = free_data; + c->contents = (unsigned char*)contents; + c->length = length; + return; + } + } + CtxFileContent *c = (CtxFileContent*)calloc (sizeof (CtxFileContent), 1); + c->free_data = free_data; + c->contents = (unsigned char*)contents; + c->length = length; + ctx_list_append (®istered_contents, c); +} + +void +_ctx_file_set_contents (const char *path, + const unsigned char *contents, + long length) +{ + FILE *file; + file = fopen (path, "wb"); + if (!file) + { return; } + if (length < 0) length = strlen ((const char*)contents); + fwrite (contents, 1, length, file); + fclose (file); +} + +static int +__ctx_file_get_contents (const char *path, + unsigned char **contents, + long *length) +{ + FILE *file; + long size; + long remaining; + char *buffer; + file = fopen (path, "rb"); + if (!file) + { return -1; } + fseek (file, 0, SEEK_END); + size = remaining = ftell (file); + if (length) + { *length =size; } + rewind (file); + buffer = (char*)malloc (size + 8); + if (!buffer) + { + fclose (file); + return -1; + } + remaining -= fread (buffer, 1, remaining, file); + if (remaining) + { + fclose (file); + free (buffer); + return -1; + } + fclose (file); + *contents = (unsigned char*) buffer; + buffer[size] = 0; + return 0; +} + +#if !__COSMOPOLITAN__ +#include <limits.h> +#endif + + + + +#endif + + +#endif + + + +static inline int +ctx_conts_for_entry (CtxEntry *entry) +{ + switch (entry->code) + { + case CTX_DATA: + return entry->data.u32[1]; + case CTX_LINEAR_GRADIENT: + //case CTX_DEFINE_TEXTURE: + return 1; + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_SOURCE_TRANSFORM: + case CTX_COLOR: + case CTX_ROUND_RECTANGLE: + case CTX_SHADOW_COLOR: + return 2; + case CTX_FILL_RECT: + case CTX_STROKE_RECT: + case CTX_RECTANGLE: + case CTX_VIEW_BOX: + case CTX_REL_QUAD_TO: + case CTX_QUAD_TO: + return 1; + + case CTX_TEXT: + case CTX_LINE_DASH: + case CTX_COLOR_SPACE: + case CTX_STROKE_TEXT: + case CTX_FONT: + case CTX_TEXTURE: + { + int eid_len = entry[1].data.u32[1]; + return eid_len + 1; + } + case CTX_DEFINE_TEXTURE: + { + int eid_len = entry[2].data.u32[1]; + int pix_len = entry[2 + eid_len + 1].data.u32[1]; + return eid_len + pix_len + 2 + 1; + } + default: + return 0; + } +} + +// expanding arc_to to arc can be the job +// of a layer in front of renderer? +// doing: +// rectangle +// arc +// ... etc reduction to beziers +// or even do the reduction to +// polylines directly here... +// making the rasterizer able to +// only do poly-lines? will that be faster? + +/* the iterator - should decode bitpacked data as well - + * making the rasterizers simpler, possibly do unpacking + * all the way to absolute coordinates.. unless mixed + * relative/not are wanted. + */ + + +static void +ctx_iterator_init (CtxIterator *iterator, + CtxDrawlist *drawlist, + int start_pos, + int flags) +{ + iterator->drawlist = drawlist; + iterator->flags = flags; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 0; + iterator->pos = start_pos; + iterator->end_pos = drawlist->count; + iterator->first_run = 1; // -1 is a marker used for first run + ctx_memset (iterator->bitpack_command, 0, sizeof (iterator->bitpack_command) ); +} + +int ctx_iterator_pos (CtxIterator *iterator) +{ + return iterator->pos; +} + +static CtxEntry *_ctx_iterator_next (CtxIterator *iterator) +{ + int ret = iterator->pos; + CtxEntry *entry = &iterator->drawlist->entries[ret]; + if (CTX_UNLIKELY(ret >= iterator->end_pos)) + { return NULL; } + + if (CTX_UNLIKELY(iterator->first_run)) + iterator->first_run = 0; + else + iterator->pos += (ctx_conts_for_entry (entry) + 1); + + if (CTX_UNLIKELY(iterator->pos >= iterator->end_pos)) + { return NULL; } + return &iterator->drawlist->entries[iterator->pos]; +} + +// 6024x4008 +#if CTX_BITPACK +static void +ctx_iterator_expand_s8_args (CtxIterator *iterator, CtxEntry *entry) +{ + int no = 0; + for (int cno = 0; cno < 4; cno++) + for (int d = 0; d < 2; d++, no++) + iterator->bitpack_command[cno].data.f[d] = + entry->data.s8[no] * 1.0f / CTX_SUBDIV; + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = + iterator->bitpack_command[3].code = CTX_CONT; + iterator->bitpack_length = 4; + iterator->bitpack_pos = 0; +} + +static void +ctx_iterator_expand_s16_args (CtxIterator *iterator, CtxEntry *entry) +{ + int no = 0; + for (int cno = 0; cno < 2; cno++) + for (int d = 0; d < 2; d++, no++) + iterator->bitpack_command[cno].data.f[d] = entry->data.s16[no] * 1.0f / + CTX_SUBDIV; + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = CTX_CONT; + iterator->bitpack_length = 2; + iterator->bitpack_pos = 0; +} +#endif + +CtxCommand * +ctx_iterator_next (CtxIterator *iterator) +{ + CtxEntry *ret; +#if CTX_BITPACK + int expand_bitpack = iterator->flags & CTX_ITERATOR_EXPAND_BITPACK; +again: + if (CTX_UNLIKELY(iterator->bitpack_length)) + { + ret = &iterator->bitpack_command[iterator->bitpack_pos]; + iterator->bitpack_pos += (ctx_conts_for_entry (ret) + 1); + if (iterator->bitpack_pos >= iterator->bitpack_length) + { + iterator->bitpack_length = 0; + } + return (CtxCommand *) ret; + } +#endif + ret = _ctx_iterator_next (iterator); +#if CTX_BITPACK + if (CTX_UNLIKELY(ret && expand_bitpack)) + switch ((CtxCode)(ret->code)) + { + case CTX_REL_CURVE_TO_REL_LINE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_CURVE_TO; + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = CTX_CONT; + iterator->bitpack_command[3].code = CTX_REL_LINE_TO; + // 0.0 here is a common optimization - so check for it + if (ret->data.s8[6]== 0 && ret->data.s8[7] == 0) + { iterator->bitpack_length = 3; } + else + iterator->bitpack_length = 4; + goto again; + case CTX_REL_LINE_TO_REL_CURVE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_LINE_TO; + iterator->bitpack_command[1].code = CTX_REL_CURVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_REL_CURVE_TO_REL_MOVE_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_CURVE_TO; + iterator->bitpack_command[3].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 4; + goto again; + case CTX_REL_LINE_TO_X4: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = + iterator->bitpack_command[2].code = + iterator->bitpack_command[3].code = CTX_REL_LINE_TO; + iterator->bitpack_length = 4; + goto again; + case CTX_REL_QUAD_TO_S16: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_QUAD_TO; + iterator->bitpack_length = 1; + goto again; + case CTX_REL_QUAD_TO_REL_QUAD_TO: + ctx_iterator_expand_s8_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[2].code = CTX_REL_QUAD_TO; + iterator->bitpack_length = 3; + goto again; + case CTX_REL_LINE_TO_X2: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = + iterator->bitpack_command[1].code = CTX_REL_LINE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_REL_LINE_TO_REL_MOVE_TO: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_REL_LINE_TO; + iterator->bitpack_command[1].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_MOVE_TO_REL_LINE_TO: + ctx_iterator_expand_s16_args (iterator, ret); + iterator->bitpack_command[0].code = CTX_MOVE_TO; + iterator->bitpack_command[1].code = CTX_REL_MOVE_TO; + iterator->bitpack_length = 2; + goto again; + case CTX_FILL_MOVE_TO: + iterator->bitpack_command[1] = *ret; + iterator->bitpack_command[0].code = CTX_FILL; + iterator->bitpack_command[1].code = CTX_MOVE_TO; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 2; + goto again; + case CTX_LINEAR_GRADIENT: + case CTX_QUAD_TO: + case CTX_REL_QUAD_TO: + case CTX_TEXTURE: + case CTX_RECTANGLE: + case CTX_VIEW_BOX: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_COLOR: + case CTX_SHADOW_COLOR: + case CTX_RADIAL_GRADIENT: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_SOURCE_TRANSFORM: + case CTX_ROUND_RECTANGLE: + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_FONT: + case CTX_LINE_DASH: + case CTX_FILL: + case CTX_NOP: + case CTX_MOVE_TO: + case CTX_LINE_TO: + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + case CTX_VER_LINE_TO: + case CTX_REL_VER_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_REL_HOR_LINE_TO: + case CTX_ROTATE: + case CTX_FLUSH: + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_MITER_LIMIT: + case CTX_GLOBAL_ALPHA: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_RESET: + case CTX_EXIT: + case CTX_BEGIN_PATH: + case CTX_CLOSE_PATH: + case CTX_SAVE: + case CTX_CLIP: + case CTX_PRESERVE: + case CTX_DEFINE_GLYPH: + case CTX_IDENTITY: + case CTX_FONT_SIZE: + case CTX_START_GROUP: + case CTX_END_GROUP: + case CTX_RESTORE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_STROKE: + case CTX_KERNING_PAIR: + case CTX_SCALE: + case CTX_GLYPH: + case CTX_SET_PIXEL: + case CTX_FILL_RULE: + case CTX_LINE_CAP: + case CTX_LINE_JOIN: + case CTX_NEW_PAGE: + case CTX_SET_KEY: + case CTX_TRANSLATE: + case CTX_DEFINE_TEXTURE: + case CTX_GRADIENT_STOP: + case CTX_DATA: // XXX : would be better if we hide the DATAs + case CTX_CONT: // shouldnt happen + default: + iterator->bitpack_length = 0; + return (CtxCommand *) ret; +#if 0 + default: // XXX remove - and get better warnings + iterator->bitpack_command[0] = ret[0]; + iterator->bitpack_command[1] = ret[1]; + iterator->bitpack_command[2] = ret[2]; + iterator->bitpack_command[3] = ret[3]; + iterator->bitpack_command[4] = ret[4]; + iterator->bitpack_pos = 0; + iterator->bitpack_length = 1; + goto again; +#endif + } +#endif + return (CtxCommand *) ret; +} + +static void ctx_drawlist_compact (CtxDrawlist *drawlist); +static void +ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size) +{ + int flags=drawlist->flags; +#if CTX_DRAWLIST_STATIC + if (flags & CTX_DRAWLIST_EDGE_LIST) + { + static CtxSegment sbuf[CTX_MAX_EDGE_LIST_SIZE]; + drawlist->entries = (CtxEntry*)&sbuf[0]; + drawlist->size = CTX_MAX_EDGE_LIST_SIZE; + } + else if (flags & CTX_DRAWLIST_CURRENT_PATH) + { + static CtxEntry sbuf[CTX_MAX_EDGE_LIST_SIZE]; + drawlist->entries = &sbuf[0]; + drawlist->size = CTX_MAX_EDGE_LIST_SIZE; + } + else + { + static CtxEntry sbuf[CTX_MAX_JOURNAL_SIZE]; + drawlist->entries = &sbuf[0]; + drawlist->size = CTX_MAX_JOURNAL_SIZE; + ctx_drawlist_compact (drawlist); + } +#else + int new_size = desired_size; + int min_size = CTX_MIN_JOURNAL_SIZE; + int max_size = CTX_MAX_JOURNAL_SIZE; + if ((flags & CTX_DRAWLIST_EDGE_LIST)) + { + min_size = CTX_MIN_EDGE_LIST_SIZE; + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + else if (flags & CTX_DRAWLIST_CURRENT_PATH) + { + min_size = CTX_MIN_EDGE_LIST_SIZE; + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + else + { +#if 0 + ctx_drawlist_compact (drawlist); +#endif + } + + if (CTX_UNLIKELY(new_size < drawlist->size)) + { return; } + if (CTX_UNLIKELY(drawlist->size == max_size)) + { return; } + new_size = ctx_maxi (new_size, min_size); + //if (new_size < drawlist->count) + // { new_size = drawlist->count + 4; } + new_size = ctx_mini (new_size, max_size); + if (new_size != drawlist->size) + { + int item_size = sizeof (CtxEntry); + if (flags & CTX_DRAWLIST_EDGE_LIST) item_size = sizeof (CtxSegment); + //fprintf (stderr, "growing drawlist %p %i to %d from %d\n", drawlist, flags, new_size, drawlist->size); + if (drawlist->entries) + { + //printf ("grow %p to %d from %d\n", drawlist, new_size, drawlist->size); + CtxEntry *ne = (CtxEntry *) malloc (item_size * new_size); + memcpy (ne, drawlist->entries, drawlist->size * item_size ); + free (drawlist->entries); + drawlist->entries = ne; + //drawlist->entries = (CtxEntry*)malloc (drawlist->entries, item_size * new_size); + } + else + { + //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size); + drawlist->entries = (CtxEntry *) malloc (item_size * new_size); + } + drawlist->size = new_size; + } + //fprintf (stderr, "drawlist %p is %d\n", drawlist, drawlist->size); +#endif +} + +static inline int +ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry) +{ + int max_size = CTX_MAX_JOURNAL_SIZE; + int ret = drawlist->count; + int flags = drawlist->flags; + if (CTX_LIKELY((flags & CTX_DRAWLIST_EDGE_LIST || + flags & CTX_DRAWLIST_CURRENT_PATH))) + { + max_size = CTX_MAX_EDGE_LIST_SIZE; + } + if (CTX_UNLIKELY(flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES)) + { + return ret; + } + if (CTX_UNLIKELY(ret + 64 >= drawlist->size - 40)) + { + int new_ = CTX_MAX (drawlist->size * 2, ret + 1024); + ctx_drawlist_resize (drawlist, new_); + } + + if (CTX_UNLIKELY(drawlist->count >= max_size - 20)) + { + return 0; + } + if ((flags & CTX_DRAWLIST_EDGE_LIST)) + ((CtxSegment*)(drawlist->entries))[drawlist->count] = *(CtxSegment*)entry; + else + drawlist->entries[drawlist->count] = *entry; + ret = drawlist->count; + drawlist->count++; + return ret; +} + +int +ctx_add_single (Ctx *ctx, void *entry) +{ + return ctx_drawlist_add_single (&ctx->drawlist, (CtxEntry *) entry); +} + +static inline int +ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int ret = 0; + for (int i = 0; i < length; i ++) + { + ret = ctx_drawlist_add_single (drawlist, &entry[i]); + } + return ret; +} + +#if 0 +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int tmp_pos = ctx_drawlist_add_entry (drawlist, entry); + for (int i = 0; i < length; i++) + { + for (int j = pos + i + 1; j < tmp_pos; j++) + drawlist->entries[j] = entry[j-1]; + drawlist->entries[pos + i] = entry[i]; + } + return pos; +} +#endif +int +ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry) +{ + int length = ctx_conts_for_entry (entry) + 1; + int tmp_pos = ctx_drawlist_add_entry (drawlist, entry); +#if 1 + for (int i = 0; i < length; i++) + { + for (int j = tmp_pos; j > pos + i; j--) + drawlist->entries[j] = drawlist->entries[j-1]; + drawlist->entries[pos + i] = entry[i]; + } + return pos; +#endif + return tmp_pos; +} + +int ctx_append_drawlist (Ctx *ctx, void *data, int length) +{ + CtxEntry *entries = (CtxEntry *) data; + if (length % sizeof (CtxEntry) ) + { + ctx_log("drawlist not multiple of 9\n"); + return -1; + } + for (unsigned int i = 0; i < length / sizeof (CtxEntry); i++) + { + ctx_drawlist_add_single (&ctx->drawlist, &entries[i]); + } + return 0; +} + +int ctx_set_drawlist (Ctx *ctx, void *data, int length) +{ + CtxDrawlist *drawlist = &ctx->drawlist; + ctx->drawlist.count = 0; + if (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) + { + return -1; + } + if (CTX_UNLIKELY(length % 9)) return -1; + ctx_drawlist_resize (drawlist, length/9); + memcpy (drawlist->entries, data, length); + drawlist->count = length / 9; + return length; +} + +int ctx_get_drawlist_count (Ctx *ctx) +{ + return ctx->drawlist.count; +} + +const CtxEntry *ctx_get_drawlist (Ctx *ctx) +{ + return ctx->drawlist.entries; +} + +int +ctx_add_data (Ctx *ctx, void *data, int length) +{ + if (CTX_UNLIKELY(length % sizeof (CtxEntry) )) + { + //ctx_log("err\n"); + return -1; + } + /* some more input verification might be in order.. like + * verify that it is well-formed up to length? + * + * also - it would be very useful to stop processing + * upon flush - and do drawlist resizing. + */ + return ctx_drawlist_add_entry (&ctx->drawlist, (CtxEntry *) data); +} + +int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2]) +{ + CtxEntry entry = {code, {{0},}}; + entry.data.u32[0] = u32[0]; + entry.data.u32[1] = u32[1]; + return ctx_drawlist_add_single (drawlist, &entry); +} + +int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length) +{ + CtxEntry entry = {CTX_DATA, {{0},}}; + entry.data.u32[0] = 0; + entry.data.u32[1] = 0; + int ret = ctx_drawlist_add_single (drawlist, &entry); + if (CTX_UNLIKELY(!data)) { return -1; } + int length_in_blocks; + if (length <= 0) { length = strlen ( (char *) data) + 1; } + length_in_blocks = length / sizeof (CtxEntry); + length_in_blocks += (length % sizeof (CtxEntry) ) ?1:0; + if (drawlist->count + length_in_blocks + 4 > drawlist->size) + { ctx_drawlist_resize (drawlist, drawlist->count * 1.2 + length_in_blocks + 32); } + if (CTX_UNLIKELY(drawlist->count >= drawlist->size)) + { return -1; } + drawlist->count += length_in_blocks; + drawlist->entries[ret].data.u32[0] = length; + drawlist->entries[ret].data.u32[1] = length_in_blocks; + memcpy (&drawlist->entries[ret+1], data, length); + { + //int reverse = ctx_drawlist_add (drawlist, CTX_DATA_REV); + CtxEntry entry = {CTX_DATA_REV, {{0},}}; + entry.data.u32[0] = length; + entry.data.u32[1] = length_in_blocks; + ctx_drawlist_add_single (drawlist, &entry); + /* this reverse marker exist to enable more efficient + front to back traversal, can be ignored in other + direction, is this needed after string setters as well? + */ + } + return ret; +} + +static inline CtxEntry +ctx_void (CtxCode code) +{ + CtxEntry command; + command.code = code; + return command; +} + +static inline CtxEntry +ctx_f (CtxCode code, float x, float y) +{ + CtxEntry command; + command.code = code; + command.data.f[0] = x; + command.data.f[1] = y; + return command; +} + +static CtxEntry +ctx_u32 (CtxCode code, uint32_t x, uint32_t y) +{ + CtxEntry command = ctx_void (code); + command.data.u32[0] = x; + command.data.u32[1] = y; + return command; +} + +#if 0 +static CtxEntry +ctx_s32 (CtxCode code, int32_t x, int32_t y) +{ + CtxEntry command = ctx_void (code); + command.data.s32[0] = x; + command.data.s32[1] = y; + return command; +} +#endif + +static inline CtxEntry +ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1) +{ + CtxEntry command; + command.code = code; + command.data.s16[0] = x0; + command.data.s16[1] = y0; + command.data.s16[2] = x1; + command.data.s16[3] = y1; + return command; +} + +static inline CtxSegment +ctx_segment_s16 (CtxCode code, int x0, int y0, int x1, int y1) +{ + CtxSegment command; + command.code = code; + command.data.s16[0] = x0; + command.data.s16[1] = y0; + command.data.s16[2] = x1; + command.data.s16[3] = y1; + return command; +} + +static CtxEntry +ctx_u8 (CtxCode code, + uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h) +{ + CtxEntry command; + command.code = code; + command.data.u8[0] = a; + command.data.u8[1] = b; + command.data.u8[2] = c; + command.data.u8[3] = d; + command.data.u8[4] = e; + command.data.u8[5] = f; + command.data.u8[6] = g; + command.data.u8[7] = h; + return command; +} + +#define CTX_PROCESS_VOID(cmd) do {\ + CtxEntry command = {cmd};\ + ctx_process (ctx, &command);}while(0) \ + +#define CTX_PROCESS_F(cmd, x, y) do {\ + CtxEntry command = ctx_f(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_F1(cmd, x) do {\ + CtxEntry command = ctx_f(cmd, x, 0);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U32(cmd, x, y) do {\ + CtxEntry command = ctx_u32(cmd, x, y);\ + ctx_process (ctx, &command);}while(0) + +#define CTX_PROCESS_U8(cmd, x) do {\ + CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\ + ctx_process (ctx, &command);}while(0) + + +static void +ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len) +{ + CtxEntry commands[1 + 2 + (len+1+1)/9]; + ctx_memset (commands, 0, sizeof (commands) ); + commands[0] = ctx_u32 (code, arg0, arg1); + commands[1].code = CTX_DATA; + commands[1].data.u32[0] = len; + commands[1].data.u32[1] = (len+1+1)/9 + 1; + memcpy( (char *) &commands[2].data.u8[0], string, len); + ( (char *) (&commands[2].data.u8[0]) ) [len]=0; + ctx_process (ctx, commands); +} + +static void +ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1) +{ + ctx_process_cmd_str_with_len (ctx, code, string, arg0, arg1, strlen (string)); +} + +static void +ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1) +{ + uint32_t iarg0; + uint32_t iarg1; + memcpy (&iarg0, &arg0, sizeof (iarg0)); + memcpy (&iarg1, &arg1, sizeof (iarg1)); + ctx_process_cmd_str_with_len (ctx, code, string, iarg0, iarg1, strlen (string)); +} + +#if CTX_BITPACK_PACKER +static int +ctx_last_history (CtxDrawlist *drawlist) +{ + int last_history = 0; + int i = 0; + while (i < drawlist->count) + { + CtxEntry *entry = &drawlist->entries[i]; + i += (ctx_conts_for_entry (entry) + 1); + } + return last_history; +} +#endif + +#if CTX_BITPACK_PACKER + +static float +find_max_dev (CtxEntry *entry, int nentrys) +{ + float max_dev = 0.0; + for (int c = 0; c < nentrys; c++) + { + for (int d = 0; d < 2; d++) + { + if (entry[c].data.f[d] > max_dev) + { max_dev = entry[c].data.f[d]; } + if (entry[c].data.f[d] < -max_dev) + { max_dev = -entry[c].data.f[d]; } + } + } + return max_dev; +} + +static void +pack_s8_args (CtxEntry *entry, int npairs) +{ + for (int c = 0; c < npairs; c++) + for (int d = 0; d < 2; d++) + { entry[0].data.s8[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; } +} + +static void +pack_s16_args (CtxEntry *entry, int npairs) +{ + for (int c = 0; c < npairs; c++) + for (int d = 0; d < 2; d++) + { entry[0].data.s16[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; } +} +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos) +{ + CtxIterator iterator; + if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0) + { return; } + ctx_iterator_init (&iterator, drawlist, start_pos, CTX_ITERATOR_FLAT); + iterator.end_pos = drawlist->count - 5; + CtxCommand *command = NULL; + while ( (command = ctx_iterator_next (&iterator) ) ) + { + CtxEntry *entry = &command->entry; + /* things smaller than this have probably been scaled down + beyond recognition, bailing for both better packing and less rasterization work + */ + if (command[0].code == CTX_REL_CURVE_TO) + { + float max_dev = find_max_dev (entry, 3); + if (max_dev < 1.0) + { + entry[0].code = CTX_REL_LINE_TO; + entry[0].data.f[0] = entry[2].data.f[0]; + entry[0].data.f[1] = entry[2].data.f[1]; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } + } + } +} +#endif + +#if CTX_BITPACK_PACKER +static void +ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos) +{ +#if CTX_BITPACK + int i = 0; + if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0) + { return; } + ctx_drawlist_remove_tiny_curves (drawlist, drawlist->bitpack_pos); + i = drawlist->bitpack_pos; + if (start_pos > i) + { i = start_pos; } + while (i < drawlist->count - 4) /* the -4 is to avoid looking past + initialized data we're not ready + to bitpack yet*/ + { + CtxEntry *entry = &drawlist->entries[i]; + if (entry[0].code == CTX_SET_RGBA_U8 && + entry[1].code == CTX_MOVE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO && + entry[4].code == CTX_REL_LINE_TO && + entry[5].code == CTX_REL_LINE_TO && + entry[6].code == CTX_FILL && + ctx_fabsf (entry[2].data.f[0] - 1.0f) < 0.02f && + ctx_fabsf (entry[3].data.f[1] - 1.0f) < 0.02f) + { + entry[0].code = CTX_SET_PIXEL; + entry[0].data.u16[2] = entry[1].data.f[0]; + entry[0].data.u16[3] = entry[1].data.f[1]; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + entry[4].code = CTX_NOP; + entry[5].code = CTX_NOP; + entry[6].code = CTX_NOP; + } +#if 1 + else if (entry[0].code == CTX_REL_LINE_TO) + { + if (entry[1].code == CTX_REL_LINE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_X4; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_CURVE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_REL_CURVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_LINE_TO && + entry[2].code == CTX_REL_LINE_TO && + entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_LINE_TO_X4; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_MOVE_TO) + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 31000 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_LINE_TO_REL_MOVE_TO; + entry[1].code = CTX_NOP; + } + } + else if (entry[1].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 31000 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_LINE_TO_X2; + entry[1].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_REL_CURVE_TO) + { + if (entry[3].code == CTX_REL_LINE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else if (entry[3].code == CTX_REL_MOVE_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_CURVE_TO_REL_MOVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else + { + float max_dev = find_max_dev (entry, 3); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 3); + ctx_arg_s8 (6) = + ctx_arg_s8 (7) = 0; + entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_REL_QUAD_TO) + { + if (entry[2].code == CTX_REL_QUAD_TO) + { + float max_dev = find_max_dev (entry, 4); + if (max_dev < 114 / CTX_SUBDIV) + { + pack_s8_args (entry, 4); + entry[0].code = CTX_REL_QUAD_TO_REL_QUAD_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + entry[3].code = CTX_NOP; + } + } + else + { + float max_dev = find_max_dev (entry, 2); + if (max_dev < 3100 / CTX_SUBDIV) + { + pack_s16_args (entry, 2); + entry[0].code = CTX_REL_QUAD_TO_S16; + entry[1].code = CTX_NOP; + } + } + } +#endif +#if 1 + else if (entry[0].code == CTX_FILL && + entry[1].code == CTX_MOVE_TO) + { + entry[0] = entry[1]; + entry[0].code = CTX_FILL_MOVE_TO; + entry[1].code = CTX_NOP; + } +#endif +#if 1 + else if (entry[0].code == CTX_MOVE_TO && + entry[1].code == CTX_MOVE_TO && + entry[2].code == CTX_MOVE_TO) + { + entry[0] = entry[2]; + entry[0].code = CTX_MOVE_TO; + entry[1].code = CTX_NOP; + entry[2].code = CTX_NOP; + } +#endif +#if 1 + else if ( (entry[0].code == CTX_MOVE_TO && + entry[1].code == CTX_MOVE_TO) || + (entry[0].code == CTX_REL_MOVE_TO && + entry[1].code == CTX_MOVE_TO) ) + { + entry[0] = entry[1]; + entry[0].code = CTX_MOVE_TO; + entry[1].code = CTX_NOP; + } +#endif + i += (ctx_conts_for_entry (entry) + 1); + } + int source = drawlist->bitpack_pos; + int target = drawlist->bitpack_pos; + int removed = 0; + /* remove nops that have been inserted as part of shortenings + */ + while (source < drawlist->count) + { + CtxEntry *sentry = &drawlist->entries[source]; + CtxEntry *tentry = &drawlist->entries[target]; + while (sentry->code == CTX_NOP && source < drawlist->count) + { + source++; + sentry = &drawlist->entries[source]; + removed++; + } + if (sentry != tentry) + { *tentry = *sentry; } + source ++; + target ++; + } + drawlist->count -= removed; + drawlist->bitpack_pos = drawlist->count; +#endif +} + +#endif + +static void +ctx_drawlist_compact (CtxDrawlist *drawlist) +{ +#if CTX_BITPACK_PACKER + int last_history; + last_history = ctx_last_history (drawlist); +#else + if (drawlist) {}; +#endif +#if CTX_BITPACK_PACKER + ctx_drawlist_bitpack (drawlist, last_history); +#endif +} + +uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry) +{ + return &entry[2 + 1 + 1 + ctx_conts_for_entry (&entry[2])].data.u8[0]; +} + +#ifndef __CTX_TRANSFORM +#define __CTX_TRANSFORM + +static inline void +_ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y) +{ + float x_in = *x; + float y_in = *y; + *x = ( (x_in * m->m[0][0]) + (y_in * m->m[1][0]) + m->m[2][0]); + *y = ( (y_in * m->m[1][1]) + (x_in * m->m[0][1]) + m->m[2][1]); +} + +void +ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y) +{ + _ctx_matrix_apply_transform (m, x, y); +} + +static inline void +_ctx_user_to_device (CtxState *state, float *x, float *y) +{ + _ctx_matrix_apply_transform (&state->gstate.transform, x, y); +} + +static void +_ctx_user_to_device_distance (CtxState *state, float *x, float *y) +{ + const CtxMatrix *m = &state->gstate.transform; + _ctx_matrix_apply_transform (m, x, y); + *x -= m->m[2][0]; + *y -= m->m[2][1]; +} + +void ctx_user_to_device (Ctx *ctx, float *x, float *y) +{ + _ctx_user_to_device (&ctx->state, x, y); +} +void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y) +{ + _ctx_user_to_device_distance (&ctx->state, x, y); +} + +static void +ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f) +{ + matrix->m[0][0] = a; + matrix->m[0][1] = b; + matrix->m[1][0] = c; + matrix->m[1][1] = d; + matrix->m[2][0] = e; + matrix->m[2][1] = f; +} + +static inline void +_ctx_matrix_identity (CtxMatrix *matrix) +{ + matrix->m[0][0] = 1.0f; + matrix->m[0][1] = 0.0f; + matrix->m[1][0] = 0.0f; + matrix->m[1][1] = 1.0f; + matrix->m[2][0] = 0.0f; + matrix->m[2][1] = 0.0f; +} + +void +ctx_matrix_identity (CtxMatrix *matrix) +{ + _ctx_matrix_identity (matrix); +} + +static void +_ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s) +{ + CtxMatrix r; + r.m[0][0] = t->m[0][0] * s->m[0][0] + t->m[0][1] * s->m[1][0]; + r.m[0][1] = t->m[0][0] * s->m[0][1] + t->m[0][1] * s->m[1][1]; + r.m[1][0] = t->m[1][0] * s->m[0][0] + t->m[1][1] * s->m[1][0]; + r.m[1][1] = t->m[1][0] * s->m[0][1] + t->m[1][1] * s->m[1][1]; + r.m[2][0] = t->m[2][0] * s->m[0][0] + t->m[2][1] * s->m[1][0] + s->m[2][0]; + r.m[2][1] = t->m[2][0] * s->m[0][1] + t->m[2][1] * s->m[1][1] + s->m[2][1]; + *result = r; +} + +void +ctx_matrix_multiply (CtxMatrix *result, + const CtxMatrix *t, + const CtxMatrix *s) +{ + _ctx_matrix_multiply (result, t, s); +} + +void +ctx_matrix_translate (CtxMatrix *matrix, float x, float y) +{ + CtxMatrix transform; + transform.m[0][0] = 1.0f; + transform.m[0][1] = 0.0f; + transform.m[1][0] = 0.0f; + transform.m[1][1] = 1.0f; + transform.m[2][0] = x; + transform.m[2][1] = y; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +void +ctx_matrix_scale (CtxMatrix *matrix, float x, float y) +{ + CtxMatrix transform; + transform.m[0][0] = x; + transform.m[0][1] = 0.0f; + transform.m[1][0] = 0.0f; + transform.m[1][1] = y; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +void +ctx_matrix_rotate (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_sin = ctx_sinf (angle); + float val_cos = ctx_cosf (angle); + transform.m[0][0] = val_cos; + transform.m[0][1] = val_sin; + transform.m[1][0] = -val_sin; + transform.m[1][1] = val_cos; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +#if 0 +static void +ctx_matrix_skew_x (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_tan = ctx_tanf (angle); + transform.m[0][0] = 1.0f; + transform.m[0][1] = 0.0f; + transform.m[1][0] = val_tan; + transform.m[1][1] = 1.0f; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} + +static void +ctx_matrix_skew_y (CtxMatrix *matrix, float angle) +{ + CtxMatrix transform; + float val_tan = ctx_tanf (angle); + transform.m[0][0] = 1.0f; + transform.m[0][1] = val_tan; + transform.m[1][0] = 0.0f; + transform.m[1][1] = 1.0f; + transform.m[2][0] = 0.0f; + transform.m[2][1] = 0.0f; + _ctx_matrix_multiply (matrix, &transform, matrix); +} +#endif + + +void +ctx_identity (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_IDENTITY); +} + + + +void +ctx_apply_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f) // htran, vtran +{ + CtxEntry command[3]= + { + ctx_f (CTX_APPLY_TRANSFORM, a, b), + ctx_f (CTX_CONT, c, d), + ctx_f (CTX_CONT, e, f) + }; + ctx_process (ctx, command); +} + +void +ctx_get_transform (Ctx *ctx, float *a, float *b, + float *c, float *d, + float *e, float *f) +{ + if (a) { *a = ctx->state.gstate.transform.m[0][0]; } + if (b) { *b = ctx->state.gstate.transform.m[0][1]; } + if (c) { *c = ctx->state.gstate.transform.m[1][0]; } + if (d) { *d = ctx->state.gstate.transform.m[1][1]; } + if (e) { *e = ctx->state.gstate.transform.m[2][0]; } + if (f) { *f = ctx->state.gstate.transform.m[2][1]; } +} + +void +ctx_source_transform (Ctx *ctx, float a, float b, // hscale, hskew + float c, float d, // vskew, vscale + float e, float f) // htran, vtran +{ + CtxEntry command[3]= + { + ctx_f (CTX_SOURCE_TRANSFORM, a, b), + ctx_f (CTX_CONT, c, d), + ctx_f (CTX_CONT, e, f) + }; + ctx_process (ctx, command); +} + +void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + ctx_apply_transform (ctx, + matrix->m[0][0], matrix->m[0][1], + matrix->m[1][0], matrix->m[1][1], + matrix->m[2][0], matrix->m[2][1]); +} + +void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + *matrix = ctx->state.gstate.transform; +} + +void ctx_set_matrix (Ctx *ctx, CtxMatrix *matrix) +{ + ctx_identity (ctx); + ctx_apply_matrix (ctx, matrix); +} + +void ctx_rotate (Ctx *ctx, float x) +{ + if (x == 0.0f) + return; + CTX_PROCESS_F1 (CTX_ROTATE, x); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void ctx_scale (Ctx *ctx, float x, float y) +{ + if (x == 1.0f && y == 1.0f) + return; + CTX_PROCESS_F (CTX_SCALE, x, y); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void ctx_translate (Ctx *ctx, float x, float y) +{ + if (x == 0.0f && y == 0.0f) + return; + CTX_PROCESS_F (CTX_TRANSLATE, x, y); + if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) + { ctx->drawlist.count--; } +} + +void +ctx_matrix_invert (CtxMatrix *m) +{ + CtxMatrix t = *m; + float invdet, det = m->m[0][0] * m->m[1][1] - + m->m[1][0] * m->m[0][1]; + if (det > -0.0000001f && det < 0.0000001f) + { + m->m[0][0] = m->m[0][1] = + m->m[1][0] = m->m[1][1] = + m->m[2][0] = m->m[2][1] = 0.0; + return; + } + invdet = 1.0f / det; + m->m[0][0] = t.m[1][1] * invdet; + m->m[1][0] = -t.m[1][0] * invdet; + m->m[2][0] = (t.m[1][0] * t.m[2][1] - t.m[1][1] * t.m[2][0]) * invdet; + m->m[0][1] = -t.m[0][1] * invdet; + m->m[1][1] = t.m[0][0] * invdet; + m->m[2][1] = (t.m[0][1] * t.m[2][0] - t.m[0][0] * t.m[2][1]) * invdet ; +} + + + +#endif +#ifndef __CTX_COLOR +#define __CTX_COLOR + +int ctx_color_model_get_components (CtxColorModel model) +{ + switch (model) + { + case CTX_GRAY: + return 1; + case CTX_GRAYA: + case CTX_GRAYA_A: + return 1; + case CTX_RGB: + case CTX_LAB: + case CTX_LCH: + case CTX_DRGB: + return 3; + case CTX_CMYK: + case CTX_DCMYK: + case CTX_LABA: + case CTX_LCHA: + case CTX_RGBA: + case CTX_DRGBA: + case CTX_RGBA_A: + case CTX_RGBA_A_DEVICE: + return 4; + case CTX_DCMYKA: + case CTX_CMYKA: + case CTX_CMYKA_A: + case CTX_DCMYKA_A: + return 5; + } + return 0; +} + +#if 0 +inline static float ctx_u8_to_float (uint8_t val_u8) +{ + float val_f = val_u8 / 255.0; + return val_f; +} +#else +float ctx_u8_float[256]; +#endif + +CtxColor *ctx_color_new () +{ + CtxColor *color = (CtxColor*)ctx_calloc (sizeof (CtxColor), 1); + return color; +} + +int ctx_color_is_transparent (CtxColor *color) +{ + return color->alpha <= 0.001f; +} + + +void ctx_color_free (CtxColor *color) +{ + free (color); +} + +static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + color->original = color->valid = CTX_VALID_RGBA_U8; + color->rgba[0] = r; + color->rgba[1] = g; + color->rgba[2] = b; + color->rgba[3] = a; +#if CTX_ENABLE_CM + color->space = state->gstate.device_space; +#endif +} + +#if 0 +static void ctx_color_set_RGBA8_ (CtxColor *color, const uint8_t *in) +{ + ctx_color_set_RGBA8 (color, in[0], in[1], in[2], in[3]); +} +#endif + +static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha) +{ + color->original = color->valid = CTX_VALID_GRAYA; + color->l = gray; + color->alpha = alpha; +} +#if 0 +static void ctx_color_set_graya_ (CtxColor *color, const float *in) +{ + return ctx_color_set_graya (color, in[0], in[1]); +} +#endif + +void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a) +{ +#if CTX_ENABLE_CM + color->original = color->valid = CTX_VALID_RGBA; + color->red = r; + color->green = g; + color->blue = b; + color->space = state->gstate.rgb_space; +#else + color->original = color->valid = CTX_VALID_RGBA_DEVICE; + color->device_red = r; + color->device_green = g; + color->device_blue = b; +#endif + color->alpha = a; +} + +static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a) +{ +#if CTX_ENABLE_CM + color->original = color->valid = CTX_VALID_RGBA_DEVICE; + color->device_red = r; + color->device_green = g; + color->device_blue = b; + color->alpha = a; + color->space = state->gstate.device_space; +#else + ctx_color_set_rgba (state, color, r, g, b, a); +#endif +} + +#if 0 +static void ctx_color_set_rgba_ (CtxState *state, CtxColor *color, const float *in) +{ + ctx_color_set_rgba (color, in[0], in[1], in[2], in[3]); +} +#endif + +/* the baseline conversions we have whether CMYK support is enabled or not, + * providing an effort at right rendering + */ +static void ctx_cmyk_to_rgb (float c, float m, float y, float k, float *r, float *g, float *b) +{ + *r = (1.0f-c) * (1.0f-k); + *g = (1.0f-m) * (1.0f-k); + *b = (1.0f-y) * (1.0f-k); +} + +void ctx_rgb_to_cmyk (float r, float g, float b, + float *c_out, float *m_out, float *y_out, float *k_out) +{ + float c = 1.0f - r; + float m = 1.0f - g; + float y = 1.0f - b; + float k = ctx_minf (c, ctx_minf (y, m) ); + if (k < 1.0f) + { + c = (c - k) / (1.0f - k); + m = (m - k) / (1.0f - k); + y = (y - k) / (1.0f - k); + } + else + { + c = m = y = 0.0f; + } + *c_out = c; + *m_out = m; + *y_out = y; + *k_out = k; +} + +#if CTX_ENABLE_CMYK +static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a) +{ + color->original = color->valid = CTX_VALID_CMYKA; + color->cyan = c; + color->magenta = m; + color->yellow = y; + color->key = k; + color->alpha = a; +#if CTX_ENABLE_CM + color->space = state->gstate.cmyk_space; +#endif +} + +static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a) +{ + color->original = color->valid = CTX_VALID_DCMYKA; + color->device_cyan = c; + color->device_magenta = m; + color->device_yellow = y; + color->device_key = k; + color->alpha = a; +#if CTX_ENABLE_CM + color->space = state->gstate.device_space; +#endif +} + +#endif + +#if CTX_ENABLE_CM + +static void ctx_rgb_user_to_device (CtxState *state, float rin, float gin, float bin, + float *rout, float *gout, float *bout) +{ +#if CTX_BABL +#if 0 + fprintf (stderr, "-[%p %p\n", + state->gstate.fish_rgbaf_user_to_device, + state->gstate.fish_rgbaf_device_to_user); +#endif + if (state->gstate.fish_rgbaf_user_to_device) + { + float rgbaf[4]={rin,gin,bin,1.0}; + float rgbafo[4]; + babl_process (state->gstate.fish_rgbaf_user_to_device, + rgbaf, rgbafo, 1); + + *rout = rgbafo[0]; + *gout = rgbafo[1]; + *bout = rgbafo[2]; + return; + } +#endif + *rout = rin; + *gout = gin; + *bout = bin; +} + +static void ctx_rgb_device_to_user (CtxState *state, float rin, float gin, float bin, + float *rout, float *gout, float *bout) +{ +#if CTX_BABL +#if 0 + fprintf (stderr, "=[%p %p\n", + state->gstate.fish_rgbaf_user_to_device, + state->gstate.fish_rgbaf_device_to_user); +#endif + if (state->gstate.fish_rgbaf_device_to_user) + { + float rgbaf[4]={rin,gin,bin,1.0}; + float rgbafo[4]; + babl_process (state->gstate.fish_rgbaf_device_to_user, + rgbaf, rgbafo, 1); + + *rout = rgbafo[0]; + *gout = rgbafo[1]; + *bout = rgbafo[2]; + return; + } +#endif + *rout = rin; + *gout = gin; + *bout = bin; +} +#endif + +static void ctx_color_get_drgba (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_RGBA_DEVICE) ) + { +#if CTX_ENABLE_CM + if (color->valid & CTX_VALID_RGBA) + { + ctx_rgb_user_to_device (state, color->red, color->green, color->blue, + & (color->device_red), & (color->device_green), & (color->device_blue) ); + } + else +#endif + if (color->valid & CTX_VALID_RGBA_U8) + { + float red = ctx_u8_to_float (color->rgba[0]); + float green = ctx_u8_to_float (color->rgba[1]); + float blue = ctx_u8_to_float (color->rgba[2]); +#if CTX_ENABLE_CM + ctx_rgb_user_to_device (state, red, green, blue, + & (color->device_red), & (color->device_green), & (color->device_blue) ); +#else + color->device_red = red; + color->device_green = green; + color->device_blue = blue; +#endif + color->alpha = ctx_u8_to_float (color->rgba[3]); + } +#if CTX_ENABLE_CMYK + else if (color->valid & CTX_VALID_CMYKA) + { + ctx_cmyk_to_rgb (color->cyan, color->magenta, color->yellow, color->key, + &color->device_red, + &color->device_green, + &color->device_blue); + } +#endif + else if (color->valid & CTX_VALID_GRAYA) + { + color->device_red = + color->device_green = + color->device_blue = color->l; + } + color->valid |= CTX_VALID_RGBA_DEVICE; + } + out[0] = color->device_red; + out[1] = color->device_green; + out[2] = color->device_blue; + out[3] = color->alpha; +} + + +static inline void +_ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out) +{ +#if CTX_ENABLE_CM + if (! (color->valid & CTX_VALID_RGBA) ) + { + ctx_color_get_drgba (state, color, out); + if (color->valid & CTX_VALID_RGBA_DEVICE) + { + ctx_rgb_device_to_user (state, color->device_red, color->device_green, color->device_blue, + & (color->red), & (color->green), & (color->blue) ); + } + color->valid |= CTX_VALID_RGBA; + } + out[0] = color->red; + out[1] = color->green; + out[2] = color->blue; + out[3] = color->alpha; +#else + ctx_color_get_drgba (state, color, out); +#endif +} + +void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out) +{ + _ctx_color_get_rgba (state, color, out); +} + + + +float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb) +{ + // XXX todo replace with correct according to primaries + return CTX_CSS_RGB_TO_LUMINANCE(rgb); +} +uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb) +{ + // XXX todo replace with correct according to primaries + return CTX_CSS_RGB_TO_LUMINANCE(rgb); +} + +void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_GRAYA) ) + { + float rgba[4]; + ctx_color_get_drgba (state, color, rgba); + color->l = ctx_float_color_rgb_to_gray (state, rgba); + color->valid |= CTX_VALID_GRAYA; + } + out[0] = color->l; + out[1] = color->alpha; +} + +#if CTX_ENABLE_CMYK +void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out) +{ + if (! (color->valid & CTX_VALID_CMYKA) ) + { + if (color->valid & CTX_VALID_GRAYA) + { + color->cyan = color->magenta = color->yellow = 0.0; + color->key = color->l; + } + else + { + float rgba[4]; + ctx_color_get_rgba (state, color, rgba); + ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2], + &color->cyan, &color->magenta, &color->yellow, &color->key); + color->alpha = rgba[3]; + } + color->valid |= CTX_VALID_CMYKA; + } + out[0] = color->cyan; + out[1] = color->magenta; + out[2] = color->yellow; + out[3] = color->key; + out[4] = color->alpha; +} + +#if 0 +static void ctx_color_get_cmyka_u8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_CMYKA_U8) ) + { + float cmyka[5]; + ctx_color_get_cmyka (color, cmyka); + for (int i = 0; i < 5; i ++) + { color->cmyka[i] = ctx_float_to_u8 (cmyka[i]); } + color->valid |= CTX_VALID_CMYKA_U8; + } + out[0] = color->cmyka[0]; + out[1] = color->cmyka[1]; + out[2] = color->cmyka[2]; + out[3] = color->cmyka[3]; +} +#endif +#endif + +static inline void +_ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_RGBA_U8) ) + { + float rgba[4]; + ctx_color_get_drgba (state, color, rgba); + for (int i = 0; i < 4; i ++) + { color->rgba[i] = ctx_float_to_u8 (rgba[i]); } + color->valid |= CTX_VALID_RGBA_U8; + } + out[0] = color->rgba[0]; + out[1] = color->rgba[1]; + out[2] = color->rgba[2]; + out[3] = color->rgba[3]; +} + +void +ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + _ctx_color_get_rgba8 (state, color, out); +} + +void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out) +{ + if (! (color->valid & CTX_VALID_GRAYA_U8) ) + { + float graya[2]; + ctx_color_get_graya (state, color, graya); + color->l_u8 = ctx_float_to_u8 (graya[0]); + color->rgba[3] = ctx_float_to_u8 (graya[1]); + color->valid |= CTX_VALID_GRAYA_U8; + } + out[0] = color->l_u8; + out[1] = color->rgba[3]; +} + +#if 0 +void +ctx_get_rgba (Ctx *ctx, float *rgba) +{ + ctx_color_get_rgba (& (ctx->state), &ctx->state.gstate.source.color, rgba); +} + +void +ctx_get_drgba (Ctx *ctx, float *rgba) +{ + ctx_color_get_drgba (& (ctx->state), &ctx->state.gstate.source.color, rgba); +} +#endif + +int ctx_in_fill (Ctx *ctx, float x, float y) +{ + float x1, y1, x2, y2; + ctx_path_extents (ctx, &x1, &y1, &x2, &y2); + + if (x1 <= x && x <= x2 && // XXX - just bounding box for now + y1 <= y && y <= y2) // + return 1; + return 0; +} + + +#if CTX_ENABLE_CMYK +#if 0 +void +ctx_get_cmyka (Ctx *ctx, float *cmyka) +{ + ctx_color_get_cmyka (& (ctx->state), &ctx->state.gstate.source.color, cmyka); +} +#endif +#endif +#if 0 +void +ctx_get_graya (Ctx *ctx, float *ya) +{ + ctx_color_get_graya (& (ctx->state), &ctx->state.gstate.source.color, ya); +} +#endif + +void ctx_stroke_source (Ctx *ctx) +{ + CtxEntry set_stroke = ctx_void (CTX_STROKE_SOURCE); + ctx_process (ctx, &set_stroke); +} + +void ctx_color_raw (Ctx *ctx, CtxColorModel model, float *components, int stroke) +{ +#if 0 + CtxSource *source = stroke? + &ctx->state.gstate.source_stroke: + &ctx->state.gstate.source_fill; + + if (model == CTX_RGB || model == CTX_RGBA) + { + float rgba[4]; + // XXX it should be possible to disable this, to get a more accurate record + // when it is intentional + float a = 1.0f; + if (model == CTX_RGBA) a = components[3]; + ctx_color_get_rgba (&ctx->state, &source->color, rgba); + if (rgba[0] == components[0] && rgba[1] == components[1] && rgba[2] == components[2] && rgba[3] == a) + return; + } +#endif + + if (stroke) + { + ctx_stroke_source (ctx); + } + + CtxEntry command[3]= { + ctx_f (CTX_COLOR, model, 0) + }; + switch (model) + { + case CTX_RGBA: + case CTX_RGBA_A: + case CTX_RGBA_A_DEVICE: + case CTX_DRGBA: + case CTX_LABA: + case CTX_LCHA: + command[2].data.f[0]=components[3]; + /*FALLTHROUGH*/ + case CTX_RGB: + case CTX_LAB: + case CTX_LCH: + case CTX_DRGB: + command[0].data.f[1]=components[0]; + command[1].data.f[0]=components[1]; + command[1].data.f[1]=components[2]; + break; + case CTX_DCMYKA: + case CTX_CMYKA: + case CTX_DCMYKA_A: + case CTX_CMYKA_A: + command[2].data.f[1]=components[4]; + /*FALLTHROUGH*/ + case CTX_CMYK: + case CTX_DCMYK: + command[0].data.f[1]=components[0]; + command[1].data.f[0]=components[1]; + command[1].data.f[1]=components[2]; + command[2].data.f[0]=components[3]; + break; + case CTX_GRAYA: + case CTX_GRAYA_A: + command[1].data.f[0]=components[1]; + /*FALLTHROUGH*/ + case CTX_GRAY: + command[0].data.f[1]=components[0]; + break; + } + ctx_process (ctx, command); +} + +void ctx_rgba (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_RGBA, components, 0); +} + +void ctx_rgba_stroke (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_RGBA, components, 1); +} + +void ctx_rgb (Ctx *ctx, float r, float g, float b) +{ + ctx_rgba (ctx, r, g, b, 1.0f); +} + +void ctx_rgb_stroke (Ctx *ctx, float r, float g, float b) +{ + ctx_rgba_stroke (ctx, r, g, b, 1.0f); +} + +void ctx_gray_stroke (Ctx *ctx, float gray) +{ + ctx_color_raw (ctx, CTX_GRAY, &gray, 1); +} +void ctx_gray (Ctx *ctx, float gray) +{ + ctx_color_raw (ctx, CTX_GRAY, &gray, 0); +} + +void ctx_drgba_stroke (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_DRGBA, components, 1); +} +void ctx_drgba (Ctx *ctx, float r, float g, float b, float a) +{ + float components[4]={r,g,b,a}; + ctx_color_raw (ctx, CTX_DRGBA, components, 0); +} + +#if CTX_ENABLE_CMYK + +void ctx_cmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_CMYKA, components, 1); +} +void ctx_cmyka (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_CMYKA, components, 0); +} +void ctx_cmyk_stroke (Ctx *ctx, float c, float m, float y, float k) +{ + float components[4]={c,m,y,k}; + ctx_color_raw (ctx, CTX_CMYK, components, 1); +} +void ctx_cmyk (Ctx *ctx, float c, float m, float y, float k) +{ + float components[4]={c,m,y,k}; + ctx_color_raw (ctx, CTX_CMYK, components, 0); +} + +static void ctx_dcmyk_raw (Ctx *ctx, float c, float m, float y, float k, int stroke) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYKA, components, stroke); +} + +static void ctx_dcmyka_raw (Ctx *ctx, float c, float m, float y, float k, float a, int stroke) +{ + CtxEntry command[3]= + { + ctx_f (CTX_COLOR, CTX_DCMYKA + 512 * stroke, c), + ctx_f (CTX_CONT, m, y), + ctx_f (CTX_CONT, k, a) + }; + ctx_process (ctx, command); +} + +void ctx_dcmyk_stroke (Ctx *ctx, float c, float m, float y, float k) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYK, components, 1); +} +void ctx_dcmyk (Ctx *ctx, float c, float m, float y, float k) +{ + float components[5]={c,m,y,k,1.0f}; + ctx_color_raw (ctx, CTX_DCMYK, components, 0); +} + +void ctx_dcmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_DCMYKA, components, 1); +} +void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a) +{ + float components[5]={c,m,y,k,a}; + ctx_color_raw (ctx, CTX_DCMYKA, components, 0); +} + +#endif + +/* XXX: missing CSS1: + * + * EM { color: rgb(110%, 0%, 0%) } // clipped to 100% + * + * + * :first-letter + * :first-list + * :link :visited :active + * + */ + +typedef struct ColorDef { + uint64_t name; + float r; + float g; + float b; + float a; +} ColorDef; + +#if 0 +#define CTX_silver CTX_STRH('s','i','l','v','e','r',0,0,0,0,0,0,0,0) +#define CTX_fuchsia CTX_STRH('f','u','c','h','s','i','a',0,0,0,0,0,0,0) +#define CTX_gray CTX_STRH('g','r','a','y',0,0,0,0,0,0,0,0,0,0) +#define CTX_yellow CTX_STRH('y','e','l','l','o','w',0,0,0,0,0,0,0,0) +#define CTX_white CTX_STRH('w','h','i','t','e',0,0,0,0,0,0,0,0,0) +#define CTX_maroon CTX_STRH('m','a','r','o','o','n',0,0,0,0,0,0,0,0) +#define CTX_magenta CTX_STRH('m','a','g','e','n','t','a',0,0,0,0,0,0,0) +#define CTX_blue CTX_STRH('b','l','u','e',0,0,0,0,0,0,0,0,0,0) +#define CTX_green CTX_STRH('g','r','e','e','n',0,0,0,0,0,0,0,0,0) +#define CTX_red CTX_STRH('r','e','d',0,0,0,0,0,0,0,0,0,0,0) +#define CTX_purple CTX_STRH('p','u','r','p','l','e',0,0,0,0,0,0,0,0) +#define CTX_olive CTX_STRH('o','l','i','v','e',0,0,0,0,0,0,0,0,0) +#define CTX_teal CTX_STRH('t','e','a','l',0,0,0,0,0,0,0,0,0,0) +#define CTX_black CTX_STRH('b','l','a','c','k',0,0,0,0,0,0,0,0,0) +#define CTX_cyan CTX_STRH('c','y','a','n',0,0,0,0,0,0,0,0,0,0) +#define CTX_navy CTX_STRH('n','a','v','y',0,0,0,0,0,0,0,0,0,0) +#define CTX_lime CTX_STRH('l','i','m','e',0,0,0,0,0,0,0,0,0,0) +#define CTX_aqua CTX_STRH('a','q','u','a',0,0,0,0,0,0,0,0,0,0) +#define CTX_transparent CTX_STRH('t','r','a','n','s','p','a','r','e','n','t',0,0,0) +#endif + +static ColorDef _ctx_colors[]={ + {CTX_black, 0, 0, 0, 1}, + {CTX_red, 1, 0, 0, 1}, + {CTX_green, 0, 1, 0, 1}, + {CTX_yellow, 1, 1, 0, 1}, + {CTX_blue, 0, 0, 1, 1}, + {CTX_fuchsia, 1, 0, 1, 1}, + {CTX_cyan, 0, 1, 1, 1}, + {CTX_white, 1, 1, 1, 1}, + {CTX_silver, 0.75294, 0.75294, 0.75294, 1}, + {CTX_gray, 0.50196, 0.50196, 0.50196, 1}, + {CTX_magenta, 0.50196, 0, 0.50196, 1}, + {CTX_maroon, 0.50196, 0, 0, 1}, + {CTX_purple, 0.50196, 0, 0.50196, 1}, + {CTX_green, 0, 0.50196, 0, 1}, + {CTX_lime, 0, 1, 0, 1}, + {CTX_olive, 0.50196, 0.50196, 0, 1}, + {CTX_navy, 0, 0, 0.50196, 1}, + {CTX_teal, 0, 0.50196, 0.50196, 1}, + {CTX_aqua, 0, 1, 1, 1}, + {CTX_transparent, 0, 0, 0, 0}, + {CTX_none, 0, 0, 0, 0}, +}; + +static int xdigit_value(const char xdigit) +{ + if (xdigit >= '0' && xdigit <= '9') + return xdigit - '0'; + switch (xdigit) + { + case 'A':case 'a': return 10; + case 'B':case 'b': return 11; + case 'C':case 'c': return 12; + case 'D':case 'd': return 13; + case 'E':case 'e': return 14; + case 'F':case 'f': return 15; + } + return 0; +} + +static int +ctx_color_parse_rgb (CtxState *ctxstate, CtxColor *color, const char *color_string) +{ + float dcolor[4] = {0,0,0,1}; + while (*color_string && *color_string != '(') + color_string++; + if (*color_string) color_string++; + + { + int n_floats = 0; + char *p = (char*)color_string; + char *prev = (char*)NULL; + for (; p && n_floats < 4 && p != prev && *p; ) + { + float val; + prev = p; + val = _ctx_parse_float (p, &p); + if (p != prev) + { + if (n_floats < 3) + dcolor[n_floats++] = val/255.0; + else + dcolor[n_floats++] = val; + + while (*p == ' ' || *p == ',') + { + p++; + prev++; + } + } + } + } + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + return 0; +} + +static int ctx_isxdigit (uint8_t ch) +{ + if (ch >= '0' && ch <= '9') return 1; + if (ch >= 'a' && ch <= 'f') return 1; + if (ch >= 'A' && ch <= 'F') return 1; + return 0; +} + +static int +mrg_color_parse_hex (CtxState *ctxstate, CtxColor *color, const char *color_string) +{ + float dcolor[4]={0,0,0,1}; + int string_length = strlen (color_string); + int i; + dcolor[3] = 1.0; + + if (string_length == 7 || /* #rrggbb */ + string_length == 9) /* #rrggbbaa */ + { + int num_iterations = (string_length - 1) / 2; + + for (i = 0; i < num_iterations; ++i) + { + if (ctx_isxdigit (color_string[2 * i + 1]) && + ctx_isxdigit (color_string[2 * i + 2])) + { + dcolor[i] = (xdigit_value (color_string[2 * i + 1]) << 4 | + xdigit_value (color_string[2 * i + 2])) / 255.f; + } + else + { + return 0; + } + } + /* Successful #rrggbb(aa) parsing! */ + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + return 1; + } + else if (string_length == 4 || /* #rgb */ + string_length == 5) /* #rgba */ + { + int num_iterations = string_length - 1; + for (i = 0; i < num_iterations; ++i) + { + if (ctx_isxdigit (color_string[i + 1])) + { + dcolor[i] = (xdigit_value (color_string[i + 1]) << 4 | + xdigit_value (color_string[i + 1])) / 255.f; + } + else + { + return 0; + } + } + ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]); + /* Successful #rgb(a) parsing! */ + return 0; + } + /* String was of unsupported length. */ + return 1; +} + +//#define CTX_currentColor CTX_STRH('c','u','r','r','e','n','t','C','o','l','o','r',0,0) + +int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string) +{ + int i; + uint64_t hash = ctx_strhash (string, 0); +// ctx_color_set_rgba (&(ctx->state), color, 0.4,0.1,0.9,1.0); +// return 0; + //rgba[0], rgba[1], rgba[2], rgba[3]); + + if (hash == CTX_currentColor) + { + float rgba[4]; + CtxColor ccolor; + ctx_get_color (ctx, CTX_color, &ccolor); + ctx_color_get_rgba (&(ctx->state), &ccolor, rgba); + ctx_color_set_rgba (&(ctx->state), color, rgba[0], rgba[1], rgba[2], rgba[3]); + return 0; + } + + for (i = (sizeof(_ctx_colors)/sizeof(_ctx_colors[0]))-1; i>=0; i--) + { + if (hash == _ctx_colors[i].name) + { + ctx_color_set_rgba (&(ctx->state), color, + _ctx_colors[i].r, _ctx_colors[i].g, _ctx_colors[i].b, _ctx_colors[i].a); + return 0; + } + } + + if (string[0] == '#') + mrg_color_parse_hex (&(ctx->state), color, string); + else if (string[0] == 'r' && + string[1] == 'g' && + string[2] == 'b' + ) + ctx_color_parse_rgb (&(ctx->state), color, string); + + return 0; +} + +int ctx_color (Ctx *ctx, const char *string) +{ + CtxColor color = {0,}; + ctx_color_set_from_string (ctx, &color, string); + float rgba[4]; + ctx_color_get_rgba (&(ctx->state), &color, rgba); + ctx_color_raw (ctx, CTX_RGBA, rgba, 0); + return 0; +} + +void +ctx_rgba8 (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ +#if 0 + CtxEntry command = ctx_u8 (CTX_SET_RGBA_U8, r, g, b, a, 0, 0, 0, 0); + + uint8_t rgba[4]; + ctx_color_get_rgba8 (&ctx->state, &ctx->state.gstate.source.color, rgba); + if (rgba[0] == r && rgba[1] == g && rgba[2] == b && rgba[3] == a) + return; + + ctx_process (ctx, &command); +#else + ctx_rgba (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f); +#endif +} + +void ctx_rgba8_stroke (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + ctx_rgba_stroke (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f); +} + + +#endif + +#if CTX_BABL +void ctx_rasterizer_colorspace_babl (CtxState *state, + CtxColorSpace space_slot, + const Babl *space) +{ + switch (space_slot) + { + case CTX_COLOR_SPACE_DEVICE_RGB: + state->gstate.device_space = space; + break; + case CTX_COLOR_SPACE_DEVICE_CMYK: + state->gstate.device_space = space; + break; + case CTX_COLOR_SPACE_USER_RGB: + state->gstate.rgb_space = space; + break; + case CTX_COLOR_SPACE_USER_CMYK: + state->gstate.cmyk_space = space; + break; + case CTX_COLOR_SPACE_TEXTURE: + state->gstate.texture_space = space; + break; + } + + const Babl *srgb = babl_space ("sRGB"); + if (!state->gstate.texture_space) + state->gstate.texture_space = srgb; + if (!state->gstate.device_space) + state->gstate.device_space = srgb; + if (!state->gstate.rgb_space) + state->gstate.rgb_space = srgb; + + //fprintf (stderr, "%s\n", babl_get_name (state->gstate.device_space)); + + state->gstate.fish_rgbaf_device_to_user = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.device_space), + babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space)); + state->gstate.fish_rgbaf_user_to_device = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space), + babl_format_with_space ("R'G'B'A float", state->gstate.device_space)); + state->gstate.fish_rgbaf_texture_to_device = babl_fish ( + babl_format_with_space ("R'G'B'A float", state->gstate.texture_space), + babl_format_with_space ("R'G'B'A float", state->gstate.device_space)); +} +#endif + +void ctx_rasterizer_colorspace_icc (CtxState *state, + CtxColorSpace space_slot, + char *icc_data, + int icc_length) +{ +#if CTX_BABL + const char *error = NULL; + const Babl *space = NULL; + + if (icc_data == NULL) space = babl_space ("sRGB"); + else if (icc_length < 32) + { + if (icc_data[0] == '0' && icc_data[1] == 'x') + sscanf (icc_data, "%p", &space); + else + { + char tmp[24]; + int i; + for (i = 0; i < icc_length; i++) + tmp[i]= (icc_data[i]>='A' && icc_data[i]<='Z')?icc_data[i]+('a'-'A'):icc_data[i]; + tmp[icc_length]=0; + if (!strcmp (tmp, "srgb")) space = babl_space ("sRGB"); + else if (!strcmp (tmp, "scrgb")) space = babl_space ("scRGB"); + else if (!strcmp (tmp, "acescg")) space = babl_space ("ACEScg"); + else if (!strcmp (tmp, "adobe")) space = babl_space ("Adobe"); + else if (!strcmp (tmp, "apple")) space = babl_space ("Apple"); + else if (!strcmp (tmp, "rec2020")) space = babl_space ("Rec2020"); + else if (!strcmp (tmp, "aces2065-1")) space = babl_space ("ACES2065-1"); + } + } + + if (!space) + { + space = babl_space_from_icc (icc_data, icc_length, BABL_ICC_INTENT_RELATIVE_COLORIMETRIC, &error); + } + if (space) + { + ctx_rasterizer_colorspace_babl (state, space_slot, space); + } +#endif +} + +void ctx_colorspace (Ctx *ctx, + CtxColorSpace space_slot, + unsigned char *data, + int data_length) +{ + if (data) + { + if (data_length <= 0) data_length = (int)strlen ((char*)data); + ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, (char*)data, space_slot, 0, data_length); + } + else + { + ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, "sRGB", space_slot, 0, 4); + } +} + +void ctx_gradient_add_stop_u8 +(Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + CtxEntry entry = ctx_f (CTX_GRADIENT_STOP, pos, 0); + entry.data.u8[4+0] = r; + entry.data.u8[4+1] = g; + entry.data.u8[4+2] = b; + entry.data.u8[4+3] = a; + ctx_process (ctx, &entry); +} + +void ctx_gradient_add_stop +(Ctx *ctx, float pos, float r, float g, float b, float a) +{ + int ir = r * 255; + int ig = g * 255; + int ib = b * 255; + int ia = a * 255; + ir = CTX_CLAMP (ir, 0,255); + ig = CTX_CLAMP (ig, 0,255); + ib = CTX_CLAMP (ib, 0,255); + ia = CTX_CLAMP (ia, 0,255); + ctx_gradient_add_stop_u8 (ctx, pos, ir, ig, ib, ia); +} + +void ctx_gradient_add_stop_string +(Ctx *ctx, float pos, const char *string) +{ + CtxColor color = {0,}; + ctx_color_set_from_string (ctx, &color, string); + float rgba[4]; + ctx_color_get_rgba (&(ctx->state), &color, rgba); + ctx_gradient_add_stop (ctx, pos, rgba[0], rgba[1], rgba[2], rgba[3]); +} + +// deviceRGB .. settable when creating an RGB image surface.. +// queryable when running in terminal - is it really needed? +// though it is settable ; and functional for changing this state at runtime.. +// +// userRGB - settable at any time, stored in save|restore +// texture - set as the space of data on subsequent + +static float ctx_state_get (CtxState *state, uint64_t hash) +{ + for (int i = state->gstate.keydb_pos-1; i>=0; i--) + { + if (state->keydb[i].key == hash) + { return state->keydb[i].value; } + } + return -0.0; +} + +static void ctx_state_set (CtxState *state, uint64_t key, float value) +{ + if (key != CTX_new_state) + { + if (ctx_state_get (state, key) == value) + { return; } + for (int i = state->gstate.keydb_pos-1; + state->keydb[i].key != CTX_new_state && i >=0; + i--) + { + if (state->keydb[i].key == key) + { + state->keydb[i].value = value; + return; + } + } + } + if (state->gstate.keydb_pos >= CTX_MAX_KEYDB) + { return; } + state->keydb[state->gstate.keydb_pos].key = key; + state->keydb[state->gstate.keydb_pos].value = value; + state->gstate.keydb_pos++; +} + + +#define CTX_KEYDB_STRING_START (-90000.0) +#define CTX_KEYDB_STRING_END (CTX_KEYDB_STRING_START + CTX_STRINGPOOL_SIZE) + +static int ctx_float_is_string (float val) +{ + return val >= CTX_KEYDB_STRING_START && val <= CTX_KEYDB_STRING_END; +} + +static int ctx_float_to_string_index (float val) +{ + int idx = -1; + if (ctx_float_is_string (val)) + { + idx = val - CTX_KEYDB_STRING_START; + } + return idx; +} + +static float ctx_string_index_to_float (int index) +{ + return CTX_KEYDB_STRING_START + index; +} + +void *ctx_state_get_blob (CtxState *state, uint64_t key) +{ + float stored = ctx_state_get (state, key); + int idx = ctx_float_to_string_index (stored); + if (idx >= 0) + { + // can we know length? + return &state->stringpool[idx]; + } + + // format number as string? + return NULL; +} + +const char *ctx_state_get_string (CtxState *state, uint64_t key) +{ + const char *ret = (char*)ctx_state_get_blob (state, key); + if (ret && ret[0] == 127) + return NULL; + return ret; +} + + +static void ctx_state_set_blob (CtxState *state, uint64_t key, uint8_t *data, int len) +{ + int idx = state->gstate.stringpool_pos; + + if (idx + len > CTX_STRINGPOOL_SIZE) + { + ctx_log ("blowing varpool size [%c..]\n", data[0]); + //fprintf (stderr, "blowing varpool size [%c%c%c..]\n", data[0],data[1], data[1]?data[2]:0); +#if 0 + for (int i = 0; i< CTX_STRINGPOOL_SIZE; i++) + { + if (i==0) fprintf (stderr, "\n%i ", i); + else fprintf (stderr, "%c", state->stringpool[i]); + } +#endif + return; + } + + memcpy (&state->stringpool[idx], data, len); + state->gstate.stringpool_pos+=len; + state->stringpool[state->gstate.stringpool_pos++]=0; + ctx_state_set (state, key, ctx_string_index_to_float (idx)); +} + +static void ctx_state_set_string (CtxState *state, uint64_t key, const char *string) +{ + float old_val = ctx_state_get (state, key); + int old_idx = ctx_float_to_string_index (old_val); + + if (old_idx >= 0) + { + const char *old_string = ctx_state_get_string (state, key); + if (old_string && !strcmp (old_string, string)) + return; + } + + if (ctx_str_is_number (string)) + { + ctx_state_set (state, key, strtod (string, NULL)); + return; + } + // should do same with color + + // XXX should special case when the string modified is at the + // end of the stringpool. + // + // for clips the behavior is howevre ideal, since + // we can have more than one clip per save/restore level + ctx_state_set_blob (state, key, (uint8_t*)string, strlen(string)); +} + +static int ctx_state_get_color (CtxState *state, uint64_t key, CtxColor *color) +{ + CtxColor *stored = (CtxColor*)ctx_state_get_blob (state, key); + if (stored) + { + if (stored->magic == 127) + { + *color = *stored; + return 0; + } + } + return -1; +} + +static void ctx_state_set_color (CtxState *state, uint64_t key, CtxColor *color) +{ + CtxColor mod_color; + CtxColor old_color; + mod_color = *color; + mod_color.magic = 127; + if (ctx_state_get_color (state, key, &old_color)==0) + { + if (!memcmp (&mod_color, &old_color, sizeof (mod_color))) + return; + } + ctx_state_set_blob (state, key, (uint8_t*)&mod_color, sizeof (CtxColor)); +} + +const char *ctx_get_string (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get_string (&ctx->state, hash); +} +float ctx_get_float (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get (&ctx->state, hash); +} +int ctx_get_int (Ctx *ctx, uint64_t hash) +{ + return ctx_state_get (&ctx->state, hash); +} +void ctx_set_float (Ctx *ctx, uint64_t hash, float value) +{ + ctx_state_set (&ctx->state, hash, value); +} +void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value) +{ + ctx_state_set_string (&ctx->state, hash, value); +} +void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color) +{ + ctx_state_set_color (&ctx->state, hash, color); +} +int ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color) +{ + return ctx_state_get_color (&ctx->state, hash, color); +} +int ctx_is_set (Ctx *ctx, uint64_t hash) +{ + return ctx_get_float (ctx, hash) != -0.0f; +} +int ctx_is_set_now (Ctx *ctx, uint64_t hash) +{ + return ctx_is_set (ctx, hash); +} + +#if CTX_COMPOSITE + +#define CTX_REFERENCE 0 + + +#define CTX_RGBA8_R_SHIFT 0 +#define CTX_RGBA8_G_SHIFT 8 +#define CTX_RGBA8_B_SHIFT 16 +#define CTX_RGBA8_A_SHIFT 24 + +#define CTX_RGBA8_R_MASK (0xff << CTX_RGBA8_R_SHIFT) +#define CTX_RGBA8_G_MASK (0xff << CTX_RGBA8_G_SHIFT) +#define CTX_RGBA8_B_MASK (0xff << CTX_RGBA8_B_SHIFT) +#define CTX_RGBA8_A_MASK (0xff << CTX_RGBA8_A_SHIFT) + +#define CTX_RGBA8_RB_MASK (CTX_RGBA8_R_MASK | CTX_RGBA8_B_MASK) +#define CTX_RGBA8_GA_MASK (CTX_RGBA8_G_MASK | CTX_RGBA8_A_MASK) + + +CTX_INLINE static void +ctx_RGBA8_associate_alpha (uint8_t *u8) +{ +#if 1 + uint32_t val = *((uint32_t*)(u8)); + uint8_t a = u8[3]; + uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT); +#else + uint32_t a = u8[3]; + u8[0] = (u8[0] * a + 255) >> 8; + u8[1] = (u8[1] * a + 255) >> 8; + u8[2] = (u8[2] * a + 255) >> 8; +#endif +} + +CTX_INLINE static void +ctx_RGBA8_associate_alpha_probably_opaque (uint8_t *u8) +{ + uint32_t val = *((uint32_t*)(u8)); + uint32_t a = val>>24;//u8[3]; + //if (CTX_UNLIKELY(a==0)) + // *((uint32_t*)(u8)) = 0; + if (CTX_UNLIKELY(a!=255)) + { + uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT); + } +} + +CTX_INLINE static uint32_t ctx_bi_RGBA8 (uint32_t isrc00, uint32_t isrc01, uint32_t isrc10, uint32_t isrc11, uint8_t dx, uint8_t dy) +{ +#if 0 + 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 +} + +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE +static uint8_t ctx_gradient_cache_u8[CTX_GRADIENT_CACHE_ELEMENTS][4]; +extern int ctx_gradient_cache_valid; + + +inline static int ctx_grad_index (float v) +{ + int ret = v * (CTX_GRADIENT_CACHE_ELEMENTS - 1.0f) + 0.5f; + if (CTX_UNLIKELY(ret >= CTX_GRADIENT_CACHE_ELEMENTS)) + return CTX_GRADIENT_CACHE_ELEMENTS - 1; + if (CTX_LIKELY(ret >= 0 && ret < CTX_GRADIENT_CACHE_ELEMENTS)) + return ret; + return 0; +} + + +//static void +//ctx_gradient_cache_reset (void) +//{ +// ctx_gradient_cache_valid = 0; +//} +#endif + + + +CTX_INLINE static void +_ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ + float v = x; + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v * 255; + rgba[3] = 255; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + uint8_t stop_rgba[4]; + uint8_t next_rgba[4]; + ctx_color_get_rgba8 (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_rgba8 (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos); +#if 1 + ((uint32_t*)rgba)[0] = ctx_lerp_RGBA8 (((uint32_t*)stop_rgba)[0], + ((uint32_t*)next_rgba)[0], dx); +#else + for (int c = 0; c < 4; c++) + { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); } +#endif + ctx_RGBA8_associate_alpha (rgba); + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_rgba8 (rasterizer->state, color, rgba); + if (rasterizer->swap_red_green) + { + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; + } + ctx_RGBA8_associate_alpha (rgba); +} + +#if CTX_GRADIENT_CACHE +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer); +#endif + +CTX_INLINE static void +ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ +#if CTX_GRADIENT_CACHE + *((uint32_t*)rgba) = *((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(x)][0])); +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, x, y, rgba); +#endif +} +#endif + +CTX_INLINE static void +ctx_u8_associate_alpha (int components, uint8_t *u8) +{ + for (int c = 0; c < components-1; c++) + u8[c] = (u8[c] * u8[components-1] + 255)>>8; +} + +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer) +{ + if (ctx_gradient_cache_valid) + return; + for (int u = 0; u < CTX_GRADIENT_CACHE_ELEMENTS; u++) + { + float v = u / (CTX_GRADIENT_CACHE_ELEMENTS - 1.0f); + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0f, &ctx_gradient_cache_u8[u][0]); + //*((uint32_t*)(&ctx_gradient_cache_u8_a[u][0]))= *((uint32_t*)(&ctx_gradient_cache_u8[u][0])); + //memcpy(&ctx_gradient_cache_u8_a[u][0], &ctx_gradient_cache_u8[u][0], 4); + //ctx_RGBA8_associate_alpha (&ctx_gradient_cache_u8_a[u][0]); + } + ctx_gradient_cache_valid = 1; +} +#endif + +CTX_INLINE static void +ctx_fragment_gradient_1d_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba) +{ + float v = x; + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v * 255; + rgba[1] = 255; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + uint8_t stop_rgba[4]; + uint8_t next_rgba[4]; + ctx_color_get_graya_u8 (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_graya_u8 (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos); + for (int c = 0; c < 2; c++) + { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); } + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_graya_u8 (rasterizer->state, color, rgba); +} + +CTX_INLINE static void +ctx_fragment_gradient_1d_RGBAF (CtxRasterizer *rasterizer, float v, float y, float *rgba) +{ + CtxGradient *g = &rasterizer->state->gradient; + if (v < 0) { v = 0; } + if (v > 1) { v = 1; } + if (g->n_stops == 0) + { + rgba[0] = rgba[1] = rgba[2] = v; + rgba[3] = 1.0; + return; + } + CtxGradientStop *stop = NULL; + CtxGradientStop *next_stop = &g->stops[0]; + CtxColor *color; + for (int s = 0; s < g->n_stops; s++) + { + stop = &g->stops[s]; + next_stop = &g->stops[s+1]; + if (s + 1 >= g->n_stops) { next_stop = NULL; } + if (v >= stop->pos && next_stop && v < next_stop->pos) + { break; } + stop = NULL; + next_stop = NULL; + } + if (stop == NULL && next_stop) + { + color = & (next_stop->color); + } + else if (stop && next_stop == NULL) + { + color = & (stop->color); + } + else if (stop && next_stop) + { + float stop_rgba[4]; + float next_rgba[4]; + ctx_color_get_rgba (rasterizer->state, & (stop->color), stop_rgba); + ctx_color_get_rgba (rasterizer->state, & (next_stop->color), next_rgba); + int dx = (v - stop->pos) / (next_stop->pos - stop->pos); + for (int c = 0; c < 4; c++) + { rgba[c] = ctx_lerpf (stop_rgba[c], next_rgba[c], dx); } + return; + } + else + { + color = & (g->stops[g->n_stops-1].color); + } + ctx_color_get_rgba (rasterizer->state, color, rgba); +} +#endif + +static void +ctx_fragment_image_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + ctx_assert (rasterizer); + ctx_assert (g); + ctx_assert (buffer); + + for (int i = 0; i < count; i ++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + int width = buffer->width; + int height = buffer->height; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba)) = 0; + } + else + { + int bpp = buffer->format->bpp/8; + if (rasterizer->state->gstate.image_smoothing) + { + uint8_t *src00 = (uint8_t *) buffer->data; + src00 += v * buffer->stride + u * bpp; + uint8_t *src01 = src00; + if ( u + 1 < width) + { + src01 = src00 + bpp; + } + uint8_t *src11 = src01; + uint8_t *src10 = src00; + if ( v + 1 < height) + { + src10 = src00 + buffer->stride; + src11 = src01 + buffer->stride; + } + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; + + switch (bpp) + { + case 1: + rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx), + ctx_lerp_u8 (src10[0], src11[0], dx), dy); + rgba[3] = 255; + break; + case 2: + rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx), + ctx_lerp_u8 (src10[0], src11[0], dx), dy); + rgba[3] = ctx_lerp_u8 (ctx_lerp_u8 (src00[1], src01[1], dx), + ctx_lerp_u8 (src10[1], src11[1], dx), dy); + break; + case 3: + for (int c = 0; c < bpp; c++) + { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + + } + rgba[3]=255; + break; + break; + case 4: + for (int c = 0; c < bpp; c++) + { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + + } + } + } + else + { + uint8_t *src = (uint8_t *) buffer->data; + src += v * buffer->stride + u * bpp; + switch (bpp) + { + case 1: + for (int c = 0; c < 3; c++) + { rgba[c] = src[0]; } + rgba[3] = 255; + break; + case 2: + for (int c = 0; c < 3; c++) + { rgba[c] = src[0]; } + rgba[3] = src[1]; + break; + case 3: + for (int c = 0; c < 3; c++) + { rgba[c] = src[c]; } + rgba[3] = 255; + break; + case 4: + for (int c = 0; c < 4; c++) + { rgba[c] = src[c]; } + break; + } + } + if (rasterizer->swap_red_green) + { + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; + } + } + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + rgba += 4; + x += dx; + y += dy; + } +} + +#if CTX_DITHER +static inline int ctx_dither_mask_a (int x, int y, int c, int divisor) +{ + /* https://pippin.gimp.org/a_dither/ */ + return ( ( ( ( (x + c * 67) + y * 236) * 119) & 255 )-127) / divisor; +} + +inline static void +ctx_dither_rgba_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green) +{ + if (dither_red_blue == 0) + { return; } + for (int c = 0; c < 3; c ++) + { + int val = rgba[c] + ctx_dither_mask_a (x, y, 0, c==1?dither_green:dither_red_blue); + rgba[c] = CTX_CLAMP (val, 0, 255); + } +} + +inline static void +ctx_dither_graya_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green) +{ + if (dither_red_blue == 0) + { return; } + for (int c = 0; c < 1; c ++) + { + int val = rgba[c] + ctx_dither_mask_a (x, y, 0, dither_red_blue); + rgba[c] = CTX_CLAMP (val, 0, 255); + } +} +#endif + +CTX_INLINE static void +ctx_RGBA8_deassociate_alpha (const uint8_t *in, uint8_t *out) +{ + uint32_t val = *((uint32_t*)(in)); + int a = val >> CTX_RGBA8_A_SHIFT; + if (a) + { + if (a ==255) + { + *((uint32_t*)(out)) = val; + } else + { + uint32_t g = (((val & CTX_RGBA8_G_MASK) * 255 / a) >> 8) & CTX_RGBA8_G_MASK; + uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * 255 / a) >> 8) & CTX_RGBA8_RB_MASK; + *((uint32_t*)(out)) = g|rb|(a << CTX_RGBA8_A_SHIFT); + } + } + else + { + *((uint32_t*)(out)) = 0; + } +} + +CTX_INLINE static void +ctx_u8_deassociate_alpha (int components, const uint8_t *in, uint8_t *out) +{ + if (in[components-1]) + { + if (in[components-1] != 255) + for (int c = 0; c < components-1; c++) + out[c] = (in[c] * 255) / in[components-1]; + else + for (int c = 0; c < components-1; c++) + out[c] = in[c]; + out[components-1] = in[components-1]; + } + else + { + for (int c = 0; c < components; c++) + out[c] = 0; + } +} + +CTX_INLINE static void +ctx_float_associate_alpha (int components, float *rgba) +{ + float alpha = rgba[components-1]; + for (int c = 0; c < components-1; c++) + rgba[c] *= alpha; +} + +CTX_INLINE static void +ctx_float_deassociate_alpha (int components, float *rgba, float *dst) +{ + float ralpha = rgba[components-1]; + if (ralpha != 0.0) ralpha = 1.0/ralpha; + + for (int c = 0; c < components-1; c++) + dst[c] = (rgba[c] * ralpha); + dst[components-1] = rgba[components-1]; +} + +CTX_INLINE static void +ctx_RGBAF_associate_alpha (float *rgba) +{ + ctx_float_associate_alpha (4, rgba); +} + +CTX_INLINE static void +ctx_RGBAF_deassociate_alpha (float *rgba, float *dst) +{ + ctx_float_deassociate_alpha (4, rgba, dst); +} + + +static inline void ctx_swap_red_green_u8 (void *data) +{ + uint8_t *rgba = (uint8_t*)data; + uint8_t tmp = rgba[0]; + rgba[0] = rgba[2]; + rgba[2] = tmp; +} + +static void +ctx_fragment_swap_red_green_u8 (void *out, int count) +{ + uint8_t *rgba = (uint8_t*)out; + for (int x = 0; x < count; x++) + { + ctx_swap_red_green_u8 (rgba); + rgba += 4; + } +} + +/**** rgb8 ***/ + +static void +ctx_fragment_image_rgb8_RGBA8_box (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int width = buffer->width; + int height = buffer->height; + + for (int i = 0; i < count; i++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 3; + rgba[3]=255; + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + int dim = (1.0 / factor) / 2; + uint64_t sum[4]={0,0,0,0}; + int count = 0; + for (int ou = - dim; ou < dim; ou++) + for (int ov = - dim; ov < dim; ov++) + { + uint8_t *src = (uint8_t *) buffer->data; + int o = (v+ov) * width + (u + ou); + + if (o>=0 && o < width * height) + { + src += o * bpp; + + for (int c = 0; c < bpp; c++) + sum[c] += src[c]; + count ++; + } + } + if (count) + for (int c = 0; c < bpp; c++) + rgba[c] = sum[c]/count; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + rgba += 4; + x += dx; + y += dy; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgb8_RGBA8_bi (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int width = buffer->width; + int height = buffer->height; + + for (int i = 0; i < count; i++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= width || + v >= height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 3; + rgba[3]=255; + uint8_t *src00 = (uint8_t *) buffer->data; + int stride = buffer->stride; + src00 += v * stride + u * bpp; + uint8_t *src01 = src00; + if ( u + 1 < width) + { + src01 = src00 + bpp; + } + uint8_t *src11 = src01; + uint8_t *src10 = src00; + if ( v + 1 < height) + { + src10 = src00 + stride; + src11 = src01 + stride; + } + float dx = (x-(int)(x)) * 255.9f; + float dy = (y-(int)(y)) * 255.9f; + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + x += dx; + y += dy; + rgba += 4; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static CTX_INLINE void +ctx_fragment_image_rgb8_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint8_t *rgba = (uint8_t *) out; + uint8_t *src = (uint8_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + int stride = buffer->stride; + + x += 0.5f; + y += 0.5f; + + if (CTX_UNLIKELY (dy == 0.0f && dx > 0.999f && dx < 1.001f)) + { + int v = y - g->texture.y0; + int u = x - g->texture.x0; + + if (v < buffer->height && v > 0) + { + int o = v * stride + u * 3; + int i; + for (i = 0; i < count && u < bwidth && u <0; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + o += 3; + u+=1; + } + + for (; i < count && u < bwidth; i++) + { + rgba[0] = src[o]; + rgba[1] = src[o+1]; + rgba[2] = src[o+2]; + rgba[3]=255; + rgba += 4; + o += 3; + u+=1; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } + else + { + for (int i = 0; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba+=4; + } + } + } + else + { + int tx0 = g->texture.x0; + int ty0 = g->texture.y0; + int u = x - tx0; + int v = y - ty0; + int i; + for (i = 0; i < count && u < bwidth && u <0; i++) + { + u = x - tx0; + v = y - ty0; + *((uint32_t*)(rgba))= 0; + x += dx; + y += dy; + rgba += 4; + } + for (; i < count && u < bwidth; i++) + { + u = x - tx0; + v = y - ty0; + if (CTX_UNLIKELY(v < 0 || v >= bheight)) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int o = v * stride + u * 3; + rgba[0] = src[o]; + rgba[1] = src[o+1]; + rgba[2] = src[o+2]; + rgba[3]=255; + } + + rgba += 4; + x += dx; + y += dy; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +#if CTX_DITHER + //ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + // rasterizer->format->dither_green); +#endif +} + + +static CTX_INLINE void +ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgb8_RGBA8 (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + if (rasterizer->state->gstate.image_smoothing) + { + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + if (factor <= 0.50f) + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + // XXX missing translate test + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#endif + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + } + } + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_DITHER + { + uint8_t *rgba = (uint8_t*)out; + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); + } +#endif +} + + +/************** rgba8 */ + +static void +ctx_fragment_image_rgba8_RGBA8_box (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + + for (int i = 0; i < count; i ++) + { + + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= buffer->width || + v >= buffer->height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 4; + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + int dim = (1.0 / factor) / 2; + uint64_t sum[4]={0,0,0,0}; + int count = 0; + int width = buffer->width; + int height = buffer->height; + for (int ou = - dim; ou < dim; ou++) + for (int ov = - dim; ov < dim; ov++) + { + uint8_t *src = (uint8_t *) buffer->data; + int o = (v+ov) * width + (u + ou); + + if (o>=0 && o < width * height) + { + src += o * bpp; + + for (int c = 0; c < bpp; c++) + sum[c] += src[c]; + count ++; + } + } + if (count) + for (int c = 0; c < bpp; c++) + rgba[c] = sum[c]/count; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + rgba += 4; + x += dx; + y += dy; + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_rgba8_RGBA8_bi (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer->color_managed; + int bwidth = buffer->width; + int bheight = buffer->height; + int i = 0; + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int u0 = x - tx0; + int v0 = y - ty0; + int u1 = x - tx0 + dx * (count-1); + int v1 = y - ty0 + dy * (count-1); + + if (u0 >= 0 && v0 >= 0 && u0 < bwidth && v0 < bheight && + u1 >= 0 && v1 >= 0 && u1 < bwidth && v1 < bheight) + { + for (i = 0; i < count;i++) + { + int u = x - tx0; + int v = y - ty0; + { + int bpp = 4; + int stride = buffer->stride; + uint8_t *src00 = ((uint8_t *) buffer->data) + v * stride + u * bpp; + uint8_t *src01 = src00 + bpp * (u + 1 < bwidth); + int got_next_row = ( v + 1 < bheight); + uint8_t *src11 = src01 + stride * got_next_row; + uint8_t *src10 = src00 + stride * got_next_row; + + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 1 + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); + +#else + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#endif + } +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + + x += dx; + y += dy; + rgba += 4; + } + return; + } + + + for (i= 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + int ut = x - tx0 + 1.5; + int vt = y - ty0 + 1.5; + if ( ut <= 0 || + vt <= 0 || + u >= buffer->width || + v >= buffer->height) + { + *((uint32_t*)(rgba))= 0; + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + int ut = x - tx0 + 1.5; + int vt = y - ty0 + 1.5; + if (CTX_UNLIKELY( + u >= buffer->width || + vt <= 0 || + ut <= 0 || + v >= buffer->height)) + { + break; + } + else if (u < 0 || v < 0) // default to next sample down and to right + { + int bpp = 4; + uint8_t *src11 = (uint8_t *) buffer->data; + int stride = buffer->stride; + src11 += (v+1) * stride + (u+1) * bpp; + uint8_t *src10 = src11; + int got_prev_pix = (u >= 0); + src10 = src11 - bpp * got_prev_pix; + int got_prev_row = (v>=0); + uint8_t *src01 = src11 - stride * got_prev_row; + uint8_t *src00 = src10 - stride * got_prev_row; + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 1 + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); + +#else + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#endif +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + //*((uint32_t*)(rgba))= 0; + } + else + { + int bpp = 4; + int stride = buffer->stride; + uint8_t *src00 = ((uint8_t *) buffer->data) + v * stride + u * bpp; + uint8_t *src01 = src00 + bpp * (u + 1 < bwidth); + int got_next_row = ( v + 1 < bheight); + uint8_t *src11 = src01 + stride * got_next_row; + uint8_t *src10 = src00 + stride * got_next_row; + float dx = (x-(int)(x)) * 255.9; + float dy = (y-(int)(y)) * 255.9; +#if 0 + for (int c = 0; c < bpp; c++) + { + rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx), + ctx_lerp_u8 (src10[c], src11[c], dx), dy); + } +#else + ((uint32_t*)(&rgba[0]))[0] = + ctx_bi_RGBA8 (((uint32_t*)src00)[0], ((uint32_t*)src01)[0], + ((uint32_t*)src10)[0], ((uint32_t*)src11)[0], dx, dy); +#endif +#if CTX_DITHER +//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, +// rasterizer->format->dither_green); +#endif + ctx_RGBA8_associate_alpha_probably_opaque (rgba); + } + + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i ++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } +} + +static void +ctx_fragment_image_rgba8_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint32_t *src = (uint32_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + x += 0.5f; + y += 0.5f; + +#if 1 + if (CTX_UNLIKELY(dy == 0.0f && dx > 0.99f && dx < 1.01f)) + { + int i = 0; + int u = x - g->texture.x0; + int v = y - g->texture.y0; + uint32_t *dst = (uint32_t*)out; + src += bwidth * v + u; + while (count && !(u >= 0 && v >= 0 && u < bwidth && v < bheight)) + { + dst[0] = 0; + dst++; + src ++; + u++; + count--; + } + for (i = 0 ; i < count && u < bwidth; i++) + { + dst[i] = ((uint32_t*)src)[i]; + ctx_RGBA8_associate_alpha_probably_opaque ((uint8_t*)&dst[i]); + u++; + } + for (;i < count; i++) + dst[i] = 0; + return; + } +#endif + + { + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int i = 0; + + int u0 = x - tx0; + int v0 = y - ty0; + int u1 = x - tx0 + dx * (count-1); + int v1 = y - ty0 + dy * (count-1); + + + if (u0 >= 0 && v0 >= 0 && u0 < bwidth && v0 < bheight && + u1 >= 0 && v1 >= 0 && u1 < bwidth && v1 < bheight) + { + for (i = 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + { + *((uint32_t*)(rgba))= src[v * bwidth + u]; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + x += dx; + y += dy; + rgba += 4; + } + return; + } + + for (i = 0; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if ((u < 0 || v < 0 || u >= bwidth || v >= bheight)) + { + *((uint32_t*)(rgba))= 0; + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if (CTX_LIKELY(u < bwidth && u >= 0 && v >= 0 && v < bheight)) + { + *((uint32_t*)(rgba))= src[v * bwidth + u]; + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +} + +#define ctx_clampi(val,min,max) \ + ctx_mini (ctx_maxi ((val), (min)), (max)) + +static inline uint32_t ctx_yuv_to_rgba32 (uint8_t y, uint8_t u, uint8_t v) +{ + int cy = ((y - 16) * 76309) >> 16; + int cr = (v - 128); + int cb = (u - 128); + int red = cy + ((cr * 104597) >> 16); + int green = cy - ((cb * 25674 + cr * 53278) >> 16); + int blue = cy + ((cb * 132201) >> 16); + return ctx_clampi (red, 0, 255) | + (ctx_clampi (green, 0, 255) << 8) | + (ctx_clampi (blue, 0, 255) << 16) | + (0xff << 24); +} + +static void +ctx_fragment_image_yuv420_RGBA8_nearest (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + if (buffer->color_managed) + buffer = buffer->color_managed; + uint8_t *src = (uint8_t *) buffer->data; + int bwidth = buffer->width; + int bheight = buffer->height; + int bwidth_div_2 = bwidth/2; + int bheight_div_2 = bheight/2; + x += 0.5f; + y += 0.5f; + + { + float tx0 = g->texture.x0; + float ty0 = g->texture.y0; + + int i = 0; + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + 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; + } + + for (; i < count; i ++) + { + int u = x - tx0; + int v = y - ty0; + if (u >= 0 && v >= 0 && u < bwidth && v < bheight) + { + uint32_t y = v * bwidth + u; + uint32_t uv = (v / 2) * bwidth_div_2 + (u / 2); + + + + *((uint32_t*)(rgba))= ctx_yuv_to_rgba32 (src[y], + //127, 127); + src[u_offset+uv], src[v_offset+uv]); + ctx_RGBA8_associate_alpha_probably_opaque (rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + } + else + { + break; + } + x += dx; + y += dy; + rgba += 4; + } + + for (; i < count; i++) + { + *((uint32_t*)(rgba))= 0; + rgba += 4; + } + } +} + +static void +ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + ctx_fragment_swap_red_green_u8 (out, count); +} + +static void +ctx_fragment_image_rgba8_RGBA8 (CtxRasterizer *rasterizer, + float x, + float y, + void *out, int count, float dx, float dy) +{ + if (rasterizer->state->gstate.image_smoothing) + { + float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + if (factor <= 0.50f) + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy); + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + // XXX: also verify translate == 0 for this fast path to be valid + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } +#endif + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy); + } + } + else + { + if (rasterizer->swap_red_green) + ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy); + else + ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy); + } + //ctx_fragment_swap_red_green_u8 (out, count); +#if CTX_DITHER + uint8_t *rgba = (uint8_t*)out; + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif +} + +static void +ctx_fragment_image_gray1_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + CtxBuffer *buffer = g->texture.buffer; + ctx_assert (rasterizer); + ctx_assert (g); + ctx_assert (buffer); + for (int i = 0; i < count; i ++) + { + int u = x - g->texture.x0; + int v = y - g->texture.y0; + if ( u < 0 || v < 0 || + u >= buffer->width || + v >= buffer->height) + { + rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0; + } + else + { + uint8_t *src = (uint8_t *) buffer->data; + src += v * buffer->stride + u / 8; + if (*src & (1<< (u & 7) ) ) + { + rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0; + } + else + { + for (int c = 0; c < 4; c++) + { rgba[c] = 255; + }//g->texture.rgba[c]; + //} + } + } + + rgba += 4; + x += dx; + y += dy; + } +} + +#if CTX_GRADIENTS +static void +ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = (ctx_hypotf_fast (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); +#if CTX_GRADIENT_CACHE + uint32_t *rgbap = (uint32_t*)&ctx_gradient_cache_u8[ctx_grad_index(v)][0]; + *((uint32_t*)rgba) = *rgbap; +#else + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba); +#endif +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + rgba += 4; + x += dx; + y += dy; + } +} + +static void +ctx_fragment_linear_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ +#if 0 + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); +#if CTX_GRADIENT_CACHE + uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(v)][0])); + *((uint32_t*)rgba) = *rgbap; +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); +#endif +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + rgba += 4; + x += dx; + y += dy; + } +#else + uint8_t *rgba = (uint8_t *) out; + + CtxSource *g = &rasterizer->state->gstate.source_fill; + float u0 = x; float v0 = y; + float ud = dx; float vd = dy; + float linear_gradient_dx = g->linear_gradient.dx; + float linear_gradient_dy = g->linear_gradient.dy; + float linear_gradient_rdelta = g->linear_gradient.rdelta; + float linear_gradient_start = g->linear_gradient.start; + float linear_gradient_length = g->linear_gradient.length; + float linear_gradient_length_recip = 1.0f/linear_gradient_length; +#if CTX_DITHER + int dither_red_blue = rasterizer->format->dither_red_blue; + int dither_green = rasterizer->format->dither_green; +#endif + linear_gradient_dx *=linear_gradient_length_recip; + linear_gradient_dy *=linear_gradient_length_recip; + + u0 *= linear_gradient_dx; + v0 *= linear_gradient_dy; + ud *= linear_gradient_dx; + vd *= linear_gradient_dy; + + u0 *= linear_gradient_rdelta; + v0 *= linear_gradient_rdelta; + ud *= linear_gradient_rdelta; + vd *= linear_gradient_rdelta; + linear_gradient_start *= linear_gradient_rdelta; + + float vv = ((u0 + v0) - linear_gradient_start); + float ud_plus_vd = ud + vd; + for (int x = 0; x < count ; x++) + { + // float vv = ((u0 + v0) - linear_gradient_start); +#if CTX_GRADIENT_CACHE + uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(vv)][0])); + *((uint32_t*)rgba) = *rgbap; +#else + _ctx_fragment_gradient_1d_RGBA8 (rasterizer, vv, 1.0, rgba); +#endif + +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, u0, v0, dither_red_blue, dither_green); +#endif + + rgba+= 4; + // u0 += ud; + // v0 += vd; + vv += ud_plus_vd; + } +#endif +} + +#endif + +static void +ctx_fragment_color_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *rgba_out = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + _ctx_color_get_rgba8 (rasterizer->state, &g->color, rgba_out); + ctx_RGBA8_associate_alpha (rgba_out); + if (rasterizer->swap_red_green) + { + int tmp = rgba_out[0]; + rgba_out[0] = rgba_out[2]; + rgba_out[2] = tmp; + } + for (int i = 1; i < count; i++, rgba_out+=4) + memcpy (rgba_out + count * 4, rgba_out, 4); +} +#if CTX_ENABLE_FLOAT + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0f, rgba); + x += dx; + y += dy; + rgba += 4; + } +} + +static void +ctx_fragment_radial_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + float v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y); + v = (v - g->radial_gradient.r0) * (g->radial_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0f, rgba); + x+=dx; + y+=dy; + rgba +=4; + } +} +#endif + + +static void +ctx_fragment_color_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *rgba = (float *) out; + for (int i = 0; i < count; i++) + { + CtxSource *g = &rasterizer->state->gstate.source_fill; + ctx_color_get_rgba (rasterizer->state, &g->color, rgba); + rgba += 4; + } +} + +static void ctx_fragment_image_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *outf = (float *) out; + uint8_t rgba[4]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int c = 0; c < 4 * count; c ++) { outf[c] = ctx_u8_to_float (rgba[c]); } +} + +static CtxFragment ctx_rasterizer_get_fragment_RGBAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_RGBAF; + case CTX_SOURCE_COLOR: return ctx_fragment_color_RGBAF; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBAF; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBAF; +#endif + } + return ctx_fragment_color_RGBAF; +} +#endif + +static CtxFragment ctx_rasterizer_get_fragment_RGBA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: + if (!buffer || !buffer->format) + return ctx_fragment_color_RGBA8; + + if (buffer->format->pixel_format == CTX_FORMAT_YUV420) + { + return ctx_fragment_image_yuv420_RGBA8_nearest; + } + else + switch (buffer->format->bpp) + { + case 1: return ctx_fragment_image_gray1_RGBA8; + case 24: + { + if (gstate->image_smoothing) + { + float factor = ctx_matrix_get_scale (&gstate->transform); + //fprintf (stderr, "{%.3f}", factor); + if (factor < 0.5f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_box_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_box; + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_nearest; + } +#endif + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_bi; + } + } + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgb8_RGBA8_nearest; + } + } + break; + case 32: + { + if (gstate->image_smoothing) + { + float factor = ctx_matrix_get_scale (&gstate->transform); + //fprintf (stderr, "[%.3f]", factor); + if (factor < 0.5f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_box_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_box; + } +#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 + else if (factor > 0.99f && factor < 1.01f) + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_nearest; + } +#endif + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_bi; + } + } + else + { + if (rasterizer->swap_red_green) + return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green; + return ctx_fragment_image_rgba8_RGBA8_nearest; + } + } + default: return ctx_fragment_image_RGBA8; + } + case CTX_SOURCE_COLOR: return ctx_fragment_color_RGBA8; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBA8; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBA8; +#endif + } + return ctx_fragment_color_RGBA8; +} + +static void +ctx_init_uv (CtxRasterizer *rasterizer, + int x0, int count, + float *u0, float *v0, float *ud, float *vd) +{ + CtxGState *gstate = &rasterizer->state->gstate; + *u0 = x0; + *v0 = rasterizer->scanline / 15;//rasterizer->aa; + float u1 = *u0 + count; + float v1 = *v0; + + _ctx_matrix_apply_transform (&gstate->source_fill.transform, u0, v0); + _ctx_matrix_apply_transform (&gstate->source_fill.transform, &u1, &v1); + + *ud = (u1-*u0) / (count); + *vd = (v1-*v0) / (count); +} + + +static void +ctx_u8_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + if (CTX_UNLIKELY(rasterizer->fragment)) + { + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + while (count--) + { + uint8_t cov = *coverage; + if (CTX_UNLIKELY(cov == 0)) + { + u0+=ud; + v0+=vd; + } + else + { + rasterizer->fragment (rasterizer, u0, v0, src, 1, ud, vd); + u0+=ud; + v0+=vd; + if (cov == 255) + { + for (int c = 0; c < components; c++) + dst[c] = src[c]; + } + else + { + uint8_t rcov = 255 - cov; + for (int c = 0; c < components; c++) + { dst[c] = (src[c]*cov + dst[c]*rcov)/255; } + } + } + dst += components; + coverage ++; + } + return; + } + + while (count--) + { + uint8_t cov = *coverage; + uint8_t rcov = 255-cov; + for (int c = 0; c < components; c++) + { dst[c] = (src[c]*cov+dst[c]*rcov)/255; } + dst += components; + coverage ++; + } +} + +static void +ctx_u8_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + while (count--) + { + uint8_t cov = *coverage; + for (int c = 0; c < components; c++) + { dst[c] = (dst[c] * (256-cov)) >> 8; } + coverage ++; + dst += components; + } +} + +typedef enum { + CTX_PORTER_DUFF_0, + CTX_PORTER_DUFF_1, + CTX_PORTER_DUFF_ALPHA, + CTX_PORTER_DUFF_1_MINUS_ALPHA, +} CtxPorterDuffFactor; + +#define \ +ctx_porter_duff_factors(mode, foo, bar)\ +{\ + switch (mode)\ + {\ + case CTX_COMPOSITE_SOURCE_ATOP:\ + f_s = CTX_PORTER_DUFF_ALPHA;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_1;\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + f_s = CTX_PORTER_DUFF_1;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_1;\ + break;\ + case CTX_COMPOSITE_XOR:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + f_s = CTX_PORTER_DUFF_ALPHA;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + case CTX_COMPOSITE_COPY:\ + f_s = CTX_PORTER_DUFF_1;\ + f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\ + break;\ + default:\ + case CTX_COMPOSITE_CLEAR:\ + f_s = CTX_PORTER_DUFF_0;\ + f_d = CTX_PORTER_DUFF_0;\ + break;\ + }\ +} + +static void +ctx_u8_source_over_normal_color (int components, + CtxRasterizer *rasterizer, + uint8_t * __restrict__ dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * __restrict__ coverage, + int count) +{ + uint8_t tsrc[5]; + *((uint32_t*)tsrc) = *((uint32_t*)src); + //ctx_u8_associate_alpha (components, tsrc); + + while (count--) + { + for (int c = 0; c < components; c++) + //dst[c] = ((tsrc[c] * *coverage)>>8) + (dst[c] * (((65536)-(tsrc[components-1] * *coverage)))>>16); + dst[c] = ((((tsrc[c] * *coverage)) + (dst[c] * (((255)-(((255+(tsrc[components-1] * *coverage))>>8))))))>>8); + coverage ++; + dst+=components; + } +} + +static void +ctx_u8_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + while (count--) + { + for (int c = 0; c < components; c++) + dst[c] = ctx_lerp_u8(dst[c],src[c],coverage[0]); + coverage ++; + dst+=components; + } +} + +static inline void +ctx_RGBA8_source_over_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc) +{ + while (count--) + { + uint32_t si_ga = ((*((uint32_t*)tsrc)) & 0xff00ff00) >> 8; + uint32_t si_rb = (*((uint32_t*)tsrc)) & 0x00ff00ff; +// uint32_t di_ga = ((*((uint32_t*)dst)) & 0xff00ff00) >> 8; +// uint32_t di_rb = (*((uint32_t*)dst)) & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + uint32_t cov = *coverage; + uint32_t racov = (256-((255+si_a*cov)>>8)); + *((uint32_t*)(dst)) = + + (((si_rb*cov+(((*((uint32_t*)(dst)))&0x00ff00ff)*racov))>>8)&0x00ff00ff)| + ((si_ga*cov+((((*((uint32_t*)(dst)))&0xff00ff00)>>8)*racov))&0xff00ff00); + + coverage ++; + tsrc += 4; + dst += 4; + } +} + +static void +ctx_RGBA8_source_copy_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc) +{ + while (count--) + { + ((uint32_t*)dst)[0]=ctx_lerp_RGBA8 (((uint32_t*)dst)[0], + ((uint32_t*)tsrc)[0], coverage[0]); + coverage ++; + tsrc += 4; + dst += 4; + } +} + +static void +ctx_RGBA8_source_over_normal_fragment (CTX_COMPOSITE_ARGUMENTS) +{ + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + uint8_t _tsrc[4 * (count)]; + rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd); + ctx_RGBA8_source_over_normal_buf (rasterizer, + dst, src, x0, coverage, count, &_tsrc[0]); +} + +static void +ctx_RGBA8_source_copy_normal_fragment (CTX_COMPOSITE_ARGUMENTS) +{ + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + uint8_t _tsrc[4 * (count)]; + rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd); + ctx_RGBA8_source_copy_normal_buf (rasterizer, + dst, src, x0, coverage, count, &_tsrc[0]); +} + + +static void +ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ +#if CTX_REFERENCE + ctx_u8_source_over_normal_color (4, rasterizer, dst, src, x0, coverage, count); +#else + uint32_t si = *((uint32_t*)src); + uint32_t si_ga = (si & 0xff00ff00) >> 8; + uint32_t si_rb = si & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + while (count--) + { + uint32_t cov = *coverage++; + uint32_t rcov = (256-((255+si_a * cov)>>8)); + 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 + di_rb * rcov) & 0xff00ff00) >> 8) | + ((si_ga * cov + 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 = *((uint32_t*)src); + uint32_t si_ga = (si & 0xff00ff00) >> 8; + uint32_t si_rb = si & 0x00ff00ff; + + while (count--) + { + uint32_t cov = *coverage++; + uint32_t di = *((uint32_t*)dst); + uint32_t di_ga = (di & 0xff00ff00); + uint32_t di_rb = (di & 0x00ff00ff); + + uint32_t d_rb = si_rb - di_rb; + uint32_t d_ga = si_ga - (di_ga>>8); + + *((uint32_t*)(dst)) = + + (((di_rb + ((d_rb * cov)>>8)) & 0x00ff00ff)) | + ((di_ga + ((d_ga * cov) & 0xff00ff00))); + dst +=4; + } +#endif +} + +static void +ctx_RGBA8_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_clear_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_u8_blend_normal (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count) +{ + for (int j = 0; j < count; j++) + { + switch (components) + { + case 3: + ((uint8_t*)(blended))[2] = ((uint8_t*)(src))[2]; + *((uint16_t*)(blended)) = *((uint16_t*)(src)); + break; + case 2: + *((uint16_t*)(blended)) = *((uint16_t*)(src)); + break; + case 5: + *((uint32_t*)(blended)) = *((uint32_t*)(src)); + ((uint8_t*)(blended))[4] = ((uint8_t*)(src))[4]; + break; + case 4: + *((uint32_t*)(blended)) = *((uint32_t*)(src)); + break; + default: + { + for (int i = 0; i<components;i++) + blended[i] = src[i]; + } + break; + } + blended+=components; + src+=components; + } +} + +/* branchless 8bit add that maxes out at 255 */ +static inline uint8_t ctx_sadd8(uint8_t a, uint8_t b) +{ + uint16_t s = (uint16_t)a+b; + return -(s>>8) | (uint8_t)s; +} + +#if CTX_BLENDING_AND_COMPOSITING + +#define ctx_u8_blend_define(name, CODE) \ +static void \ +ctx_u8_blend_##name (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count)\ +{\ + for (int j = 0; j < count; j++) { \ + uint8_t *s=src; uint8_t b[components];\ + ctx_u8_deassociate_alpha (components, dst, b);\ + CODE;\ + blended[components-1] = src[components-1];\ + ctx_u8_associate_alpha (components, blended);\ + src += components;\ + dst += components;\ + blended += components;\ + }\ +} + +#define ctx_u8_blend_define_seperable(name, CODE) \ + ctx_u8_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \ + +ctx_u8_blend_define_seperable(multiply, blended[c] = (b[c] * s[c])/255;) +ctx_u8_blend_define_seperable(screen, blended[c] = s[c] + b[c] - (s[c] * b[c])/255;) +ctx_u8_blend_define_seperable(overlay, blended[c] = b[c] < 127 ? (s[c] * b[c])/255 : + s[c] + b[c] - (s[c] * b[c])/255;) +ctx_u8_blend_define_seperable(darken, blended[c] = ctx_mini (b[c], s[c])) +ctx_u8_blend_define_seperable(lighten, blended[c] = ctx_maxi (b[c], s[c])) +ctx_u8_blend_define_seperable(color_dodge, blended[c] = b[c] == 0 ? 0 : + s[c] == 255 ? 255 : ctx_mini(255, (255 * b[c]) / (255-s[c]))) +ctx_u8_blend_define_seperable(color_burn, blended[c] = b[c] == 1 ? 1 : + s[c] == 0 ? 0 : 255 - ctx_mini(255, (255*(255 - b[c])) / s[c])) +ctx_u8_blend_define_seperable(hard_light, blended[c] = s[c] < 127 ? (b[c] * s[c])/255 : + b[c] + s[c] - (b[c] * s[c])/255;) +ctx_u8_blend_define_seperable(difference, blended[c] = (b[c] - s[c])) +ctx_u8_blend_define_seperable(divide, blended[c] = s[c]?(255 * b[c]) / s[c]:0) +ctx_u8_blend_define_seperable(addition, blended[c] = ctx_sadd8 (s[c], b[c])) +ctx_u8_blend_define_seperable(subtract, blended[c] = ctx_maxi(0, s[c]-b[c])) +ctx_u8_blend_define_seperable(exclusion, blended[c] = b[c] + s[c] - 2 * (b[c] * s[c]/255)) +ctx_u8_blend_define_seperable(soft_light, + if (s[c] <= 255/2) + { + blended[c] = b[c] - (255 - 2 * s[c]) * b[c] * (255 - b[c]) / (255 * 255); + } + else + { + int d; + if (b[c] <= 255/4) + d = (((16 * b[c] - 12 * 255)/255 * b[c] + 4 * 255) * b[c])/255; + else + d = ctx_sqrtf(b[c]/255.0) * 255.4; + blended[c] = (b[c] + (2 * s[c] - 255) * (d - b[c]))/255; + } +) + +static int ctx_int_get_max (int components, int *c) +{ + int max = 0; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] > max) max = c[i]; + } + return max; +} + +static int ctx_int_get_min (int components, int *c) +{ + int min = 400; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + } + return min; +} + +static int ctx_int_get_lum (int components, int *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + int sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + break; + } +} + +static int ctx_u8_get_lum (int components, uint8_t *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + int sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + break; + } +} +static int ctx_u8_get_sat (int components, uint8_t *c) +{ + switch (components) + { + case 3: + case 4: + { int r = c[0]; + int g = c[1]; + int b = c[2]; + return ctx_maxi(r, ctx_maxi(g,b)) - ctx_mini(r,ctx_mini(g,b)); + } + break; + case 1: + case 2: + return 0.0; + break; + default: + { + int min = 1000; + int max = -1000; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + if (c[i] > max) max = c[i]; + } + return max-min; + } + break; + } +} + +static void ctx_u8_set_lum (int components, uint8_t *c, uint8_t lum) +{ + int d = lum - ctx_u8_get_lum (components, c); + int tc[components]; + for (int i = 0; i < components - 1; i++) + { + tc[i] = c[i] + d; + } + + int l = ctx_int_get_lum (components, tc); + int n = ctx_int_get_min (components, tc); + int x = ctx_int_get_max (components, tc); + + if (n < 0 && l!=n) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * l) / (l-n)); + } + + if (x > 255 && x!=l) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * (255 - l)) / (x-l)); + } + for (int i = 0; i < components - 1; i++) + c[i] = tc[i]; +} + +static void ctx_u8_set_sat (int components, uint8_t *c, uint8_t sat) +{ + int max = 0, mid = 1, min = 2; + + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + if (c[mid] > c[max]){int t = mid; mid = max; max = t;} + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + + if (c[max] > c[min]) + { + c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]); + c[max] = sat; + } + else + { + c[mid] = c[max] = 0; + } + c[min] = 0; +} + +ctx_u8_blend_define(color, + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_u8_set_lum(components, blended, ctx_u8_get_lum (components, s)); +) + +ctx_u8_blend_define(hue, + int in_sat = ctx_u8_get_sat(components, b); + int in_lum = ctx_u8_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_u8_set_sat(components, blended, in_sat); + ctx_u8_set_lum(components, blended, in_lum); +) + +ctx_u8_blend_define(saturation, + int in_sat = ctx_u8_get_sat(components, s); + int in_lum = ctx_u8_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_u8_set_sat(components, blended, in_sat); + ctx_u8_set_lum(components, blended, in_lum); +) + +ctx_u8_blend_define(luminosity, + int in_lum = ctx_u8_get_lum(components, s); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_u8_set_lum(components, blended, in_lum); +) +#endif + +CTX_INLINE static void +ctx_u8_blend (int components, CtxBlend blend, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count) +{ +#if CTX_BLENDING_AND_COMPOSITING + switch (blend) + { + case CTX_BLEND_NORMAL: ctx_u8_blend_normal (components, dst, src, blended, count); break; + case CTX_BLEND_MULTIPLY: ctx_u8_blend_multiply (components, dst, src, blended, count); break; + case CTX_BLEND_SCREEN: ctx_u8_blend_screen (components, dst, src, blended, count); break; + case CTX_BLEND_OVERLAY: ctx_u8_blend_overlay (components, dst, src, blended, count); break; + case CTX_BLEND_DARKEN: ctx_u8_blend_darken (components, dst, src, blended, count); break; + case CTX_BLEND_LIGHTEN: ctx_u8_blend_lighten (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR_DODGE: ctx_u8_blend_color_dodge (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR_BURN: ctx_u8_blend_color_burn (components, dst, src, blended, count); break; + case CTX_BLEND_HARD_LIGHT: ctx_u8_blend_hard_light (components, dst, src, blended, count); break; + case CTX_BLEND_SOFT_LIGHT: ctx_u8_blend_soft_light (components, dst, src, blended, count); break; + case CTX_BLEND_DIFFERENCE: ctx_u8_blend_difference (components, dst, src, blended, count); break; + case CTX_BLEND_EXCLUSION: ctx_u8_blend_exclusion (components, dst, src, blended, count); break; + case CTX_BLEND_COLOR: ctx_u8_blend_color (components, dst, src, blended, count); break; + case CTX_BLEND_HUE: ctx_u8_blend_hue (components, dst, src, blended, count); break; + case CTX_BLEND_SATURATION: ctx_u8_blend_saturation (components, dst, src, blended, count); break; + case CTX_BLEND_LUMINOSITY: ctx_u8_blend_luminosity (components, dst, src, blended, count); break; + case CTX_BLEND_ADDITION: ctx_u8_blend_addition (components, dst, src, blended, count); break; + case CTX_BLEND_DIVIDE: ctx_u8_blend_divide (components, dst, src, blended, count); break; + case CTX_BLEND_SUBTRACT: ctx_u8_blend_subtract (components, dst, src, blended, count); break; + } +#else + switch (blend) + { + default: ctx_u8_blend_normal (components, dst, src, blended, count); break; + } + +#endif +} + +CTX_INLINE static void +__ctx_u8_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * dst, + uint8_t * src, + int x0, + uint8_t * __restrict__ coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + CtxPorterDuffFactor f_s, f_d; + ctx_porter_duff_factors (compositing_mode, &f_s, &f_d); + CtxGState *gstate = &rasterizer->state->gstate; + uint8_t global_alpha_u8 = gstate->global_alpha_u8; + uint8_t tsrc[components * count]; + int src_step = 0; + + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + src = &tsrc[0]; + fragment (rasterizer, 0, 0, src, 1, 0, 0); + if (blend != CTX_BLEND_NORMAL) + ctx_u8_blend (components, blend, dst, src, src, 1); + } + else + { + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + src = &tsrc[0]; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + fragment (rasterizer, u0, v0, src, count, ud, vd); + if (blend != CTX_BLEND_NORMAL) + ctx_u8_blend (components, blend, dst, src, src, count); + src_step = components; + } + + while (count--) + { + uint32_t cov = *coverage; + + if (CTX_UNLIKELY(global_alpha_u8 != 255)) + cov = (cov * global_alpha_u8 + 255) >> 8; + + uint8_t csrc[components]; + for (int c = 0; c < components; c++) + csrc[c] = (src[c] * cov + 255) >> 8; + + for (int c = 0; c < components; c++) + { + uint32_t res = 0; +#if 1 + switch (f_s) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += (csrc[c] ); break; + case CTX_PORTER_DUFF_ALPHA: res += (csrc[c] * dst[components-1] + 255) >> 8; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (256-dst[components-1])) >> 8; break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += dst[c]; break; + case CTX_PORTER_DUFF_ALPHA: res += (dst[c] * csrc[components-1] + 255) >> 8; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (256-csrc[components-1])) >> 8; break; + } +#else + switch (f_s) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += (csrc[c] ); break; + case CTX_PORTER_DUFF_ALPHA: res += (csrc[c] * dst[components-1])/255; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (255-dst[components-1]))/255; break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: break; + case CTX_PORTER_DUFF_1: res += dst[c]; break; + case CTX_PORTER_DUFF_ALPHA: res += (dst[c] * csrc[components-1])/255; break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (255-csrc[components-1]))/255; break; + } +#endif + dst[c] = res; + } + coverage ++; + src+=src_step; + dst+=components; + } +} + +CTX_INLINE static void +_ctx_u8_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + __ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, compositing_mode, fragment, blend); +} + +#define _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend) \ + switch (rasterizer->state->gstate.compositing_mode) \ + { \ + case CTX_COMPOSITE_SOURCE_ATOP: \ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \ + CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_XOR:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_XOR, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_COPY:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_COPY, fragment, blend);\ + break;\ + case CTX_COMPOSITE_CLEAR:\ + _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_CLEAR, fragment, blend);\ + break;\ + } + +/* generating one function per compositing_mode would be slightly more efficient, + * but on embedded targets leads to slightly more code bloat, + * here we trade off a slight amount of performance + */ +#define ctx_u8_porter_duff(comp_format, components, source, fragment, blend) \ +static void \ +ctx_##comp_format##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \ +{ \ + _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend);\ +} + +ctx_u8_porter_duff(RGBA8, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +//ctx_u8_porter_duff(comp_name, components,color_##blend_name, NULL, blend_mode) + +static void +ctx_RGBA8_nop (CTX_COMPOSITE_ARGUMENTS) +{ +} + + +static void +ctx_setup_RGBA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 4; + rasterizer->fragment = ctx_rasterizer_get_fragment_RGBA8 (rasterizer); + rasterizer->comp_op = ctx_RGBA8_porter_duff_generic; + + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + ctx_fragment_color_RGBA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0); + if (gstate->global_alpha_u8 != 255) + { + for (int c = 0; c < 4; c ++) + rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8 + 255)>>8; + } + + if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color; + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (rasterizer->color[components-1] == 255) + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color; + else + rasterizer->comp_op = ctx_RGBA8_source_over_normal_color; + } + else if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + { + rasterizer->comp_op = ctx_RGBA8_clear_normal; + } + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + rasterizer->comp_op = ctx_RGBA8_source_over_normal_fragment; + } + else if (gstate->blend_mode == CTX_BLEND_NORMAL && + gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBA8_source_copy_normal_fragment; + } +} + +static void +ctx_composite_convert (CTX_COMPOSITE_ARGUMENTS) +{ + uint8_t pixels[count * rasterizer->format->ebpp]; + rasterizer->format->to_comp (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + rasterizer->format->from_comp (rasterizer, x0, &pixels[0], dst, count); +} + +#if CTX_ENABLE_FLOAT +static void +ctx_float_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + + while (count--) + { + uint8_t cov = *coverage; + float covf = ctx_u8_to_float (cov); + for (int c = 0; c < components; c++) + dstf[c] = dstf[c]*(1.0-covf) + srcf[c]*covf; + dstf += components; + coverage ++; + } +} + +static void +ctx_float_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + while (count--) + { +#if 0 + uint8_t cov = *coverage; + if (cov == 0) + { + } + else if (cov == 255) + { +#endif + switch (components) + { + case 2: + ((uint64_t*)(dst))[0] = 0; + break; + case 4: + ((uint64_t*)(dst))[0] = 0; + ((uint64_t*)(dst))[1] = 0; + break; + default: + for (int c = 0; c < components; c++) + dstf[c] = 0.0f; + } +#if 0 + } + else + { + float ralpha = 1.0 - ctx_u8_to_float (cov); + for (int c = 0; c < components; c++) + { dstf[c] = (dstf[c] * ralpha); } + } + coverage ++; +#endif + dstf += components; + } +} + + +static void +ctx_float_source_over_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + while (count--) + { + uint8_t cov = *coverage; + float fcov = ctx_u8_to_float (cov); + float ralpha = 1.0f - fcov * srcf[components-1]; + for (int c = 0; c < components-1; c++) + dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha); + coverage ++; + dstf+= components; + } +} + +static void +ctx_float_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + float *srcf = (float*)src; + + while (count--) + { + uint8_t cov = *coverage; + float fcov = ctx_u8_to_float (cov); + float ralpha = 1.0f - fcov; + for (int c = 0; c < components-1; c++) + dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha); + coverage ++; + dstf+= components; + } +} + +inline static void +ctx_float_blend_normal (int components, float *dst, float *src, float *blended) +{ + float a = src[components-1]; + for (int c = 0; c < components - 1; c++) + blended[c] = src[c] * a; + blended[components-1]=a; +} + +static float ctx_float_get_max (int components, float *c) +{ + float max = -1000.0f; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] > max) max = c[i]; + } + return max; +} + +static float ctx_float_get_min (int components, float *c) +{ + float min = 400.0; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + } + return min; +} + +static float ctx_float_get_lum (int components, float *c) +{ + switch (components) + { + case 3: + case 4: + return CTX_CSS_RGB_TO_LUMINANCE(c); + case 1: + case 2: + return c[0]; + break; + default: + { + float sum = 0; + for (int i = 0; i < components - 1; i ++) + { + sum += c[i]; + } + return sum / (components - 1); + } + } +} + +static float ctx_float_get_sat (int components, float *c) +{ + switch (components) + { + case 3: + case 4: + { float r = c[0]; + float g = c[1]; + float b = c[2]; + return ctx_maxf(r, ctx_maxf(g,b)) - ctx_minf(r,ctx_minf(g,b)); + } + break; + case 1: + case 2: return 0.0; + break; + default: + { + float min = 1000; + float max = -1000; + for (int i = 0; i < components - 1; i ++) + { + if (c[i] < min) min = c[i]; + if (c[i] > max) max = c[i]; + } + return max-min; + } + } +} + +static void ctx_float_set_lum (int components, float *c, float lum) +{ + float d = lum - ctx_float_get_lum (components, c); + float tc[components]; + for (int i = 0; i < components - 1; i++) + { + tc[i] = c[i] + d; + } + + float l = ctx_float_get_lum (components, tc); + float n = ctx_float_get_min (components, tc); + float x = ctx_float_get_max (components, tc); + + if (n < 0.0f && l != n) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * l) / (l-n)); + } + + if (x > 1.0f && x != l) + { + for (int i = 0; i < components - 1; i++) + tc[i] = l + (((tc[i] - l) * (1.0f - l)) / (x-l)); + } + for (int i = 0; i < components - 1; i++) + c[i] = tc[i]; +} + +static void ctx_float_set_sat (int components, float *c, float sat) +{ + int max = 0, mid = 1, min = 2; + + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + if (c[mid] > c[max]){int t = mid; mid = max; max = t;} + if (c[min] > c[mid]){int t = min; min = mid; mid = t;} + + if (c[max] > c[min]) + { + c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]); + c[max] = sat; + } + else + { + c[mid] = c[max] = 0.0f; + } + c[min] = 0.0f; + +} + +#define ctx_float_blend_define(name, CODE) \ +static void \ +ctx_float_blend_##name (int components, float * __restrict__ dst, float *src, float *blended)\ +{\ + float *s = src; float b[components];\ + ctx_float_deassociate_alpha (components, dst, b);\ + CODE;\ + blended[components-1] = s[components-1];\ + ctx_float_associate_alpha (components, blended);\ +} + +#define ctx_float_blend_define_seperable(name, CODE) \ + ctx_float_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \ + +ctx_float_blend_define_seperable(multiply, blended[c] = (b[c] * s[c]);) +ctx_float_blend_define_seperable(screen, blended[c] = b[c] + s[c] - (b[c] * s[c]);) +ctx_float_blend_define_seperable(overlay, blended[c] = b[c] < 0.5f ? (s[c] * b[c]) : + s[c] + b[c] - (s[c] * b[c]);) +ctx_float_blend_define_seperable(darken, blended[c] = ctx_minf (b[c], s[c])) +ctx_float_blend_define_seperable(lighten, blended[c] = ctx_maxf (b[c], s[c])) +ctx_float_blend_define_seperable(color_dodge, blended[c] = (b[c] == 0.0f) ? 0.0f : + s[c] == 1.0f ? 1.0f : ctx_minf(1.0f, (b[c]) / (1.0f-s[c]))) +ctx_float_blend_define_seperable(color_burn, blended[c] = (b[c] == 1.0f) ? 1.0f : + s[c] == 0.0f ? 0.0f : 1.0f - ctx_minf(1.0f, ((1.0f - b[c])) / s[c])) +ctx_float_blend_define_seperable(hard_light, blended[c] = s[c] < 0.f ? (b[c] * s[c]) : + b[c] + s[c] - (b[c] * s[c]);) +ctx_float_blend_define_seperable(difference, blended[c] = (b[c] - s[c])) + +ctx_float_blend_define_seperable(divide, blended[c] = s[c]?(b[c]) / s[c]:0.0f) +ctx_float_blend_define_seperable(addition, blended[c] = s[c]+b[c]) +ctx_float_blend_define_seperable(subtract, blended[c] = s[c]-b[c]) + +ctx_float_blend_define_seperable(exclusion, blended[c] = b[c] + s[c] - 2.0f * b[c] * s[c]) +ctx_float_blend_define_seperable(soft_light, + if (s[c] <= 0.5f) + { + blended[c] = b[c] - (1.0f - 2.0f * s[c]) * b[c] * (1.0f - b[c]); + } + else + { + int d; + if (b[c] <= 255/4) + d = (((16 * b[c] - 12.0f) * b[c] + 4.0f) * b[c]); + else + d = ctx_sqrtf(b[c]); + blended[c] = (b[c] + (2.0f * s[c] - 1.0f) * (d - b[c])); + } +) + + +ctx_float_blend_define(color, + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_float_set_lum(components, blended, ctx_float_get_lum (components, s)); +) + +ctx_float_blend_define(hue, + float in_sat = ctx_float_get_sat(components, b); + float in_lum = ctx_float_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = s[i]; + ctx_float_set_sat(components, blended, in_sat); + ctx_float_set_lum(components, blended, in_lum); +) + +ctx_float_blend_define(saturation, + float in_sat = ctx_float_get_sat(components, s); + float in_lum = ctx_float_get_lum(components, b); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_float_set_sat(components, blended, in_sat); + ctx_float_set_lum(components, blended, in_lum); +) + +ctx_float_blend_define(luminosity, + float in_lum = ctx_float_get_lum(components, s); + for (int i = 0; i < components; i++) + blended[i] = b[i]; + ctx_float_set_lum(components, blended, in_lum); +) + +inline static void +ctx_float_blend (int components, CtxBlend blend, float * __restrict__ dst, float *src, float *blended) +{ + switch (blend) + { + case CTX_BLEND_NORMAL: ctx_float_blend_normal (components, dst, src, blended); break; + case CTX_BLEND_MULTIPLY: ctx_float_blend_multiply (components, dst, src, blended); break; + case CTX_BLEND_SCREEN: ctx_float_blend_screen (components, dst, src, blended); break; + case CTX_BLEND_OVERLAY: ctx_float_blend_overlay (components, dst, src, blended); break; + case CTX_BLEND_DARKEN: ctx_float_blend_darken (components, dst, src, blended); break; + case CTX_BLEND_LIGHTEN: ctx_float_blend_lighten (components, dst, src, blended); break; + case CTX_BLEND_COLOR_DODGE: ctx_float_blend_color_dodge (components, dst, src, blended); break; + case CTX_BLEND_COLOR_BURN: ctx_float_blend_color_burn (components, dst, src, blended); break; + case CTX_BLEND_HARD_LIGHT: ctx_float_blend_hard_light (components, dst, src, blended); break; + case CTX_BLEND_SOFT_LIGHT: ctx_float_blend_soft_light (components, dst, src, blended); break; + case CTX_BLEND_DIFFERENCE: ctx_float_blend_difference (components, dst, src, blended); break; + case CTX_BLEND_EXCLUSION: ctx_float_blend_exclusion (components, dst, src, blended); break; + case CTX_BLEND_COLOR: ctx_float_blend_color (components, dst, src, blended); break; + case CTX_BLEND_HUE: ctx_float_blend_hue (components, dst, src, blended); break; + case CTX_BLEND_SATURATION: ctx_float_blend_saturation (components, dst, src, blended); break; + case CTX_BLEND_LUMINOSITY: ctx_float_blend_luminosity (components, dst, src, blended); break; + case CTX_BLEND_ADDITION: ctx_float_blend_addition (components, dst, src, blended); break; + case CTX_BLEND_SUBTRACT: ctx_float_blend_subtract (components, dst, src, blended); break; + case CTX_BLEND_DIVIDE: ctx_float_blend_divide (components, dst, src, blended); break; + } +} + +/* this is the grunt working function, when inlined code-path elimination makes + * it produce efficient code. + */ +CTX_INLINE static void +ctx_float_porter_duff (CtxRasterizer *rasterizer, + int components, + uint8_t * __restrict__ dst, + uint8_t * __restrict__ src, + int x0, + uint8_t * __restrict__ coverage, + int count, + CtxCompositingMode compositing_mode, + CtxFragment fragment, + CtxBlend blend) +{ + float *dstf = (float*)dst; + + CtxPorterDuffFactor f_s, f_d; + ctx_porter_duff_factors (compositing_mode, &f_s, &f_d); + uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8; + float global_alpha_f = rasterizer->state->gstate.global_alpha_f; + + { + float tsrc[components]; + float u0 = 0; float v0 = 0; + float ud = 0; float vd = 0; + + ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd); + + while (count--) + { + uint8_t cov = *coverage; +#if 1 + if ( + CTX_UNLIKELY((compositing_mode == CTX_COMPOSITE_DESTINATION_OVER && dst[components-1] == 1.0f)|| + (cov == 0 && (compositing_mode == CTX_COMPOSITE_SOURCE_OVER || + compositing_mode == CTX_COMPOSITE_XOR || + compositing_mode == CTX_COMPOSITE_DESTINATION_OUT || + compositing_mode == CTX_COMPOSITE_SOURCE_ATOP + )))) + { + u0 += ud; + v0 += vd; + coverage ++; + dstf+=components; + continue; + } +#endif + + fragment (rasterizer, u0, v0, tsrc, 1, ud, vd); + if (blend != CTX_BLEND_NORMAL) + ctx_float_blend (components, blend, dstf, tsrc, tsrc); + u0 += ud; + v0 += vd; + float covf = ctx_u8_to_float (cov); + + if (global_alpha_u8 != 255) + covf = covf * global_alpha_f; + + if (covf != 1.0f) + { + for (int c = 0; c < components; c++) + tsrc[c] *= covf; + } + + for (int c = 0; c < components; c++) + { + float res; + /* these switches and this whole function is written to be + * inlined when compiled when the enum values passed in are + * constants. + */ + switch (f_s) + { + case CTX_PORTER_DUFF_0: res = 0.0f; break; + case CTX_PORTER_DUFF_1: res = (tsrc[c]); break; + case CTX_PORTER_DUFF_ALPHA: res = (tsrc[c] * dstf[components-1]); break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: res = (tsrc[c] * (1.0f-dstf[components-1])); break; + } + switch (f_d) + { + case CTX_PORTER_DUFF_0: dstf[c] = res; break; + case CTX_PORTER_DUFF_1: dstf[c] = res + (dstf[c]); break; + case CTX_PORTER_DUFF_ALPHA: dstf[c] = res + (dstf[c] * tsrc[components-1]); break; + case CTX_PORTER_DUFF_1_MINUS_ALPHA: dstf[c] = res + (dstf[c] * (1.0f-tsrc[components-1])); break; + } + } + coverage ++; + dstf +=components; + } + } +} + +/* generating one function per compositing_mode would be slightly more efficient, + * but on embedded targets leads to slightly more code bloat, + * here we trade off a slight amount of performance + */ +#define ctx_float_porter_duff(compformat, components, source, fragment, blend) \ +static void \ +ctx_##compformat##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \ +{ \ + switch (rasterizer->state->gstate.compositing_mode) \ + { \ + case CTX_COMPOSITE_SOURCE_ATOP: \ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \ + CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_ATOP:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_IN:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OVER:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OVER:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\ + break;\ + case CTX_COMPOSITE_XOR:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_XOR, fragment, blend);\ + break;\ + case CTX_COMPOSITE_DESTINATION_OUT:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_OUT:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\ + break;\ + case CTX_COMPOSITE_SOURCE_IN:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_SOURCE_IN, fragment, blend);\ + break;\ + case CTX_COMPOSITE_COPY:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_COPY, fragment, blend);\ + break;\ + case CTX_COMPOSITE_CLEAR:\ + ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\ + CTX_COMPOSITE_CLEAR, fragment, blend);\ + break;\ + }\ +} +#endif + +#if CTX_ENABLE_RGBAF + +ctx_float_porter_duff(RGBAF, 4,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(RGBAF, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +#if CTX_GRADIENTS +ctx_float_porter_duff(RGBAF, 4,linear_gradient, ctx_fragment_linear_gradient_RGBAF, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(RGBAF, 4,radial_gradient, ctx_fragment_radial_gradient_RGBAF, rasterizer->state->gstate.blend_mode) +#endif +ctx_float_porter_duff(RGBAF, 4,image, ctx_fragment_image_RGBAF, rasterizer->state->gstate.blend_mode) + + +#if CTX_GRADIENTS +#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\ +ctx_float_porter_duff(comp_name, components,color_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,generic_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,linear_gradient_##blend_name, ctx_fragment_linear_gradient_RGBA8, blend_mode)\ +ctx_float_porter_duff(comp_name, components,radial_gradient_##blend_name, ctx_fragment_radial_gradient_RGBA8, blend_mode)\ +ctx_float_porter_duff(comp_name, components,image_##blend_name, ctx_fragment_image_RGBAF, blend_mode) +#else +#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\ +ctx_float_porter_duff(comp_name, components,color_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,generic_##blend_name, rasterizer->fragment, blend_mode)\ +ctx_float_porter_duff(comp_name, components,image_##blend_name, ctx_fragment_image_RGBAF, blend_mode) +#endif + +ctx_float_porter_duff_blend(RGBAF, 4, CTX_BLEND_NORMAL, normal) + + +static void +ctx_RGBAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_RGBAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (4, rasterizer, dst, src, x0, coverage, count); +} + +#if 1 +static void +ctx_RGBAF_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_over_normal_color (4, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif +#endif + +static void +ctx_setup_RGBAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 4; + rasterizer->fragment = ctx_rasterizer_get_fragment_RGBAF (rasterizer); +#if 1 + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_RGBAF_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; + } + else +#endif + { + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_RGBAF_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_RGBAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + { + rasterizer->comp_op = ctx_RGBA8_nop; + } + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)(rasterizer->color))[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; + // else if (((float*)(rasterizer->color))[components-1] == 0.0f) + else + rasterizer->comp_op = ctx_RGBAF_source_over_normal_color; + //rasterizer->comp_op = ctx_RGBAF_source_over_normal_color; + } + else + { + rasterizer->comp_op = ctx_RGBAF_porter_duff_color_normal; + } + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient_normal; + break; + case CTX_SOURCE_RADIAL_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient_normal; + break; +#endif + case CTX_SOURCE_TEXTURE: + rasterizer->comp_op = ctx_RGBAF_porter_duff_image_normal; + break; + default: + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_RGBAF_porter_duff_color; + //rasterizer->fragment = NULL; + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient; + break; + case CTX_SOURCE_RADIAL_GRADIENT: + rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient; + break; +#endif + case CTX_SOURCE_TEXTURE: + rasterizer->comp_op = ctx_RGBAF_porter_duff_image; + break; + default: + rasterizer->comp_op = ctx_RGBAF_porter_duff_generic; + break; + } + break; + } +#endif +} + +#endif +#if CTX_ENABLE_GRAYAF + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float rgba[4]; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0 ; i < count; i++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0, rgba); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba); + ((float*)out)[1] = rgba[3]; + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} + +static void +ctx_fragment_radial_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float rgba[4]; + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i ++) + { + float v = 0.0f; + if ((g->radial_gradient.r1-g->radial_gradient.r0) > 0.0f) + { + v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y); + v = (v - g->radial_gradient.r0) / (g->radial_gradient.rdelta); + } + ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0, rgba); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba); + ((float*)out)[1] = rgba[3]; + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} +#endif + +static void +ctx_fragment_color_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + for (int i = 0; i < count; i++) + { + ctx_color_get_graya (rasterizer->state, &g->color, (float*)out); + out = ((float*)(out)) + 2; + x += dx; + y += dy; + } +} + +static void ctx_fragment_image_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t rgba[4]; + float rgbaf[4]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int c = 0; c < 2 * count; c ++) { + rgbaf[c] = ctx_u8_to_float (rgba[c]); + ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgbaf); + ((float*)out)[1] = rgbaf[3]; + out = ((float*)out) + 2; + } +} + +static CtxFragment ctx_rasterizer_get_fragment_GRAYAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_GRAYAF; + case CTX_SOURCE_COLOR: return ctx_fragment_color_GRAYAF; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYAF; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYAF; +#endif + } + return ctx_fragment_color_GRAYAF; +} + +ctx_float_porter_duff(GRAYAF, 2,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff(GRAYAF, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +ctx_float_porter_duff(GRAYAF, 2,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_float_porter_duff(GRAYAF, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_GRAYAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +static void +ctx_setup_GRAYAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 2; + rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYAF (rasterizer); + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color; + // rasterizer->fragment = NULL; + ctx_color_get_rgba (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color); + if (gstate->global_alpha_u8 != 255) + for (int c = 0; c < components; c ++) + ((float*)rasterizer->color)[c] *= gstate->global_alpha_f; + } + else + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_GRAYAF_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_GRAYAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; +#if 1 + else //if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_GRAYAF_source_copy_normal_color; +#endif + //else + // rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal; +// rasterizer->fragment = NULL; + } + else + { + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal; +// rasterizer->fragment = NULL; + } + break; + default: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_color; +// rasterizer->fragment = NULL; + break; + default: + rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic; + break; + } + break; + } +#endif +} + +#endif +#if CTX_ENABLE_GRAYF + +static void +ctx_composite_GRAYF (CTX_COMPOSITE_ARGUMENTS) +{ + float *dstf = (float*)dst; + + float temp[count*2]; + for (int i = 0; i < count; i++) + { + temp[i*2] = dstf[i]; + temp[i*2+1] = 1.0f; + } + rasterizer->comp_op (rasterizer, (uint8_t*)temp, rasterizer->color, x0, coverage, count); + for (int i = 0; i < count; i++) + { + dstf[i] = temp[i*2]; + } +} + +#endif +#if CTX_ENABLE_BGRA8 + +inline static void +ctx_swap_red_green (uint8_t *rgba) +{ + uint32_t *buf = (uint32_t *) rgba; + uint32_t orig = *buf; + uint32_t green_alpha = (orig & 0xff00ff00); + uint32_t red_blue = (orig & 0x00ff00ff); + uint32_t red = red_blue << 16; + uint32_t blue = red_blue >> 16; + *buf = green_alpha | red | blue; +} + +static void +ctx_BGRA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + uint32_t *srci = (uint32_t *) buf; + uint32_t *dsti = (uint32_t *) rgba; + while (count--) + { + uint32_t val = *srci++; + ctx_swap_red_green ( (uint8_t *) &val); + *dsti++ = val; + } +} + +static void +ctx_RGBA8_to_BGRA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + ctx_BGRA8_to_RGBA8 (rasterizer, x, rgba, (uint8_t *) buf, count); +} + +static void +ctx_composite_BGRA8 (CTX_COMPOSITE_ARGUMENTS) +{ + // for better performance, this could be done without a pre/post conversion, + // by swapping R and B of source instead... as long as it is a color instead + // of gradient or image + // + // + uint8_t pixels[count * 4]; + ctx_BGRA8_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_BGRA8_to_RGBA8 (rasterizer, x0, &pixels[0], dst, count); +} + + +#endif +#if CTX_ENABLE_CMYKAF + +static void +ctx_fragment_other_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + float *cmyka = (float*)out; + float _rgba[4 * count]; + float *rgba = &_rgba[0]; + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: + ctx_fragment_image_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; + case CTX_SOURCE_COLOR: + ctx_fragment_color_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: + ctx_fragment_linear_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; + case CTX_SOURCE_RADIAL_GRADIENT: + ctx_fragment_radial_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy); + break; +#endif + default: + rgba[0]=rgba[1]=rgba[2]=rgba[3]=0.0f; + break; + } + for (int i = 0; i < count; i++) + { + cmyka[4]=rgba[3]; + ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2], &cmyka[0], &cmyka[1], &cmyka[2], &cmyka[3]); + cmyka += 5; + rgba += 4; + } +} + +static void +ctx_fragment_color_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxGState *gstate = &rasterizer->state->gstate; + float *cmyka = (float*)out; + float cmyka_in[5]; + ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, cmyka_in); + for (int i = 0; i < count; i++) + { + for (int c = 0; c < 4; c ++) + { + cmyka[c] = (1.0f - cmyka_in[c]); + } + cmyka[4] = cmyka_in[4]; + cmyka += 5; + } +} + +static CtxFragment ctx_rasterizer_get_fragment_CMYKAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + return ctx_fragment_color_CMYKAF; + } + return ctx_fragment_other_CMYKAF; +} + +ctx_float_porter_duff (CMYKAF, 5,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_float_porter_duff (CMYKAF, 5,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +ctx_float_porter_duff (CMYKAF, 5,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_float_porter_duff (CMYKAF, 5,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_CMYKAF_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_copy_normal (5, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_CMYKAF_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_clear_normal (5, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_CMYKAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_float_source_copy_normal_color (5, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +static void +ctx_setup_CMYKAF (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 5; + rasterizer->fragment = ctx_rasterizer_get_fragment_CMYKAF (rasterizer); + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color; + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + // rasterizer->fragment = NULL; + ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color); + if (gstate->global_alpha_u8 != 255) + ((float*)rasterizer->color)[components-1] *= gstate->global_alpha_f; + } + else + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_CMYKAF_clear_normal; +#if 1 + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_CMYKAF_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (((float*)rasterizer->color)[components-1] == 0.0f) + rasterizer->comp_op = ctx_RGBA8_nop; +#if 1 + else //if (((float*)rasterizer->color)[components-1] == 1.0f) + rasterizer->comp_op = ctx_CMYKAF_source_copy_normal_color; + // else + // rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal; + rasterizer->fragment = NULL; +#endif + } + else + { + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal; + // rasterizer->fragment = NULL; + } + break; + default: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic_normal; + break; + } + break; + default: + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_color; + // rasterizer->fragment = NULL; + break; + default: + rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic; + break; + } + break; + } +#endif +#endif +} + +#endif +#if CTX_ENABLE_CMYKA8 + +static void +ctx_CMYKA8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + for (int c = 0; c < 4; c ++) + { dst[c] = ctx_u8_to_float ( (255-src[c]) ); } + dst[4] = ctx_u8_to_float (src[4]); + for (int c = 0; c < 4; c++) + { dst[c] *= dst[4]; } + src += 5; + dst += 5; + } +} +static void +ctx_CMYKAF_to_CMYKA8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + int a = ctx_float_to_u8 (src[4]); + if (a != 0 && a != 255) + { + float recip = 1.0f/src[4]; + for (int c = 0; c < 4; c++) + { + dst[c] = ctx_float_to_u8 (1.0f - src[c] * recip); + } + } + else + { + for (int c = 0; c < 4; c++) + dst[c] = 255 - ctx_float_to_u8 (src[c]); + } + dst[4]=a; + + src += 5; + dst += 5; + } +} + +static void +ctx_composite_CMYKA8 (CTX_COMPOSITE_ARGUMENTS) +{ + float pixels[count * 5]; + ctx_CMYKA8_to_CMYKAF (rasterizer, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], rasterizer->color, x0, coverage, count); + ctx_CMYKAF_to_CMYKA8 (rasterizer, &pixels[0], dst, count); +} + +#endif +#if CTX_ENABLE_CMYK8 + +static void +ctx_CMYK8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + dst[0] = ctx_u8_to_float (255-src[0]); + dst[1] = ctx_u8_to_float (255-src[1]); + dst[2] = ctx_u8_to_float (255-src[2]); + dst[3] = ctx_u8_to_float (255-src[3]); + dst[4] = 1.0f; + src += 4; + dst += 5; + } +} +static void +ctx_CMYKAF_to_CMYK8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count) +{ + for (int i = 0; i < count; i ++) + { + float c = src[0]; + float m = src[1]; + float y = src[2]; + float k = src[3]; + float a = src[4]; + if (a != 0.0f && a != 1.0f) + { + float recip = 1.0f/a; + c *= recip; + m *= recip; + y *= recip; + k *= recip; + } + c = 1.0 - c; + m = 1.0 - m; + y = 1.0 - y; + k = 1.0 - k; + dst[0] = ctx_float_to_u8 (c); + dst[1] = ctx_float_to_u8 (m); + dst[2] = ctx_float_to_u8 (y); + dst[3] = ctx_float_to_u8 (k); + src += 5; + dst += 4; + } +} + +static void +ctx_composite_CMYK8 (CTX_COMPOSITE_ARGUMENTS) +{ + float pixels[count * 5]; + ctx_CMYK8_to_CMYKAF (rasterizer, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], src, x0, coverage, count); + ctx_CMYKAF_to_CMYK8 (rasterizer, &pixels[0], dst, count); +} +#endif + +#if CTX_ENABLE_RGB8 + +inline static void +ctx_RGB8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (const uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[1]; + rgba[2] = pixel[2]; + rgba[3] = 255; + pixel+=3; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_RGB8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = rgba[0]; + pixel[1] = rgba[1]; + pixel[2] = rgba[2]; + pixel+=3; + rgba +=4; + } +} + +#endif +#if CTX_ENABLE_GRAY1 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY1_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = 255 * (*pixel & (1<< (x&7) ) ); + rgba[1] = 255; + pixel+= ( (x&7) ==7); + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + *pixel = 0; + while (count--) + { + int gray = rgba[0]; + //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127); + if (gray >= 127) + { + *pixel = *pixel | ((1<< (x&7) ) * (gray >= 127)); + } +#if 0 + else + { + *pixel = *pixel & (~ (1<< (x&7) ) ); + } +#endif + if ( (x&7) ==7) + { pixel+=1; + if(count>0)*pixel = 0; + } + x++; + rgba +=2; + } +} + +#else + +inline static void +ctx_GRAY1_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + *((uint32_t*)(rgba))=0xff000000 + 0x00ffffff * ((*pixel & (1<< (x&7) ) )!=0); + pixel+= ( (x&7) ==7); + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + *pixel = 0; + while (count--) + { + int gray = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127); + if (gray <= 127) + { + //*pixel = *pixel & (~ (1<< (x&7) ) ); + } + else + { + *pixel = *pixel | (1<< (x&7) ); + } + if ( (x&7) ==7) + { pixel+=1; + if(count>0)*pixel = 0; + } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY2 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY2_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1); + val <<= 6; + rgba[0] = val; + rgba[1] = 255; + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = rgba[0]; + val >>= 6; + *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) ); + *pixel = *pixel | ( (val << ( (x&3) <<1) ) ); + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=2; + } +} +#else + +inline static void +ctx_GRAY2_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1); + val <<= 6; + rgba[0] = val; + rgba[1] = val; + rgba[2] = val; + rgba[3] = 255; + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + val >>= 6; + *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) ); + *pixel = *pixel | ( (val << ( (x&3) <<1) ) ); + if ( (x&3) ==3) + { pixel+=1; } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY4 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY4_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2); + val <<= 4; + rgba[0] = val; + rgba[1] = 255; + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = rgba[0]; + val >>= 4; + *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) ); + *pixel = *pixel | ( (val << ( (x&1) <<2) ) ); + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=2; + } +} +#else +inline static void +ctx_GRAY4_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2); + val <<= 4; + rgba[0] = val; + rgba[1] = val; + rgba[2] = val; + rgba[3] = 255; + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + val >>= 4; + *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) ); + *pixel = *pixel | ( (val << ( (x&1) <<2) ) ); + if ( (x&1) ==1) + { pixel+=1; } + x++; + rgba +=4; + } +} +#endif + +#endif +#if CTX_ENABLE_GRAY8 + +#if CTX_NATIVE_GRAYA8 +inline static void +ctx_GRAY8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = 255; + pixel+=1; + rgba +=2; + } +} + +inline static void +ctx_GRAYA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = rgba[0]; + pixel+=1; + rgba +=2; + } +} +#else +inline static void +ctx_GRAY8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[0]; + rgba[2] = pixel[0]; + rgba[3] = 255; + pixel+=1; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + for (int i = 0; i < count; i ++) + { + pixel[i] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba + i * 4); + } +} +#endif + +#endif +#if CTX_ENABLE_GRAYA8 + +inline static void +ctx_GRAYA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (const uint8_t *) buf; + while (count--) + { + rgba[0] = pixel[0]; + rgba[1] = pixel[0]; + rgba[2] = pixel[0]; + rgba[3] = pixel[1]; + pixel+=2; + rgba +=4; + } +} + +inline static void +ctx_RGBA8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + pixel[0] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba); + pixel[1] = rgba[3]; + pixel+=2; + rgba +=4; + } +} + +#if CTX_NATIVE_GRAYA8 +CTX_INLINE static void ctx_rgba_to_graya_u8 (CtxState *state, uint8_t *in, uint8_t *out) +{ + out[0] = ctx_u8_color_rgb_to_gray (state, in); + out[1] = in[3]; +} + +#if CTX_GRADIENTS +static void +ctx_fragment_linear_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + uint8_t *dst = (uint8_t*)out; + for (int i = 0; i < count;i ++) + { + float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) / + g->linear_gradient.length) - + g->linear_gradient.start) * (g->linear_gradient.rdelta); + { + uint8_t rgba[4]; + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); + ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst); + + } + + +#if CTX_DITHER + ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + dst += 2; + x += dx; + y += dy; + } +} + +#if 0 +static void +ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out) +{ + uint8_t *rgba = (uint8_t *) out; + CtxSource *g = &rasterizer->state->gstate.source_fill; + float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba); +#if CTX_DITHER + ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif +} +#endif + + +static void +ctx_fragment_radial_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t *dst = (uint8_t*)out; + for (int i = 0; i < count;i ++) + { + CtxSource *g = &rasterizer->state->gstate.source_fill; + float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) - + g->radial_gradient.r0) * (g->radial_gradient.rdelta); + { + uint8_t rgba[4]; + ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba); + ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst); + } +#if CTX_DITHER + ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue, + rasterizer->format->dither_green); +#endif + dst += 2; + x += dx; + y += dy; + } +} +#endif + +static void +ctx_fragment_color_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + CtxSource *g = &rasterizer->state->gstate.source_fill; + uint16_t *dst = (uint16_t*)out; + uint16_t pix; + ctx_color_get_graya_u8 (rasterizer->state, &g->color, (void*)&pix); + for (int i = 0; i <count; i++) + { + dst[i]=pix; + } +} + +static void ctx_fragment_image_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy) +{ + uint8_t rgba[4*count]; + CtxGState *gstate = &rasterizer->state->gstate; + CtxBuffer *buffer = gstate->source_fill.texture.buffer; + switch (buffer->format->bpp) + { + case 1: ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break; + } + for (int i = 0; i < count; i++) + ctx_rgba_to_graya_u8 (rasterizer->state, &rgba[i*4], &((uint8_t*)out)[i*2]); +} + +static CtxFragment ctx_rasterizer_get_fragment_GRAYA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + switch (gstate->source_fill.type) + { + case CTX_SOURCE_TEXTURE: return ctx_fragment_image_GRAYA8; + case CTX_SOURCE_COLOR: return ctx_fragment_color_GRAYA8; +#if CTX_GRADIENTS + case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYA8; + case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYA8; +#endif + } + return ctx_fragment_color_GRAYA8; +} + +//ctx_u8_porter_duff(GRAYA8, 2,color, rasterizer->fragment, rasterizer->state->gstate.blend_mode) +ctx_u8_porter_duff(GRAYA8, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode) + +#if CTX_INLINED_NORMAL +//ctx_u8_porter_duff(GRAYA8, 2,color_normal, rasterizer->fragment, CTX_BLEND_NORMAL) +ctx_u8_porter_duff(GRAYA8, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL) + +static void +ctx_GRAYA8_copy_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_copy_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYA8_clear_normal (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_clear_normal (2, rasterizer, dst, src, x0, coverage, count); +} + +static void +ctx_GRAYA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_source_over_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} + +static void +ctx_GRAYA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS) +{ + ctx_u8_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count); +} +#endif + +inline static int +ctx_is_opaque_color (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + if (gstate->global_alpha_u8 != 255) + return 0; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + uint8_t ga[2]; + ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga); + return ga[1] == 255; + } + return 0; +} + +static void +ctx_setup_GRAYA8 (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + int components = 2; + rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYA8 (rasterizer); + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + ctx_fragment_color_GRAYA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0); + if (gstate->global_alpha_u8 != 255) + for (int c = 0; c < components; c ++) + rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8)/255; + } + +#if CTX_INLINED_NORMAL + if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR) + rasterizer->comp_op = ctx_GRAYA8_clear_normal; + else + switch (gstate->blend_mode) + { + case CTX_BLEND_NORMAL: + if (gstate->compositing_mode == CTX_COMPOSITE_COPY) + { + rasterizer->comp_op = ctx_GRAYA8_copy_normal; + } + else if (gstate->global_alpha_u8 == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else + switch (gstate->source_fill.type) + { + case CTX_SOURCE_COLOR: + if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + if (rasterizer->color[components-1] == 0) + rasterizer->comp_op = ctx_RGBA8_nop; + else if (rasterizer->color[components-1] == 255) + rasterizer->comp_op = ctx_GRAYA8_source_copy_normal_color; + else + rasterizer->comp_op = ctx_GRAYA8_source_over_normal_color; + } + else + { + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal; + } + break; + default: + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal; + break; + } + break; + default: + rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic; + break; + } +#endif +} +#endif + +#endif +#if CTX_ENABLE_RGB332 + +inline static void +ctx_332_unpack (uint8_t pixel, + uint8_t *red, + uint8_t *green, + uint8_t *blue) +{ + *blue = (pixel & 3) <<6; + *green = ( (pixel >> 2) & 7) <<5; + *red = ( (pixel >> 5) & 7) <<5; + if (*blue > 223) { *blue = 255; } + if (*green > 223) { *green = 255; } + if (*red > 223) { *red = 255; } +} + +static inline uint8_t +ctx_332_pack (uint8_t red, + uint8_t green, + uint8_t blue) +{ + uint8_t c = (red >> 5) << 5; + c |= (green >> 5) << 2; + c |= (blue >> 6); + return c; +} + +static inline void +ctx_RGB332_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint8_t *pixel = (uint8_t *) buf; + while (count--) + { + ctx_332_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2]); +#if CTX_RGB332_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } + else +#endif + { rgba[3] = 255; } + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB332 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint8_t *pixel = (uint8_t *) buf; + while (count--) + { +#if CTX_RGB332_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_332_pack (255, 0, 255); } + else +#endif + { pixel[0] = ctx_332_pack (rgba[0], rgba[1], rgba[2]); } + pixel+=1; + rgba +=4; + } +} + +#endif +#if CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED + +static inline void +ctx_565_unpack (const uint16_t pixel, + uint8_t *red, + uint8_t *green, + uint8_t *blue, + const int byteswap) +{ + uint16_t byteswapped; + if (byteswap) + { byteswapped = (pixel>>8) | (pixel<<8); } + else + { byteswapped = pixel; } + *blue = (byteswapped & 31) <<3; + *green = ( (byteswapped>>5) & 63) <<2; + *red = ( (byteswapped>>11) & 31) <<3; +#if 0 + if (*blue > 248) { *blue = 255; } + if (*green > 248) { *green = 255; } + if (*red > 248) { *red = 255; } +#endif +} + +static inline uint32_t +ctx_565_unpack_32 (const uint16_t pixel, + const int byteswap) +{ + uint16_t byteswapped; + if (byteswap) + { byteswapped = (pixel>>8) | (pixel<<8); } + else + { byteswapped = pixel; } + uint8_t blue = (byteswapped & 31) <<3; + uint8_t green = ( (byteswapped>>5) & 63) <<2; + uint8_t red = ( (byteswapped>>11) & 31) <<3; +#if 0 + if (*blue > 248) { *blue = 255; } + if (*green > 248) { *green = 255; } + if (*red > 248) { *red = 255; } +#endif + return red + (green << 8) + (blue << 16) + (0xff << 24); +} + +static inline uint16_t +ctx_565_pack (const uint8_t red, + const uint8_t green, + const uint8_t blue, + const int byteswap) +{ + uint32_t c = (red >> 3) << 11; + c |= (green >> 2) << 5; + c |= blue >> 3; + if (byteswap) + { return (c>>8) | (c<<8); } /* swap bytes */ + return c; +} + +static inline uint16_t +ctx_888_to_565 (uint32_t in, int byteswap) +{ + uint8_t *rgb=(uint8_t*)(&in); + return ctx_565_pack (rgb[0],rgb[1],rgb[2], byteswap); +} + +static inline uint32_t +ctx_565_to_888 (uint16_t in, int byteswap) +{ + uint32_t ret = 0; + uint8_t *rgba=(uint8_t*)&ret; + ctx_565_unpack (in, + &rgba[0], + &rgba[1], + &rgba[2], + byteswap); + return ret; +} + +#endif +#if CTX_ENABLE_RGB565 + + +static inline void +ctx_RGB565_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint16_t *pixel = (uint16_t *) buf; + while (count--) + { + ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 0); +#if CTX_RGB565_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } +#endif + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB565 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint16_t *pixel = (uint16_t *) buf; + while (count--) + { +#if CTX_RGB565_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_565_pack (255, 0, 255, 0); } + else +#endif + { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 0); } + pixel+=1; + rgba +=4; + } +} + +static void +ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS); +static void +ctx_RGBA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS); + +static void +ctx_composite_RGB565 (CTX_COMPOSITE_ARGUMENTS) +{ +#if 0 + if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color)) + { + int byteswap = 0; + uint32_t si = *((uint32_t*)(src)); + uint16_t si_16 = ctx_888_to_565 (si, byteswap); + uint32_t sval = (si_16 & ( (31 << 11 ) | 31)); + uint32_t sg = (si_16 & (63 << 5)) >> 5; + uint32_t si_a = si >> (24 + 3); + while (count--) + { + uint32_t di_16 = *((uint16_t*)(dst)); + uint32_t cov = (*coverage) >> 3; + uint32_t racov = (32-((31+si_a*cov)>>5)); + uint32_t dval = (di_16 & ( (31 << 11 ) | 31)); + uint32_t dg = (di_16 >> 5) & 63; // faster outside than + // remerged as part of dval + *((uint16_t*)(dst)) = + (( + (((sval * cov) + (dval * racov)) >> 5) + ) & ((31 << 11 )|31)) | + ((((sg * cov) + (dg * racov)) & 63) ); + + } + return; + } +#endif + uint8_t pixels[count * 4]; + ctx_RGB565_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_RGBA8_to_RGB565 (rasterizer, x0, &pixels[0], dst, count); +} +#endif +#if CTX_ENABLE_RGB565_BYTESWAPPED + +static inline void +ctx_RGB565_BS_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count) +{ + const uint16_t *pixel = (uint16_t *) buf; + while (count--) + { + //ctx_565_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2], 1); + ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 1); +#if CTX_RGB565_ALPHA + if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0) + { rgba[3] = 0; } + else + { rgba[3] = 255; } +#endif + pixel+=1; + rgba +=4; + } +} + +static inline void +ctx_RGBA8_to_RGB565_BS (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count) +{ + uint16_t *pixel = (uint16_t *) buf; + while (count--) + { +#if CTX_RGB565_ALPHA + if (rgba[3]==0) + { pixel[0] = ctx_565_pack (255, 0, 255, 1); } + else +#endif + { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 1); } + pixel+=1; + rgba +=4; + } +} + +static void +ctx_composite_RGB565_BS (CTX_COMPOSITE_ARGUMENTS) +{ + uint8_t pixels[count * 4]; + ctx_RGB565_BS_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count); + rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count); + ctx_RGBA8_to_RGB565_BS (rasterizer, x0, &pixels[0], dst, count); +} +#endif + +static CtxPixelFormatInfo ctx_pixel_formats[]= +{ +#if CTX_ENABLE_RGBA8 + { + CTX_FORMAT_RGBA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8, + NULL, NULL, NULL, ctx_setup_RGBA8 + }, +#endif +#if CTX_ENABLE_BGRA8 + { + CTX_FORMAT_BGRA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_BGRA8_to_RGBA8, ctx_RGBA8_to_BGRA8, ctx_composite_BGRA8, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_GRAYF + { + CTX_FORMAT_GRAYF, 1, 32, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF, + NULL, NULL, ctx_composite_GRAYF, ctx_setup_GRAYAF, + }, +#endif +#if CTX_ENABLE_GRAYAF + { + CTX_FORMAT_GRAYAF, 2, 64, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF, + NULL, NULL, NULL, ctx_setup_GRAYAF, + }, +#endif +#if CTX_ENABLE_RGBAF + { + CTX_FORMAT_RGBAF, 4, 128, 4 * 4, 0, 0, CTX_FORMAT_RGBAF, + NULL, NULL, NULL, ctx_setup_RGBAF, + }, +#endif +#if CTX_ENABLE_RGB8 + { + CTX_FORMAT_RGB8, 3, 24, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_RGB8_to_RGBA8, ctx_RGBA8_to_RGB8, ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_GRAY1 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY1, 1, 1, 2, 1, 1, CTX_FORMAT_GRAYA8, + ctx_GRAY1_to_GRAYA8, ctx_GRAYA8_to_GRAY1, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY1, 1, 1, 4, 1, 1, CTX_FORMAT_RGBA8, + ctx_GRAY1_to_RGBA8, ctx_RGBA8_to_GRAY1, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY2 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY2, 1, 2, 2, 4, 4, CTX_FORMAT_GRAYA8, + ctx_GRAY2_to_GRAYA8, ctx_GRAYA8_to_GRAY2, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY2, 1, 2, 4, 4, 4, CTX_FORMAT_RGBA8, + ctx_GRAY2_to_RGBA8, ctx_RGBA8_to_GRAY2, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY4 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY4, 1, 4, 2, 16, 16, CTX_FORMAT_GRAYA8, + ctx_GRAY4_to_GRAYA8, ctx_GRAYA8_to_GRAY4, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY4, 1, 4, 4, 16, 16, CTX_FORMAT_GRAYA8, + ctx_GRAY4_to_RGBA8, ctx_RGBA8_to_GRAY4, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAY8 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAY8, 1, 8, 2, 0, 0, CTX_FORMAT_GRAYA8, + ctx_GRAY8_to_GRAYA8, ctx_GRAYA8_to_GRAY8, ctx_composite_convert, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAY8, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_GRAY8_to_RGBA8, ctx_RGBA8_to_GRAY8, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_GRAYA8 + { +#if CTX_NATIVE_GRAYA8 + CTX_FORMAT_GRAYA8, 2, 16, 2, 0, 0, CTX_FORMAT_GRAYA8, + ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, NULL, ctx_setup_GRAYA8, +#else + CTX_FORMAT_GRAYA8, 2, 16, 4, 0, 0, CTX_FORMAT_RGBA8, + ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, ctx_composite_convert, ctx_setup_RGBA8, +#endif + }, +#endif +#if CTX_ENABLE_RGB332 + { + CTX_FORMAT_RGB332, 3, 8, 4, 10, 12, CTX_FORMAT_RGBA8, + ctx_RGB332_to_RGBA8, ctx_RGBA8_to_RGB332, + ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_RGB565 + { + CTX_FORMAT_RGB565, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8, + ctx_RGB565_to_RGBA8, ctx_RGBA8_to_RGB565, + ctx_composite_RGB565, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_RGB565_BYTESWAPPED + { + CTX_FORMAT_RGB565_BYTESWAPPED, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8, + ctx_RGB565_BS_to_RGBA8, + ctx_RGBA8_to_RGB565_BS, + ctx_composite_RGB565_BS, ctx_setup_RGBA8, + }, +#endif +#if CTX_ENABLE_CMYKAF + { + CTX_FORMAT_CMYKAF, 5, 160, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, NULL, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_CMYKA8 + { + CTX_FORMAT_CMYKA8, 5, 40, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, ctx_composite_CMYKA8, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_CMYK8 + { + CTX_FORMAT_CMYK8, 5, 32, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF, + NULL, NULL, ctx_composite_CMYK8, ctx_setup_CMYKAF, + }, +#endif +#if CTX_ENABLE_YUV420 + { + CTX_FORMAT_YUV420, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8, + NULL, NULL, ctx_composite_convert, ctx_setup_RGBA8, + }, +#endif + { + CTX_FORMAT_NONE + } +}; + + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format) +{ + for (unsigned int i = 0; ctx_pixel_formats[i].pixel_format; i++) + { + if (ctx_pixel_formats[i].pixel_format == format) + { + return &ctx_pixel_formats[i]; + } + } + return NULL; +} + +#endif +#if CTX_RASTERIZER +#define CTX_AA_HALFSTEP2 (CTX_FULL_AA/2) +#define CTX_AA_HALFSTEP ((CTX_FULL_AA/2)+1) + +static void +ctx_gradient_cache_prime (CtxRasterizer *rasterizer); + +static inline void +_ctx_setup_compositor (CtxRasterizer *rasterizer) +{ + if (CTX_UNLIKELY (rasterizer->comp_op==0)) + { + rasterizer->format->setup (rasterizer); + } +#if CTX_GRADIENTS +#if CTX_GRADIENT_CACHE + switch (rasterizer->state->gstate.source_fill.type) + { + case CTX_SOURCE_LINEAR_GRADIENT: + case CTX_SOURCE_RADIAL_GRADIENT: + ctx_gradient_cache_prime (rasterizer); + break; + case CTX_SOURCE_TEXTURE: + if (!rasterizer->state->gstate.source_fill.texture.buffer->color_managed) + _ctx_texture_prepare_color_management (rasterizer, + rasterizer->state->gstate.source_fill.texture.buffer); + break; + } +#endif +#endif +} + +#define CTX_FULL_AA 15 +inline static void +ctx_rasterizer_apply_coverage (CtxRasterizer *rasterizer, + uint8_t * dst, + int x, + uint8_t * coverage, + int count) +{ + if (CTX_UNLIKELY(rasterizer->format->apply_coverage)) + rasterizer->format->apply_coverage(rasterizer, dst, rasterizer->color, x, coverage, count); + else + /* it is faster to dispatch in this condition, than using a shared + * direct trampoline + */ + rasterizer->comp_op (rasterizer, dst, rasterizer->color, x, coverage, count); +} + +static void +ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba) +{ + /* FIXME XXX we only have one gradient, but might need separate gradients + * for fill/stroke ! + * + */ + CtxGradient *gradient = &rasterizer->state->gradient; + CtxGradientStop *stop = &gradient->stops[gradient->n_stops]; + stop->pos = pos; + ctx_color_set_rgba (rasterizer->state, & (stop->color), rgba[0], rgba[1], rgba[2], rgba[3]); + if (gradient->n_stops < 15) //we'll keep overwriting the last when out of stops + { gradient->n_stops++; } +} + +static inline int ctx_rasterizer_add_point (CtxRasterizer *rasterizer, int x1, int y1) +{ + CtxSegment entry = {CTX_EDGE, {{0},}}; + rasterizer->scan_min = ctx_mini (y1, rasterizer->scan_min); + rasterizer->scan_max = ctx_maxi (y1, rasterizer->scan_max); + + rasterizer->col_min = ctx_mini (x1, rasterizer->col_min); + rasterizer->col_max = ctx_maxi (x1, rasterizer->col_max); + + entry.data.s16[2]=x1; + entry.data.s16[3]=y1; + return ctx_drawlist_add_single (&rasterizer->edge_list, (CtxEntry*)&entry); +} + +#if 0 +#define CTX_SHAPE_CACHE_PRIME1 7853 +#define CTX_SHAPE_CACHE_PRIME2 4129 +#define CTX_SHAPE_CACHE_PRIME3 3371 +#define CTX_SHAPE_CACHE_PRIME4 4221 +#else +#define CTX_SHAPE_CACHE_PRIME1 283 +#define CTX_SHAPE_CACHE_PRIME2 599 +#define CTX_SHAPE_CACHE_PRIME3 101 +#define CTX_SHAPE_CACHE_PRIME4 661 +#endif + +float ctx_shape_cache_rate = 0.0; +#if CTX_SHAPE_CACHE +int _ctx_shape_cache_enabled = 1; + +//static CtxShapeCache ctx_cache = {{NULL,}, 0}; + +static long ctx_shape_cache_hits = 0; +static long ctx_shape_cache_misses = 0; + + +/* this returns the buffer to use for rendering, it always + succeeds.. + */ +static inline CtxShapeEntry *ctx_shape_entry_find (CtxRasterizer *rasterizer, uint32_t hash, int width, int height) +{ + /* use both some high and some low bits */ + int entry_no = ( (hash >> 10) ^ (hash & 1023) ) % CTX_SHAPE_CACHE_ENTRIES; + int i; + { + static int i = 0; + i++; + if (i>1000) + { + ctx_shape_cache_rate = ctx_shape_cache_hits * 100.0 / (ctx_shape_cache_hits+ctx_shape_cache_misses); + i = 0; + ctx_shape_cache_hits = 0; + ctx_shape_cache_misses = 0; + } + } +// XXX : this 1 one is needed to silence a false positive: +// ==90718== Invalid write of size 1 +// ==90718== at 0x1189EF: ctx_rasterizer_generate_coverage (ctx.h:4786) +// ==90718== by 0x118E57: ctx_rasterizer_rasterize_edges (ctx.h:4907) +// + int size = sizeof (CtxShapeEntry) + width * height + 1; + + i = entry_no; + if (rasterizer->shape_cache.entries[i]) + { + CtxShapeEntry *entry = rasterizer->shape_cache.entries[i]; + int old_size = sizeof (CtxShapeEntry) + width + height + 1; + if (entry->hash == hash && + entry->width == width && + entry->height == height) + { + if (entry->uses < 1<<30) + { entry->uses++; } + ctx_shape_cache_hits ++; + return entry; + } + + if (old_size >= size) + { + } + else + { + rasterizer->shape_cache.entries[i] = NULL; + rasterizer->shape_cache.size -= entry->width * entry->height; + rasterizer->shape_cache.size -= sizeof (CtxShapeEntry); + free (entry); + rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1); + } + } + else + { + rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1); + } + + ctx_shape_cache_misses ++; + rasterizer->shape_cache.size += size; + rasterizer->shape_cache.entries[i]->hash = hash; + rasterizer->shape_cache.entries[i]->width = width; + rasterizer->shape_cache.entries[i]->height = height; + rasterizer->shape_cache.entries[i]->uses = 0; + return rasterizer->shape_cache.entries[i]; +} + +#endif + +static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer) +{ + int x = 0; + int y = 0; + + CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0]; + + int ox = entry->data.s16[2]; + int oy = entry->data.s16[3]; + uint32_t hash = rasterizer->edge_list.count; + hash = ox;//(ox % CTX_SUBDIV); + hash *= CTX_SHAPE_CACHE_PRIME1; + hash += oy; //(oy % CTX_RASTERIZER_AA); + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[i]; + x = entry->data.s16[2]; + y = entry->data.s16[3]; + int dx = x-ox; + int dy = y-oy; + ox = x; + oy = y; + hash *= CTX_SHAPE_CACHE_PRIME3; + hash += dx; + hash *= CTX_SHAPE_CACHE_PRIME4; + hash += dy; + } + return hash; +} + +static uint32_t ctx_rasterizer_poly_to_edges (CtxRasterizer *rasterizer) +{ + int x = 0; + int y = 0; + if (rasterizer->edge_list.count == 0) + return 0; +#if CTX_SHAPE_CACHE + CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0]; + int ox = entry->data.s16[2]; + int oy = entry->data.s16[3]; + uint32_t hash = rasterizer->edge_list.count; + hash = (ox & CTX_SUBDIV); + hash *= CTX_SHAPE_CACHE_PRIME1; + hash += (oy & CTX_SUBDIV); +#endif + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[i]; + if (CTX_UNLIKELY (entry->code == CTX_NEW_EDGE)) + { + entry->code = CTX_EDGE; +#if CTX_SHAPE_CACHE + hash *= CTX_SHAPE_CACHE_PRIME2; +#endif + } + else + { + entry->data.s16[0] = x; + entry->data.s16[1] = y; + } + x = entry->data.s16[2]; + y = entry->data.s16[3]; +#if CTX_SHAPE_CACHE + 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 (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]); + } + } +#if CTX_SHAPE_CACHE + return hash; +#else + return 0; +#endif +} + +static inline void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer) +{ + if (rasterizer->has_shape && rasterizer->has_prev) + { + ctx_rasterizer_line_to (rasterizer, rasterizer->first_x, rasterizer->first_y); + rasterizer->has_prev = 0; + } +} + +static inline void ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y) +{ + float tx = x; float ty = y; + int aa = 15;//rasterizer->aa; + rasterizer->x = x; + rasterizer->y = y; + rasterizer->first_x = x; + rasterizer->first_y = y; + rasterizer->has_prev = -1; + if (rasterizer->uses_transforms) + { + _ctx_user_to_device (rasterizer->state, &tx, &ty); + } + + tx = (tx - rasterizer->blit_x) * CTX_SUBDIV; + ty = ty * aa; + + rasterizer->scan_min = ctx_mini (ty, rasterizer->scan_min); + rasterizer->scan_max = ctx_maxi (ty, rasterizer->scan_max); + + rasterizer->col_min = ctx_mini (tx, rasterizer->col_min); + rasterizer->col_max = ctx_maxi (tx, rasterizer->col_max); +} + +static inline void ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y) +{ + float tx = x; + float ty = y; + float ox = rasterizer->x; + float oy = rasterizer->y; + if ((rasterizer->uses_transforms)) + { + _ctx_user_to_device (rasterizer->state, &tx, &ty); + } + tx -= rasterizer->blit_x; +#define MIN_Y -1000 +#define MAX_Y 1400 + + ty = ctx_maxf (MIN_Y, ty); + ty = ctx_minf (MAX_Y, ty); + + + ctx_rasterizer_add_point (rasterizer, tx * CTX_SUBDIV, ty * CTX_FULL_AA);//rasterizer->aa); + + if (CTX_UNLIKELY(rasterizer->has_prev<=0)) + { + if ((rasterizer->uses_transforms)) + { + // storing transformed would save some processing for a tiny + // amount of runtime RAM XXX + _ctx_user_to_device (rasterizer->state, &ox, &oy); + } + ox -= rasterizer->blit_x; + oy = ctx_maxf (oy, MIN_Y); + oy = ctx_minf (oy, MAX_Y); + + CtxSegment *entry = & ((CtxSegment*)rasterizer->edge_list.entries)[rasterizer->edge_list.count-1]; + + entry->data.s16[0] = ox * CTX_SUBDIV; + entry->data.s16[1] = oy * CTX_FULL_AA; + entry->code = CTX_NEW_EDGE; + rasterizer->has_prev = 1; + } + rasterizer->has_shape = 1; + rasterizer->y = y; + rasterizer->x = x; +} + + +CTX_INLINE static float +ctx_bezier_sample_1d (float x0, float x1, float x2, float x3, float dt) +{ + float ab = ctx_lerpf (x0, x1, dt); + float bc = ctx_lerpf (x1, x2, dt); + float cd = ctx_lerpf (x2, x3, dt); + float abbc = ctx_lerpf (ab, bc, dt); + float bccd = ctx_lerpf (bc, cd, dt); + return ctx_lerpf (abbc, bccd, dt); +} + +inline static void +ctx_bezier_sample (float x0, float y0, + float x1, float y1, + float x2, float y2, + float x3, float y3, + float dt, float *x, float *y) +{ + *x = ctx_bezier_sample_1d (x0, x1, x2, x3, dt); + *y = ctx_bezier_sample_1d (y0, y1, y2, y3, dt); +} + +static inline void +ctx_rasterizer_bezier_divide (CtxRasterizer *rasterizer, + float ox, float oy, + float x0, float y0, + float x1, float y1, + float x2, float y2, + float sx, float sy, + float ex, float ey, + float s, + float e, + int iteration, + float tolerance) +{ + float t = (s + e) * 0.5f; + float x, y, lx, ly, dx, dy; + ctx_bezier_sample (ox, oy, x0, y0, x1, y1, x2, y2, t, &x, &y); + if (iteration) + { + lx = ctx_lerpf (sx, ex, t); + ly = ctx_lerpf (sy, ey, t); + dx = lx - x; + dy = ly - y; + if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance)) + /* bailing - because for the mid-point straight line difference is + tiny */ + { return; } + dx = sx - ex; + dy = ey - ey; + if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance)) + /* bailing on tiny segments */ + { return; } + } + if (iteration < 8) + { + ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2, + sx, sy, x, y, s, t, iteration + 1, + tolerance); + ctx_rasterizer_line_to (rasterizer, x, y); + ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2, + x, y, ex, ey, t, e, iteration + 1, + tolerance); + } +} + +static void +ctx_rasterizer_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + float tolerance = + 2.0f/(ctx_pow2 (rasterizer->state->gstate.transform.m[0][0]) + + ctx_pow2 (rasterizer->state->gstate.transform.m[1][1])); + //float tolerance = ctx_matrix_get_scale (&rasterizer->state->gstate.transform); + float ox = rasterizer->x; + float oy = rasterizer->y; + ox = rasterizer->state->x; + oy = rasterizer->state->y; + //tolerance = 10.0/(tolerance*tolerance); + //tolerance = 10.0f/tolerance; +#if 0 // skipping this to preserve hash integrity + if (tolerance == 1.0f || 1) + { + float maxx = ctx_maxf (x1,x2); + maxx = ctx_maxf (maxx, ox); + maxx = ctx_maxf (maxx, x0); + float maxy = ctx_maxf (y1,y2); + maxy = ctx_maxf (maxy, oy); + maxy = ctx_maxf (maxy, y0); + float minx = ctx_minf (x1,x2); + minx = ctx_minf (minx, ox); + minx = ctx_minf (minx, x0); + float miny = ctx_minf (y1,y2); + miny = ctx_minf (miny, oy); + miny = ctx_minf (miny, y0); + + _ctx_user_to_device (rasterizer->state, &minx, &miny); + _ctx_user_to_device (rasterizer->state, &maxx, &maxy); +#if 1 + if( + (minx > rasterizer->blit_x + rasterizer->blit_width) || + (miny > rasterizer->blit_y + rasterizer->blit_height) || + (maxx < rasterizer->blit_x) || + (maxy < rasterizer->blit_y) ) + { + } + else +#endif + { + ctx_rasterizer_bezier_divide (rasterizer, + ox, oy, x0, y0, + x1, y1, x2, y2, + ox, oy, x2, y2, + 0.0f, 1.0f, 0.0f, tolerance); + } + } + else +#endif + { + ctx_rasterizer_bezier_divide (rasterizer, + ox, oy, x0, y0, + x1, y1, x2, y2, + ox, oy, x2, y2, + 0.0f, 1.0f, 0.0f, tolerance); + } + ctx_rasterizer_line_to (rasterizer, x2, y2); +} + +static void +ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y) +{ + //if (CTX_UNLIKELY(x == 0.f && y == 0.f)) + //{ return; } + x += rasterizer->x; + y += rasterizer->y; + ctx_rasterizer_move_to (rasterizer, x, y); +} + +static void +ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y) +{ + //if (CTX_UNLIKELY(x== 0.f && y==0.f)) + // { return; } + x += rasterizer->x; + y += rasterizer->y; + ctx_rasterizer_line_to (rasterizer, x, y); +} + +static void +ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer, + float x0, float y0, float x1, float y1, float x2, float y2) +{ + x0 += rasterizer->x; + y0 += rasterizer->y; + x1 += rasterizer->x; + y1 += rasterizer->y; + x2 += rasterizer->x; + y2 += rasterizer->y; + ctx_rasterizer_curve_to (rasterizer, x0, y0, x1, y1, x2, y2); +} + + +static int +ctx_rasterizer_find_texture (CtxRasterizer *rasterizer, + const char *eid) +{ + int no; + for (no = 0; no < CTX_MAX_TEXTURES; no++) + { + if (rasterizer->texture_source->texture[no].data && + rasterizer->texture_source->texture[no].eid && + !strcmp (rasterizer->texture_source->texture[no].eid, eid)) + return no; + } + return -1; +} + +static void +ctx_rasterizer_set_texture (CtxRasterizer *rasterizer, + const char *eid, + float x, + float y) +{ + int is_stroke = (rasterizer->state->source != 0); + CtxSource *source = is_stroke && (rasterizer->state->gstate.source_stroke.type != CTX_SOURCE_INHERIT_FILL)? + &rasterizer->state->gstate.source_stroke: + &rasterizer->state->gstate.source_fill; + rasterizer->state->source = 0; + + int no = ctx_rasterizer_find_texture (rasterizer, eid); + if (no < 0 || no >= CTX_MAX_TEXTURES) { no = 0; } + if (rasterizer->texture_source->texture[no].data == NULL) + { + fprintf (stderr, "ctx tex fail %p %s %i\n", rasterizer->texture_source, eid, no); + return; + } + else + { + rasterizer->texture_source->texture[no].frame = rasterizer->texture_source->frame; + } + source->type = CTX_SOURCE_TEXTURE; + source->texture.buffer = &rasterizer->texture_source->texture[no]; + source->texture.x0 = 0; + source->texture.y0 = 0; + source->transform = rasterizer->state->gstate.transform; + ctx_matrix_translate (&source->transform, x, y); + ctx_matrix_invert (&source->transform); +} + + +static void ctx_rasterizer_define_texture (CtxRasterizer *rasterizer, + const char *eid, + int width, + int height, + int format, + char unsigned *data) +{ + _ctx_texture_lock (); // we're using the same texture_source from all threads, keeping allocaitons down + // need synchronizing (it could be better to do a pre-pass) + ctx_texture_init (rasterizer->texture_source, + eid, + width, + height, + ctx_pixel_format_get_stride ((CtxPixelFormat)format, width), + (CtxPixelFormat)format, +#if CTX_ENABLE_CM + (void*)rasterizer->state->gstate.texture_space, +#else + NULL, +#endif + data, + ctx_buffer_pixels_free, (void*)23); + /* when userdata for ctx_buffer_pixels_free is 23, texture_init dups the data on + * use + */ + + ctx_rasterizer_set_texture (rasterizer, eid, 0.0, 0.0); + _ctx_texture_unlock (); +} + + +CTX_INLINE static int ctx_compare_edges (const void *ap, const void *bp) +{ + const CtxSegment *a = (const CtxSegment *) ap; + const CtxSegment *b = (const CtxSegment *) bp; + return a->data.s16[1] - b->data.s16[1]; +} + +CTX_INLINE static int ctx_edge_qsort_partition (CtxSegment *A, int low, int high) +{ + CtxSegment pivot = A[ (high+low) /2]; + int i = low; + int j = high; + while (i <= j) + { + while (ctx_compare_edges (&A[i], &pivot) < 0) { i ++; } + while (ctx_compare_edges (&pivot, &A[j]) < 0) { j --; } + if (i <= j) + { + CtxSegment tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + i++; + j--; + } + } + return i; +} + +static inline void ctx_edge_qsort (CtxSegment *entries, int low, int high) +{ + { + int p = ctx_edge_qsort_partition (entries, low, high); + if (low < p -1 ) + { ctx_edge_qsort (entries, low, p - 1); } + if (low < high) + { ctx_edge_qsort (entries, p, high); } + } +} + +static CTX_INLINE void ctx_rasterizer_sort_edges (CtxRasterizer *rasterizer) +{ + ctx_edge_qsort ((CtxSegment*)& (rasterizer->edge_list.entries[0]), 0, rasterizer->edge_list.count-1); +} + +//extern int _ctx_fast_aa; + +static inline void ctx_rasterizer_discard_edges (CtxRasterizer *rasterizer) +{ + int scanline = rasterizer->scanline; +#if CTX_ONLY_FAST_AA + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#else + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3; + if (rasterizer->fast_aa) + limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#endif + for (int i = 0; i < rasterizer->active_edges; i++) + { + int edge_end = ((CtxSegment*)(rasterizer->edge_list.entries))[rasterizer->edges[i].index].data.s16[3]-1; + if (CTX_LIKELY(edge_end < scanline)) + { + + int dx_dy = abs(rasterizer->edges[i].delta); + rasterizer->needs_aa3 -= (dx_dy > limit3); +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5); + rasterizer->needs_aa15 -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15); +#endif + rasterizer->edges[i] = rasterizer->edges[rasterizer->active_edges-1]; + rasterizer->active_edges--; + i--; + } + else if (edge_end < scanline + CTX_FULL_AA) + rasterizer->ending_edges++; + } +#if 0 + // we should - but for 99% of the cases we do not need to, so we skip it + for (int i = 0; i < rasterizer->pending_edges; i++) + { + int edge_end = ((CtxSegment*)(rasterizer->edge_list.entries))[rasterizer->edges[CTX_MAX_EDGES-1-i].index].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; + for (int i = 0; i < rasterizer->active_edges; i++) + { + rasterizer->edges[i].val += rasterizer->edges[i].delta * count; + } + for (int i = 0; i < rasterizer->pending_edges; i++) + { + rasterizer->edges[CTX_MAX_EDGES-1-i].val += rasterizer->edges[CTX_MAX_EDGES-1-i].delta * count; + } +} + +/* feeds up to rasterizer->scanline, + keeps a pending buffer of edges - that encompass + the full incoming scanline, + feed until the start of the scanline and check for need for aa + in all of pending + active edges, then + again feed_edges until middle of scanline if doing non-AA + or directly render when doing AA +*/ +CTX_INLINE static void ctx_edge2_insertion_sort (CtxEdge *entries, int count) +{ + for(int i=1; i<count; i++) + { + CtxEdge temp = entries[i]; + int j = i-1; + while (j >= 0 && temp.val - entries[j].val < 0) + { + entries[j+1] = entries[j]; + j--; + } + entries[j+1] = temp; + } +} + +CTX_INLINE static int ctx_edge2_compare2 (const CtxEdge *a, const CtxEdge *b) +{ + int minval_a = ctx_mini (a->val - a->delta * CTX_AA_HALFSTEP2, a->val + a->delta * CTX_AA_HALFSTEP); + int minval_b = ctx_mini (b->val - b->delta * CTX_AA_HALFSTEP2, b->val + b->delta * CTX_AA_HALFSTEP); + return minval_a - minval_b; +} + +CTX_INLINE static void ctx_edge2_insertion_sort2 (CtxEdge *entries, int count) +{ + for(int i=1; i<count; i++) + { + CtxEdge temp = entries[i]; + int j = i-1; + while (j >= 0 && ctx_edge2_compare2 (&temp, &entries[j]) < 0) + { + entries[j+1] = entries[j]; + j--; + } + entries[j+1] = temp; + } +} + +inline static void ctx_rasterizer_feed_edges (CtxRasterizer *rasterizer, int apply2_sort) +{ + int miny; + CtxSegment *entries = (CtxSegment*)&rasterizer->edge_list.entries[0]; + rasterizer->horizontal_edges = 0; + rasterizer->ending_edges = 0; + for (int i = 0; i < rasterizer->pending_edges; i++) + { + if (entries[rasterizer->edges[CTX_MAX_EDGES-1-i].index].data.s16[1] - 1 <= rasterizer->scanline) + { + if (CTX_LIKELY(rasterizer->active_edges < CTX_MAX_EDGES-2)) + { + int no = rasterizer->active_edges; + rasterizer->active_edges++; + rasterizer->edges[no] = rasterizer->edges[CTX_MAX_EDGES-1-i]; + rasterizer->edges[CTX_MAX_EDGES-1-i] = + rasterizer->edges[CTX_MAX_EDGES-1-rasterizer->pending_edges + 1]; + rasterizer->pending_edges--; + i--; + } + } + } +#if CTX_ONLY_FAST_AA + const int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#else + int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3; + if (rasterizer->fast_aa) + limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA; +#endif + int scanline = rasterizer->scanline; + while (CTX_LIKELY(rasterizer->edge_pos < rasterizer->edge_list.count && + (miny=entries[rasterizer->edge_pos].data.s16[1]-1) <= scanline + 15)) + { + int maxy=entries[rasterizer->edge_pos].data.s16[3]-1; + if (CTX_LIKELY(rasterizer->active_edges < CTX_MAX_EDGES-2 && + maxy >= scanline)) + { + int dy = (entries[rasterizer->edge_pos].data.s16[3] - 1 - miny); + if (CTX_LIKELY(dy)) + { + int yd = scanline - miny; + int no = rasterizer->active_edges; + rasterizer->active_edges++; + rasterizer->edges[no].index = rasterizer->edge_pos; + int index = rasterizer->edges[no].index; + int x0 = entries[index].data.s16[0]; + int x1 = entries[index].data.s16[2]; + int dx_dy; + dx_dy = CTX_RASTERIZER_EDGE_MULTIPLIER * (x1 - x0) / dy; + rasterizer->edges[no].delta = dx_dy; + rasterizer->edges[no].val = x0 * CTX_RASTERIZER_EDGE_MULTIPLIER + + (yd * dx_dy); + + { + int abs_dx_dy = abs(dx_dy); + rasterizer->needs_aa3 += (abs_dx_dy > limit3); +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5); + rasterizer->needs_aa15 += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15); +#endif + } + + if ((miny > scanline) ) + { + /* it is a pending edge - we add it to the end of the array + and keep a different count for items stored here, like + a heap and stack growing against each other + */ + if (CTX_LIKELY(rasterizer->pending_edges < CTX_MAX_PENDING-1)) + { + rasterizer->edges[CTX_MAX_EDGES-1-rasterizer->pending_edges] = + rasterizer->edges[no]; + rasterizer->pending_edges++; + rasterizer->active_edges--; + } + } + } + else + rasterizer->horizontal_edges ++; + } + rasterizer->edge_pos++; + } + ctx_rasterizer_discard_edges (rasterizer); + if (apply2_sort) + ctx_edge2_insertion_sort2 (rasterizer->edges, rasterizer->active_edges); + else + ctx_edge2_insertion_sort (rasterizer->edges, rasterizer->active_edges); +} + + +#undef CTX_CMPSWP + +static inline void ctx_coverage_post_process (CtxRasterizer *rasterizer, int minx, int maxx, uint8_t *coverage, int *first_col, int *last_col) +{ + int scanline = rasterizer->scanline; +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (CTX_UNLIKELY (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM)) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + { + uint16_t temp[maxx-minx+1]; + memset (temp, 0, sizeof (temp)); + for (int x = dim/2; x < maxx-minx + 1 - dim/2; x ++) + for (int u = 0; u < dim; u ++) + { + temp[x] += coverage[minx+x+u-dim/2] * rasterizer->kernel[u] * 256; + } + for (int x = 0; x < maxx-minx + 1; x ++) + coverage[minx+x] = temp[x] >> 8; + } + } +#endif + +#if CTX_ENABLE_CLIP + if (CTX_UNLIKELY(rasterizer->clip_buffer && !rasterizer->clip_rectangle)) + { + /* perhaps not working right for clear? */ + int y = scanline / 15;//rasterizer->aa; + uint8_t *clip_line = &((uint8_t*)(rasterizer->clip_buffer->data))[rasterizer->blit_width*y]; + // XXX SIMD candidate + for (int x = minx; x <= maxx; x ++) + { +#if CTX_1BIT_CLIP + coverage[x] = (coverage[x] * ((clip_line[x/8]&(1<<(x&8)))?255:0))/255; +#else + coverage[x] = (255 + coverage[x] * clip_line[x])>>8; +#endif + } + } + if (CTX_UNLIKELY(rasterizer->aa == 1)) + { + for (int x = minx; x <= maxx; x ++) + coverage[x] = coverage[x] > 127?255:0; + } +#endif +} + +inline static void +ctx_rasterizer_generate_coverage (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *coverage, + const int fill_rule, + uint8_t aa_factor) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int active_edges = rasterizer->active_edges; + int parity = 0; + coverage -= minx; + uint8_t fraction = 255/aa_factor; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + if (!active_edges) + return; + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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 fill_rule) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int active_edges = rasterizer->active_edges; + int parity = 0; + if (!active_edges) + return; + coverage -= minx; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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) + { + coverage[first] += graystart; +#if 0 + for (int x = first + 1; x < last; x++) + coverage[x] = 255; +#else + memset(&coverage[first+1], 255, last-(first+1)); +#endif + coverage[last] += grayend; + } + else if (first == last) + coverage[first] += (graystart-(255-grayend)); + } + } +} + +static inline uint32_t +ctx_over_RGBA8 (uint32_t dst, uint32_t src, uint32_t cov) +{ + uint32_t si_ga = (src & 0xff00ff00) >> 8; + uint32_t si_rb = src & 0x00ff00ff; + uint32_t si_a = si_ga >> 16; + uint32_t rcov = (256-((255+si_a * cov)>>8)); + uint32_t di_ga = ( dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb * cov) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga * cov) + (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 = (256-si_a); + uint32_t di_ga = (dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb << 8) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga << 8) + (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 = (256-((si_a * cov)/255)); + uint32_t di_ga = (dst & 0xff00ff00) >> 8; + uint32_t di_rb = dst & 0x00ff00ff; + return + ((((si_rb * cov) + (di_rb * rcov)) & 0xff00ff00) >> 8) | + (((si_ga * cov) + (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 = (256-si_a); + 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); +} + +#if 0 + +how to check for no intersections within line? + + iterate through edges and check that order is still increasing after increment + bail at first that isnt + +compute the horizontal coverage of each intersection as if it was a slow intersect, +process this with one dispatch + + +#endif + + +inline static void +ctx_rasterizer_generate_coverage_apply (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *coverage, + int fill_rule, + int fast_source_copy, + int fast_source_over) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int bpp = rasterizer->format->bpp/8; + int active_edges = rasterizer->active_edges; + int parity = 0; +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) + + uint32_t src_pix = ((uint32_t*)rasterizer->color)[0]; + uint32_t si_ga_full = (src_pix & 0xff00ff00); + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb = src_pix & 0x00ff00ff; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + uint8_t *dst = ( (uint8_t *) rasterizer->buf) + + (rasterizer->blit_stride * (scanline / CTX_FULL_AA)); + int accumulator_x=0; + uint8_t accumulated = 0; + for (int t = 0; t < active_edges -1;t++) + { + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + 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 (accumulated) + { + if (accumulator_x == first) + { + graystart += accumulated; + } + else + { + uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]); + if (fast_source_copy) + { + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated); + } + else if (fast_source_over) + { + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated); + } + else + { + ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1); + } + } + accumulated = 0; + } + + if (first < last) + { + if (fast_source_copy) + { +#if 1 + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, graystart); + + dst_pix++; + for (int i = first + 1; i < last; i++) + { + *dst_pix = src_pix; + dst_pix++; + } +#endif + } + else if (fast_source_over) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, graystart); + dst_pix++; + for (int i = first + 1; i < last; i++) + { + *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a); + dst_pix++; + } + } + else + { + rasterizer->opaque[0] = graystart; + ctx_rasterizer_apply_coverage (rasterizer, + &dst[(first * bpp)], + first, + &rasterizer->opaque[0], + last-first); + rasterizer->opaque[0] = 255; + } + accumulated = grayend; + } + else if (first == last) + { + accumulated = (graystart-(255-grayend)); + } + accumulator_x = last; + } + } + + if (accumulated) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]); + if (fast_source_copy) + { + *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated); + } + else if (fast_source_over) + { + *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated); + } + else + { + ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1); + } + accumulated = 0; + } +} +#define CTX_EDGE(no) entries[edges[no].index] +#define CTX_EDGE_YMIN(no) (CTX_EDGE(no).data.s16[1]-1) +#define CTX_EDGE_X(no) (edges[no].val) +#define CTX_EDGE_DELTA(no) (edges[no].delta) + +inline static int ctx_rasterizer_is_simple (CtxRasterizer *rasterizer) +{ + if (rasterizer->fast_aa == 0 || + rasterizer->ending_edges || + rasterizer->pending_edges) + return 0; +#if 0 + // crossing edges is what we do worst, perhaps we should do better on + // them specifically? just ignoring them also works + // + CtxEdge *edges = rasterizer->edges; + + int active_edges = rasterizer->active_edges; + for (int t = 0; t < active_edges -1;t++) + { + int delta0 = CTX_EDGE_DELTA (t); + int delta1 = CTX_EDGE_DELTA (t+1); + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + // reverse the edge for other side proper? + int x0_end = x0 + delta0 * CTX_AA_HALFSTEP; + int x1_end = x1 + delta1 * CTX_AA_HALFSTEP; + if (x1_end < x0_end) + return 0; + } +#endif + return 1; +} + + +inline static void +ctx_rasterizer_generate_coverage_apply2 (CtxRasterizer *rasterizer, + int minx, + int maxx, + uint8_t *ignored_coverage, + int fill_rule, + int fast_source_copy, + int fast_source_over) +{ + CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]); + CtxEdge *edges = rasterizer->edges; + int scanline = rasterizer->scanline; + int bpp = rasterizer->format->bpp/8; + int active_edges = rasterizer->active_edges; + int parity = 0; + + uint32_t src_pix = ((uint32_t*)rasterizer->color)[0]; + uint32_t si_ga_full = (src_pix & 0xff00ff00); + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb = src_pix & 0x00ff00ff; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + uint8_t *dst = ( (uint8_t *) rasterizer->buf) + + (rasterizer->blit_stride * (scanline / CTX_FULL_AA)); + + uint8_t _coverage[maxx-minx+1+2]; + uint8_t *coverage=_coverage - minx; + memset(_coverage,0, sizeof(_coverage)); + + int minx_ = minx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV; + int maxx_ = maxx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV; + + int accumulated_x0 = 65536; + int accumulated_x1 = 65536; + + for (int t = 0; t < active_edges -1;t++) + { + int delta0 = CTX_EDGE_DELTA (t); + if (scanline != CTX_EDGE_YMIN(t)) + { + if (fill_rule == CTX_FILL_RULE_WINDING) + { parity += ( (CTX_EDGE (t).code == CTX_EDGE_FLIPPED) ?1:-1); } + else + { parity = 1 - parity; } + } + + if (parity) + { + int x0 = CTX_EDGE_X (t); + int x1 = CTX_EDGE_X (t+1); + int delta1 = CTX_EDGE_DELTA (t+1); + + int x0_start = x0 - delta0 * CTX_AA_HALFSTEP2; + int x1_start = x1 - delta1 * CTX_AA_HALFSTEP2; + int x0_end = x0 + delta0 * CTX_AA_HALFSTEP; + int x1_end = x1 + delta1 * CTX_AA_HALFSTEP; + + int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256); + int grayend = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256); + int first = graystart >> 8; + int last = grayend >> 8; + + first = ctx_maxi (first, minx); + last = ctx_mini (last, maxx); + + if (first < last) + { + int pre = 1; + int post = 1; + + if (abs(delta0) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA) + { + graystart = 255 - (graystart&0xff); + //grayend = (grayend & 0xff); + coverage[first] += graystart; + + accumulated_x1 = first; + accumulated_x0 = ctx_mini (accumulated_x0, first); +#if 0 + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((first) * bpp)], + first, + &coverage[first], + 1); +#endif + } + else + { + int u0 = x0_start; + int u1 = x0_end; + if (u0 > u1) + { + int tmp = u0; + u0 = u1;u1=tmp; + } + u0 = ctx_maxi (u0, minx_); + u1 = ctx_mini (u1, maxx_); + int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV); + int count = 0; + for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) + { + coverage[us + count] += (u - u0 + (0.5f)*CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) / (u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 1.0) * 255; + count++; + } + pre = (us+count-1)-first+1; + + + + if (accumulated_x1 != 65536 && us - accumulated_x1 > 4 && + accumulated_x1-accumulated_x0+1>0 + ) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + } + + accumulated_x0 = ctx_mini (accumulated_x0, us); + accumulated_x1 = us + count - 1; + } + + + if (accumulated_x0 != 65536 && accumulated_x1-accumulated_x0+1>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + accumulated_x0 = 65536; + accumulated_x1 = 65536; + } + +#if 1 + if (abs(delta1) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA) + { + //graystart = 255 - (graystart&0xff); + grayend = (grayend & 0xff); + coverage[last] += grayend; + accumulated_x1 = last; + accumulated_x0 = last; + } + else + { + int u0 = x1_start; + int u1 = x1_end; + if (u0 > u1) + { + int tmp = u0; + u0 = u1;u1=tmp; + } + u0 = ctx_maxi (u0, minx_); + u1 = ctx_mini (u1, maxx_); + int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV); + int count = 0; + for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV) + { + coverage[us + count] += 255-(u - u0 * 1.0 + ((127)/255.0)*(CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)) / (u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 0.5) * 255; + count++; + } + post = count/2; + post = last-us+1;// + + accumulated_x1 = us + count; + accumulated_x0 = us; + } +#endif + if (fast_source_copy) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + dst_pix+=pre; + for (int i = first + pre; i <= last - post; i++) + { + *dst_pix = src_pix; + dst_pix++; + } + } + else if (fast_source_over) + { + uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]); + dst_pix+=pre; + for (int i = first + pre; i <= last - post; i++) + { + *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a); + dst_pix++; + } + } + else if (last-first-pre>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((first + pre) * bpp)], + first + pre, + rasterizer->opaque, + last-first-pre); + } + } + else if (first == last) + { + graystart = 255 - (graystart&0xff); + grayend = (grayend & 0xff); + coverage[last]+=(graystart-(255-grayend)); + + accumulated_x1 = last; + accumulated_x0 = ctx_maxi (accumulated_x0, last); + } + } + } + + if (accumulated_x0 != 65536 && accumulated_x1-accumulated_x0+1>0) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[((accumulated_x0) * bpp)], + accumulated_x0, + &coverage[accumulated_x0], + accumulated_x1-accumulated_x0+1); + } +} + +#undef CTX_EDGE_Y0 +#undef CTX_EDGE + +static inline void +ctx_rasterizer_reset (CtxRasterizer *rasterizer) +{ + rasterizer->pending_edges = 0; + rasterizer->active_edges = 0; + rasterizer->has_shape = 0; + rasterizer->has_prev = 0; + rasterizer->edge_list.count = 0; // ready for new edges + rasterizer->edge_pos = 0; + rasterizer->needs_aa3 = 0; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 = 0; + rasterizer->needs_aa15 = 0; +#endif + rasterizer->scanline = 0; + if (CTX_LIKELY(!rasterizer->preserve)) + { + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + rasterizer->col_min = 5000; + rasterizer->col_max = -5000; + } + //rasterizer->comp_op = NULL; // keep comp_op cached + // between rasterizations where rendering attributes are + // nonchanging +} + + +static void +ctx_rasterizer_rasterize_edges (CtxRasterizer *rasterizer, const int fill_rule +#if CTX_SHAPE_CACHE + ,CtxShapeEntry *shape +#endif + ) +{ + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + + int real_aa = rasterizer->aa; + + int scan_start = rasterizer->blit_y * CTX_FULL_AA; + int scan_end = scan_start + rasterizer->blit_height * CTX_FULL_AA; + int blit_width = rasterizer->blit_width; + 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; + + int fast_source_copy = + (rasterizer->format->components == 4 && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR && + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_COPY && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->format->bpp == 32); + int fast_source_over = + (rasterizer->format->components == 4 && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR && + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OVER && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->format->bpp == 32); + + if (fast_source_over && rasterizer->color[3]==255) + { + fast_source_over = 0; + fast_source_copy = 1; + } + + rasterizer->prev_active_edges = -1; + if ( +#if CTX_SHAPE_CACHE + !shape && +#endif + maxx > blit_max_x - 1) + { maxx = blit_max_x - 1; } + + + minx = ctx_maxi (rasterizer->state->gstate.clip_min_x, minx); + maxx = ctx_mini (rasterizer->state->gstate.clip_max_x, maxx); + minx = ctx_maxi (0, minx); // redundant? + if (minx >= maxx) + { + ctx_rasterizer_reset (rasterizer); + return; + } +#if CTX_SHAPE_CACHE + uint8_t _coverage[shape?2:maxx-minx+1]; +#else + uint8_t _coverage[maxx-minx+1]; +#endif + uint8_t *coverage = &_coverage[0]; + + int coverage_size = +#if CTX_SHAPE_CACHE + shape?shape->width: +#endif + sizeof (_coverage); + +#if CTX_SHAPE_CACHE + if (shape) + { + coverage = &shape->data[0]; + } +#endif + //ctx_assert (coverage); + rasterizer->scan_min -= (rasterizer->scan_min % CTX_FULL_AA); +#if CTX_SHAPE_CACHE + if (shape) + { + scan_start = rasterizer->scan_min; + scan_end = rasterizer->scan_max; + } + else +#endif + { + if (rasterizer->scan_min > scan_start) + { + dst += (rasterizer->blit_stride * (rasterizer->scan_min-scan_start) / CTX_FULL_AA); + scan_start = rasterizer->scan_min; + } + scan_end = ctx_mini (rasterizer->scan_max, scan_end); + } + if (CTX_UNLIKELY(rasterizer->state->gstate.clip_min_y * CTX_FULL_AA > scan_start )) + { + dst += (rasterizer->blit_stride * (rasterizer->state->gstate.clip_min_y * CTX_FULL_AA -scan_start) / CTX_FULL_AA); + scan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + } + scan_end = ctx_mini (rasterizer->state->gstate.clip_max_y * CTX_FULL_AA, scan_end); + if (CTX_UNLIKELY(scan_start > scan_end || + (scan_start > (rasterizer->blit_y + rasterizer->blit_height) * CTX_FULL_AA) || + (scan_end < (rasterizer->blit_y) * CTX_FULL_AA))) + { + /* not affecting this rasterizers scanlines */ + ctx_rasterizer_reset (rasterizer); + return; + } + + rasterizer->horizontal_edges = 0; + ctx_rasterizer_sort_edges (rasterizer); + { + rasterizer->needs_aa3 = 0; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 = 0; + rasterizer->needs_aa15 = 0; +#endif + rasterizer->scanline = scan_start; + ctx_rasterizer_feed_edges (rasterizer, 0); + + int enable_aa3 = (real_aa >= 3); +#if CTX_ONLY_FAST_AA==0 + int enable_aa5 = (real_aa >= 5); + int enable_aa15 = (real_aa >= 15); +#endif + + int avoid_direct = (0 +#if CTX_ENABLE_CLIP + || rasterizer->clip_buffer +#endif +#if CTX_SHAPE_CACHE + || shape != NULL +#endif + ); + + for (; rasterizer->scanline <= scan_end;) + { + + rasterizer->needs_aa3 *= enable_aa3; +#if CTX_ONLY_FAST_AA==0 + rasterizer->needs_aa5 *= enable_aa5; + rasterizer->needs_aa15 *= enable_aa15; +#endif + + int needs_full_aa = + (rasterizer->horizontal_edges!=0) + || (rasterizer->active_edges != rasterizer->prev_active_edges + || (rasterizer->active_edges + rasterizer->pending_edges == rasterizer->ending_edges) + // || rasterizer->needs_aa15 + ); + +#if 0 + if (1) + { + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + else +#endif + if (CTX_UNLIKELY (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, fill_rule, real_aa); + ctx_rasterizer_increment_edges (rasterizer, increment); + } + } + else if (CTX_LIKELY(!rasterizer->needs_aa3)) // if it doesnt need aa3 it doesnt need aa5 or aa15 either + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2); + ctx_rasterizer_feed_edges (rasterizer, 0); + + if (CTX_LIKELY(rasterizer->active_edges)) + { + if (!avoid_direct) + { +#if CTX_RASTERIZER_INLINED_FAST_COPY_OVER + + if (fast_source_copy) + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 1, 0); + else if (fast_source_over) + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 0, 1); + else + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, 0, 0); +#else + ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, fill_rule, fast_source_copy, fast_source_over); +#endif + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } + else + { + memset (coverage, 0, coverage_size); + ctx_rasterizer_generate_coverage_set (rasterizer, minx, maxx, coverage, fill_rule); + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + } + } + else + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } + } +#if 1 + else if (!avoid_direct && ctx_rasterizer_is_simple (rasterizer)) + { + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2); + ctx_rasterizer_feed_edges (rasterizer, 1); + + if (CTX_LIKELY(rasterizer->active_edges)) + { +#if CTX_RASTERIZER_INLINED_FAST_COPY_OVER + if (fast_source_copy) + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 1, 0); + else if (fast_source_over) + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 0, 1); + else + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, 0, 0); +#else + ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, fill_rule, fast_source_copy, fast_source_over); +#endif + } + ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP); + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + continue; + } +#endif +#if CTX_ONLY_FAST_AA==0 +#if 1 + else if (rasterizer->needs_aa15) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i++) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA); + ctx_rasterizer_increment_edges (rasterizer, 1); + } + } +#endif + else if (rasterizer->needs_aa5) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + else if (rasterizer->needs_aa3) + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=5) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/5); + ctx_rasterizer_increment_edges (rasterizer, 5); + } + } +#endif + else + { + memset (coverage, 0, coverage_size); + for (int i = 0; i < CTX_FULL_AA; i+=3) + { + ctx_rasterizer_feed_edges (rasterizer, 0); + ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, fill_rule, CTX_FULL_AA/3); + ctx_rasterizer_increment_edges (rasterizer, 3); + } + } + + ctx_coverage_post_process (rasterizer, minx, maxx, coverage - minx, + NULL, NULL); +#if CTX_SHAPE_CACHE + if (shape == NULL) +#endif + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[(minx * rasterizer->format->bpp) /8], + minx, + coverage, + maxx-minx+ 1); + } +#if CTX_SHAPE_CACHE + if (shape) + { + coverage += shape->width; + } +#endif + dst += rasterizer->blit_stride; + rasterizer->prev_active_edges = rasterizer->active_edges; + } + } + + if (CTX_UNLIKELY(rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OUT || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_IN || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_IN || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_ATOP || + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_CLEAR)) + { + /* fill in the rest of the blitrect when compositing mode permits it */ + uint8_t nocoverage[rasterizer->blit_width]; + //int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; + int gscan_end = rasterizer->state->gstate.clip_max_y * CTX_FULL_AA; + memset (nocoverage, 0, sizeof(nocoverage)); + int startx = rasterizer->state->gstate.clip_min_x; + int endx = rasterizer->state->gstate.clip_max_x; + int clipw = endx-startx + 1; + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (gscan_start / CTX_FULL_AA); + for (rasterizer->scanline = gscan_start; rasterizer->scanline < scan_start;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, clipw); + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } + if (minx < startx) + { + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA); + for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, minx-startx); + dst += rasterizer->blit_stride; + } + } + + if (endx > maxx) + { + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA); + for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (maxx * rasterizer->format->bpp) /8], + 0, + nocoverage, endx-maxx); + + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } + } +#if 1 + dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_end / CTX_FULL_AA); + // XXX valgrind/asan this + if(0)for (rasterizer->scanline = scan_end; rasterizer->scanline/CTX_FULL_AA < gscan_end-1;) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[ (startx * rasterizer->format->bpp) /8], + 0, + nocoverage, clipw-1); + + rasterizer->scanline += CTX_FULL_AA; + dst += rasterizer->blit_stride; + } +#endif + } + ctx_rasterizer_reset (rasterizer); +} + +inline static int +ctx_is_transparent (CtxRasterizer *rasterizer, int stroke) +{ + CtxGState *gstate = &rasterizer->state->gstate; + if (gstate->global_alpha_u8 == 0) + return 1; + if (gstate->source_fill.type == CTX_SOURCE_COLOR) + { + uint8_t ga[2]; + ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga); + if (ga[1] == 0) + return 1; + } + return 0; +} + +#define CTX_RECT_FILL 1 + +#if CTX_RECT_FILL +static void +ctx_rasterizer_fill_rect (CtxRasterizer *rasterizer, + int x0, + int y0, + int x1, + int y1, + uint8_t cov) +{ + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + CtxGState *gstate = &rasterizer->state->gstate; + int blit_stride = rasterizer->blit_stride; + x0 = ctx_maxi (x0, rasterizer->blit_x); + y0 = ctx_maxi (y0, rasterizer->blit_y); + x1 = ctx_mini (x1, rasterizer->blit_x + rasterizer->blit_width); + y1 = ctx_mini (y1, rasterizer->blit_y + rasterizer->blit_height); + + dst += (y0 - rasterizer->blit_y) * rasterizer->blit_stride; + int width = x1 - x0; + + if (CTX_UNLIKELY(width <=0)) + return; + _ctx_setup_compositor (rasterizer); + + if (cov == 255 && rasterizer->format->components == 4 && + gstate->source_fill.type == CTX_SOURCE_COLOR && + rasterizer->format->bpp == 32 && + gstate->blend_mode == CTX_BLEND_NORMAL) + { + if ( (rasterizer->color[3]==255 && gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + ||(gstate->compositing_mode == CTX_COMPOSITE_COPY) + ) + { + if (CTX_UNLIKELY(width == 1)) + { + for (int y = y0; y < y1; y++) + { + memcpy (&dst[(x0)*4], rasterizer->color, 4); + dst += blit_stride; + } + } + else + { + uint32_t color; + memcpy(&color, rasterizer->color, 4); + dst += x0 * 4; + for (int y = y0; y < y1; y++) + { + uint32_t *dst_i = (uint32_t*)&dst[0]; + for (int i = 0; i < width; i++) + { + dst_i[i] = color; + } + dst += blit_stride; + } + } + return; + } + else if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER) + { + uint32_t *src = (uint32_t*)&rasterizer->color[0]; + uint32_t si_ga_full = (*src & 0xff00ff00); + uint32_t si_rb = *src & 0x00ff00ff; + + uint32_t si_ga = si_ga_full >> 8; + uint32_t si_rb_full = si_rb << 8; + uint32_t si_a = si_ga >> 16; + dst += x0 * 4; + if (CTX_UNLIKELY(width == 1)) + { + for (int y = y0; y < y1; y++) + { + ((uint32_t*)(dst))[0] = ctx_over_RGBA8_full_2 ( + ((uint32_t*)(dst))[0], si_ga_full, si_rb_full, si_a); + dst += blit_stride; + } + } + else + { + for (int y = y0; y < y1; y++) + { + uint32_t *dst_i = (uint32_t*)&dst[0]; + for (int i = 0; i < width; i++) + { + dst_i[i] = ctx_over_RGBA8_full_2 (dst_i[i], si_ga_full, si_rb_full, si_a); + } + dst += blit_stride; + } + } + return; + } + } + + if (CTX_UNLIKELY(width == 1)) + { + uint8_t coverage[1]={cov}; + dst += (x0) * rasterizer->format->bpp/8; + for (int y = y0; y < y1; y++) + { + ctx_rasterizer_apply_coverage (rasterizer, + &dst[0], + x0, coverage, 1); + dst += blit_stride; + } + } + else + { + uint8_t coverage[width]; + memset (coverage, cov, sizeof (coverage) ); + rasterizer->scanline = y0 * CTX_FULL_AA; + dst += (x0) * rasterizer->format->bpp/8; + for (int y = y0; y < y1; y++) + { + rasterizer->scanline += CTX_FULL_AA; + ctx_rasterizer_apply_coverage (rasterizer, + &dst[0], + x0, + coverage, width); + dst += blit_stride; + } + } + return; +} +#endif + +static inline float ctx_fmod1f (float val) +{ + int vali = val; + return val - vali; +} + +static void +ctx_rasterizer_fill (CtxRasterizer *rasterizer) +{ + int count = rasterizer->preserve?rasterizer->edge_list.count:0; + + CtxSegment temp[count]; /* copy of already built up path's poly line + XXX - by building a large enough path + the stack can be smashed! + */ + if (CTX_UNLIKELY(rasterizer->preserve)) + { memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) ); } + +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + for (int i = 0; i < rasterizer->edge_list.count; i++) + { + CtxSegment *entry = &((CtxSegment*)rasterizer->edge_list.entries)[i]; + entry->data.s16[2] += rasterizer->shadow_x * CTX_SUBDIV; + entry->data.s16[3] += rasterizer->shadow_y * CTX_FULL_AA; + } + rasterizer->scan_min += rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->scan_max += rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->col_min += (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + rasterizer->col_max += (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + } +#endif + + if (CTX_UNLIKELY(ctx_is_transparent (rasterizer, 0) || + rasterizer->scan_min / CTX_FULL_AA > rasterizer->blit_y + rasterizer->blit_height || + rasterizer->scan_max / CTX_FULL_AA < rasterizer->blit_y || + rasterizer->col_min / CTX_SUBDIV > rasterizer->blit_x + rasterizer->blit_width || + rasterizer->col_max / CTX_SUBDIV < rasterizer->blit_x)) + { + ctx_rasterizer_reset (rasterizer); + } + else + { + _ctx_setup_compositor (rasterizer); + +#if 1 + rasterizer->state->min_x = ctx_mini (rasterizer->state->min_x, rasterizer->col_min / CTX_SUBDIV); + rasterizer->state->max_x = ctx_maxi (rasterizer->state->min_x, rasterizer->col_max / CTX_SUBDIV); + rasterizer->state->min_y = ctx_mini (rasterizer->state->min_y, rasterizer->scan_min / CTX_FULL_AA); + rasterizer->state->max_y = ctx_maxi (rasterizer->state->min_y, rasterizer->scan_max / CTX_FULL_AA); +#else + if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV < rasterizer->state->min_x)) + rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV; + if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV > rasterizer->state->max_x)) + rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV; + + if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA < rasterizer->state->min_y)) + rasterizer->state->min_y = rasterizer->scan_min / CTX_FULL_AA; + if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA > rasterizer->state->max_y)) + rasterizer->state->max_y = rasterizer->scan_max / CTX_FULL_AA; +#endif + +#if CTX_RECT_FILL + if (rasterizer->edge_list.count == 6) + { + 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 + ) + { + if(((entry1->data.s16[2] % (CTX_SUBDIV)) == 0) && + ((entry1->data.s16[3] % (CTX_FULL_AA)) == 0) && + ((entry3->data.s16[2] % (CTX_SUBDIV)) == 0) && + ((entry3->data.s16[3] % (CTX_FULL_AA)) == 0)) + { + /* best-case axis aligned rectangle */ + int x0 = entry3->data.s16[2] / CTX_SUBDIV; + int y0 = entry3->data.s16[3] / CTX_FULL_AA; + int x1 = entry1->data.s16[2] / CTX_SUBDIV; + int y1 = entry1->data.s16[3] / CTX_FULL_AA; + + ctx_rasterizer_fill_rect (rasterizer, x0, y0, x1, y1, 255); + ctx_rasterizer_reset (rasterizer); + goto done; + } + else + { + float x0 = entry3->data.s16[2] * 1.0f / CTX_SUBDIV; + float y0 = entry3->data.s16[3] * 1.0f / CTX_FULL_AA; + float x1 = entry1->data.s16[2] * 1.0f / CTX_SUBDIV; + float y1 = entry1->data.s16[3] * 1.0f / CTX_FULL_AA; + + x0 = ctx_maxf (x0, rasterizer->blit_x); + y0 = ctx_maxf (y0, rasterizer->blit_y); + x1 = ctx_minf (x1, rasterizer->blit_x + rasterizer->blit_width); + y1 = ctx_minf (y1, rasterizer->blit_y + rasterizer->blit_height); + + uint8_t left = 255-ctx_fmod1f (x0) * 255; + uint8_t top = 255-ctx_fmod1f (y0) * 255; + uint8_t right = ctx_fmod1f (x1) * 255; + uint8_t bottom = ctx_fmod1f (y1) * 255; + + x0 = ctx_floorf (x0); + y0 = ctx_floorf (y0); + x1 = ctx_floorf (x1+7/8.0f); + y1 = ctx_floorf (y1+14/15.0f); + + int has_top = (top < 255); + int has_bottom = (bottom <255); + int has_right = (right >0); + int has_left = (left >0); + + int width = x1 - x0; + + if (CTX_LIKELY(width >0)) + { + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + uint8_t coverage[width+2]; + dst += (((int)y0) - rasterizer->blit_y) * rasterizer->blit_stride; + dst += ((int)x0) * rasterizer->format->bpp/8; + + if (has_top) + { + int i = 0; + if (has_left) + { + coverage[i++] = top * left / 255; + } + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = top; + coverage[i++]= top * right / 255; + + ctx_rasterizer_apply_coverage (rasterizer,dst, + x0, + coverage, width); + dst += rasterizer->blit_stride; + } + + if (!( + (rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_COPY|| + rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OVER) && + rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL && + rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR + )) + { + int i = 0; + if (has_left) + { + coverage[i++] = left; + } + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = 255; + coverage[i++] = right; + + for (int ty = y0+has_top; ty < y1-has_bottom; ty++) + { + ctx_rasterizer_apply_coverage (rasterizer, dst, x0, coverage, width); + dst += rasterizer->blit_stride; + } + } + else + { + if (has_left) + ctx_rasterizer_fill_rect (rasterizer, x0, y0 + has_top, x0+1, y1 - has_bottom, left); + if (has_right) + ctx_rasterizer_fill_rect (rasterizer, x1-1, y0 + has_top, x1, y1 - has_bottom, right); + x0 += has_left; + y0 += has_top; + y1 -= has_bottom; + x1 -= has_right; + ctx_rasterizer_fill_rect (rasterizer, x0,y0,x1,y1,255); + + dst += rasterizer->blit_stride * ((((int)y1)-has_bottom) - (((int)y0)+has_top)); + } + + if (has_bottom) + { + int i = 0; + if (has_left) + coverage[i++] = bottom * left / 255; + for (int x = x0 + has_left; x < x1 - has_right; x++) + coverage[i++] = bottom; + coverage[i++]= bottom * right / 255; + + ctx_rasterizer_apply_coverage (rasterizer,dst, x0, coverage, width); + } + } + + ctx_rasterizer_reset (rasterizer); + goto done; + } + } + } +#endif + ctx_rasterizer_finish_shape (rasterizer); + + uint32_t hash = ctx_rasterizer_poly_to_edges (rasterizer); + if (hash){}; + +#if CTX_SHAPE_CACHE + int width = (rasterizer->col_max + (CTX_SUBDIV-1) ) / CTX_SUBDIV - rasterizer->col_min/CTX_SUBDIV + 1; + int height = (rasterizer->scan_max + (CTX_FULL_AA-1) ) / CTX_FULL_AA - rasterizer->scan_min / CTX_FULL_AA + 1; + if (width * height < CTX_SHAPE_CACHE_DIM && width >=1 && height >= 1 + && width < CTX_SHAPE_CACHE_MAX_DIM + && height < CTX_SHAPE_CACHE_MAX_DIM +#if CTX_ENABLE_SHADOW_BLUR + && !rasterizer->in_shadow +#endif + ) + { + int scan_min = rasterizer->scan_min; + int col_min = rasterizer->col_min; + scan_min -= (scan_min % CTX_FULL_AA); + int y0 = scan_min / CTX_FULL_AA; + int y1 = y0 + height; + int x0 = col_min / CTX_SUBDIV; + int ymin = y0; + int x1 = x0 + width; + int clip_x_min = rasterizer->blit_x; + int clip_x_max = rasterizer->blit_x + rasterizer->blit_width - 1; + int clip_y_min = rasterizer->blit_y; + int clip_y_max = rasterizer->blit_y + rasterizer->blit_height - 1; + + int dont_cache = 0; + if (CTX_UNLIKELY(x1 >= clip_x_max)) + { x1 = clip_x_max; + dont_cache = 1; + } + int xo = 0; + if (CTX_UNLIKELY(x0 < clip_x_min)) + { + xo = clip_x_min - x0; + x0 = clip_x_min; + dont_cache = 1; + } + if (CTX_UNLIKELY(y0 < clip_y_min || y1 >= clip_y_max)) + dont_cache = 1; + if (dont_cache || !_ctx_shape_cache_enabled) + { + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule +#if CTX_SHAPE_CACHE + , NULL +#endif + ); + } + else + { + rasterizer->scanline = scan_min; + CtxShapeEntry *shape = ctx_shape_entry_find (rasterizer, hash, width, height); + + if (shape->uses == 0) + { + CtxBuffer *buffer_backup = rasterizer->clip_buffer; + rasterizer->clip_buffer = NULL; + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule, shape); + rasterizer->clip_buffer = buffer_backup; + } + rasterizer->scanline = scan_min; + + int ewidth = x1 - x0; + if (ewidth>0) + { + if (rasterizer->clip_buffer && !rasterizer->clip_rectangle) + { + uint8_t composite[ewidth]; + 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*((uint8_t*)rasterizer->clip_buffer->data) [ + ((y-rasterizer->blit_y) * rasterizer->blit_width) + x0 + x])/255; + composite[x] = val; + } + ctx_rasterizer_apply_coverage (rasterizer, + ( (uint8_t *) rasterizer->buf) + (y-rasterizer->blit_y) * rasterizer->blit_stride + (int) (x0) * rasterizer->format->bpp/8, + x0, // is 0 + composite, + ewidth ); + rasterizer->scanline += CTX_FULL_AA; + } + } + } + else + for (int y = y0; y < y1; y++) + { + if (CTX_LIKELY((y >= clip_y_min) && (y <= clip_y_max) )) + { + ctx_rasterizer_apply_coverage (rasterizer, + ( (uint8_t *) rasterizer->buf) + (y-rasterizer->blit_y) * rasterizer->blit_stride + (int) (x0) * rasterizer->format->bpp/8, + x0, + &shape->data[shape->width * (int) (y-ymin) + xo], + ewidth ); + } + rasterizer->scanline += CTX_FULL_AA; + } + } + if (shape->uses != 0) + { + ctx_rasterizer_reset (rasterizer); + } + } + } + else +#endif + { + + ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule +#if CTX_SHAPE_CACHE + , NULL +#endif + ); + } + } +done: + if (CTX_UNLIKELY(rasterizer->preserve)) + { + memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) ); + rasterizer->edge_list.count = count; + } +#if CTX_ENABLE_SHADOW_BLUR + if (CTX_UNLIKELY(rasterizer->in_shadow)) + { + rasterizer->scan_min -= rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->scan_max -= rasterizer->shadow_y * CTX_FULL_AA; + rasterizer->col_min -= (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + rasterizer->col_max -= (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV; + } +#endif + rasterizer->preserve = 0; +} + +#if 0 +static void +ctx_rasterizer_triangle (CtxRasterizer *rasterizer, + int x0, int y0, + int x1, int y1, + int x2, int y2, + int r0, int g0, int b0, int a0, + int r1, int g1, int b1, int a1, + int r2, int g2, int b2, int a2, + int u0, int v0, + int u1, int v1) +{ + +} +#endif + + +typedef struct _CtxTermGlyph CtxTermGlyph; + +struct _CtxTermGlyph +{ + uint32_t unichar; + int col; + int row; + uint8_t rgba_bg[4]; + uint8_t rgba_fg[4]; +}; + +static int _ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke); +static void +ctx_rasterizer_glyph (CtxRasterizer *rasterizer, uint32_t unichar, int stroke) +{ + float tx = rasterizer->state->x; + float ty = rasterizer->state->y - rasterizer->state->gstate.font_size; + float tx2 = rasterizer->state->x + rasterizer->state->gstate.font_size; + float ty2 = rasterizer->state->y + rasterizer->state->gstate.font_size; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device (rasterizer->state, &tx2, &ty2); + + if (tx2 < rasterizer->blit_x || ty2 < rasterizer->blit_y) return; + if (tx > rasterizer->blit_x + rasterizer->blit_width || + ty > rasterizer->blit_y + rasterizer->blit_height) + return; + +#if CTX_BRAILLE_TEXT + float font_size = 0; + int ch = 1; + int cw = 1; + + if (rasterizer->term_glyphs) + { + float tx = 0; + float ty = rasterizer->state->gstate.font_size; + float txb = 0; + float tyb = 0; + + ch = ctx_term_get_cell_height (rasterizer->ctx); + cw = ctx_term_get_cell_width (rasterizer->ctx); + + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device (rasterizer->state, &txb, &tyb); + font_size = ty-tyb; + } + if (rasterizer->term_glyphs && !stroke && + fabs (font_size - ch) < 0.5) + { + float tx = rasterizer->x; + float ty = rasterizer->y; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + int col = tx / cw + 1; + int row = ty / ch + 1; + CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1); + ctx_list_append (&rasterizer->glyphs, glyph); + glyph->unichar = unichar; + glyph->col = col; + glyph->row = row; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, + &glyph->rgba_fg[0]); + } + else +#endif + _ctx_glyph (rasterizer->ctx, unichar, stroke); +} + + +static void +_ctx_text (Ctx *ctx, + const char *string, + int stroke, + int visible); +static void +ctx_rasterizer_text (CtxRasterizer *rasterizer, const char *string, int stroke) +{ +#if CTX_BRAILLE_TEXT + float font_size = 0; + if (rasterizer->term_glyphs) + { + float tx = 0; + float ty = rasterizer->state->gstate.font_size; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + font_size = ty; + } + int ch = ctx_term_get_cell_height (rasterizer->ctx); + int cw = ctx_term_get_cell_width (rasterizer->ctx); + + if (rasterizer->term_glyphs && !stroke && + fabs (font_size - ch) < 0.5) + { + float tx = rasterizer->x; + float ty = rasterizer->y; + _ctx_user_to_device (rasterizer->state, &tx, &ty); + int col = tx / cw + 1; + int row = ty / ch + 1; + for (int i = 0; string[i]; i++, col++) + { + CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1); + ctx_list_prepend (&rasterizer->glyphs, glyph); + glyph->unichar = string[i]; + glyph->col = col; + glyph->row = row; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, + glyph->rgba_fg); + } + } + else +#endif + { + _ctx_text (rasterizer->ctx, string, stroke, 1); + } +} + +void +_ctx_font (Ctx *ctx, const char *name); +static void +ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name) +{ + _ctx_font (rasterizer->ctx, font_name); +} + +static void +ctx_rasterizer_arc (CtxRasterizer *rasterizer, + float x, + float y, + float radius, + float start_angle, + float end_angle, + int anticlockwise) +{ + int full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; + full_segments = radius * CTX_PI * 2 / 4.0; + if (full_segments > CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS) + { full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; } + if (full_segments < 24) full_segments = 24; + float step = CTX_PI*2.0/full_segments; + int steps; + + if (end_angle < -30.0) + end_angle = -30.0; + if (start_angle < -30.0) + start_angle = -30.0; + if (end_angle > 30.0) + end_angle = 30.0; + if (start_angle > 30.0) + start_angle = 30.0; + + if (radius <= 0.0001) + return; + + if (end_angle == start_angle) + // XXX also detect arcs fully outside render view + { + if (rasterizer->has_prev!=0) + ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); + else + ctx_rasterizer_move_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); + return; + } +#if 1 + if ( (!anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f) || + ( (anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f ) ) + || (anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f) || (!anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f ) ) + { + start_angle = start_angle; + steps = full_segments - 1; + } + else +#endif + { + steps = (end_angle - start_angle) / (CTX_PI*2) * full_segments; + if (anticlockwise) + { steps = full_segments - steps; }; + // if (steps > full_segments) + // steps = full_segments; + } + if (anticlockwise) { step = step * -1; } + int first = 1; + if (steps == 0 /* || steps==full_segments -1 || (anticlockwise && steps == full_segments) */) + { + float xv = x + ctx_cosf (start_angle) * radius; + float yv = y + ctx_sinf (start_angle) * radius; + if (!rasterizer->has_prev) + { ctx_rasterizer_move_to (rasterizer, xv, yv); } + first = 0; + } + else + { + for (float angle = start_angle, i = 0; i < steps; angle += step, i++) + { + float xv = x + ctx_cosf (angle) * radius; + float yv = y + ctx_sinf (angle) * radius; + if (first && !rasterizer->has_prev) + { ctx_rasterizer_move_to (rasterizer, xv, yv); } + else + { ctx_rasterizer_line_to (rasterizer, xv, yv); } + first = 0; + } + } + ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius, + y + ctx_sinf (end_angle) * radius); +} + +static void +ctx_rasterizer_quad_to (CtxRasterizer *rasterizer, + float cx, + float cy, + float x, + float y) +{ + /* XXX : it is probably cheaper/faster to do quad interpolation directly - + * though it will increase the code-size, an + * alternative is to turn everything into cubic + * and deal with cubics more directly during + * rasterization + */ + ctx_rasterizer_curve_to (rasterizer, + (cx * 2 + rasterizer->x) / 3.0f, (cy * 2 + rasterizer->y) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); +} + +static void +ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer, + float cx, float cy, + float x, float y) +{ + ctx_rasterizer_quad_to (rasterizer, cx + rasterizer->x, cy + rasterizer->y, + x + rasterizer->x, y + rasterizer->y); +} + +#define LENGTH_OVERSAMPLE 1 +static void +ctx_rasterizer_pset (CtxRasterizer *rasterizer, int x, int y, uint8_t cov) +{ + // XXX - we avoid rendering here x==0 - to keep with + // an off-by one elsewhere + // + // XXX onlt works in rgba8 formats + if (x <= 0 || y < 0 || x >= rasterizer->blit_width || + y >= rasterizer->blit_height) + { return; } + uint8_t fg_color[4]; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, fg_color); + uint8_t pixel[4]; + uint8_t *dst = ( (uint8_t *) rasterizer->buf); + dst += y * rasterizer->blit_stride; + dst += x * rasterizer->format->bpp / 8; + if (!rasterizer->format->to_comp || + !rasterizer->format->from_comp) + { return; } + if (cov == 255) + { + for (int c = 0; c < 4; c++) + { + pixel[c] = fg_color[c]; + } + } + else + { + rasterizer->format->to_comp (rasterizer, x, dst, &pixel[0], 1); + for (int c = 0; c < 4; c++) + { + pixel[c] = ctx_lerp_u8 (pixel[c], fg_color[c], cov); + } + } + rasterizer->format->from_comp (rasterizer, x, &pixel[0], dst, 1); +} + +static void +ctx_rasterizer_stroke_1px (CtxRasterizer *rasterizer) +{ + int count = rasterizer->edge_list.count; + CtxSegment *temp = (CtxSegment*)rasterizer->edge_list.entries; + float prev_x = 0.0f; + float prev_y = 0.0f; + int aa = 15;//rasterizer->aa; + int start = 0; + int end = 0; +#if 0 + float factor = ctx_matrix_get_scale (&state->gstate.transform); +#endif + + while (start < count) + { + int started = 0; + int i; + for (i = start; i < count; i++) + { + CtxSegment *entry = &temp[i]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + if (started) + { + end = i - 1; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + int dx = x - prev_x; + int dy = y - prev_y; + int length = ctx_maxf (abs (dx), abs (dy) ); + if (length) + { + length *= LENGTH_OVERSAMPLE; + int len = length; + int tx = prev_x * 256; + int ty = prev_y * 256; + dx *= 256; + dy *= 256; + dx /= length; + dy /= length; + for (int i = 0; i < len; i++) + { + ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255); + tx += dx; + ty += dy; + ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255); + } + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + start = end+1; + } + ctx_rasterizer_reset (rasterizer); +} + +static void +ctx_rasterizer_stroke (CtxRasterizer *rasterizer) +{ + CtxGState *gstate = &rasterizer->state->gstate; + CtxSource source_backup; + if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL) + { + source_backup = gstate->source_fill; + gstate->source_fill = rasterizer->state->gstate.source_stroke; + } + int count = rasterizer->edge_list.count; + int preserved = rasterizer->preserve; + float factor = ctx_matrix_get_scale (&gstate->transform); + float line_width = gstate->line_width * factor; + + CtxSegment temp[count]; /* copy of already built up path's poly line */ + memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) ); + + if (rasterizer->edge_list.count == 6) + { + CtxSegment *entry0 = &((CtxSegment*)rasterizer->edge_list.entries)[0]; + CtxSegment *entry1 = &((CtxSegment*)rasterizer->edge_list.entries)[1]; + CtxSegment *entry2 = &((CtxSegment*)rasterizer->edge_list.entries)[2]; + CtxSegment *entry3 = &((CtxSegment*)rasterizer->edge_list.entries)[3]; + //fprintf (stderr, "{%i %.2f %.2f}", lw, lwmod, line_width); + + if (!rasterizer->state->gstate.clipped && + (entry0->data.s16[2] == entry1->data.s16[2]) && + (entry0->data.s16[3] == entry3->data.s16[3]) && + (entry1->data.s16[3] == entry2->data.s16[3]) && + (entry2->data.s16[2] == entry3->data.s16[2]) +#if CTX_ENABLE_SHADOW_BLUR + && !rasterizer->in_shadow +#endif + ) + { + float lwmod = ctx_fmod1f (line_width); + int lw = ctx_floorf (line_width + 0.5f); + int is_compat_even = (lw % 2 == 0) && (lwmod < 0.1); // only even linewidths implemented properly + int is_compat_odd = (lw % 2 == 1) && (lwmod < 0.1); // only even linewidths implemented properly + + int off_x = 0; + int off_y = 0; + + + if (is_compat_odd) + { + off_x = CTX_SUBDIV/2; + off_y = CTX_FULL_AA/2; + } + + if((is_compat_odd || is_compat_even) && + (((entry1->data.s16[2]-off_x) % (CTX_SUBDIV)) == 0) && + (((entry1->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0) && + (((entry3->data.s16[2]-off_x) % (CTX_SUBDIV)) == 0) && + (((entry3->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0)) + { + float x0 = entry3->data.s16[2] * 1.0f / CTX_SUBDIV; + float y0 = entry3->data.s16[3] * 1.0f / CTX_FULL_AA; + float x1 = entry1->data.s16[2] * 1.0f / CTX_SUBDIV; + float y1 = entry1->data.s16[3] * 1.0f / CTX_FULL_AA; + + int bw = lw/2+1; + int bwb = lw/2; + + if (is_compat_even) + { + bw = lw/2; + } + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0-bwb, x1+bw, y0+bw, 255); + + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y1-bwb, x1-bwb, y1+bw, 255); + ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0, x0+bw, y1, 255); + ctx_rasterizer_fill_rect (rasterizer, x1-bwb, y0, x1+bw, y1+bw, 255); + ctx_rasterizer_reset (rasterizer); + goto done; + } + } + } + + { + + int aa = CTX_FULL_AA; +#if 0 + if (CTX_UNLIKELY(gstate->line_width * factor <= 0.0f && + gstate->line_width * factor > -10.0f)) + { + ctx_rasterizer_stroke_1px (rasterizer); + } + else +#endif + { + if (line_width < 5.0f) + { + factor *= 0.89; /* this hack adjustment makes sharp 1px and 2px strokewidths + // end up sharp without erronious AA; we seem to be off by + // one somewhere else, causing the need for this + // */ + line_width *= 0.89f; + } + ctx_rasterizer_reset (rasterizer); /* then start afresh with our stroked shape */ + CtxMatrix transform_backup = gstate->transform; + _ctx_matrix_identity (&gstate->transform); + float prev_x = 0.0f; + float prev_y = 0.0f; + float half_width_x = line_width/2; + float half_width_y = line_width/2; + if (CTX_UNLIKELY(line_width <= 0.0f)) + { // makes 0 width be hairline + half_width_x = .5f; + half_width_y = .5f; + } + int start = 0; + int end = 0; + while (start < count) + { + int started = 0; + int i; + for (i = start; i < count; i++) + { + CtxSegment *entry = &temp[i]; + float x, y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (CTX_LIKELY(started)) + { + end = i - 1; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + float dx = x - prev_x; + float dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + if (CTX_LIKELY(length>0.001f)) + { + float recip_length = 1.0/length; + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + ctx_rasterizer_finish_shape (rasterizer); + ctx_rasterizer_move_to (rasterizer, prev_x+dy, prev_y-dx); + } + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + + // we need to know the slope of the other side + + // XXX possible miter line-to + //ctx_rasterizer_line_to (rasterizer, prev_x-dy+4, prev_y+dx+10); + //ctx_rasterizer_line_to (rasterizer, prev_x-dy+8, prev_y+dx+0); + + + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + for (int i = end; i >= start; i--) + { + CtxSegment *entry = &temp[i]; + float x, y, dx, dy; + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + dx = x - prev_x; + dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + float recip_length = 1.0f/length; + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + if (CTX_LIKELY(length>0.001f)) + { + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + // XXX possible miter line-to + // ctx_rasterizer_line_to (rasterizer, prev_x-dy+10, prev_y+dx+10); + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + prev_x = x; + prev_y = y; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + dx = x - prev_x; + dy = y - prev_y; + length = ctx_fast_hypotf (dx, dy); + recip_length = 1.0f/length; + if (CTX_LIKELY(length>0.001f)) + { + dx = dx * recip_length * half_width_x; + dy = dy * recip_length * half_width_y; + ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx); + ctx_rasterizer_line_to (rasterizer, x-dy, y+dx); + } + } + if ( (prev_x != x) && (prev_y != y) ) + { + prev_x = x; + prev_y = y; + } + } + start = end+1; + } + ctx_rasterizer_finish_shape (rasterizer); + switch (gstate->line_cap) + { + case CTX_CAP_SQUARE: // XXX: incorrect - if rectangles were in + // reverse order - rotation would be off + // better implement correct here + { + float x = 0, y = 0; + int has_prev = 0; + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &temp[i]; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (has_prev) + { + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x, half_width_y); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + has_prev = 1; + } + ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2); + ctx_rasterizer_finish_shape (rasterizer); + } + break; + case CTX_CAP_NONE: /* nothing to do */ + break; + case CTX_CAP_ROUND: + { + float x = 0, y = 0; + int has_prev = 0; + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &temp[i]; + if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE)) + { + if (has_prev) + { + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + has_prev = 1; + } + ctx_rasterizer_move_to (rasterizer, x, y); + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + break; + } + } + switch (gstate->line_join) + { + case CTX_JOIN_BEVEL: + case CTX_JOIN_MITER: + break; + case CTX_JOIN_ROUND: + { + float x = 0, y = 0; + for (int i = 0; i < count-1; i++) + { + CtxSegment *entry = &temp[i]; + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + if (CTX_UNLIKELY(entry[1].code == CTX_EDGE)) + { + ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*2, 0, 1); + ctx_rasterizer_finish_shape (rasterizer); + } + } + break; + } + } + CtxFillRule rule_backup = gstate->fill_rule; + gstate->fill_rule = CTX_FILL_RULE_WINDING; + rasterizer->preserve = 0; // so fill isn't tripped + ctx_rasterizer_fill (rasterizer); + gstate->fill_rule = rule_backup; + gstate->transform = transform_backup; + } + } +done: + if (preserved) + { + memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) ); + rasterizer->edge_list.count = count; + rasterizer->preserve = 0; + } + if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL) + gstate->source_fill = source_backup; +} + +#if CTX_1BIT_CLIP +#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY1 +#else +#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY8 +#endif + + +static void +ctx_rasterizer_clip_reset (CtxRasterizer *rasterizer) +{ +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + ctx_buffer_free (rasterizer->clip_buffer); + rasterizer->clip_buffer = NULL; +#endif + rasterizer->state->gstate.clip_min_x = rasterizer->blit_x; + rasterizer->state->gstate.clip_min_y = rasterizer->blit_y; + + rasterizer->state->gstate.clip_max_x = rasterizer->blit_x + rasterizer->blit_width - 1; + rasterizer->state->gstate.clip_max_y = rasterizer->blit_y + rasterizer->blit_height - 1; +} + +static void +ctx_rasterizer_clip_apply (CtxRasterizer *rasterizer, + CtxSegment *edges) +{ + int count = edges[0].data.u32[0]; + + int minx = 5000; + int miny = 5000; + int maxx = -5000; + int maxy = -5000; + int prev_x = 0; + int prev_y = 0; + int blit_width = rasterizer->blit_width; + int blit_height = rasterizer->blit_height; + + int aa = 15;//rasterizer->aa; + float coords[6][2]; + + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &edges[i+1]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + if (prev_x < minx) { minx = prev_x; } + if (prev_y < miny) { miny = prev_y; } + if (prev_x > maxx) { maxx = prev_x; } + if (prev_y > maxy) { maxy = prev_y; } + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + if (x < minx) { minx = x; } + if (y < miny) { miny = y; } + if (x > maxx) { maxx = x; } + if (y > maxy) { maxy = y; } + + if (i < 6) + { + coords[i][0] = x; + coords[i][1] = y; + } + } + +#if CTX_ENABLE_CLIP + + if ((rasterizer->clip_rectangle==1 + || !rasterizer->clip_buffer) + ) + { + if (count == 6) + { + if (coords[3][0] == coords[5][0] && + coords[3][1] == coords[5][1]) + { +#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; + } + } + } + rasterizer->clip_rectangle = 0; + + if ((minx == maxx) || (miny == maxy)) // XXX : reset hack + { + ctx_rasterizer_clip_reset (rasterizer); + return;//goto done; + } + + int we_made_it = 0; + CtxBuffer *clip_buffer; + + if (!rasterizer->clip_buffer) + { + rasterizer->clip_buffer = ctx_buffer_new (blit_width, + blit_height, + CTX_CLIP_FORMAT); + clip_buffer = rasterizer->clip_buffer; + we_made_it = 1; + if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1) + memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height/8); + else + memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height); + } + else + { + clip_buffer = ctx_buffer_new (blit_width, blit_height, + CTX_CLIP_FORMAT); + } + + { + + int prev_x = 0; + int prev_y = 0; + + Ctx *ctx = ctx_new_for_framebuffer (clip_buffer->data, blit_width, blit_height, + blit_width, + CTX_CLIP_FORMAT); + + for (int i = 0; i < count; i++) + { + CtxSegment *entry = &edges[i+1]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + ctx_move_to (ctx, prev_x, prev_y); + } + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + ctx_line_to (ctx, x, y); + } + ctx_gray (ctx, 1.0f); + ctx_fill (ctx); + ctx_free (ctx); + } + + int maybe_rect = 1; + rasterizer->clip_rectangle = 0; + + if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1) + { + int count = blit_width * blit_height / 8; + for (int i = 0; i < count; i++) + { + ((uint8_t*)rasterizer->clip_buffer->data)[i] = + (((uint8_t*)rasterizer->clip_buffer->data)[i] & + ((uint8_t*)clip_buffer->data)[i]); + } + } + else + { + int count = blit_width * blit_height; + + + int i; + int x0 = 0; + int y0 = 0; + int width = -1; + int next_stage = 0; + uint8_t *p_data = (uint8_t*)rasterizer->clip_buffer->data; + uint8_t *data = (uint8_t*)clip_buffer->data; + + i=0; + /* find upper left */ + for (; i < count && maybe_rect && !next_stage; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + switch (val) + { + case 255: + x0 = i % blit_width; + y0 = i / blit_width; + next_stage = 1; + break; + case 0: break; + default: + maybe_rect = 0; + break; + } + } + + next_stage = 0; + /* figure out with */ + for (; i < count && !next_stage && maybe_rect; i++) + { + int x = i % blit_width; + int y = i / blit_width; + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (y == y0) + { + switch (val) + { + case 255: + width = x - x0 + 1; + break; + case 0: + next_stage = 1; + break; + default: + maybe_rect = 0; + break; + } + if (x % blit_width == blit_width - 1) next_stage = 1; + } + else next_stage = 1; + } + + next_stage = 0; + /* body */ + for (; i < count && maybe_rect && !next_stage; i++) + { + int x = i % blit_width; + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (x < x0) + { + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } else if (x < x0 + width) + { + if (val != 255){ if (val != 0) maybe_rect = 0; next_stage = 1; } + } else { + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } + } + + next_stage = 0; + /* foot */ + for (; i < count && maybe_rect && !next_stage; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + + if (val != 0){ maybe_rect = 0; next_stage = 1; } + } + + + for (; i < count; i++) + { + uint8_t val = (p_data[i] * data[i])/255; + data[i] = val; + } + + if (maybe_rect) + rasterizer->clip_rectangle = 1; + } + if (!we_made_it) + ctx_buffer_free (clip_buffer); +#else + if (coords[0][0]){}; +#endif + + rasterizer->state->gstate.clip_min_x = ctx_maxi (minx, + rasterizer->state->gstate.clip_min_x); + rasterizer->state->gstate.clip_min_y = ctx_maxi (miny, + rasterizer->state->gstate.clip_min_y); + rasterizer->state->gstate.clip_max_x = ctx_mini (maxx, + rasterizer->state->gstate.clip_max_x); + rasterizer->state->gstate.clip_max_y = ctx_mini (maxy, + rasterizer->state->gstate.clip_max_y); +} + +static void +ctx_rasterizer_clip (CtxRasterizer *rasterizer) +{ + int count = rasterizer->edge_list.count; + CtxSegment temp[count+1]; /* copy of already built up path's poly line */ + rasterizer->state->has_clipped=1; + rasterizer->state->gstate.clipped=1; + //if (rasterizer->preserve) + { memcpy (temp + 1, rasterizer->edge_list.entries, sizeof (temp) - sizeof (temp[0])); + temp[0].code = CTX_NOP; + temp[0].data.u32[0] = count; + ctx_state_set_blob (rasterizer->state, CTX_clip, (uint8_t*)temp, sizeof(temp)); + } + ctx_rasterizer_clip_apply (rasterizer, temp); + ctx_rasterizer_reset (rasterizer); + if (rasterizer->preserve) + { + memcpy (rasterizer->edge_list.entries, temp + 1, sizeof (temp) - sizeof(temp[0])); + rasterizer->edge_list.count = count; + rasterizer->preserve = 0; + } +} + + +#if 0 +static void +ctx_rasterizer_load_image (CtxRasterizer *rasterizer, + const char *path, + float x, + float y) +{ + // decode PNG, put it in image is slot 1, + // magic width height stride format data + ctx_buffer_load_png (&rasterizer->ctx->texture[0], path); + ctx_rasterizer_set_texture (rasterizer, 0, x, y); +} +#endif + + +CTX_INLINE void +ctx_rasterizer_rectangle (CtxRasterizer *rasterizer, + float x, + float y, + float width, + float height) +{ + ctx_rasterizer_move_to (rasterizer, x, y); + ctx_rasterizer_rel_line_to (rasterizer, width, 0); + ctx_rasterizer_rel_line_to (rasterizer, 0, height); + ctx_rasterizer_rel_line_to (rasterizer, -width, 0); + ctx_rasterizer_rel_line_to (rasterizer, 0, -height); + ctx_rasterizer_rel_line_to (rasterizer, width/2, 0); + ctx_rasterizer_finish_shape (rasterizer); +} + +static void +ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer, + uint16_t x, + uint16_t y, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a) +{ + rasterizer->state->gstate.source_fill.type = CTX_SOURCE_COLOR; + ctx_color_set_RGBA8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, r, g, b, a); + rasterizer->comp_op = NULL; +#if 0 + // XXX : doesn't take transforms into account - and has + // received less testing than code paths part of protocol, + // using rectangle properly will trigger the fillrect fastpath + ctx_rasterizer_pset (rasterizer, x, y, 255); +#else + ctx_rasterizer_rectangle (rasterizer, x, y, 1.0, 1.0); + ctx_rasterizer_fill (rasterizer); +#endif +} + +#if CTX_ENABLE_SHADOW_BLUR +static float +ctx_gaussian (float x, float mu, float sigma) +{ + float a = ( x- mu) / sigma; + return ctx_expf (-0.5 * a * a); +} + +static void +ctx_compute_gaussian_kernel (int dim, float radius, float *kernel) +{ + float sigma = radius / 2; + float sum = 0.0; + int i = 0; + //for (int row = 0; row < dim; row ++) + for (int col = 0; col < dim; col ++, i++) + { + float val = //ctx_gaussian (row, radius, sigma) * + ctx_gaussian (col, radius, sigma); + kernel[i] = val; + sum += val; + } + i = 0; + //for (int row = 0; row < dim; row ++) + for (int col = 0; col < dim; col ++, i++) + kernel[i] /= sum; +} +#endif + +static void +ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius) +{ + float aspect = 1.0f; + float radius = corner_radius / aspect; + float degrees = CTX_PI / 180.0f; + + if (radius > width*0.5f) radius = width/2; + if (radius > height*0.5f) radius = height/2; + + ctx_rasterizer_finish_shape (rasterizer); + ctx_rasterizer_arc (rasterizer, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees, 0); + ctx_rasterizer_arc (rasterizer, x + radius, y + radius, radius, 180 * degrees, 270 * degrees, 0); + + ctx_rasterizer_finish_shape (rasterizer); +} + +static void +ctx_rasterizer_process (void *user_data, CtxCommand *command); + +int +_ctx_is_rasterizer (Ctx *ctx) +{ + if (ctx->renderer && ctx->renderer->process == ctx_rasterizer_process) + return 1; + return 0; +} + +#if CTX_COMPOSITING_GROUPS +static void +ctx_rasterizer_start_group (CtxRasterizer *rasterizer) +{ + CtxEntry save_command = ctx_void(CTX_SAVE); + // allocate buffer, and set it as temporary target + int no; + if (rasterizer->group[0] == NULL) // first group + { + rasterizer->saved_buf = rasterizer->buf; + } + for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++); + + if (no >= CTX_GROUP_MAX) + return; + rasterizer->group[no] = ctx_buffer_new (rasterizer->blit_width, + rasterizer->blit_height, + rasterizer->format->composite_format); + rasterizer->buf = rasterizer->group[no]->data; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); +} + +static void +ctx_rasterizer_end_group (CtxRasterizer *rasterizer) +{ + CtxEntry restore_command = ctx_void(CTX_RESTORE); + CtxEntry save_command = ctx_void(CTX_SAVE); + int no = 0; + for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++); + no--; + + if (no < 0) + return; + + CtxCompositingMode comp = rasterizer->state->gstate.compositing_mode; + CtxBlend blend = rasterizer->state->gstate.blend_mode; + float global_alpha = rasterizer->state->gstate.global_alpha_f; + // fetch compositing, blending, global alpha + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + CtxEntry set_state[3]= + { + ctx_u32 (CTX_COMPOSITING_MODE, comp, 0), + ctx_u32 (CTX_BLEND_MODE, blend, 0), + ctx_f (CTX_GLOBAL_ALPHA, global_alpha, 0.0) + }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[0]); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[1]); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[2]); + if (no == 0) + { + rasterizer->buf = rasterizer->saved_buf; + } + else + { + rasterizer->buf = rasterizer->group[no-1]->data; + } + // XXX use texture_source ? + ctx_texture_init (rasterizer->ctx, ".ctx-group", // XXX ? count groups.. + rasterizer->blit_width, // or have group based on thread-id? + rasterizer->blit_height, // .. this would mean threadsafe + // allocation + rasterizer->blit_width * rasterizer->format->bpp/8, + rasterizer->format->pixel_format, + NULL, // space + (uint8_t*)rasterizer->group[no]->data, + NULL, NULL); + { + const char *eid = ".ctx-group"; + int eid_len = strlen (eid); + + CtxEntry commands[4] = + { + ctx_f (CTX_TEXTURE, rasterizer->blit_x, rasterizer->blit_y), + ctx_u32 (CTX_DATA, eid_len, eid_len/9+1), + ctx_u32 (CTX_CONT, 0,0), + ctx_u32 (CTX_CONT, 0,0) + }; + memcpy( (char *) &commands[2].data.u8[0], eid, eid_len); + ( (char *) (&commands[2].data.u8[0]) ) [eid_len]=0; + + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + { + CtxEntry commands[2]= + { + ctx_f (CTX_RECTANGLE, rasterizer->blit_x, rasterizer->blit_y), + ctx_f (CTX_CONT, rasterizer->blit_width, rasterizer->blit_height) + }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + { + CtxEntry commands[1]= { ctx_void (CTX_FILL) }; + ctx_rasterizer_process (rasterizer, (CtxCommand*)commands); + } + //ctx_texture_release (rasterizer->ctx, ".ctx-group"); + ctx_buffer_free (rasterizer->group[no]); + rasterizer->group[no] = 0; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} +#endif + +#if CTX_ENABLE_SHADOW_BLUR +static void +ctx_rasterizer_shadow_stroke (CtxRasterizer *rasterizer) +{ + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + { + int i = 0; + for (int v = 0; v < dim; v += 1, i++) + { + float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2; + set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command[0]); +#if CTX_ENABLE_SHADOW_BLUR + rasterizer->in_shadow = 1; +#endif + rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x; + rasterizer->shadow_y = dy; + rasterizer->preserve = 1; + ctx_rasterizer_stroke (rasterizer); +#if CTX_ENABLE_SHADOW_BLUR + rasterizer->in_shadow = 0; +#endif + } + } + //free (kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} + +static void +ctx_rasterizer_shadow_text (CtxRasterizer *rasterizer, const char *str) +{ + float x = rasterizer->state->x; + float y = rasterizer->state->y; + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry move_to_command [1]= + { + ctx_f (CTX_MOVE_TO, x, y), + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + + { + { + move_to_command[0].data.f[0] = x; + move_to_command[0].data.f[1] = y; + set_color_command[2].data.f[0] = rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command); + rasterizer->in_shadow=1; + ctx_rasterizer_text (rasterizer, str, 0); + rasterizer->in_shadow=0; + } + } + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); + move_to_command[0].data.f[0] = x; + move_to_command[0].data.f[1] = y; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command); +} + +static void +ctx_rasterizer_shadow_fill (CtxRasterizer *rasterizer) +{ + CtxColor color; + CtxEntry save_command = ctx_void(CTX_SAVE); + + float rgba[4] = {0, 0, 0, 1.0}; + if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0) + ctx_color_get_rgba (rasterizer->state, &color, rgba); + + CtxEntry set_color_command [3]= + { + ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]), + ctx_f (CTX_CONT, rgba[1], rgba[2]), + ctx_f (CTX_CONT, rgba[3], 0) + }; + CtxEntry restore_command = ctx_void(CTX_RESTORE); + float radius = rasterizer->state->gstate.shadow_blur; + int dim = 2 * radius + 1; + if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM) + dim = CTX_MAX_GAUSSIAN_KERNEL_DIM; + ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel); + ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command); + + { + for (int v = 0; v < dim; v ++) + { + int i = v; + float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2; + set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3]; + ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command); + rasterizer->in_shadow = 1; + rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x; + rasterizer->shadow_y = dy; + rasterizer->preserve = 1; + ctx_rasterizer_fill (rasterizer); + rasterizer->in_shadow = 0; + } + } + ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command); +} +#endif + +static void +ctx_rasterizer_line_dash (CtxRasterizer *rasterizer, int count, float *dashes) +{ + if (!dashes) + { + rasterizer->state->gstate.n_dashes = 0; + return; + } + count = CTX_MIN(count, CTX_PARSER_MAX_ARGS-1); + rasterizer->state->gstate.n_dashes = count; + memcpy(&rasterizer->state->gstate.dashes[0], dashes, count * sizeof(float)); + for (int i = 0; i < count; i ++) + { + if (rasterizer->state->gstate.dashes[i] < 0.0001f) + rasterizer->state->gstate.dashes[i] = 0.0001f; // hang protection + } +} + + +static void +ctx_rasterizer_process (void *user_data, CtxCommand *command) +{ + CtxEntry *entry = &command->entry; + CtxRasterizer *rasterizer = (CtxRasterizer *) user_data; + CtxState *state = rasterizer->state; + CtxCommand *c = (CtxCommand *) entry; + int clear_clip = 0; + ctx_interpret_style (state, entry, NULL); + switch (c->code) + { +#if CTX_ENABLE_SHADOW_BLUR + case CTX_SHADOW_COLOR: + { + CtxColor col; + CtxColor *color = &col; + //state->gstate.source_fill.type = CTX_SOURCE_COLOR; + switch ((int)c->rgba.model) + { + case CTX_RGB: + ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f); + break; + case CTX_RGBA: + //ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; + case CTX_DRGBA: + ctx_color_set_drgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYKA: + ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_CMYK: + ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; + case CTX_DCMYKA: + ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; +#endif + case CTX_GRAYA: + ctx_color_set_graya (state, color, c->graya.g, c->graya.a); + break; + case CTX_GRAY: + ctx_color_set_graya (state, color, c->graya.g, 1.0f); + break; + } + ctx_set_color (rasterizer->ctx, CTX_shadowColor, color); + } + break; +#endif + case CTX_LINE_DASH: + if (c->line_dash.count) + { + ctx_rasterizer_line_dash (rasterizer, c->line_dash.count, c->line_dash.data); + } + else + ctx_rasterizer_line_dash (rasterizer, 0, NULL); + break; + + case CTX_LINE_TO: + ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_LINE_TO: + ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_MOVE_TO: + ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_MOVE_TO: + ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_CURVE_TO: + ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_REL_CURVE_TO: + ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_QUAD_TO: + ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_REL_QUAD_TO: + ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_ARC: + ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction); + break; + case CTX_RECTANGLE: + ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_ROUND_RECTANGLE: + ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height, + c->rectangle.radius); + break; + case CTX_SET_PIXEL: + ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y, + c->set_pixel.rgba[0], + c->set_pixel.rgba[1], + c->set_pixel.rgba[2], + c->set_pixel.rgba[3]); + break; + case CTX_DEFINE_TEXTURE: + { + uint8_t *pixel_data = ctx_define_texture_pixel_data (entry); + ctx_rasterizer_define_texture (rasterizer, c->define_texture.eid, + c->define_texture.width, c->define_texture.height, + c->define_texture.format, + pixel_data); + rasterizer->comp_op = NULL; + rasterizer->fragment = NULL; + } + break; + case CTX_TEXTURE: + ctx_rasterizer_set_texture (rasterizer, c->texture.eid, + c->texture.x, c->texture.y); + rasterizer->comp_op = NULL; + rasterizer->fragment = NULL; + break; +#if 0 + case CTX_LOAD_IMAGE: + ctx_rasterizer_load_image (rasterizer, ctx_arg_string(), + ctx_arg_float (0), ctx_arg_float (1) ); + break; +#endif +#if CTX_GRADIENTS + case CTX_GRADIENT_STOP: + { + float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (4+1) ), + ctx_u8_to_float (ctx_arg_u8 (4+2) ), + ctx_u8_to_float (ctx_arg_u8 (4+3) ) + }; + ctx_rasterizer_gradient_add_stop (rasterizer, + ctx_arg_float (0), rgba); + rasterizer->comp_op = NULL; + } + break; + case CTX_LINEAR_GRADIENT: + ctx_state_gradient_clear_stops (state); + rasterizer->comp_op = NULL; + break; + case CTX_RADIAL_GRADIENT: + ctx_state_gradient_clear_stops (state); + rasterizer->comp_op = NULL; + break; +#endif + case CTX_PRESERVE: + rasterizer->preserve = 1; + break; + case CTX_COLOR: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + rasterizer->comp_op = NULL; + //_ctx_setup_compositor (rasterizer); + break; +#if CTX_COMPOSITING_GROUPS + case CTX_START_GROUP: + ctx_rasterizer_start_group (rasterizer); + break; + case CTX_END_GROUP: + ctx_rasterizer_end_group (rasterizer); + break; +#endif + + case CTX_RESTORE: + for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0; + i < state->gstate.keydb_pos; i++) + { + if (state->keydb[i].key == CTX_clip) + { + clear_clip = 1; + } + } + /* FALLTHROUGH */ + case CTX_ROTATE: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_IDENTITY: + case CTX_SAVE: + rasterizer->comp_op = NULL; + rasterizer->uses_transforms = 1; + ctx_interpret_transforms (state, entry, NULL); + if (clear_clip) + { + ctx_rasterizer_clip_reset (rasterizer); + for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0; + i < state->gstate.keydb_pos; i++) + { + if (state->keydb[i].key == CTX_clip) + { + int idx = ctx_float_to_string_index (state->keydb[i].value); + if (idx >=0) + { + CtxSegment *edges = (CtxSegment*)&state->stringpool[idx]; + ctx_rasterizer_clip_apply (rasterizer, edges); + } + } + } + } + break; + case CTX_STROKE: +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0 && + !rasterizer->in_text) + ctx_rasterizer_shadow_stroke (rasterizer); +#endif + { + int count = rasterizer->edge_list.count; + if (state->gstate.n_dashes) + { + int n_dashes = state->gstate.n_dashes; + float *dashes = state->gstate.dashes; + float factor = ctx_matrix_get_scale (&state->gstate.transform); + + int aa = 15;//rasterizer->aa; + CtxEntry temp[count]; /* copy of already built up path's poly line */ + memcpy (temp, rasterizer->edge_list.entries, sizeof (temp)); + int start = 0; + int end = 0; + CtxMatrix transform_backup = state->gstate.transform; + _ctx_matrix_identity (&state->gstate.transform); + ctx_rasterizer_reset (rasterizer); /* for dashing we create + a dashed path to stroke */ + float prev_x = 0.0f; + float prev_y = 0.0f; + float pos = 0.0; + + int dash_no = 0.0; + float dash_lpos = state->gstate.line_dash_offset * factor; + int is_down = 0; + + while (start < count) + { + int started = 0; + int i; + is_down = 0; + + if (!is_down) + { + CtxEntry *entry = &temp[0]; + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + ctx_rasterizer_move_to (rasterizer, prev_x, prev_y); + is_down = 1; + } + + + for (i = start; i < count; i++) + { + CtxEntry *entry = &temp[i]; + float x, y; + if (entry->code == CTX_NEW_EDGE) + { + if (started) + { + end = i - 1; + dash_no = 0; + dash_lpos = 0.0; + goto foo; + } + prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV; + prev_y = entry->data.s16[1] * 1.0f / aa; + started = 1; + start = i; + is_down = 1; + ctx_rasterizer_move_to (rasterizer, prev_x, prev_y); + } + +again: + + x = entry->data.s16[2] * 1.0f / CTX_SUBDIV; + y = entry->data.s16[3] * 1.0f / aa; + float dx = x - prev_x; + float dy = y - prev_y; + float length = ctx_fast_hypotf (dx, dy); + + if (dash_lpos + length >= dashes[dash_no] * factor) + { + float p = (dashes[dash_no] * factor - dash_lpos) / length; + float splitx = x * p + (1.0f - p) * prev_x; + float splity = y * p + (1.0f - p) * prev_y; + if (is_down) + { + ctx_rasterizer_line_to (rasterizer, splitx, splity); + is_down = 0; + } + else + { + ctx_rasterizer_move_to (rasterizer, splitx, splity); + is_down = 1; + } + prev_x = splitx; + prev_y = splity; + dash_no++; + dash_lpos=0; + if (dash_no >= n_dashes) dash_no = 0; + goto again; + } + else + { + pos += length; + dash_lpos += length; + { + if (is_down) + ctx_rasterizer_line_to (rasterizer, x, y); + } + } + prev_x = x; + prev_y = y; + } + end = i-1; +foo: + start = end+1; + } + state->gstate.transform = transform_backup; + } + ctx_rasterizer_stroke (rasterizer); + } + + break; + case CTX_FONT: + ctx_rasterizer_set_font (rasterizer, ctx_arg_string() ); + break; + case CTX_TEXT: + rasterizer->in_text++; +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0) + ctx_rasterizer_shadow_text (rasterizer, ctx_arg_string ()); +#endif + ctx_rasterizer_text (rasterizer, ctx_arg_string(), 0); + rasterizer->in_text--; + ctx_rasterizer_reset (rasterizer); + break; + case CTX_STROKE_TEXT: + ctx_rasterizer_text (rasterizer, ctx_arg_string(), 1); + ctx_rasterizer_reset (rasterizer); + break; + case CTX_GLYPH: + ctx_rasterizer_glyph (rasterizer, entry[0].data.u32[0], entry[0].data.u8[4]); + break; + case CTX_FILL: +#if CTX_ENABLE_SHADOW_BLUR + if (state->gstate.shadow_blur > 0.0 && + !rasterizer->in_text) + ctx_rasterizer_shadow_fill (rasterizer); +#endif + ctx_rasterizer_fill (rasterizer); + break; + case CTX_RESET: + case CTX_BEGIN_PATH: + ctx_rasterizer_reset (rasterizer); + break; + case CTX_CLIP: + ctx_rasterizer_clip (rasterizer); + break; + case CTX_CLOSE_PATH: + ctx_rasterizer_finish_shape (rasterizer); + break; + case CTX_IMAGE_SMOOTHING: + rasterizer->comp_op = NULL; + break; + } + ctx_interpret_pos_bare (state, entry, NULL); +} + +void +ctx_rasterizer_deinit (CtxRasterizer *rasterizer) +{ + ctx_drawlist_deinit (&rasterizer->edge_list); +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + { + ctx_buffer_free (rasterizer->clip_buffer); + rasterizer->clip_buffer = NULL; + } +#endif +#if CTX_SHAPE_CACHE + for (int i = 0; i < CTX_SHAPE_CACHE_ENTRIES; i ++) + if (rasterizer->shape_cache.entries[i]) + { + free (rasterizer->shape_cache.entries[i]); + rasterizer->shape_cache.entries[i] = NULL; + } + +#endif + if (rasterizer->opaque) + free (rasterizer->opaque); + free (rasterizer); +} + + +CtxAntialias ctx_get_antialias (Ctx *ctx) +{ +#if CTX_EVENTS + if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx)) + { + CtxTiled *fb = (CtxTiled*)(ctx->renderer); + return fb->antialias; + } +#endif + if (!_ctx_is_rasterizer (ctx)) return CTX_ANTIALIAS_DEFAULT; + + switch (((CtxRasterizer*)(ctx->renderer))->aa) + { + case 1: return CTX_ANTIALIAS_NONE; + case 3: return CTX_ANTIALIAS_FAST; + //case 5: return CTX_ANTIALIAS_GOOD; + default: + case 5: return CTX_ANTIALIAS_DEFAULT; + case 17: return CTX_ANTIALIAS_BEST; + } +} + +int _ctx_antialias_to_aa (CtxAntialias antialias) +{ + switch (antialias) + { + case CTX_ANTIALIAS_NONE: return 1; + case CTX_ANTIALIAS_FAST: return 3; + case CTX_ANTIALIAS_GOOD: return 5; + default: + case CTX_ANTIALIAS_DEFAULT: return CTX_RASTERIZER_AA; + case CTX_ANTIALIAS_BEST: return 17; + } +} + +void +ctx_set_antialias (Ctx *ctx, CtxAntialias antialias) +{ +#if CTX_EVENTS + if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx)) + { + CtxTiled *fb = (CtxTiled*)(ctx->renderer); + fb->antialias = antialias; + for (int i = 0; i < _ctx_max_threads; i++) + { + ctx_set_antialias (fb->host[i], antialias); + } + return; + } +#endif + if (!_ctx_is_rasterizer (ctx)) return; + + ((CtxRasterizer*)(ctx->renderer))->aa = + _ctx_antialias_to_aa (antialias); + ((CtxRasterizer*)(ctx->renderer))->fast_aa = 0; + if (antialias == CTX_ANTIALIAS_DEFAULT|| + antialias == CTX_ANTIALIAS_FAST) + ((CtxRasterizer*)(ctx->renderer))->fast_aa = 1; +} + +CtxRasterizer * +ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias) +{ +#if CTX_ENABLE_CLIP + if (rasterizer->clip_buffer) + ctx_buffer_free (rasterizer->clip_buffer); +#endif + if (rasterizer->edge_list.size) + ctx_drawlist_deinit (&rasterizer->edge_list); + + memset (rasterizer, 0, sizeof (CtxRasterizer) ); + rasterizer->vfuncs.process = ctx_rasterizer_process; + rasterizer->vfuncs.free = (CtxDestroyNotify)ctx_rasterizer_deinit; + rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST; + rasterizer->state = state; + rasterizer->ctx = ctx; + rasterizer->texture_source = texture_source?texture_source:ctx; + + rasterizer->aa = _ctx_antialias_to_aa (antialias); + rasterizer->fast_aa = (antialias == CTX_ANTIALIAS_DEFAULT||antialias == CTX_ANTIALIAS_FAST); + ctx_state_init (rasterizer->state); + rasterizer->buf = data; + rasterizer->blit_x = x; + rasterizer->blit_y = y; + rasterizer->blit_width = width; + rasterizer->blit_height = height; + rasterizer->state->gstate.clip_min_x = x; + rasterizer->state->gstate.clip_min_y = y; + rasterizer->state->gstate.clip_max_x = x + width - 1; + rasterizer->state->gstate.clip_max_y = y + height - 1; + rasterizer->blit_stride = stride; + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + + if (pixel_format == CTX_FORMAT_BGRA8) + { + pixel_format = CTX_FORMAT_RGBA8; + rasterizer->swap_red_green = 1; + } + + rasterizer->format = ctx_pixel_format_info (pixel_format); + + if (rasterizer->opaque==NULL) + { + rasterizer->opaque=(uint8_t*)malloc(CTX_MAX_FRAMEBUFFER_WIDTH); + memset (rasterizer->opaque, 255, CTX_MAX_FRAMEBUFFER_WIDTH); + } + + return rasterizer; +} + +Ctx * +ctx_new_for_buffer (CtxBuffer *buffer) +{ + Ctx *ctx = ctx_new (); + ctx_set_renderer (ctx, + ctx_rasterizer_init ( (CtxRasterizer *) malloc (sizeof (CtxRasterizer) ), + ctx, NULL, &ctx->state, + buffer->data, 0, 0, buffer->width, buffer->height, + buffer->stride, buffer->format->pixel_format, + CTX_ANTIALIAS_DEFAULT)); + return ctx; +} + +Ctx * +ctx_new_for_framebuffer (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format) +{ + Ctx *ctx = ctx_new (); + CtxRasterizer *r = ctx_rasterizer_init ( (CtxRasterizer *) ctx_calloc (sizeof (CtxRasterizer), 1), + ctx, NULL, &ctx->state, data, 0, 0, width, height, + stride, pixel_format, CTX_ANTIALIAS_DEFAULT); + ctx_set_renderer (ctx, r); + return ctx; +} + +// ctx_new_for_stream (FILE *stream); + +#if 0 +CtxRasterizer *ctx_rasterizer_new (void *data, int x, int y, int width, int height, + int stride, CtxPixelFormat pixel_format) +{ + CtxState *state = (CtxState *) malloc (sizeof (CtxState) ); + CtxRasterizer *rasterizer = (CtxRasterizer *) malloc (sizeof (CtxRenderer) ); + ctx_rasterizer_init (rasterizer, state, data, x, y, width, height, + stride, pixel_format, CTX_ANTIALIAS_DEFAULT); +} +#endif + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format); + +#else + +CtxPixelFormatInfo * +ctx_pixel_format_info (CtxPixelFormat format) +{ + return NULL; +} +#endif + +void +ctx_current_point (Ctx *ctx, float *x, float *y) +{ + if (!ctx) + { + if (x) { *x = 0.0f; } + if (y) { *y = 0.0f; } + } +#if CTX_RASTERIZER + if (ctx->renderer) + { + if (x) { *x = ( (CtxRasterizer *) (ctx->renderer) )->x; } + if (y) { *y = ( (CtxRasterizer *) (ctx->renderer) )->y; } + return; + } +#endif + if (x) { *x = ctx->state.x; } + if (y) { *y = ctx->state.y; } +} + +float ctx_x (Ctx *ctx) +{ + float x = 0, y = 0; + ctx_current_point (ctx, &x, &y); + return x; +} + +float ctx_y (Ctx *ctx) +{ + float x = 0, y = 0; + ctx_current_point (ctx, &x, &y); + return y; +} + +static void +ctx_process (Ctx *ctx, CtxEntry *entry) +{ +#if CTX_CURRENT_PATH + switch (entry->code) + { + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_BEGIN_PATH: + ctx->current_path.count = 0; + break; + case CTX_CLIP: + case CTX_FILL: + case CTX_STROKE: + // XXX unless preserve + ctx->current_path.count = 0; + break; + case CTX_CLOSE_PATH: + case CTX_LINE_TO: + case CTX_MOVE_TO: + case CTX_QUAD_TO: + case CTX_SMOOTH_TO: + case CTX_SMOOTHQ_TO: + case CTX_REL_QUAD_TO: + case CTX_REL_SMOOTH_TO: + case CTX_REL_SMOOTHQ_TO: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_ARC: + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: + ctx_drawlist_add_entry (&ctx->current_path, entry); + break; + default: + break; + } +#endif +#if CTX_RASTERIZER + if (CTX_LIKELY(ctx->renderer && ctx->renderer->process == ctx_rasterizer_process)) + { + ctx_rasterizer_process (ctx->renderer, (CtxCommand *) entry); + } + else +#endif + if (CTX_LIKELY(ctx->renderer && ctx->renderer->process)) + { + ctx->renderer->process (ctx->renderer, (CtxCommand *) entry); + } + else + { + /* these functions might alter the code and coordinates of + command that in the end gets added to the drawlist + */ + ctx_interpret_style (&ctx->state, entry, ctx); + ctx_interpret_transforms (&ctx->state, entry, ctx); + ctx_interpret_pos (&ctx->state, entry, ctx); + ctx_drawlist_add_entry (&ctx->drawlist, entry); + } +} + + +int ctx_gradient_cache_valid = 0; + +void +ctx_state_gradient_clear_stops (CtxState *state) +{ +//#if CTX_GRADIENT_CACHE +// ctx_gradient_cache_reset (); +//#endif + ctx_gradient_cache_valid = 0; + state->gradient.n_stops = 0; +} + + +/**** end of engine ****/ + +CtxBuffer *ctx_buffer_new_bare (void) +{ + CtxBuffer *buffer = (CtxBuffer *) ctx_calloc (sizeof (CtxBuffer), 1); + return buffer; +} + +void ctx_buffer_set_data (CtxBuffer *buffer, + void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + if (buffer->free_func) + { buffer->free_func (buffer->data, buffer->user_data); } + if (stride <= 0) + stride = ctx_pixel_format_get_stride (pixel_format, width); + buffer->data = data; + buffer->width = width; + buffer->height = height; + buffer->stride = stride; + buffer->format = ctx_pixel_format_info (pixel_format); + buffer->free_func = freefunc; + buffer->user_data = user_data; +} + +CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height, + int stride, + CtxPixelFormat pixel_format, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + CtxBuffer *buffer = ctx_buffer_new_bare (); + ctx_buffer_set_data (buffer, data, width, height, stride, pixel_format, + freefunc, user_data); + return buffer; +} + +void ctx_buffer_pixels_free (void *pixels, void *userdata) +{ + free (pixels); +} + +CtxBuffer *ctx_buffer_new (int width, int height, + CtxPixelFormat pixel_format) +{ + //CtxPixelFormatInfo *info = ctx_pixel_format_info (pixel_format); + CtxBuffer *buffer = ctx_buffer_new_bare (); + int stride = ctx_pixel_format_get_stride (pixel_format, width); + int data_len = stride * height; + if (pixel_format == CTX_FORMAT_YUV420) + data_len = width * height + ((width/2) * (height/2)) * 2; + + uint8_t *pixels = (uint8_t*)ctx_calloc (data_len, 1); + + ctx_buffer_set_data (buffer, pixels, width, height, stride, pixel_format, + ctx_buffer_pixels_free, NULL); + return buffer; +} + +void ctx_buffer_deinit (CtxBuffer *buffer) +{ + if (buffer->free_func) + buffer->free_func (buffer->data, buffer->user_data); + if (buffer->eid) + { + free (buffer->eid); + } + buffer->eid = NULL; + buffer->data = NULL; + buffer->free_func = NULL; + buffer->user_data = NULL; + if (buffer->color_managed) + { + if (buffer->color_managed != buffer) + { + ctx_buffer_free (buffer->color_managed); + } + buffer->color_managed = NULL; + } +} + +void ctx_buffer_free (CtxBuffer *buffer) +{ + ctx_buffer_deinit (buffer); + free (buffer); +} + +static int +ctx_texture_check_eid (Ctx *ctx, const char *eid, int *tw, int *th) +{ + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data && + ctx->texture[i].eid && + !strcmp (ctx->texture[i].eid, eid)) + { + if (tw) *tw = ctx->texture[i].width; + if (th) *th = ctx->texture[i].height; + ctx->texture[i].frame = ctx->texture_cache->frame; + return i; + } + } + return -1; +} + +const char* ctx_texture_init (Ctx *ctx, + const char *eid, + int width, + int height, + int stride, + CtxPixelFormat format, + void *space, + uint8_t *pixels, + void (*freefunc) (void *pixels, void *user_data), + void *user_data) +{ + int id = 0; + if (eid) + { + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data && + ctx->texture[i].eid && + !strcmp (ctx->texture[i].eid, eid)) + { + ctx->texture[i].frame = ctx->texture_cache->frame; + if (freefunc && user_data != (void*)23) + freefunc (pixels, user_data); + return ctx->texture[i].eid; + } + if (ctx->texture[i].data == NULL + || (ctx->texture_cache->frame - ctx->texture[i].frame >= 2)) + id = i; + } + } else + { + for (int i = 0; i < CTX_MAX_TEXTURES; i++) + { + if (ctx->texture[i].data == NULL + || (ctx->texture_cache->frame - ctx->texture[i].frame > 2)) + id = i; + } + } + //int bpp = ctx_pixel_format_bits_per_pixel (format); + ctx_buffer_deinit (&ctx->texture[id]); + + if (stride<=0) + { + stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width); + } + + int data_len = stride * height; + if (format == CTX_FORMAT_YUV420) + data_len = width * height + + 2 * ((width/2)*(height/2)); + + if (freefunc == ctx_buffer_pixels_free && user_data == (void*)23) + { + uint8_t *tmp = (uint8_t*)malloc (data_len); + memcpy (tmp, pixels, data_len); + pixels = tmp; + } + + ctx_buffer_set_data (&ctx->texture[id], + pixels, width, height, + stride, format, + freefunc, user_data); +#if CTX_ENABLE_CM + ctx->texture[id].space = space; +#endif + ctx->texture[id].frame = ctx->texture_cache->frame; + if (eid) + { + /* we got an eid, this is the fast path */ + ctx->texture[id].eid = strdup (eid); + } + else + { + uint8_t hash[20]; + char ascii[41]; + + CtxSHA1 *sha1 = ctx_sha1_new (); + ctx_sha1_process (sha1, pixels, stride * height); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + ctx->texture[id].eid = strdup (ascii); + } + return ctx->texture[id].eid; +} + +static void +_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer, + CtxBuffer *buffer) +{ + switch (buffer->format->pixel_format) + { +#ifndef NO_BABL +#if CTX_BABL + case CTX_FORMAT_RGBA8: + if (buffer->space == rasterizer->state->gstate.device_space) + { + buffer->color_managed = buffer; + } + else + { + buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height, + CTX_FORMAT_RGBA8); + babl_process ( + babl_fish (babl_format_with_space ("R'G'B'A u8", buffer->space), + babl_format_with_space ("R'G'B'A u8", rasterizer->state->gstate.device_space)), + buffer->data, buffer->color_managed->data, + buffer->width * buffer->height + ); + } + break; + case CTX_FORMAT_RGB8: + if (buffer->space == rasterizer->state->gstate.device_space) + { + buffer->color_managed = buffer; + } + else + { + buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height, + CTX_FORMAT_RGB8); + babl_process ( + babl_fish (babl_format_with_space ("R'G'B' u8", buffer->space), + babl_format_with_space ("R'G'B' u8", rasterizer->state->gstate.device_space)), + buffer->data, buffer->color_managed->data, + buffer->width * buffer->height + ); + } + break; +#endif +#endif + default: + buffer->color_managed = buffer; + } +} + + + +int ctx_utf8_len (const unsigned char first_byte) +{ + if ( (first_byte & 0x80) == 0) + { return 1; } /* ASCII */ + else if ( (first_byte & 0xE0) == 0xC0) + { return 2; } + else if ( (first_byte & 0xF0) == 0xE0) + { return 3; } + else if ( (first_byte & 0xF8) == 0xF0) + { return 4; } + return 1; +} + + +const char *ctx_utf8_skip (const char *s, int utf8_length) +{ + int count; + if (!s) + { return NULL; } + for (count = 0; *s; s++) + { + if ( (*s & 0xC0) != 0x80) + { count++; } + if (count == utf8_length + 1) + { return s; } + } + return s; +} + +// XXX : unused +int ctx_utf8_strlen (const char *s) +{ + int count; + if (!s) + { return 0; } + for (count = 0; *s; s++) + if ( (*s & 0xC0) != 0x80) + { count++; } + return count; +} + +int +ctx_unichar_to_utf8 (uint32_t ch, + uint8_t *dest) +{ + /* http://www.cprogramming.com/tutorial/utf8.c */ + /* Basic UTF-8 manipulation routines + by Jeff Bezanson + placed in the public domain Fall 2005 ... */ + if (ch < 0x80) + { + dest[0] = (char) ch; + return 1; + } + if (ch < 0x800) + { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) + { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ( (ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) + { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ( (ch>>12) & 0x3F) | 0x80; + dest[2] = ( (ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} + +uint32_t +ctx_utf8_to_unichar (const char *input) +{ + const uint8_t *utf8 = (const uint8_t *) input; + uint8_t c = utf8[0]; + if ( (c & 0x80) == 0) + { return c; } + else if ( (c & 0xE0) == 0xC0) + return ( (utf8[0] & 0x1F) << 6) | + (utf8[1] & 0x3F); + else if ( (c & 0xF0) == 0xE0) + return ( (utf8[0] & 0xF) << 12) | + ( (utf8[1] & 0x3F) << 6) | + (utf8[2] & 0x3F); + else if ( (c & 0xF8) == 0xF0) + return ( (utf8[0] & 0x7) << 18) | + ( (utf8[1] & 0x3F) << 12) | + ( (utf8[2] & 0x3F) << 6) | + (utf8[3] & 0x3F); + else if ( (c & 0xFC) == 0xF8) + return ( (utf8[0] & 0x3) << 24) | + ( (utf8[1] & 0x3F) << 18) | + ( (utf8[2] & 0x3F) << 12) | + ( (utf8[3] & 0x3F) << 6) | + (utf8[4] & 0x3F); + else if ( (c & 0xFE) == 0xFC) + return ( (utf8[0] & 0x1) << 30) | + ( (utf8[1] & 0x3F) << 24) | + ( (utf8[2] & 0x3F) << 18) | + ( (utf8[3] & 0x3F) << 12) | + ( (utf8[4] & 0x3F) << 6) | + (utf8[5] & 0x3F); + return 0; +} + +#if CTX_RASTERIZER + + + +static int +ctx_rect_intersect (const CtxIntRectangle *a, const CtxIntRectangle *b) +{ + if (a->x >= b->x + b->width || + b->x >= a->x + a->width || + a->y >= b->y + b->height || + b->y >= a->y + a->height) return 0; + + return 1; +} + +static void +_ctx_add_hash (CtxHasher *hasher, CtxIntRectangle *shape_rect, char *hash) +{ + CtxIntRectangle rect = {0,0, hasher->rasterizer.blit_width/hasher->cols, + hasher->rasterizer.blit_height/hasher->rows}; + int hno = 0; + for (int row = 0; row < hasher->rows; row++) + for (int col = 0; col < hasher->cols; col++, hno++) + { + rect.x = col * rect.width; + rect.y = row * rect.height; + if (ctx_rect_intersect (shape_rect, &rect)) + { + int temp = hasher->hashes[(row * hasher->cols + col) *20 + 0]; + for (int i = 0; i <19;i++) + hasher->hashes[(row * hasher->cols + col) *20 + i] = + hasher->hashes[(row * hasher->cols + col) *20 + i+1]^ + hash[i]; + hasher->hashes[(row * hasher->cols + col) *20 + 19] = + temp ^ hash[19]; + } + } +} + + +static void +ctx_hasher_process (void *user_data, CtxCommand *command) +{ + CtxEntry *entry = &command->entry; + CtxRasterizer *rasterizer = (CtxRasterizer *) user_data; + CtxHasher *hasher = (CtxHasher*) user_data; + CtxState *state = rasterizer->state; + CtxCommand *c = (CtxCommand *) entry; + int aa = 15;//rasterizer->aa; + + ctx_interpret_pos_bare (rasterizer->state, entry, NULL); + ctx_interpret_style (rasterizer->state, entry, NULL); + + switch (c->code) + { + case CTX_TEXT: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + float width = ctx_text_width (rasterizer->ctx, ctx_arg_string()); + + + float height = ctx_get_font_size (rasterizer->ctx); + CtxIntRectangle shape_rect; + + shape_rect.x=rasterizer->x; + shape_rect.y=rasterizer->y - height, + shape_rect.width = width; + shape_rect.height = height * 2; + switch ((int)ctx_state_get (rasterizer->state, CTX_text_align)) + { + case CTX_TEXT_ALIGN_LEFT: + case CTX_TEXT_ALIGN_START: + break; + case CTX_TEXT_ALIGN_END: + case CTX_TEXT_ALIGN_RIGHT: + shape_rect.x -= shape_rect.width; + break; + case CTX_TEXT_ALIGN_CENTER: + shape_rect.x -= shape_rect.width/2; + break; + // XXX : doesn't take all text-alignments into account + } + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, (const unsigned char*)ctx_arg_string(), strlen (ctx_arg_string())); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + } + ctx_rasterizer_reset (rasterizer); + break; + case CTX_STROKE_TEXT: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + float width = ctx_text_width (rasterizer->ctx, ctx_arg_string()); + float height = ctx_get_font_size (rasterizer->ctx); + + CtxIntRectangle shape_rect = { + (int)rasterizer->x, (int)(rasterizer->y - height), + (int)width, (int)(height * 2) + }; + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, (unsigned char*)ctx_arg_string(), strlen (ctx_arg_string())); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + } + ctx_rasterizer_reset (rasterizer); + break; + case CTX_GLYPH: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + + char ctx_sha1_hash[20]; + uint8_t string[8]; + string[ctx_unichar_to_utf8 (c->u32.a0, string)]=0; + float width = ctx_text_width (rasterizer->ctx, (char*)string); + float height = ctx_get_font_size (rasterizer->ctx); + + float tx = rasterizer->x; + float ty = rasterizer->y; + float tw = width; + float th = height * 2; + + _ctx_user_to_device (rasterizer->state, &tx, &ty); + _ctx_user_to_device_distance (rasterizer->state, &tw, &th); + CtxIntRectangle shape_rect = {(int)tx,(int)(ty-th/2),(int)tw,(int)th}; + + +#if 0 + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); +#endif + ctx_sha1_process(&sha1, string, strlen ((const char*)string)); +#if 0 + ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); +#endif + ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle)); + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + ctx_rasterizer_rel_move_to (rasterizer, width, 0); + ctx_rasterizer_reset (rasterizer); + } + break; + + case CTX_FILL: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + + /* we eant this hasher to be as good as possible internally, + * since it is also used in the small shapes rasterization + * cache + */ + uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer); // + hasher->salt; + CtxIntRectangle shape_rect = { + (int)(rasterizer->col_min / CTX_SUBDIV - 2), + (int)(rasterizer->scan_min / aa - 2), + (int)(3+(rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV), + (int)(3+(rasterizer->scan_max - rasterizer->scan_min + 1) / aa) + }; + + hash ^= (rasterizer->state->gstate.fill_rule * 23); + + ctx_sha1_process(&sha1, (unsigned char*)&hash, 8); + + { + int is = rasterizer->state->gstate.image_smoothing; + ctx_sha1_process(&sha1, (uint8_t*)&is, sizeof(int)); + } + + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + + if (!rasterizer->preserve) + ctx_rasterizer_reset (rasterizer); + rasterizer->preserve = 0; + } + break; + case CTX_STROKE: + { + CtxSHA1 sha1; + memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1)); + char ctx_sha1_hash[20]; + uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer); + CtxIntRectangle shape_rect = { + (int)(rasterizer->col_min / CTX_SUBDIV - rasterizer->state->gstate.line_width), + (int)(rasterizer->scan_min / aa - rasterizer->state->gstate.line_width), + (int)((rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV + rasterizer->state->gstate.line_width), + (int)((rasterizer->scan_max - rasterizer->scan_min + 1) / aa + rasterizer->state->gstate.line_width) + }; + + shape_rect.width += rasterizer->state->gstate.line_width * 2; + shape_rect.height += rasterizer->state->gstate.line_width * 2; + shape_rect.x -= rasterizer->state->gstate.line_width; + shape_rect.y -= rasterizer->state->gstate.line_width; + + hash ^= (int)(rasterizer->state->gstate.line_width * 110); + hash ^= (rasterizer->state->gstate.line_cap * 23); + hash ^= (rasterizer->state->gstate.source_stroke.type * 117); + + ctx_sha1_process(&sha1, (unsigned char*)&hash, 8); + + uint32_t color; + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); + + ctx_sha1_process(&sha1, (unsigned char*)&color, 4); + + ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash); + _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash); + } + if (!rasterizer->preserve) + ctx_rasterizer_reset (rasterizer); + rasterizer->preserve = 0; + break; + /* the above cases are the painting cases and + * the only ones differing from the rasterizer's process switch + */ + + case CTX_LINE_TO: + ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_LINE_TO: + ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_MOVE_TO: + ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_REL_MOVE_TO: + ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0); + break; + case CTX_CURVE_TO: + ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_REL_CURVE_TO: + ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0, + c->c.x1, c->c.y1, + c->c.x2, c->c.y2); + break; + case CTX_QUAD_TO: + ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_REL_QUAD_TO: + ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1); + break; + case CTX_ARC: + ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction); + break; + case CTX_RECTANGLE: + ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_ROUND_RECTANGLE: + ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height, + c->rectangle.radius); + break; + case CTX_SET_PIXEL: + ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y, + c->set_pixel.rgba[0], + c->set_pixel.rgba[1], + c->set_pixel.rgba[2], + c->set_pixel.rgba[3]); + break; + case CTX_PRESERVE: + rasterizer->preserve = 1; + break; + case CTX_ROTATE: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_SAVE: + case CTX_RESTORE: + rasterizer->uses_transforms = 1; + ctx_interpret_transforms (rasterizer->state, entry, NULL); + + + break; + case CTX_FONT: + ctx_rasterizer_set_font (rasterizer, ctx_arg_string() ); + break; + case CTX_BEGIN_PATH: + ctx_rasterizer_reset (rasterizer); + break; + case CTX_CLIP: + // should perhaps modify a global state to include + // in hash? + ctx_rasterizer_clip (rasterizer); + break; + case CTX_CLOSE_PATH: + ctx_rasterizer_finish_shape (rasterizer); + break; + case CTX_DEFINE_TEXTURE: + { + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->define_texture.eid, strlen (c->define_texture.eid)); + ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + + rasterizer->comp_op = NULL; // why? + } + break; + case CTX_TEXTURE: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->texture.eid, strlen (c->texture.eid)); + ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + rasterizer->comp_op = NULL; // why? + break; + case CTX_COLOR: + { + uint32_t color; + if (((int)(ctx_arg_float(0))&512)) + { + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color)); + ctx_sha1_init (&hasher->sha1_stroke); + ctx_sha1_process(&hasher->sha1_stroke, (unsigned char*)&color, 4); + } + else + { + ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color)); + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)&color, 4); + } + } + break; + case CTX_LINEAR_GRADIENT: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*)c, sizeof (c->linear_gradient)); + ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + break; + case CTX_RADIAL_GRADIENT: + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*)c, sizeof (c->radial_gradient)); + ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform)); + //ctx_state_gradient_clear_stops (rasterizer->state); + break; +#if CTX_GRADIENTS + case CTX_GRADIENT_STOP: + { + float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (4+1) ), + ctx_u8_to_float (ctx_arg_u8 (4+2) ), + ctx_u8_to_float (ctx_arg_u8 (4+3) ) + }; + ctx_sha1_process(&hasher->sha1_fill, + (uint8_t*) &rgba[0], sizeof(rgba)); + } + break; +#endif + } + if (command->code == CTX_LINE_WIDTH) + { + float x = state->gstate.line_width; + /* normalize line width according to scaling factor + */ + x = x * ctx_maxf (ctx_maxf (ctx_fabsf (state->gstate.transform.m[0][0]), + ctx_fabsf (state->gstate.transform.m[0][1]) ), + ctx_maxf (ctx_fabsf (state->gstate.transform.m[1][0]), + ctx_fabsf (state->gstate.transform.m[1][1]) ) ); + state->gstate.line_width = x; + } +} + +static CtxRasterizer * +ctx_hasher_init (CtxRasterizer *rasterizer, Ctx *ctx, CtxState *state, int width, int height, int cols, int rows) +{ + CtxHasher *hasher = (CtxHasher*)rasterizer; + ctx_memset (rasterizer, 0, sizeof (CtxHasher) ); + rasterizer->vfuncs.process = ctx_hasher_process; + rasterizer->vfuncs.free = (CtxDestroyNotify)ctx_rasterizer_deinit; + // XXX need own destructor to not leak ->hashes + rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST; + rasterizer->state = state; + rasterizer->ctx = ctx; + ctx_state_init (rasterizer->state); + rasterizer->blit_x = 0; + rasterizer->blit_y = 0; + rasterizer->blit_width = width; + rasterizer->blit_height = height; + rasterizer->state->gstate.clip_min_x = 0; + rasterizer->state->gstate.clip_min_y = 0; + rasterizer->state->gstate.clip_max_x = width - 1; + rasterizer->state->gstate.clip_max_y = height - 1; + rasterizer->scan_min = 5000; + rasterizer->scan_max = -5000; + //rasterizer->aa = 15; + + hasher->rows = rows; + hasher->cols = cols; + + hasher->hashes = (uint8_t*)ctx_calloc (20, rows * cols); + ctx_sha1_init (&hasher->sha1_fill); + ctx_sha1_init (&hasher->sha1_stroke); + + return rasterizer; +} + +Ctx *ctx_hasher_new (int width, int height, int cols, int rows) +{ + Ctx *ctx = ctx_new (); + CtxState *state = &ctx->state; + CtxRasterizer *rasterizer = (CtxRasterizer *) ctx_calloc (sizeof (CtxHasher), 1); + ctx_hasher_init (rasterizer, ctx, state, width, height, cols, rows); + ctx_set_renderer (ctx, (void*)rasterizer); + return ctx; +} +uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row) +{ + CtxHasher *hasher = (CtxHasher*)ctx->renderer; + if (row < 0) row =0; + if (col < 0) col =0; + if (row >= hasher->rows) row = hasher->rows-1; + if (col >= hasher->cols) col = hasher->cols-1; + + return &hasher->hashes[(row*hasher->cols+col)*20]; +} + +#endif +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <termios.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +int ctx_terminal_width (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[14t"); + //tcflush(STDIN_FILENO, 1); +#if __COSMOPOLITAN__ + /// XXX ? +#else + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi) {semi++; semi = strchr (semi, ';');} + if (semi) + { + return atoi(semi + 1); + } + return 0; +} + +int ctx_terminal_height (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[14t"); + //tcflush(STDIN_FILENO, 1); +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi) + { + return atoi(semi + 1); + } + return 0; +} + +int ctx_terminal_cols (void) +{ + struct winsize ws; + if (ioctl(0,TIOCGWINSZ,&ws)!=0) + return 80; + return ws.ws_col; +} + +int ctx_terminal_rows (void) +{ + struct winsize ws; + if (ioctl(0,TIOCGWINSZ,&ws)!=0) + return 25; + return ws.ws_row; +} + + + + + +#define DECTCEM_CURSOR_SHOW "\033[?25h" +#define DECTCEM_CURSOR_HIDE "\033[?25l" +#define TERMINAL_MOUSE_OFF "\033[?1000l\033[?1003l" +#define TERMINAL_MOUSE_ON_BASIC "\033[?1000h" +#define TERMINAL_MOUSE_ON_DRAG "\033[?1000h\033[?1003h" /* +ON_BASIC for wider */ +#define TERMINAL_MOUSE_ON_FULL "\033[?1000h\033[?1004h" /* compatibility */ +#define XTERM_ALTSCREEN_ON "\033[?47h" +#define XTERM_ALTSCREEN_OFF "\033[?47l" + +/*************************** input handling *************************/ + +#if !__COSMOPOLITAN__ +#include <termios.h> +#include <errno.h> +#include <signal.h> +#endif + +#define DELAY_MS 100 + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +static int size_changed = 0; /* XXX: global state */ +static int signal_installed = 0; /* XXX: global state */ + +static const char *mouse_modes[]= +{TERMINAL_MOUSE_OFF, + TERMINAL_MOUSE_ON_BASIC, + TERMINAL_MOUSE_ON_DRAG, + TERMINAL_MOUSE_ON_FULL, + NULL}; + +/* note that a nick can have multiple occurences, the labels + * should be kept the same for all occurences of a combination. */ +typedef struct NcKeyCode { + const char *nick; /* programmers name for key (combo) */ + const char *label; /* utf8 label for key */ + const char sequence[10]; /* terminal sequence */ +} NcKeyCode; +static const NcKeyCode keycodes[]={ + + {"up", "↑", "\033[A"}, + {"down", "↓", "\033[B"}, + {"right", "→", "\033[C"}, + {"left", "←", "\033[D"}, + + {"shift-up", "⇧↑", "\033[1;2A"}, + {"shift-down", "⇧↓", "\033[1;2B"}, + {"shift-right", "⇧→", "\033[1;2C"}, + {"shift-left", "⇧←", "\033[1;2D"}, + + {"alt-up", "^↑", "\033[1;3A"}, + {"alt-down", "^↓", "\033[1;3B"}, + {"alt-right", "^→", "\033[1;3C"}, + {"alt-left", "^←", "\033[1;3D"}, + + {"alt-shift-up", "alt-s↑", "\033[1;4A"}, + {"alt-shift-down", "alt-s↓", "\033[1;4B"}, + {"alt-shift-right", "alt-s→", "\033[1;4C"}, + {"alt-shift-left", "alt-s←", "\033[1;4D"}, + + {"control-up", "^↑", "\033[1;5A"}, + {"control-down", "^↓", "\033[1;5B"}, + {"control-right", "^→", "\033[1;5C"}, + {"control-left", "^←", "\033[1;5D"}, + + /* putty */ + {"control-up", "^↑", "\033OA"}, + {"control-down", "^↓", "\033OB"}, + {"control-right", "^→", "\033OC"}, + {"control-left", "^←", "\033OD"}, + + {"control-shift-up", "^⇧↑", "\033[1;6A"}, + {"control-shift-down", "^⇧↓", "\033[1;6B"}, + {"control-shift-right", "^⇧→", "\033[1;6C"}, + {"control-shift-left", "^⇧←", "\033[1;6D"}, + + {"control-up", "^↑", "\033Oa"}, + {"control-down", "^↓", "\033Ob"}, + {"control-right", "^→", "\033Oc"}, + {"control-left", "^←", "\033Od"}, + + {"shift-up", "⇧↑", "\033[a"}, + {"shift-down", "⇧↓", "\033[b"}, + {"shift-right", "⇧→", "\033[c"}, + {"shift-left", "⇧←", "\033[d"}, + + {"insert", "ins", "\033[2~"}, + {"delete", "del", "\033[3~"}, + {"page-up", "PgUp", "\033[5~"}, + {"page-down", "PdDn", "\033[6~"}, + {"home", "Home", "\033OH"}, + {"end", "End", "\033OF"}, + {"home", "Home", "\033[H"}, + {"end", "End", "\033[F"}, + {"control-delete", "^del", "\033[3;5~"}, + {"shift-delete", "⇧del", "\033[3;2~"}, + {"control-shift-delete","^⇧del", "\033[3;6~"}, + + {"F1", "F1", "\033[11~"}, + {"F2", "F2", "\033[12~"}, + {"F3", "F3", "\033[13~"}, + {"F4", "F4", "\033[14~"}, + {"F1", "F1", "\033OP"}, + {"F2", "F2", "\033OQ"}, + {"F3", "F3", "\033OR"}, + {"F4", "F4", "\033OS"}, + {"F5", "F5", "\033[15~"}, + {"F6", "F6", "\033[16~"}, + {"F7", "F7", "\033[17~"}, + {"F8", "F8", "\033[18~"}, + {"F9", "F9", "\033[19~"}, + {"F9", "F9", "\033[20~"}, + {"F10", "F10", "\033[21~"}, + {"F11", "F11", "\033[22~"}, + {"F12", "F12", "\033[23~"}, + {"tab", "↹", {9, '\0'}}, + {"shift-tab", "shift+↹", "\033[Z"}, + {"backspace", "⌫", {127, '\0'}}, + {"space", "␣", " "}, + {"esc", "␛", "\033"}, + {"return", "⏎", {10,0}}, + {"return", "⏎", {13,0}}, + /* this section could be autogenerated by code */ + {"control-a", "^A", {1,0}}, + {"control-b", "^B", {2,0}}, + {"control-c", "^C", {3,0}}, + {"control-d", "^D", {4,0}}, + {"control-e", "^E", {5,0}}, + {"control-f", "^F", {6,0}}, + {"control-g", "^G", {7,0}}, + {"control-h", "^H", {8,0}}, /* backspace? */ + {"control-i", "^I", {9,0}}, /* tab */ + {"control-j", "^J", {10,0}}, + {"control-k", "^K", {11,0}}, + {"control-l", "^L", {12,0}}, + {"control-n", "^N", {14,0}}, + {"control-o", "^O", {15,0}}, + {"control-p", "^P", {16,0}}, + {"control-q", "^Q", {17,0}}, + {"control-r", "^R", {18,0}}, + {"control-s", "^S", {19,0}}, + {"control-t", "^T", {20,0}}, + {"control-u", "^U", {21,0}}, + {"control-v", "^V", {22,0}}, + {"control-w", "^W", {23,0}}, + {"control-x", "^X", {24,0}}, + {"control-y", "^Y", {25,0}}, + {"control-z", "^Z", {26,0}}, + {"alt-0", "%0", "\0330"}, + {"alt-1", "%1", "\0331"}, + {"alt-2", "%2", "\0332"}, + {"alt-3", "%3", "\0333"}, + {"alt-4", "%4", "\0334"}, + {"alt-5", "%5", "\0335"}, + {"alt-6", "%6", "\0336"}, + {"alt-7", "%7", "\0337"}, /* backspace? */ + {"alt-8", "%8", "\0338"}, + {"alt-9", "%9", "\0339"}, + {"alt-+", "%+", "\033+"}, + {"alt--", "%-", "\033-"}, + {"alt-/", "%/", "\033/"}, + {"alt-a", "%A", "\033a"}, + {"alt-b", "%B", "\033b"}, + {"alt-c", "%C", "\033c"}, + {"alt-d", "%D", "\033d"}, + {"alt-e", "%E", "\033e"}, + {"alt-f", "%F", "\033f"}, + {"alt-g", "%G", "\033g"}, + {"alt-h", "%H", "\033h"}, /* backspace? */ + {"alt-i", "%I", "\033i"}, + {"alt-j", "%J", "\033j"}, + {"alt-k", "%K", "\033k"}, + {"alt-l", "%L", "\033l"}, + {"alt-n", "%N", "\033m"}, + {"alt-n", "%N", "\033n"}, + {"alt-o", "%O", "\033o"}, + {"alt-p", "%P", "\033p"}, + {"alt-q", "%Q", "\033q"}, + {"alt-r", "%R", "\033r"}, + {"alt-s", "%S", "\033s"}, + {"alt-t", "%T", "\033t"}, + {"alt-u", "%U", "\033u"}, + {"alt-v", "%V", "\033v"}, + {"alt-w", "%W", "\033w"}, + {"alt-x", "%X", "\033x"}, + {"alt-y", "%Y", "\033y"}, + {"alt-z", "%Z", "\033z"}, + {"shift-tab", "shift-↹", {27, 9, 0}}, + /* Linux Console */ + {"home", "Home", "\033[1~"}, + {"end", "End", "\033[4~"}, + {"F1", "F1", "\033[[A"}, + {"F2", "F2", "\033[[B"}, + {"F3", "F3", "\033[[C"}, + {"F4", "F4", "\033[[D"}, + {"F5", "F5", "\033[[E"}, + {"F6", "F6", "\033[[F"}, + {"F7", "F7", "\033[[G"}, + {"F8", "F8", "\033[[H"}, + {"F9", "F9", "\033[[I"}, + {"F10", "F10", "\033[[J"}, + {"F11", "F11", "\033[[K"}, + {"F12", "F12", "\033[[L"}, + {"ok", "", "\033[0n"}, + {NULL, } +}; + +static struct termios orig_attr; /* in order to restore at exit */ +static int nc_is_raw = 0; +static int atexit_registered = 0; +static int mouse_mode = NC_MOUSE_NONE; + +static void _nc_noraw (void) +{ + if (nc_is_raw && tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr) != -1) + nc_is_raw = 0; +} + +void +nc_at_exit (void) +{ + printf (TERMINAL_MOUSE_OFF); + printf (XTERM_ALTSCREEN_OFF); + _nc_noraw(); + fprintf (stdout, "\e[?25h"); + //if (ctx_native_events) + fprintf (stdout, "\e[?201l"); + fprintf (stdout, "\e[?1049l"); +} + +static const char *mouse_get_event_int (Ctx *n, int *x, int *y) +{ + static int prev_state = 0; + const char *ret = "mouse-motion"; + float relx, rely; + signed char buf[3]; + read (n->mouse_fd, buf, 3); + relx = buf[1]; + rely = -buf[2]; + + n->mouse_x += relx * 0.1; + n->mouse_y += rely * 0.1; + + if (n->mouse_x < 1) n->mouse_x = 1; + if (n->mouse_y < 1) n->mouse_y = 1; + if (n->mouse_x >= n->events.width) n->mouse_x = n->events.width; + if (n->mouse_y >= n->events.height) n->mouse_y = n->events.height; + + if (x) *x = n->mouse_x; + if (y) *y = n->mouse_y; + + if ((prev_state & 1) != (buf[0] & 1)) + { + if (buf[0] & 1) ret = "mouse-press"; + } + else if (buf[0] & 1) + ret = "mouse-drag"; + + if ((prev_state & 2) != (buf[0] & 2)) + { + if (buf[0] & 2) ret = "mouse2-press"; + } + else if (buf[0] & 2) + ret = "mouse2-drag"; + + if ((prev_state & 4) != (buf[0] & 4)) + { + if (buf[0] & 4) ret = "mouse1-press"; + } + else if (buf[0] & 4) + ret = "mouse1-drag"; + + prev_state = buf[0]; + return ret; +} + +static const char *mev_type = NULL; +static int mev_x = 0; +static int mev_y = 0; +static int mev_q = 0; + +static const char *mouse_get_event (Ctx *n, int *x, int *y) +{ + if (!mev_q) + return NULL; + *x = mev_x; + *y = mev_y; + mev_q = 0; + return mev_type; +} + +static int mouse_has_event (Ctx *n) +{ + struct timeval tv; + int retval; + + if (mouse_mode == NC_MOUSE_NONE) + return 0; + + if (mev_q) + return 1; + + if (n->mouse_fd == 0) + return 0; + return 0; + + { + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(n->mouse_fd, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (n->mouse_fd+1, &rfds, NULL, NULL, &tv); + } + + if (retval != 0) + { + int nx = 0, ny = 0; + const char *type = mouse_get_event_int (n, &nx, &ny); + + if ((mouse_mode < NC_MOUSE_DRAG && mev_type && !strcmp (mev_type, "drag")) || + (mouse_mode < NC_MOUSE_ALL && mev_type && !strcmp (mev_type, "motion"))) + { + mev_q = 0; + return mouse_has_event (n); + } + + if ((mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse-motion")) || + (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse1-drag")) || + (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse2-drag"))) + { + if (nx == mev_x && ny == mev_y) + { + mev_q = 0; + return mouse_has_event (n); + } + } + mev_x = nx; + mev_y = ny; + mev_type = type; + mev_q = 1; + } + return retval != 0; +} + + +static int _nc_raw (void) +{ + struct termios raw; + if (!isatty (STDIN_FILENO)) + return -1; + if (!atexit_registered) + { + atexit (nc_at_exit); + atexit_registered = 1; + } + if (tcgetattr (STDIN_FILENO, &orig_attr) == -1) + return -1; + raw = orig_attr; /* modify the original mode */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return -1; + nc_is_raw = 1; +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); + tcflush(STDIN_FILENO, 1); +#endif + return 0; +} + +static int match_keycode (const char *buf, int length, const NcKeyCode **ret) +{ + int i; + int matches = 0; + + if (!strncmp (buf, "\033[M", MIN(length,3))) + { + if (length >= 6) + return 9001; + return 2342; + } + for (i = 0; keycodes[i].nick; i++) + if (!strncmp (buf, keycodes[i].sequence, length)) + { + matches ++; + if ((int)strlen (keycodes[i].sequence) == length && ret) + { + *ret = &keycodes[i]; + return 1; + } + } + if (matches != 1 && ret) + *ret = NULL; + return matches==1?2:matches; +} + +static void nc_resize_term (int dummy) +{ + size_changed = 1; +} + +int ctx_nct_has_event (Ctx *n, int delay_ms) +{ + struct timeval tv; + int retval; + fd_set rfds; + + if (size_changed) + return 1; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; tv.tv_usec = delay_ms * 1000; + retval = select (1, &rfds, NULL, NULL, &tv); + if (size_changed) + return 1; + return retval == 1 && retval != -1; +} + +const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y) +{ + unsigned char buf[20]; + int length; + + + if (x) *x = -1; + if (y) *y = -1; + + if (!signal_installed) + { + _nc_raw (); + signal_installed = 1; + signal (SIGWINCH, nc_resize_term); + } + if (mouse_mode) // XXX too often to do it all the time! + printf("%s", mouse_modes[mouse_mode]); + + { + int elapsed = 0; + int got_event = 0; + + do { + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + got_event = mouse_has_event (n); + if (!got_event) + got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed)); + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + /* only do this if the client has asked for idle events, + * and perhaps programmed the ms timer? + */ + elapsed += MIN(DELAY_MS, timeoutms-elapsed); + if (!got_event && timeoutms && elapsed >= timeoutms) + return "idle"; + } while (!got_event); + } + + if (mouse_has_event (n)) + return mouse_get_event (n, x, y); + + for (length = 0; length < 10; length ++) + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + const NcKeyCode *match = NULL; + + /* special case ESC, so that we can use it alone in keybindings */ + if (length == 0 && buf[0] == 27) + { + struct timeval tv; + fd_set rfds; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1000 * DELAY_MS; + if (select (1, &rfds, NULL, NULL, &tv) == 0) + return "esc"; + } + + switch (match_keycode ((const char*)buf, length + 1, &match)) + { + case 1: /* unique match */ + if (!match) + return NULL; + if (!strcmp(match->nick, "ok")) + { + ctx_frame_ack = 1; + return NULL; + } + return match->nick; + break; + case 9001: /* mouse event */ + if (x) *x = ((unsigned char)buf[4]-32)*1.0; + if (y) *y = ((unsigned char)buf[5]-32)*1.0; + switch (buf[3]) + { + /* XXX : todo reduce this to less string constants */ + case 32: return "mouse-press"; + case 33: return "mouse1-press"; + case 34: return "mouse2-press"; + case 40: return "alt-mouse-press"; + case 41: return "alt-mouse1-press"; + case 42: return "alt-mouse2-press"; + case 48: return "control-mouse-press"; + case 49: return "control-mouse1-press"; + case 50: return "control-mouse2-press"; + case 56: return "alt-control-mouse-press"; + case 57: return "alt-control-mouse1-press"; + case 58: return "alt-control-mouse2-press"; + case 64: return "mouse-drag"; + case 65: return "mouse1-drag"; + case 66: return "mouse2-drag"; + case 71: return "mouse-motion"; /* shift+motion */ + case 72: return "alt-mouse-drag"; + case 73: return "alt-mouse1-drag"; + case 74: return "alt-mouse2-drag"; + case 75: return "mouse-motion"; /* alt+motion */ + case 80: return "control-mouse-drag"; + case 81: return "control-mouse1-drag"; + case 82: return "control-mouse2-drag"; + case 83: return "mouse-motion"; /* ctrl+motion */ + case 91: return "mouse-motion"; /* ctrl+alt+motion */ + case 95: return "mouse-motion"; /* ctrl+alt+shift+motion */ + case 96: return "scroll-up"; + case 97: return "scroll-down"; + case 100: return "shift-scroll-up"; + case 101: return "shift-scroll-down"; + case 104: return "alt-scroll-up"; + case 105: return "alt-scroll-down"; + case 112: return "control-scroll-up"; + case 113: return "control-scroll-down"; + case 116: return "control-shift-scroll-up"; + case 117: return "control-shift-scroll-down"; + case 35: /* (or release) */ + case 51: /* (or ctrl-release) */ + case 43: /* (or alt-release) */ + case 67: return "mouse-motion"; + /* have a separate mouse-drag ? */ + default: { + static char rbuf[100]; + sprintf (rbuf, "mouse (unhandled state: %i)", buf[3]); + return rbuf; + } + } + case 0: /* no matches, bail*/ + { + static char ret[256]; + if (length == 0 && ctx_utf8_len (buf[0])>1) /* single unicode + char */ + { + int n_read = + read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1); + if (n_read) + { + buf[ctx_utf8_len(buf[0])]=0; + strcpy (ret, (const char*)buf); + } + return ret; + } + if (length == 0) /* ascii */ + { + buf[1]=0; + strcpy (ret, (const char*)buf); + return ret; + } + sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'", + length>=0? buf[0]: 0, length>=0? buf[0]>31?buf[0]:'?': ' ', + length>=1? buf[1]: 0, length>=1? buf[1]>31?buf[1]:'?': ' ', + length>=2? buf[2]: 0, length>=2? buf[2]>31?buf[2]:'?': ' ', + length>=3? buf[3]: 0, length>=3? buf[3]>31?buf[3]:'?': ' ', + length>=4? buf[4]: 0, length>=4? buf[4]>31?buf[4]:'?': ' ', + length>=5? buf[5]: 0, length>=5? buf[5]>31?buf[5]:'?': ' ', + length>=6? buf[6]: 0, length>=6? buf[6]>31?buf[6]:'?': ' '); + return ret; + } + return NULL; + default: /* continue */ + break; + } + } + else + return "key read eek"; + return "fail"; +} + +int ctx_nct_consume_events (Ctx *ctx) +{ + int ix, iy; + CtxCtx *ctxctx = (CtxCtx*)ctx->renderer; + const char *event = NULL; + + { + float x, y; + event = ctx_nct_get_event (ctx, 50, &ix, &iy); + + x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width; + y = (iy - 1.0) / ctxctx->rows * ctx->events.height; + + if (!strcmp (event, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, 0, 0); + ctxctx->was_down = 1; + } else if (!strcmp (event, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } else if (!strcmp (event, "mouse-motion")) + { + //nct_set_cursor_pos (backend->term, ix, iy); + //nct_flush (backend->term); + if (ctxctx->was_down) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-drag")) + { + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "size-changed")) + { +#if 0 + int width = nct_sys_terminal_width (); + int height = nct_sys_terminal_height (); + nct_set_size (backend->term, width, height); + width *= CPX; + height *= CPX; + free (mrg->glyphs); + free (mrg->styles); + free (backend->nct_pixels); + backend->nct_pixels = calloc (width * height * 4, 1); + mrg->glyphs = calloc ((width/CPX) * (height/CPX) * 4, 1); + mrg->styles = calloc ((width/CPX) * (height/CPX) * 1, 1); + mrg_set_size (mrg, width, height); + mrg_queue_draw (mrg, NULL); +#endif + + } + else + { + if (!strcmp (event, "esc")) + ctx_key_press (ctx, 0, "escape", 0); + else if (!strcmp (event, "space")) + ctx_key_press (ctx, 0, "space", 0); + else if (!strcmp (event, "enter")) + ctx_key_press (ctx, 0, "\n", 0); + else if (!strcmp (event, "return")) + ctx_key_press (ctx, 0, "return", 0); + else if (!strcmp (event, "idle")) + { + } + else + ctx_key_press (ctx, 0, event, 0); + } + } + + return 1; +} + +const char *ctx_native_get_event (Ctx *n, int timeoutms) +{ + static unsigned char buf[256]; + int length; + + if (!signal_installed) + { + _nc_raw (); + signal_installed = 1; + signal (SIGWINCH, nc_resize_term); + } +//if (mouse_mode) // XXX too often to do it all the time! +// printf("%s", mouse_modes[mouse_mode]); + + int got_event = 0; + { + int elapsed = 0; + + do { + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed)); + if (size_changed) + { + size_changed = 0; + return "size-changed"; + } + /* only do this if the client has asked for idle events, + * and perhaps programmed the ms timer? + */ + elapsed += MIN(DELAY_MS, timeoutms-elapsed); + if (!got_event && timeoutms && elapsed >= timeoutms) + { + return "idle"; + } + } while (!got_event); + } + + for (length = 0; got_event && length < 200; length ++) + { + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + buf[length+1] = 0; + if (!strcmp ((char*)buf, "\e[0n")) + { + ctx_frame_ack = 1; + return NULL; + } + else if (buf[length]=='\n') + { + buf[length]=0; + return (const char*)buf; + } + } + got_event = ctx_nct_has_event (n, 5); + } + return NULL; +} + +const char *ctx_key_get_label (Ctx *n, const char *nick) +{ + int j; + int found = -1; + for (j = 0; keycodes[j].nick; j++) + if (found == -1 && !strcmp (keycodes[j].nick, nick)) + return keycodes[j].label; + return NULL; +} + +void _ctx_mouse (Ctx *term, int mode) +{ + //if (term->is_st && mode > 1) + // mode = 1; + if (mode != mouse_mode) + { + printf ("%s", mouse_modes[mode]); + fflush (stdout); + } + mouse_mode = mode; +} + + +#endif + +#if !__COSMOPOLITAN__ +#include <sys/time.h> +#endif + + +#define usecs(time) ((uint64_t)(time.tv_sec - start_time.tv_sec) * 1000000 + time. tv_usec) + +#if !__COSMOPOLITAN__ +static struct timeval start_time; + +static void +_ctx_init_ticks (void) +{ + static int done = 0; + if (done) + return; + done = 1; + gettimeofday (&start_time, NULL); +} + +static inline unsigned long +_ctx_ticks (void) +{ + struct timeval measure_time; + gettimeofday (&measure_time, NULL); + return usecs (measure_time) - usecs (start_time); +} + +unsigned long +ctx_ticks (void) +{ + _ctx_init_ticks (); + return _ctx_ticks (); +} + +uint32_t ctx_ms (Ctx *ctx) +{ + return _ctx_ticks () / 1000; +} + + + +enum _CtxFlags { + CTX_FLAG_DIRECT = (1<<0), +}; +typedef enum _CtxFlags CtxFlags; + + +int _ctx_max_threads = 1; +int _ctx_enable_hash_cache = 1; +#if CTX_SHAPE_CACHE +extern int _ctx_shape_cache_enabled; +#endif + +#if CTX_THREADS +static mtx_t _ctx_texture_mtx; +#endif + +void _ctx_texture_lock (void) +{ +#if CTX_THREADS + mtx_lock (&_ctx_texture_mtx); +#endif +} + +void _ctx_texture_unlock (void) +{ +#if CTX_THREADS + mtx_unlock (&_ctx_texture_mtx); +#endif +} + + +void +ctx_init (int *argc, char ***argv) +{ +#if 0 + if (!getenv ("CTX_VERSION")) + { + int i; + char *new_argv[*argc+3]; + new_argv[0] = "ctx"; + for (i = 0; i < *argc; i++) + { + new_argv[i+1] = *argv[i]; + } + new_argv[i+1] = NULL; + execvp (new_argv[0], new_argv); + // if this fails .. we continue normal startup + // and end up in self-hosted braille + } +#endif +} + +int ctx_count (Ctx *ctx) +{ + return ctx->drawlist.count; +} + + + +extern int _ctx_damage_control; + +static void ctx_list_backends() +{ + fprintf (stderr, "possible values for CTX_BACKEND:\n"); + fprintf (stderr, " ctx"); +#if CTX_SDL + fprintf (stderr, " SDL"); +#endif +#if CTX_FB + fprintf (stderr, " fb"); + fprintf (stderr, " drm"); +#endif + fprintf (stderr, " term"); + fprintf (stderr, " termimg"); + fprintf (stderr, "\n"); +} + +#if CTX_EVENTS +static int is_in_ctx (void); +Ctx *ctx_new_ui (int width, int height) +{ +#if CTX_TILED + if (getenv ("CTX_DAMAGE_CONTROL")) + { + const char * val = getenv ("CTX_DAMAGE_CONTROL"); + if (!strcmp (val, "0") || + !strcmp (val, "off")) + _ctx_damage_control = 0; + else + _ctx_damage_control = 1; + } +#endif + + if (getenv ("CTX_HASH_CACHE")) + { + const char * val = getenv ("CTX_HASH_CACHE"); + if (!strcmp (val, "0")) + _ctx_enable_hash_cache = 0; + if (!strcmp (val, "off")) + _ctx_enable_hash_cache = 0; + } +#if CTX_SHAPE_CACHE + if (getenv ("CTX_SHAPE_CACHE")) + { + const char * val = getenv ("CTX_SHAPE_CACHE"); + if (!strcmp (val, "0")) + _ctx_shape_cache_enabled = 0; + if (!strcmp (val, "off")) + _ctx_shape_cache_enabled = 0; + } +#endif + + if (getenv ("CTX_THREADS")) + { + int val = atoi (getenv ("CTX_THREADS")); + _ctx_max_threads = val; + } + else + { + _ctx_max_threads = 2; +#ifdef _SC_NPROCESSORS_ONLN + _ctx_max_threads = sysconf (_SC_NPROCESSORS_ONLN) / 2; +#endif + } + +#if CTX_THREADS + mtx_init (&_ctx_texture_mtx, mtx_plain); +#endif + + if (_ctx_max_threads < 1) _ctx_max_threads = 1; + if (_ctx_max_threads > CTX_MAX_THREADS) _ctx_max_threads = CTX_MAX_THREADS; + + //fprintf (stderr, "ctx using %i threads\n", _ctx_max_threads); + const char *backend = getenv ("CTX_BACKEND"); + + if (backend && !strcmp (backend, "")) + backend = NULL; + if (backend && !strcmp (backend, "auto")) + backend = NULL; + if (backend && !strcmp (backend, "list")) + { + ctx_list_backends (); + exit (-1); + } + + Ctx *ret = NULL; + + /* we do the query on auto but not on directly set ctx + * + */ + if ((backend && !strcmp(backend, "ctx")) || + (backend == NULL && is_in_ctx ())) + { + if (!backend || !strcmp (backend, "ctx")) + { + // full blown ctx protocol - in terminal or standalone + ret = ctx_new_ctx (width, height); + } + } + +#if CTX_SDL + if (!ret && getenv ("DISPLAY")) + { + if ((backend==NULL) || (!strcmp (backend, "SDL"))) + ret = ctx_new_sdl (width, height); + } +#endif + +#if CTX_FB + if (!ret && !getenv ("DISPLAY")) + { + if ((backend==NULL) || (!strcmp (backend, "drm"))) + ret = ctx_new_fb (width, height, 1); + + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "fb"))) + ret = ctx_new_fb (width, height, 0); + } + } +#endif + +#if CTX_RASTERIZER + // braille in terminal + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "term"))) + ret = ctx_new_term (width, height); + } + if (!ret) + { + if ((backend==NULL) || (!strcmp (backend, "termimg"))) + ret = ctx_new_termimg (width, height); + } +#endif + if (!ret) + { + fprintf (stderr, "no interactive ctx backend\n"); + ctx_list_backends (); + exit (2); + } + ctx_get_event (ret); // enables events + return ret; +} +#endif +#else +void _ctx_texture_unlock (void) +{ +} +void _ctx_texture_lock (void) +{ +} + +#endif +void _ctx_resized (Ctx *ctx, int width, int height, long time); + +void ctx_set_size (Ctx *ctx, int width, int height) +{ +#if CTX_EVENTS + if (ctx->events.width != width || ctx->events.height != height) + { + ctx->events.width = width; + ctx->events.height = height; + _ctx_resized (ctx, width, height, 0); + } +#endif +} + +#if CTX_EVENTS + + +static int is_in_ctx (void) +{ + char buf[1024]; + struct termios orig_attr; + struct termios raw; + tcgetattr (STDIN_FILENO, &orig_attr); + raw = orig_attr; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; + fprintf (stderr, "\e[?200$p"); + //tcflush(STDIN_FILENO, 1); +#if !__COSMOPOLITAN__ + tcdrain(STDIN_FILENO); +#endif + int length = 0; + usleep (1000 * 60); // to account for possibly lowish latency ssh, + // should be made configurable ; perhaps in + // an env var + struct timeval tv = {0,0}; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_usec = 1000 * 5; + + for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++) + { + length += read (STDIN_FILENO, &buf[length], 1); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + if (length == -1) + { + return 0; + } + char *semi = strchr (buf, ';'); + buf[length]=0; + if (semi && semi[1] == '2') + { + return 1; + } + return 0; +} + +typedef struct CtxIdleCb { + int (*cb) (Ctx *ctx, void *idle_data); + void *idle_data; + + void (*destroy_notify)(void *destroy_data); + void *destroy_data; + + int ticks_full; + int ticks_remaining; + int is_idle; + int id; +} CtxIdleCb; + +void _ctx_events_init (Ctx *ctx) +{ + CtxEvents *events = &ctx->events; + _ctx_init_ticks (); + events->tap_delay_min = 40; + events->tap_delay_max = 800; + events->tap_delay_max = 8000000; /* quick reflexes needed making it hard for some is an argument against very short values */ + + events->tap_delay_hold = 1000; + events->tap_hysteresis = 32; /* XXX: should be ppi dependent */ +} + + +void _ctx_idle_iteration (Ctx *ctx) +{ + static unsigned long prev_ticks = 0; + CtxList *l; + CtxList *to_remove = NULL; + unsigned long ticks = _ctx_ticks (); + unsigned long tick_delta = (prev_ticks == 0) ? 0 : ticks - prev_ticks; + prev_ticks = ticks; + + if (!ctx->events.idles) + { + return; + } + for (l = ctx->events.idles; l; l = l->next) + { + CtxIdleCb *item = l->data; + + if (item->ticks_remaining >= 0) + item->ticks_remaining -= tick_delta; + + if (item->ticks_remaining < 0) + { + if (item->cb (ctx, item->idle_data) == 0) + ctx_list_prepend (&to_remove, item); + else + item->ticks_remaining = item->ticks_full; + } + } + for (l = to_remove; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->destroy_notify) + item->destroy_notify (item->destroy_data); + ctx_list_remove (&ctx->events.idles, l->data); + } +} + + +void ctx_add_key_binding_full (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data, + CtxDestroyNotify destroy_notify, + void *destroy_data) +{ + CtxEvents *events = &ctx->events; + if (events->n_bindings +1 >= CTX_MAX_KEYBINDINGS) + { + fprintf (stderr, "warning: binding overflow\n"); + return; + } + events->bindings[events->n_bindings].nick = strdup (key); + strcpy (events->bindings[events->n_bindings].nick, key); + + if (action) + events->bindings[events->n_bindings].command = action ? strdup (action) : NULL; + if (label) + events->bindings[events->n_bindings].label = label ? strdup (label) : NULL; + events->bindings[events->n_bindings].cb = cb; + events->bindings[events->n_bindings].cb_data = cb_data; + events->bindings[events->n_bindings].destroy_notify = destroy_notify; + events->bindings[events->n_bindings].destroy_data = destroy_data; + events->n_bindings++; +} + +void ctx_add_key_binding (Ctx *ctx, + const char *key, + const char *action, + const char *label, + CtxCb cb, + void *cb_data) +{ + ctx_add_key_binding_full (ctx, key, action, label, cb, cb_data, NULL, NULL); +} + +void ctx_clear_bindings (Ctx *ctx) +{ + CtxEvents *events = &ctx->events; + int i; + for (i = 0; events->bindings[i].nick; i ++) + { + if (events->bindings[i].destroy_notify) + events->bindings[i].destroy_notify (events->bindings[i].destroy_data); + free (events->bindings[i].nick); + if (events->bindings[i].command) + free (events->bindings[i].command); + if (events->bindings[i].label) + free (events->bindings[i].label); + } + memset (&events->bindings, 0, sizeof (events->bindings)); + events->n_bindings = 0; +} + +static void +ctx_collect_events (CtxEvent *event, void *data, void *data2); +static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2) +{ + Ctx *ctx = event->ctx; + CtxEvents *events = &ctx->events; + int i; + int handled = 0; + + for (i = events->n_bindings-1; i>=0; i--) + if (!strcmp (events->bindings[i].nick, event->string)) + { + if (events->bindings[i].cb) + { + events->bindings[i].cb (event, events->bindings[i].cb_data, NULL); + if (event->stop_propagate) + return; + handled = 1; + } + } + if (!handled) + for (i = events->n_bindings-1; i>=0; i--) + if (!strcmp (events->bindings[i].nick, "unhandled")) + { + if (events->bindings[i].cb) + { + events->bindings[i].cb (event, events->bindings[i].cb_data, NULL); + if (event->stop_propagate) + return; + } + } + ctx_collect_events (event, data1, data2); +} + +CtxBinding *ctx_get_bindings (Ctx *ctx) +{ + return &ctx->events.bindings[0]; +} + +void ctx_remove_idle (Ctx *ctx, int handle) +{ + CtxList *l; + CtxList *to_remove = NULL; + + if (!ctx->events.idles) + { + return; + } + for (l = ctx->events.idles; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->id == handle) + ctx_list_prepend (&to_remove, item); + } + for (l = to_remove; l; l = l->next) + { + CtxIdleCb *item = l->data; + if (item->destroy_notify) + item->destroy_notify (item->destroy_data); + ctx_list_remove (&ctx->events.idles, l->data); + } +} + +int ctx_add_timeout_full (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data) +{ + CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1); + item->cb = idle_cb; + item->idle_data = idle_data; + item->id = ++ctx->events.idle_id; + item->ticks_full = + item->ticks_remaining = ms * 1000; + item->destroy_notify = destroy_notify; + item->destroy_data = destroy_data; + ctx_list_append (&ctx->events.idles, item); + return item->id; +} + +int ctx_add_timeout (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data) +{ + return ctx_add_timeout_full (ctx, ms, idle_cb, idle_data, NULL, NULL); +} + +int ctx_add_idle_full (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data, + void (*destroy_notify)(void *destroy_data), void *destroy_data) +{ + CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1); + item->cb = idle_cb; + item->idle_data = idle_data; + item->id = ++ctx->events.idle_id; + item->ticks_full = + item->ticks_remaining = -1; + item->is_idle = 1; + item->destroy_notify = destroy_notify; + item->destroy_data = destroy_data; + ctx_list_append (&ctx->events.idles, item); + return item->id; +} + +int ctx_add_idle (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data) +{ + return ctx_add_idle_full (ctx, idle_cb, idle_data, NULL, NULL); +} + +#endif +/* using bigger primes would be a good idea, this falls apart due to rounding + * when zoomed in close + */ +static inline double ctx_path_hash (void *path) +{ + double ret = 0; +#if 0 + int i; + cairo_path_data_t *data; + if (!path) + return 0.99999; + for (i = 0; i <path->num_data; i += path->data[i].header.length) + { + data = &path->data[i]; + switch (data->header.type) { + case CAIRO_PATH_MOVE_TO: + ret *= 17; + ret += data[1].point.x; + ret *= 113; + ret += data[1].point.y; + break; + case CAIRO_PATH_LINE_TO: + ret *= 121; + ret += data[1].point.x; + ret *= 1021; + ret += data[1].point.y; + break; + case CAIRO_PATH_CURVE_TO: + ret *= 3111; + ret += data[1].point.x; + ret *= 23; + ret += data[1].point.y; + ret *= 107; + ret += data[2].point.x; + ret *= 739; + ret += data[2].point.y; + ret *= 3; + ret += data[3].point.x; + ret *= 51; + ret += data[3].point.y; + break; + case CAIRO_PATH_CLOSE_PATH: + ret *= 51; + break; + } + } +#endif + return ret; +} + +#if CTX_EVENTS +void _ctx_item_ref (CtxItem *item) +{ + if (item->ref_count < 0) + { + fprintf (stderr, "EEEEK!\n"); + } + item->ref_count++; +} + + +void _ctx_item_unref (CtxItem *item) +{ + if (item->ref_count <= 0) + { + fprintf (stderr, "EEEEK!\n"); + return; + } + item->ref_count--; + if (item->ref_count <=0) + { + { + int i; + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].finalize) + item->cb[i].finalize (item->cb[i].data1, item->cb[i].data2, + item->cb[i].finalize_data); + } + } + if (item->path) + { + //cairo_path_destroy (item->path); + } + free (item); + } +} + + +static int +path_equal (void *path, + void *path2) +{ + // XXX + return 0; +} + +void ctx_listen_set_cursor (Ctx *ctx, + CtxCursor cursor) +{ + if (ctx->events.last_item) + { + ctx->events.last_item->cursor = cursor; + } +} + +void ctx_listen_full (Ctx *ctx, + float x, + float y, + float width, + float height, + CtxEventType types, + CtxCb cb, + void *data1, + void *data2, + void (*finalize)(void *listen_data, + void *listen_data2, + void *finalize_data), + void *finalize_data) +{ + if (!ctx->events.frozen) + { + CtxItem *item; + + /* early bail for listeners outside screen */ + /* XXX: fixme respect clipping */ + { + float tx = x; + float ty = y; + float tw = width; + float th = height; + _ctx_user_to_device (&ctx->state, &tx, &ty); + _ctx_user_to_device_distance (&ctx->state, &tw, &th); + if (ty > ctx->events.height * 2 || + tx > ctx->events.width * 2 || + tx + tw < 0 || + ty + th < 0) + { + if (finalize) + finalize (data1, data2, finalize_data); + return; + } + } + + item = calloc (sizeof (CtxItem), 1); + item->x0 = x; + item->y0 = y; + item->x1 = x + width; + item->y1 = y + height; + item->cb[0].types = types; + item->cb[0].cb = cb; + item->cb[0].data1 = data1; + item->cb[0].data2 = data2; + item->cb[0].finalize = finalize; + item->cb[0].finalize_data = finalize_data; + item->cb_count = 1; + item->types = types; + //item->path = cairo_copy_path (cr); // XXX + item->path_hash = ctx_path_hash (item->path); + ctx_get_matrix (ctx, &item->inv_matrix); + ctx_matrix_invert (&item->inv_matrix); + + if (ctx->events.items) + { + CtxList *l; + for (l = ctx->events.items; l; l = l->next) + { + CtxItem *item2 = l->data; + + /* store multiple callbacks for one entry when the paths + * are exact matches, reducing per event traversal checks at the + * cost of a little paint-hit (XXX: is this the right tradeoff, + * perhaps it is better to spend more time during event processing + * than during paint?) + */ + if (item->path_hash == item2->path_hash && + path_equal (item->path, item2->path)) + { + /* found an item, copy over cb data */ + item2->cb[item2->cb_count] = item->cb[0]; + free (item); + item2->cb_count++; + item2->types |= types; + return; + } + } + } + item->ref_count = 1; + ctx->events.last_item = item; + ctx_list_prepend_full (&ctx->events.items, item, (void*)_ctx_item_unref, NULL); + } +} + +void ctx_event_stop_propagate (CtxEvent *event) +{ + if (event) + event->stop_propagate = 1; +} + +void ctx_listen (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2) +{ + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + if (types & CTX_KEY) + { + x = 0; + y = 0; + width = 0; + height = 0; + } + else + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + if (types == CTX_DRAG_MOTION) + types = CTX_DRAG_MOTION | CTX_DRAG_PRESS; + return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, NULL, NULL); +} + +void ctx_listen_with_finalize (Ctx *ctx, + CtxEventType types, + CtxCb cb, + void* data1, + void* data2, + void (*finalize)(void *listen_data, void *listen_data2, + void *finalize_data), + void *finalize_data) +{ + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + if (types & CTX_KEY) + { + x = 0; + y = 0; + width = 0; + height = 0; + } + else + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + if (types == CTX_DRAG_MOTION) + types = CTX_DRAG_MOTION | CTX_DRAG_PRESS; + return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, finalize, finalize_data); +} + + +static void ctx_report_hit_region (CtxEvent *event, + void *data, + void *data2) +{ + const char *id = data; + + fprintf (stderr, "hit region %s\n", id); + // XXX: NYI +} + +void ctx_add_hit_region (Ctx *ctx, const char *id) +{ + char *id_copy = strdup (id); + float x, y, width, height; + /* generate bounding box of what to listen for - from current cairo path */ + { + float ex1,ey1,ex2,ey2; + ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2); + x = ex1; + y = ey1; + width = ex2 - ex1; + height = ey2 - ey1; + } + + return ctx_listen_full (ctx, x, y, width, height, + CTX_POINTER, ctx_report_hit_region, + id_copy, NULL, (void*)free, NULL); +} + +typedef struct _CtxGrab CtxGrab; + +struct _CtxGrab +{ + CtxItem *item; + int device_no; + int timeout_id; + int start_time; + float x; // for tap and hold + float y; + CtxEventType type; +}; + +static void grab_free (Ctx *ctx, CtxGrab *grab) +{ + if (grab->timeout_id) + { + ctx_remove_idle (ctx, grab->timeout_id); + grab->timeout_id = 0; + } + _ctx_item_unref (grab->item); + free (grab); +} + +static void device_remove_grab (Ctx *ctx, CtxGrab *grab) +{ + ctx_list_remove (&ctx->events.grabs, grab); + grab_free (ctx, grab); +} + +static CtxGrab *device_add_grab (Ctx *ctx, int device_no, CtxItem *item, CtxEventType type) +{ + CtxGrab *grab = calloc (1, sizeof (CtxGrab)); + grab->item = item; + grab->type = type; + _ctx_item_ref (item); + grab->device_no = device_no; + ctx_list_append (&ctx->events.grabs, grab); + return grab; +} + +static CtxList *_ctx_device_get_grabs (Ctx *ctx, int device_no) +{ + CtxList *ret = NULL; + CtxList *l; + for (l = ctx->events.grabs; l; l = l->next) + { + CtxGrab *grab = l->data; + if (grab->device_no == device_no) + ctx_list_append (&ret, grab); + } + return ret; +} + +static void _mrg_restore_path (Ctx *ctx, void *path) //XXX +{ + //int i; + //cairo_path_data_t *data; + //cairo_new_path (cr); + //cairo_append_path (cr, path); +} + +CtxList *_ctx_detect_list (Ctx *ctx, float x, float y, CtxEventType type) +{ + CtxList *a; + CtxList *ret = NULL; + + if (type == CTX_KEY_DOWN || + type == CTX_KEY_UP || + type == CTX_KEY_PRESS || + type == CTX_MESSAGE || + type == (CTX_KEY_DOWN|CTX_MESSAGE) || + type == (CTX_KEY_DOWN|CTX_KEY_UP) || + type == (CTX_KEY_DOWN|CTX_KEY_UP|CTX_MESSAGE)) + { + for (a = ctx->events.items; a; a = a->next) + { + CtxItem *item = a->data; + if (item->types & type) + { + ctx_list_prepend (&ret, item); + return ret; + } + } + return NULL; + } + + for (a = ctx->events.items; a; a = a->next) + { + CtxItem *item= a->data; + + float u, v; + u = x; + v = y; + _ctx_matrix_apply_transform (&item->inv_matrix, &u, &v); + + if (u >= item->x0 && v >= item->y0 && + u < item->x1 && v < item->y1 && + ((item->types & type) || ((type == CTX_SET_CURSOR) && + item->cursor))) + { + if (item->path) + { + _mrg_restore_path (ctx, item->path); + if (ctx_in_fill (ctx, u, v)) + { + ctx_begin_path (ctx); + ctx_list_prepend (&ret, item); + } + ctx_begin_path (ctx); + } + else + { + ctx_list_prepend (&ret, item); + } + } + } + return ret; +} + +CtxItem *_ctx_detect (Ctx *ctx, float x, float y, CtxEventType type) +{ + CtxList *l = _ctx_detect_list (ctx, x, y, type); + if (l) + { + ctx_list_reverse (&l); + CtxItem *ret = l->data; + ctx_list_free (&l); + return ret; + } + return NULL; +} + +static int +_ctx_emit_cb_item (Ctx *ctx, CtxItem *item, CtxEvent *event, CtxEventType type, float x, float y) +{ + static CtxEvent s_event; + CtxEvent transformed_event; + int i; + + + if (!event) + { + event = &s_event; + event->type = type; + event->x = x; + event->y = y; + } + event->ctx = ctx; + transformed_event = *event; + transformed_event.device_x = event->x; + transformed_event.device_y = event->y; + + { + float tx, ty; + tx = transformed_event.x; + ty = transformed_event.y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.x = tx; + transformed_event.y = ty; + + if ((type & CTX_DRAG_PRESS) || + (type & CTX_DRAG_MOTION) || + (type & CTX_MOTION)) /* probably a worthwhile check for the performance + benefit + */ + { + tx = transformed_event.start_x; + ty = transformed_event.start_y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.start_x = tx; + transformed_event.start_y = ty; + } + + + tx = transformed_event.delta_x; + ty = transformed_event.delta_y; + _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty); + transformed_event.delta_x = tx; + transformed_event.delta_y = ty; + } + + transformed_event.state = ctx->events.modifier_state; + transformed_event.type = type; + + for (i = item->cb_count-1; i >= 0; i--) + { + if (item->cb[i].types & type) + { + item->cb[i].cb (&transformed_event, item->cb[i].data1, item->cb[i].data2); + event->stop_propagate = transformed_event.stop_propagate; /* copy back the response */ + if (event->stop_propagate) + return event->stop_propagate; + } + } + return 0; +} +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <stdatomic.h> +#endif + +int ctx_native_events = 0; +#if CTX_SDL +int ctx_sdl_events = 0; +int ctx_sdl_consume_events (Ctx *ctx); +#endif + +#if CTX_FB +int ctx_fb_events = 0; +int ctx_fb_consume_events (Ctx *ctx); +#endif + +int ctx_nct_consume_events (Ctx *ctx); +int ctx_nct_has_event (Ctx *n, int delay_ms); +int ctx_ctx_consume_events (Ctx *ctx); + + + +void ctx_consume_events (Ctx *ctx) +{ +#if CTX_SDL + if (ctx_sdl_events) + ctx_sdl_consume_events (ctx); + else +#endif +#if CTX_FB + if (ctx_fb_events) + ctx_fb_consume_events (ctx); + else +#endif + if (ctx_native_events) + ctx_ctx_consume_events (ctx); + else + ctx_nct_consume_events (ctx); +} + +int ctx_has_event (Ctx *ctx, int timeout) +{ +#if CTX_SDL + if (ctx_sdl_events) + { + return SDL_WaitEventTimeout (NULL, timeout); + } + else +#endif +#if CTX_FB + if (ctx_fb_events) + { + return ctx_nct_has_event (ctx, timeout); + } + else +#endif + if (ctx_native_events) + { + return ctx_nct_has_event (ctx, timeout); + } + else + { + return ctx_nct_has_event (ctx, timeout); + } + + ctx_consume_events (ctx); + if (ctx->events.events) + return 1; + return 0; +} + +#if CTX_FB +static int ctx_fb_get_mice_fd (Ctx *ctx); +#endif + +void ctx_get_event_fds (Ctx *ctx, int *fd, int *count) +{ +#if CTX_SDL + if (ctx_sdl_events) + { + *count = 0; + } + else +#endif +#if CTX_FB + if (ctx_fb_events) + { + int mice_fd = ctx_fb_get_mice_fd (ctx); + fd[0] = STDIN_FILENO; + if (mice_fd) + { + fd[1] = mice_fd; + *count = 2; + } + else + { + *count = 1; + } + } + else +#endif + if (ctx_native_events) + { + fd[0] = STDIN_FILENO; + *count = 1; + } + else + { + fd[0] = STDIN_FILENO; + *count = 1; + } +} + +CtxEvent *ctx_get_event (Ctx *ctx) +{ + static CtxEvent event_copy; + _ctx_idle_iteration (ctx); + if (!ctx->events.ctx_get_event_enabled) + ctx->events.ctx_get_event_enabled = 1; + + ctx_consume_events (ctx); + + if (ctx->events.events) + { + event_copy = *((CtxEvent*)(ctx->events.events->data)); + ctx_list_remove (&ctx->events.events, ctx->events.events->data); + return &event_copy; + } + return NULL; +} + +static int +_ctx_emit_cb (Ctx *ctx, CtxList *items, CtxEvent *event, CtxEventType type, float x, float y) +{ + CtxList *l; + event->stop_propagate = 0; + for (l = items; l; l = l->next) + { + _ctx_emit_cb_item (ctx, l->data, event, type, x, y); + if (event->stop_propagate) + return event->stop_propagate; + } + return 0; +} + +/* + * update what is the currently hovered item and returns it.. and the list of hits + * a well. + * + */ +static CtxItem *_ctx_update_item (Ctx *ctx, int device_no, float x, float y, CtxEventType type, CtxList **hitlist) +{ + CtxItem *current = NULL; + + CtxList *l = _ctx_detect_list (ctx, x, y, type); + if (l) + { + ctx_list_reverse (&l); + current = l->data; + } + if (hitlist) + *hitlist = l; + else + ctx_list_free (&l); + + if (ctx->events.prev[device_no] == NULL || current == NULL || (current->path_hash != ctx->events.prev[device_no]->path_hash)) + { +// enter/leave should snapshot chain to root +// and compare with previous snapshotted chain to root +// and emit/enter/leave as appropriate.. +// +// leave might be registered for emission on enter..emission? + + + //int focus_radius = 2; + if (current) + _ctx_item_ref (current); + + if (ctx->events.prev[device_no]) + { + { +#if 0 + CtxIntRectangle rect = {floor(ctx->events.prev[device_no]->x0-focus_radius), + floor(ctx->events.prev[device_no]->y0-focus_radius), + ceil(ctx->events.prev[device_no]->x1)-floor(ctx->events.prev[device_no]->x0) + focus_radius * 2, + ceil(ctx->events.prev[device_no]->y1)-floor(ctx->events.prev[device_no]->y0) + focus_radius * 2}; + mrg_queue_draw (mrg, &rect); +#endif + } + + _ctx_emit_cb_item (ctx, ctx->events.prev[device_no], NULL, CTX_LEAVE, x, y); + _ctx_item_unref (ctx->events.prev[device_no]); + ctx->events.prev[device_no] = NULL; + } + if (current) + { +#if 0 + { + CtxIntRectangle rect = {floor(current->x0-focus_radius), + floor(current->y0-focus_radius), + ceil(current->x1)-floor(current->x0) + focus_radius * 2, + ceil(current->y1)-floor(current->y0) + focus_radius * 2}; + mrg_queue_draw (mrg, &rect); + } +#endif + _ctx_emit_cb_item (ctx, current, NULL, CTX_ENTER, x, y); + ctx->events.prev[device_no] = current; + } + } + current = _ctx_detect (ctx, x, y, type); + //fprintf (stderr, "%p\n", current); + return current; +} + +static int tap_and_hold_fire (Ctx *ctx, void *data) +{ + CtxGrab *grab = data; + CtxList *list = NULL; + ctx_list_prepend (&list, grab->item); + CtxEvent event = {0, }; + + event.ctx = ctx; + event.time = ctx_ms (ctx); + + event.device_x = + event.x = ctx->events.pointer_x[grab->device_no]; + event.device_y = + event.y = ctx->events.pointer_y[grab->device_no]; + + // XXX: x and y coordinates + int ret = _ctx_emit_cb (ctx, list, &event, CTX_TAP_AND_HOLD, + ctx->events.pointer_x[grab->device_no], ctx->events.pointer_y[grab->device_no]); + + ctx_list_free (&list); + + grab->timeout_id = 0; + + return 0; + + return ret; +} + +int ctx_pointer_drop (Ctx *ctx, float x, float y, int device_no, uint32_t time, + char *string) +{ + CtxList *l; + CtxList *hitlist = NULL; + + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + if (device_no <= 3) + { + ctx->events.pointer_x[0] = x; + ctx->events.pointer_y[0] = y; + } + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &ctx->events.drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->ctx = ctx; + event->x = x; + event->y = y; + + event->delta_x = event->delta_y = 0; + + event->device_no = device_no; + event->string = string; + event->time = time; + event->stop_propagate = 0; + + _ctx_update_item (ctx, device_no, x, y, CTX_DROP, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + _ctx_emit_cb_item (ctx, item, event, CTX_DROP, x, y); + + if (event->stop_propagate) + { + ctx_list_free (&hitlist); + return 0; + } + } + + //mrg_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + + return 0; +} + +int ctx_pointer_press (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxEvents *events = &ctx->events; + CtxList *hitlist = NULL; + events->pointer_x[device_no] = x; + events->pointer_y[device_no] = y; + if (device_no <= 3) + { + events->pointer_x[0] = x; + events->pointer_y[0] = y; + } + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &events->drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->x = event->start_x = event->prev_x = x; + event->y = event->start_y = event->prev_y = y; + + event->delta_x = event->delta_y = 0; + + event->device_no = device_no; + event->time = time; + event->stop_propagate = 0; + + if (events->pointer_down[device_no] == 1) + { + fprintf (stderr, "events thought device %i was already down\n", device_no); + } + /* doing just one of these two should be enough? */ + events->pointer_down[device_no] = 1; + switch (device_no) + { + case 1: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON1; + break; + case 2: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON2; + break; + case 3: + events->modifier_state |= CTX_MODIFIER_STATE_BUTTON3; + break; + default: + break; + } + + CtxGrab *grab = NULL; + CtxList *l; + + _ctx_update_item (ctx, device_no, x, y, + CTX_PRESS | CTX_DRAG_PRESS | CTX_TAP | CTX_TAP_AND_HOLD, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + if (item && + ((item->types & CTX_DRAG)|| + (item->types & CTX_TAP) || + (item->types & CTX_TAP_AND_HOLD))) + { + grab = device_add_grab (ctx, device_no, item, item->types); + grab->start_time = time; + + if (item->types & CTX_TAP_AND_HOLD) + { + grab->timeout_id = ctx_add_timeout (ctx, events->tap_delay_hold, tap_and_hold_fire, grab); + } + } + _ctx_emit_cb_item (ctx, item, event, CTX_PRESS, x, y); + if (!event->stop_propagate) + _ctx_emit_cb_item (ctx, item, event, CTX_DRAG_PRESS, x, y); + + if (event->stop_propagate) + { + ctx_list_free (&hitlist); + return 0; + } + } + + //events_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + return 0; +} + +void _ctx_resized (Ctx *ctx, int width, int height, long time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS); + CtxEvent event = {0, }; + + if (!time) + time = ctx_ms (ctx); + + event.ctx = ctx; + event.time = time; + event.string = "resize-event"; /* gets delivered to clients as a key_down event, maybe message shouldbe used instead? + */ + + if (item) + { + event.stop_propagate = 0; + _ctx_emit_cb_item (ctx, item, &event, CTX_KEY_PRESS, 0, 0); + } + +} + +int ctx_pointer_release (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxEvents *events = &ctx->events; + if (time == 0) + time = ctx_ms (ctx); + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &events->drag_event[device_no]; + + event->time = time; + event->x = x; + event->ctx = ctx; + event->y = y; + event->device_no = device_no; + event->stop_propagate = 0; + + switch (device_no) + { + case 1: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON1) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON1; + break; + case 2: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON2) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON2; + break; + case 3: + if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON3) + events->modifier_state -= CTX_MODIFIER_STATE_BUTTON3; + break; + default: + break; + } + + //events_queue_draw (mrg, NULL); /* in case of style change */ + + if (events->pointer_down[device_no] == 0) + { + fprintf (stderr, "device %i already up\n", device_no); + } + events->pointer_down[device_no] = 0; + + events->pointer_x[device_no] = x; + events->pointer_y[device_no] = y; + if (device_no <= 3) + { + events->pointer_x[0] = x; + events->pointer_y[0] = y; + } + CtxList *hitlist = NULL; + CtxList *grablist = NULL , *g= NULL; + CtxGrab *grab; + + _ctx_update_item (ctx, device_no, x, y, CTX_RELEASE | CTX_DRAG_RELEASE, &hitlist); + grablist = _ctx_device_get_grabs (ctx, device_no); + + for (g = grablist; g; g = g->next) + { + grab = g->data; + + if (!event->stop_propagate) + { + if (grab->item->types & CTX_TAP) + { + long delay = time - grab->start_time; + + if (delay > events->tap_delay_min && + delay < events->tap_delay_max && + ( + (event->start_x - x) * (event->start_x - x) + + (event->start_y - y) * (event->start_y - y)) < ctx_pow2(events->tap_hysteresis) + ) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_TAP, x, y); + } + } + + if (!event->stop_propagate && grab->item->types & CTX_DRAG_RELEASE) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_RELEASE, x, y); + } + } + + device_remove_grab (ctx, grab); + } + + if (hitlist) + { + if (!event->stop_propagate) + _ctx_emit_cb (ctx, hitlist, event, CTX_RELEASE, x, y); + ctx_list_free (&hitlist); + } + ctx_list_free (&grablist); + return 0; +} + +/* for multi-touch, we use a list of active grabs - thus a grab corresponds to + * a device id. even during drag-grabs events propagate; to stop that stop + * propagation. + */ +int ctx_pointer_motion (Ctx *ctx, float x, float y, int device_no, uint32_t time) +{ + CtxList *hitlist = NULL; + CtxList *grablist = NULL, *g; + CtxGrab *grab; + + if (device_no < 0) device_no = 0; + if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1; + CtxEvent *event = &ctx->events.drag_event[device_no]; + + if (time == 0) + time = ctx_ms (ctx); + + event->ctx = ctx; + event->x = x; + event->y = y; + event->time = time; + event->device_no = device_no; + event->stop_propagate = 0; + + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + + if (device_no <= 3) + { + ctx->events.pointer_x[0] = x; + ctx->events.pointer_y[0] = y; + } + + grablist = _ctx_device_get_grabs (ctx, device_no); + _ctx_update_item (ctx, device_no, x, y, CTX_MOTION, &hitlist); + + { + CtxItem *cursor_item = _ctx_detect (ctx, x, y, CTX_SET_CURSOR); + if (cursor_item) + { + ctx_set_cursor (ctx, cursor_item->cursor); + } + else + { + ctx_set_cursor (ctx, CTX_CURSOR_ARROW); + } + CtxItem *hovered_item = _ctx_detect (ctx, x, y, CTX_ANY); + static CtxItem *prev_hovered_item = NULL; + if (prev_hovered_item != hovered_item) + { + ctx_set_dirty (ctx, 1); + } + prev_hovered_item = hovered_item; + } + + event->delta_x = x - event->prev_x; + event->delta_y = y - event->prev_y; + event->prev_x = x; + event->prev_y = y; + + CtxList *remove_grabs = NULL; + + for (g = grablist; g; g = g->next) + { + grab = g->data; + + if ((grab->type & CTX_TAP) || + (grab->type & CTX_TAP_AND_HOLD)) + { + if ( + ( + (event->start_x - x) * (event->start_x - x) + + (event->start_y - y) * (event->start_y - y)) > + ctx_pow2(ctx->events.tap_hysteresis) + ) + { + //fprintf (stderr, "-"); + ctx_list_prepend (&remove_grabs, grab); + } + else + { + //fprintf (stderr, ":"); + } + } + + if (grab->type & CTX_DRAG_MOTION) + { + _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_MOTION, x, y); + if (event->stop_propagate) + break; + } + } + if (remove_grabs) + { + for (g = remove_grabs; g; g = g->next) + device_remove_grab (ctx, g->data); + ctx_list_free (&remove_grabs); + } + if (hitlist) + { + if (!event->stop_propagate) + _ctx_emit_cb (ctx, hitlist, event, CTX_MOTION, x, y); + ctx_list_free (&hitlist); + } + ctx_list_free (&grablist); + return 0; +} + +void ctx_incoming_message (Ctx *ctx, const char *message, long time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_MESSAGE); + CtxEvent event = {0, }; + + if (!time) + time = ctx_ms (ctx); + + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_MESSAGE; + event.time = time; + event.string = message; + + fprintf (stderr, "{%s|\n", message); + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_MESSAGE)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + return;// event.stop_propagate; + } + } + } +} + +int ctx_scrolled (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time) +{ + CtxList *hitlist = NULL; + CtxList *l; + + int device_no = 0; + ctx->events.pointer_x[device_no] = x; + ctx->events.pointer_y[device_no] = y; + + CtxEvent *event = &ctx->events.drag_event[device_no]; /* XXX: might + conflict with other code + create a sibling member + of drag_event?*/ + if (time == 0) + time = ctx_ms (ctx); + + event->x = event->start_x = event->prev_x = x; + event->y = event->start_y = event->prev_y = y; + event->delta_x = event->delta_y = 0; + event->device_no = device_no; + event->time = time; + event->stop_propagate = 0; + event->scroll_direction = scroll_direction; + + _ctx_update_item (ctx, device_no, x, y, CTX_SCROLL, &hitlist); + + for (l = hitlist; l; l = l?l->next:NULL) + { + CtxItem *item = l->data; + + _ctx_emit_cb_item (ctx, item, event, CTX_SCROLL, x, y); + + if (event->stop_propagate) + l = NULL; + } + + //mrg_queue_draw (mrg, NULL); /* in case of style change, and more */ + ctx_list_free (&hitlist); + return 0; +} + +static int ctx_str_has_prefix (const char *string, const char *prefix) +{ + for (int i = 0; prefix[i]; i++) + { + if (!string[i]) return 0; + if (string[i] != prefix[i]) return 0; + } + return 0; +} + +int ctx_key_press (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + char event_type[128]=""; + float x, y; int b; + sscanf (string, "%s %f %f %i", event_type, &x, &y, &b); + if (!strcmp (event_type, "mouse-motion") || + !strcmp (event_type, "mouse-drag")) + return ctx_pointer_motion (ctx, x, y, b, 0); + else if (!strcmp (event_type, "mouse-press")) + return ctx_pointer_press (ctx, x, y, b, 0); + else if (!strcmp (event_type, "mouse-release")) + return ctx_pointer_release (ctx, x, y, b, 0); + //else if (!strcmp (event_type, "keydown")) + // return ctx_key_down (ctx, keyval, string + 8, time); + //else if (!strcmp (event_type, "keyup")) + // return ctx_key_up (ctx, keyval, string + 6, time); + + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_PRESS; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_PRESS)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; +} + +int ctx_key_down (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_DOWN); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_DOWN; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_DOWN)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; +} + +int ctx_key_up (Ctx *ctx, unsigned int keyval, + const char *string, uint32_t time) +{ + CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_UP); + CtxEvent event = {0,}; + + if (time == 0) + time = ctx_ms (ctx); + if (item) + { + int i; + event.ctx = ctx; + event.type = CTX_KEY_UP; + event.unicode = keyval; + event.string = strdup(string); + event.stop_propagate = 0; + event.time = time; + + for (i = 0; i < item->cb_count; i++) + { + if (item->cb[i].types & (CTX_KEY_UP)) + { + event.state = ctx->events.modifier_state; + item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2); + if (event.stop_propagate) + { + free ((void*)event.string); + return event.stop_propagate; + } + } + } + free ((void*)event.string); + } + return 0; + + return 0; +} + +void ctx_freeze (Ctx *ctx) +{ + ctx->events.frozen ++; +} + +void ctx_thaw (Ctx *ctx) +{ + ctx->events.frozen --; +} +int ctx_events_frozen (Ctx *ctx) +{ + return ctx && ctx->events.frozen; +} +void ctx_events_clear_items (Ctx *ctx) +{ + ctx_list_free (&ctx->events.items); +} +int ctx_events_width (Ctx *ctx) +{ + return ctx->events.width; +} +int ctx_events_height (Ctx *ctx) +{ + return ctx->events.height; +} + +float ctx_pointer_x (Ctx *ctx) +{ + return ctx->events.pointer_x[0]; +} + +float ctx_pointer_y (Ctx *ctx) +{ + return ctx->events.pointer_y[0]; +} + +int ctx_pointer_is_down (Ctx *ctx, int no) +{ + if (no < 0 || no > CTX_MAX_DEVICES) return 0; + return ctx->events.pointer_down[no]; +} + +void _ctx_debug_overlays (Ctx *ctx) +{ + CtxList *a; + ctx_save (ctx); + + ctx_line_width (ctx, 2); + ctx_rgba (ctx, 0,0,0.8,0.5); + for (a = ctx->events.items; a; a = a->next) + { + float current_x = ctx_pointer_x (ctx); + float current_y = ctx_pointer_y (ctx); + CtxItem *item = a->data; + CtxMatrix matrix = item->inv_matrix; + + _ctx_matrix_apply_transform (&matrix, ¤t_x, ¤t_y); + + if (current_x >= item->x0 && current_x < item->x1 && + current_y >= item->y0 && current_y < item->y1) + { + ctx_matrix_invert (&matrix); + ctx_set_matrix (ctx, &matrix); + _mrg_restore_path (ctx, item->path); + ctx_stroke (ctx); + } + } + ctx_restore (ctx); +} + +void ctx_set_render_threads (Ctx *ctx, int n_threads) +{ + // XXX +} +int ctx_get_render_threads (Ctx *ctx) +{ + return _ctx_max_threads; +} +void ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache) +{ + _ctx_enable_hash_cache = enable_hash_cache; +} +int ctx_get_hash_cache (Ctx *ctx) +{ + return _ctx_enable_hash_cache; +} + +int ctx_is_dirty (Ctx *ctx) +{ + return ctx->dirty; +} +void ctx_set_dirty (Ctx *ctx, int dirty) +{ + ctx->dirty = dirty; +} + +/* + * centralized global API for managing file descriptors that + * wake us up, this to remove sleeping and polling + */ + +#define CTX_MAX_LISTEN_FDS 128 // becomes max clients.. + +static int _ctx_listen_fd[CTX_MAX_LISTEN_FDS]; +static int _ctx_listen_fds = 0; +static int _ctx_listen_max_fd = 0; + +void _ctx_add_listen_fd (int fd) +{ + _ctx_listen_fd[_ctx_listen_fds++]=fd; + if (fd > _ctx_listen_max_fd) + _ctx_listen_max_fd = fd; +} + +void _ctx_remove_listen_fd (int fd) +{ + for (int i = 0; i < _ctx_listen_fds; i++) + { + if (_ctx_listen_fd[i] == fd) + { + _ctx_listen_fd[i] = _ctx_listen_fd[_ctx_listen_fds-1]; + _ctx_listen_fds--; + return; + } + } +} + +int ctx_input_pending (Ctx *ctx, int timeout) +{ + struct timeval tv; + fd_set fdset; + FD_ZERO (&fdset); + for (int i = 0; i < _ctx_listen_fds; i++) + { + FD_SET (_ctx_listen_fd[i], &fdset); + } + int input_fds[5]; + int n_fds; + ctx_get_event_fds (ctx, input_fds, &n_fds); + for (int i = 0; i < n_fds; i++) + { + FD_SET (input_fds[i], &fdset); + } + + tv.tv_sec = 0; + tv.tv_usec = timeout; + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + int retval = select (_ctx_listen_max_fd + 1, &fdset, NULL, NULL, &tv); + if (retval == -1) + { + perror ("select"); + return 0; + } + return retval; +} + +void ctx_sdl_set_title (void *self, const char *new_title); +void ctx_set_title (Ctx *ctx, const char *title) +{ +#if CTX_SDL + // XXX also check we're first/only client? + if (ctx_renderer_is_sdl (ctx)) + ctx_sdl_set_title (ctx_get_renderer (ctx), title); +#endif +} + +#endif +/* the parser comes in the end, nothing in ctx knows about the parser */ + +#if CTX_PARSER + +/* ctx parser, */ + +#define CTX_ID_MAXLEN 64 // in use should not be more than 40! + // to offer headroom for multiplexing + + +#define CTX_REPORT_COL_ROW 0 + +struct + _CtxParser +{ + Ctx *ctx; + int t_args; // total number of arguments seen for current command + int state; +#if CTX_PARSER_FIXED_TEMP + uint8_t holding[CTX_PARSER_MAXLEN]; /* */ +#else + uint8_t *holding; +#endif + int hold_len; + int pos; + +#if CTX_REPORT_COL_ROW + int line; /* for error reporting */ + int col; /* for error reporting */ +#endif + float numbers[CTX_PARSER_MAX_ARGS+1]; + int n_numbers; + int decimal; + CtxCode command; + int expected_args; /* low digits are literal higher values + carry special meaning */ + int n_args; + int texture_done; + uint8_t texture_id[CTX_ID_MAXLEN]; // used in defineTexture only + uint64_t set_key_hash; + float pcx; + float pcy; + int color_components; + int color_stroke; // 0 is fill source 1 is stroke source + CtxColorModel color_model; // 1 gray 3 rgb 4 cmyk + float left_margin; // set by last user provided move_to + int width; // <- maybe should be float + int height; + float cell_width; + float cell_height; + int cursor_x; // <- leaking in from terminal + int cursor_y; + + int translate_origin; + + CtxColorSpace color_space_slot; + + void (*exit) (void *exit_data); + void *exit_data; + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len); + int (*get_prop)(void *prop_data, const char *key, char **data, int *len); + void *prop_data; + int prev_byte; +}; + +void +ctx_parser_set_size (CtxParser *parser, + int width, + int height, + float cell_width, + float cell_height) +{ + if (cell_width > 0) + parser->cell_width = cell_width; + if (cell_height > 0) + parser->cell_height = cell_height; + if (width > 0) + parser->width = width; + if (height > 0) + parser->height = height; +} + +static CtxParser * +ctx_parser_init (CtxParser *parser, + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data + ) +{ + ctx_memset (parser, 0, sizeof (CtxParser) ); +#if CTX_REPORT_COL_ROW + parser->line = 1; +#endif + parser->ctx = ctx; + parser->cell_width = cell_width; + parser->cell_height = cell_height; + parser->cursor_x = cursor_x; + parser->cursor_y = cursor_y; + parser->width = width; + parser->height = height; + parser->exit = exit; + parser->exit_data = exit_data; + parser->color_model = CTX_RGBA; + parser->color_stroke = 0; + parser->color_components = 4; + parser->command = CTX_MOVE_TO; + parser->set_prop = set_prop; + parser->get_prop = get_prop; + parser->prop_data = prop_data; + return parser; +} + +CtxParser *ctx_parser_new ( + Ctx *ctx, + int width, + int height, + float cell_width, + float cell_height, + int cursor_x, + int cursor_y, + int (*set_prop)(void *prop_data, uint64_t key, const char *data, int len), + int (*get_prop)(void *prop_Data, const char *key, char **data, int *len), + void *prop_data, + void (*exit) (void *exit_data), + void *exit_data) +{ + return ctx_parser_init ( (CtxParser *) ctx_calloc (sizeof (CtxParser), 1), + ctx, + width, height, + cell_width, cell_height, + cursor_x, cursor_y, set_prop, get_prop, prop_data, + exit, exit_data); +} + +void ctx_parser_free (CtxParser *parser) +{ +#if !CTX_PARSER_FIXED_TEMP + if (parser->holding) + free (parser->holding); +#endif + free (parser); +} + +#define CTX_ARG_COLLECT_NUMBERS 50 +#define CTX_ARG_STRING_OR_NUMBER 100 +#define CTX_ARG_NUMBER_OF_COMPONENTS 200 +#define CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1 201 + +static int ctx_arguments_for_code (CtxCode code) +{ + switch (code) + { + case CTX_SAVE: + case CTX_START_GROUP: + case CTX_END_GROUP: + case CTX_IDENTITY: + case CTX_CLOSE_PATH: + case CTX_BEGIN_PATH: + case CTX_RESET: + case CTX_FLUSH: + case CTX_RESTORE: + case CTX_STROKE: + case CTX_FILL: + case CTX_NEW_PAGE: + case CTX_CLIP: + case CTX_EXIT: + return 0; + case CTX_GLOBAL_ALPHA: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_FONT_SIZE: + case CTX_LINE_JOIN: + case CTX_LINE_CAP: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_IMAGE_SMOOTHING: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_FILL_RULE: + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_MITER_LIMIT: + case CTX_REL_VER_LINE_TO: + case CTX_REL_HOR_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_VER_LINE_TO: + case CTX_FONT: + case CTX_ROTATE: + case CTX_GLYPH: + return 1; + case CTX_TRANSLATE: + case CTX_REL_SMOOTHQ_TO: + case CTX_LINE_TO: + case CTX_MOVE_TO: + case CTX_SCALE: + case CTX_REL_LINE_TO: + case CTX_REL_MOVE_TO: + case CTX_SMOOTHQ_TO: + return 2; + case CTX_LINEAR_GRADIENT: + case CTX_REL_QUAD_TO: + case CTX_QUAD_TO: + case CTX_RECTANGLE: + case CTX_FILL_RECT: + case CTX_STROKE_RECT: + case CTX_REL_SMOOTH_TO: + case CTX_VIEW_BOX: + case CTX_SMOOTH_TO: + return 4; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + case CTX_ROUND_RECTANGLE: + return 5; + case CTX_ARC: + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_APPLY_TRANSFORM: + case CTX_RADIAL_GRADIENT: + return 6; + case CTX_STROKE_TEXT: + case CTX_TEXT: + case CTX_COLOR_SPACE: + case CTX_DEFINE_GLYPH: + case CTX_KERNING_PAIR: + case CTX_TEXTURE: + case CTX_DEFINE_TEXTURE: + return CTX_ARG_STRING_OR_NUMBER; + case CTX_LINE_DASH: /* append to current dashes for each argument encountered */ + return CTX_ARG_COLLECT_NUMBERS; + //case CTX_SET_KEY: + case CTX_COLOR: + case CTX_SHADOW_COLOR: + return CTX_ARG_NUMBER_OF_COMPONENTS; + case CTX_GRADIENT_STOP: + return CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1; + + default: +#if 1 + case CTX_SET_RGBA_U8: + case CTX_NOP: + case CTX_NEW_EDGE: + case CTX_EDGE: + case CTX_EDGE_FLIPPED: + case CTX_CONT: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_SET_PIXEL: + case CTX_REL_LINE_TO_X4: + case CTX_REL_LINE_TO_REL_CURVE_TO: + case CTX_REL_CURVE_TO_REL_LINE_TO: + case CTX_REL_CURVE_TO_REL_MOVE_TO: + case CTX_REL_LINE_TO_X2: + case CTX_MOVE_TO_REL_LINE_TO: + case CTX_REL_LINE_TO_REL_MOVE_TO: + case CTX_FILL_MOVE_TO: + case CTX_REL_QUAD_TO_REL_QUAD_TO: + case CTX_REL_QUAD_TO_S16: + case CTX_STROKE_SOURCE: +#endif + return 0; + } +} + +static int ctx_parser_set_command (CtxParser *parser, CtxCode code) +{ + if (code < 150 && code >= 32) + { + parser->expected_args = ctx_arguments_for_code (code); + parser->n_args = 0; + parser->texture_done = 0; + if (parser->expected_args >= CTX_ARG_NUMBER_OF_COMPONENTS) + { + parser->expected_args = (parser->expected_args % 100) + parser->color_components; + } + } + return code; +} + +static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke); + +static int ctx_parser_resolve_command (CtxParser *parser, const uint8_t *str) +{ + uint64_t ret = str[0]; /* if it is single char it already is the CtxCode */ + + /* this is handled outside the hashing to make it possible to be case insensitive + * with the rest. + */ + if (str[0] == CTX_SET_KEY && str[1] && str[2] == 0) + { + switch (str[1]) + { + case 'm': return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE); + case 'B': return ctx_parser_set_command (parser, CTX_BLEND_MODE); + case 'l': return ctx_parser_set_command (parser, CTX_MITER_LIMIT); + case 't': return ctx_parser_set_command (parser, CTX_TEXT_ALIGN); + case 'b': return ctx_parser_set_command (parser, CTX_TEXT_BASELINE); + case 'd': return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION); + case 'j': return ctx_parser_set_command (parser, CTX_LINE_JOIN); + case 'c': return ctx_parser_set_command (parser, CTX_LINE_CAP); + case 'w': return ctx_parser_set_command (parser, CTX_LINE_WIDTH); + case 'D': return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET); + case 'S': return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING); + case 'C': return ctx_parser_set_command (parser, CTX_SHADOW_COLOR); + case 's': return ctx_parser_set_command (parser, CTX_SHADOW_BLUR); + case 'x': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X); + case 'y': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y); + case 'a': return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA); + case 'f': return ctx_parser_set_command (parser, CTX_FONT_SIZE); + case 'r': return ctx_parser_set_command (parser, CTX_FILL_RULE); + } + } + + if (str[0] && str[1]) + { + uint64_t str_hash; + /* trim ctx_ and CTX_ prefix */ + if ( (str[0] == 'c' && str[1] == 't' && str[2] == 'x' && str[3] == '_') || + (str[0] == 'C' && str[1] == 'T' && str[2] == 'X' && str[3] == '_') ) + { + str += 4; + } + if ( (str[0] == 's' && str[1] == 'e' && str[2] == 't' && str[3] == '_') ) + { str += 4; } + str_hash = ctx_strhash ( (char *) str, 0); + switch (str_hash) + { + /* first a list of mappings to one_char hashes, handled in a + * separate fast path switch without hashing + */ + case CTX_arcTo: ret = CTX_ARC_TO; break; + case CTX_arc: ret = CTX_ARC; break; + case CTX_curveTo: ret = CTX_CURVE_TO; break; + case CTX_restore: ret = CTX_RESTORE; break; + case CTX_stroke: ret = CTX_STROKE; break; + case CTX_fill: ret = CTX_FILL; break; + case CTX_flush: ret = CTX_FLUSH; break; + case CTX_horLineTo: ret = CTX_HOR_LINE_TO; break; + case CTX_rotate: ret = CTX_ROTATE; break; + case CTX_color: ret = CTX_COLOR; break; + case CTX_lineTo: ret = CTX_LINE_TO; break; + case CTX_moveTo: ret = CTX_MOVE_TO; break; + case CTX_scale: ret = CTX_SCALE; break; + case CTX_newPage: ret = CTX_NEW_PAGE; break; + case CTX_quadTo: ret = CTX_QUAD_TO; break; + case CTX_viewBox: ret = CTX_VIEW_BOX; break; + case CTX_smooth_to: ret = CTX_SMOOTH_TO; break; + case CTX_smooth_quad_to: ret = CTX_SMOOTHQ_TO; break; + case CTX_clear: ret = CTX_COMPOSITE_CLEAR; break; + case CTX_copy: ret = CTX_COMPOSITE_COPY; break; + case CTX_destinationOver: ret = CTX_COMPOSITE_DESTINATION_OVER; break; + case CTX_destinationIn: ret = CTX_COMPOSITE_DESTINATION_IN; break; + case CTX_destinationOut: ret = CTX_COMPOSITE_DESTINATION_OUT; break; + case CTX_sourceOver: ret = CTX_COMPOSITE_SOURCE_OVER; break; + case CTX_sourceAtop: ret = CTX_COMPOSITE_SOURCE_ATOP; break; + case CTX_destinationAtop: ret = CTX_COMPOSITE_DESTINATION_ATOP; break; + case CTX_sourceOut: ret = CTX_COMPOSITE_SOURCE_OUT; break; + case CTX_sourceIn: ret = CTX_COMPOSITE_SOURCE_IN; break; + case CTX_xor: ret = CTX_COMPOSITE_XOR; break; + case CTX_darken: ret = CTX_BLEND_DARKEN; break; + case CTX_lighten: ret = CTX_BLEND_LIGHTEN; break; + //case CTX_color: ret = CTX_BLEND_COLOR; break; + // + // XXX check that he special casing for color works + // it is the first collision and it is due to our own + // color, not w3c for now unique use of it + // + case CTX_hue: ret = CTX_BLEND_HUE; break; + case CTX_multiply: ret = CTX_BLEND_MULTIPLY; break; + case CTX_normal: ret = CTX_BLEND_NORMAL;break; + case CTX_screen: ret = CTX_BLEND_SCREEN;break; + case CTX_difference: ret = CTX_BLEND_DIFFERENCE; break; + case CTX_reset: ret = CTX_RESET; break; + case CTX_verLineTo: ret = CTX_VER_LINE_TO; break; + case CTX_exit: + case CTX_done: ret = CTX_EXIT; break; + case CTX_closePath: ret = CTX_CLOSE_PATH; break; + case CTX_beginPath: + case CTX_newPath: ret = CTX_BEGIN_PATH; break; + case CTX_relArcTo: ret = CTX_REL_ARC_TO; break; + case CTX_clip: ret = CTX_CLIP; break; + case CTX_relCurveTo: ret = CTX_REL_CURVE_TO; break; + case CTX_startGroup: ret = CTX_START_GROUP; break; + case CTX_endGroup: ret = CTX_END_GROUP; break; + case CTX_save: ret = CTX_SAVE; break; + case CTX_translate: ret = CTX_TRANSLATE; break; + case CTX_linearGradient: ret = CTX_LINEAR_GRADIENT; break; + case CTX_relHorLineTo: ret = CTX_REL_HOR_LINE_TO; break; + case CTX_relLineTo: ret = CTX_REL_LINE_TO; break; + case CTX_relMoveTo: ret = CTX_REL_MOVE_TO; break; + case CTX_font: ret = CTX_FONT; break; + case CTX_radialGradient:ret = CTX_RADIAL_GRADIENT; break; + case CTX_gradientAddStop: + case CTX_addStop: ret = CTX_GRADIENT_STOP; break; + case CTX_relQuadTo: ret = CTX_REL_QUAD_TO; break; + case CTX_rectangle: + case CTX_rect: ret = CTX_RECTANGLE; break; + case CTX_roundRectangle: ret = CTX_ROUND_RECTANGLE; break; + case CTX_relSmoothTo: ret = CTX_REL_SMOOTH_TO; break; + case CTX_relSmoothqTo: ret = CTX_REL_SMOOTHQ_TO; break; + case CTX_strokeText: ret = CTX_STROKE_TEXT; break; + case CTX_strokeRect: ret = CTX_STROKE_RECT; break; + case CTX_fillRect: ret = CTX_FILL_RECT; break; + case CTX_relVerLineTo: ret = CTX_REL_VER_LINE_TO; break; + case CTX_text: ret = CTX_TEXT; break; + case CTX_identity: ret = CTX_IDENTITY; break; + case CTX_transform: ret = CTX_APPLY_TRANSFORM; break; + case CTX_texture: ret = CTX_TEXTURE; break; + case CTX_defineTexture: ret = CTX_DEFINE_TEXTURE; break; +#if 0 + case CTX_rgbSpace: + return ctx_parser_set_command (parser, CTX_SET_RGB_SPACE); + case CTX_cmykSpace: + return ctx_parser_set_command (parser, CTX_SET_CMYK_SPACE); + case CTX_drgbSpace: + return ctx_parser_set_command (parser, CTX_SET_DRGB_SPACE); +#endif + case CTX_defineGlyph: + return ctx_parser_set_command (parser, CTX_DEFINE_GLYPH); + case CTX_kerningPair: + return ctx_parser_set_command (parser, CTX_KERNING_PAIR); + + case CTX_colorSpace: + return ctx_parser_set_command (parser, CTX_COLOR_SPACE); + case CTX_fillRule: + return ctx_parser_set_command (parser, CTX_FILL_RULE); + case CTX_fontSize: + case CTX_setFontSize: + return ctx_parser_set_command (parser, CTX_FONT_SIZE); + case CTX_compositingMode: + return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE); + + case CTX_blend: + case CTX_blending: + case CTX_blendMode: + return ctx_parser_set_command (parser, CTX_BLEND_MODE); + + case CTX_miterLimit: + return ctx_parser_set_command (parser, CTX_MITER_LIMIT); + case CTX_textAlign: + return ctx_parser_set_command (parser, CTX_TEXT_ALIGN); + case CTX_textBaseline: + return ctx_parser_set_command (parser, CTX_TEXT_BASELINE); + case CTX_textDirection: + return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION); + case CTX_join: + case CTX_lineJoin: + case CTX_setLineJoin: + return ctx_parser_set_command (parser, CTX_LINE_JOIN); + case CTX_glyph: + return ctx_parser_set_command (parser, CTX_GLYPH); + case CTX_cap: + case CTX_lineCap: + case CTX_setLineCap: + return ctx_parser_set_command (parser, CTX_LINE_CAP); + case CTX_lineDash: + return ctx_parser_set_command (parser, CTX_LINE_DASH); + case CTX_lineWidth: + case CTX_setLineWidth: + return ctx_parser_set_command (parser, CTX_LINE_WIDTH); + case CTX_lineDashOffset: + return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET); + case CTX_imageSmoothing: + return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING); + case CTX_shadowColor: + return ctx_parser_set_command (parser, CTX_SHADOW_COLOR); + case CTX_shadowBlur: + return ctx_parser_set_command (parser, CTX_SHADOW_BLUR); + case CTX_shadowOffsetX: + return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X); + case CTX_shadowOffsetY: + return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y); + case CTX_globalAlpha: + return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA); + + case CTX_strokeSource: + return ctx_parser_set_command (parser, CTX_STROKE_SOURCE); + + /* strings are handled directly here, + * instead of in the one-char handler, using return instead of break + */ + case CTX_gray: + ctx_parser_set_color_model (parser, CTX_GRAY, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_graya: + ctx_parser_set_color_model (parser, CTX_GRAYA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgb: + ctx_parser_set_color_model (parser, CTX_RGB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgb: + ctx_parser_set_color_model (parser, CTX_DRGB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgba: + ctx_parser_set_color_model (parser, CTX_RGBA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgba: + ctx_parser_set_color_model (parser, CTX_DRGBA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmyk: + ctx_parser_set_color_model (parser, CTX_CMYK, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmyka: + ctx_parser_set_color_model (parser, CTX_CMYKA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lab: + ctx_parser_set_color_model (parser, CTX_LAB, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_laba: + ctx_parser_set_color_model (parser, CTX_LABA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lch: + ctx_parser_set_color_model (parser, CTX_LCH, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lcha: + ctx_parser_set_color_model (parser, CTX_LCHA, 0); + return ctx_parser_set_command (parser, CTX_COLOR); + + /* and a full repeat of the above, with S for Stroke suffix */ + case CTX_grayS: + ctx_parser_set_color_model (parser, CTX_GRAY, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_grayaS: + ctx_parser_set_color_model (parser, CTX_GRAYA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgbS: + ctx_parser_set_color_model (parser, CTX_RGB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgbS: + ctx_parser_set_color_model (parser, CTX_DRGB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_rgbaS: + ctx_parser_set_color_model (parser, CTX_RGBA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_drgbaS: + ctx_parser_set_color_model (parser, CTX_DRGBA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmykS: + ctx_parser_set_color_model (parser, CTX_CMYK, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_cmykaS: + ctx_parser_set_color_model (parser, CTX_CMYKA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_labS: + ctx_parser_set_color_model (parser, CTX_LAB, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_labaS: + ctx_parser_set_color_model (parser, CTX_LABA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lchS: + ctx_parser_set_color_model (parser, CTX_LCH, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + case CTX_lchaS: + ctx_parser_set_color_model (parser, CTX_LCHA, 1); + return ctx_parser_set_command (parser, CTX_COLOR); + + /* words that correspond to low integer constants + */ + case CTX_nonzero: return CTX_FILL_RULE_WINDING; + case CTX_non_zero: return CTX_FILL_RULE_WINDING; + case CTX_winding: return CTX_FILL_RULE_WINDING; + case CTX_evenOdd: + case CTX_even_odd: return CTX_FILL_RULE_EVEN_ODD; + case CTX_bevel: return CTX_JOIN_BEVEL; + case CTX_round: return CTX_JOIN_ROUND; + case CTX_miter: return CTX_JOIN_MITER; + case CTX_none: return CTX_CAP_NONE; + case CTX_square: return CTX_CAP_SQUARE; + case CTX_start: return CTX_TEXT_ALIGN_START; + case CTX_end: return CTX_TEXT_ALIGN_END; + case CTX_left: return CTX_TEXT_ALIGN_LEFT; + case CTX_right: return CTX_TEXT_ALIGN_RIGHT; + case CTX_center: return CTX_TEXT_ALIGN_CENTER; + case CTX_top: return CTX_TEXT_BASELINE_TOP; + case CTX_bottom : return CTX_TEXT_BASELINE_BOTTOM; + case CTX_middle: return CTX_TEXT_BASELINE_MIDDLE; + case CTX_alphabetic: return CTX_TEXT_BASELINE_ALPHABETIC; + case CTX_hanging: return CTX_TEXT_BASELINE_HANGING; + case CTX_ideographic: return CTX_TEXT_BASELINE_IDEOGRAPHIC; + + case CTX_userRGB: return CTX_COLOR_SPACE_USER_RGB; + case CTX_deviceRGB: return CTX_COLOR_SPACE_DEVICE_RGB; + case CTX_userCMYK: return CTX_COLOR_SPACE_USER_CMYK; + case CTX_deviceCMYK: return CTX_COLOR_SPACE_DEVICE_CMYK; +#undef STR +#undef LOWER + default: + ret = str_hash; + } + } + if (ret == CTX_CLOSE_PATH2) + { + ret = CTX_CLOSE_PATH; + } + + return ctx_parser_set_command (parser, (CtxCode) ret); +} + +enum +{ + CTX_PARSER_NEUTRAL = 0, + CTX_PARSER_NUMBER, + CTX_PARSER_NEGATIVE_NUMBER, + CTX_PARSER_WORD, + CTX_PARSER_COMMENT, + CTX_PARSER_STRING_APOS, + CTX_PARSER_STRING_QUOT, + CTX_PARSER_STRING_APOS_ESCAPED, + CTX_PARSER_STRING_QUOT_ESCAPED, + CTX_PARSER_STRING_A85, + CTX_PARSER_STRING_YENC, +} CTX_STATE; + +static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke) +{ + parser->color_model = color_model; + parser->color_stroke = stroke; + parser->color_components = ctx_color_model_get_components (color_model); +} + +static void ctx_parser_get_color_rgba (CtxParser *parser, int offset, float *red, float *green, float *blue, float *alpha) +{ + /* XXX - this function is to be deprecated */ + *alpha = 1.0; + switch (parser->color_model) + { + case CTX_GRAYA: + *alpha = parser->numbers[offset + 1]; + /* FALLTHROUGH */ + case CTX_GRAY: + *red = *green = *blue = parser->numbers[offset + 0]; + break; + default: + case CTX_LABA: // NYI - needs RGB profile + case CTX_LCHA: // NYI - needs RGB profile + case CTX_RGBA: + *alpha = parser->numbers[offset + 3]; + /* FALLTHROUGH */ + case CTX_LAB: // NYI + case CTX_LCH: // NYI + case CTX_RGB: + *red = parser->numbers[offset + 0]; + *green = parser->numbers[offset + 1]; + *blue = parser->numbers[offset + 2]; + break; + case CTX_CMYKA: + *alpha = parser->numbers[offset + 4]; + /* FALLTHROUGH */ + case CTX_CMYK: + /* should use profile instead */ + *red = (1.0-parser->numbers[offset + 0]) * + (1.0 - parser->numbers[offset + 3]); + *green = (1.0-parser->numbers[offset + 1]) * + (1.0 - parser->numbers[offset + 3]); + *blue = (1.0-parser->numbers[offset + 2]) * + (1.0 - parser->numbers[offset + 3]); + break; + } +} + +static void ctx_parser_dispatch_command (CtxParser *parser) +{ + CtxCode cmd = parser->command; + Ctx *ctx = parser->ctx; + + if (parser->expected_args != CTX_ARG_STRING_OR_NUMBER && + parser->expected_args != CTX_ARG_COLLECT_NUMBERS && + parser->expected_args != parser->n_numbers) + { +#if CTX_REPORT_COL_ROW + fprintf (stderr, "ctx:%i:%i %c got %i instead of %i args\n", + parser->line, parser->col, + cmd, parser->n_numbers, parser->expected_args); +#endif + //return; + } + +#define arg(a) (parser->numbers[a]) + parser->command = CTX_NOP; + //parser->n_args = 0; + switch (cmd) + { + default: + break; // to silence warnings about missing ones + case CTX_PRESERVE: + ctx_preserve (ctx); + break; + case CTX_FILL: + ctx_fill (ctx); + break; + case CTX_SAVE: + ctx_save (ctx); + break; + case CTX_START_GROUP: + ctx_start_group (ctx); + break; + case CTX_END_GROUP: + ctx_end_group (ctx); + break; + case CTX_STROKE: + ctx_stroke (ctx); + break; + case CTX_STROKE_SOURCE: + ctx_stroke_source (ctx); + break; + case CTX_RESTORE: + ctx_restore (ctx); + break; +#if CTX_ENABLE_CM + case CTX_COLOR_SPACE: + if (parser->n_numbers == 1) + { + parser->color_space_slot = (CtxColorSpace) arg(0); + parser->command = CTX_COLOR_SPACE; // did this work without? + } + else + { + ctx_colorspace (ctx, (CtxColorSpace)parser->color_space_slot, + parser->holding, parser->pos); + } + break; +#endif + case CTX_KERNING_PAIR: + switch (parser->n_args) + { + case 0: + parser->numbers[0] = ctx_utf8_to_unichar ((char*)parser->holding); + break; + case 1: + parser->numbers[1] = ctx_utf8_to_unichar ((char*)parser->holding); + break; + case 2: + parser->numbers[2] = strtod ((char*)parser->holding, NULL); + { + CtxEntry e = {CTX_KERNING_PAIR, }; + e.data.u16[0] = parser->numbers[0]; + e.data.u16[1] = parser->numbers[1]; + e.data.s32[1] = parser->numbers[2] * 256; + ctx_process (ctx, &e); + } + break; + } + parser->command = CTX_KERNING_PAIR; + parser->n_args ++; // make this more generic? + break; + case CTX_TEXTURE: + if (parser->texture_done) + { + } + else + if (parser->n_numbers == 2) + { + const char *eid = (char*)parser->holding; + float x0 = arg(0); + float x1 = arg(1); + ctx_texture (ctx, eid, x0, x1); + parser->texture_done = 1; + } + parser->command = CTX_TEXTURE; + //parser->n_args++; + break; + case CTX_DEFINE_TEXTURE: + if (parser->texture_done) + { + if (parser->texture_done++ == 1) + { + const char *eid = (char*)parser->texture_id; + int width = arg(0); + int height = arg(1); + CtxPixelFormat format = (CtxPixelFormat)arg(2); + int stride = ctx_pixel_format_get_stride (format, width); + int data_len = stride * height; + if (format == CTX_FORMAT_YUV420) + data_len = height * width + 2*(height/2) * (width/2); + + + if (parser->pos != data_len) + { + fprintf (stderr, "unexpected datasize for define texture %s %ix%i\n size:%i != expected:%i - start of data: %i %i %i %i\n", eid, width, height, + parser->pos, + stride * height, + parser->holding[0], + parser->holding[1], + parser->holding[2], + parser->holding[3] + ); + } + else + ctx_define_texture (ctx, eid, width, height, stride, format, parser->holding, NULL); + } + } + else + { + switch (parser->n_numbers) + { + case 0: + strncpy ((char*)parser->texture_id, (char*)parser->holding, sizeof(parser->texture_id)); + parser->texture_id[sizeof(parser->texture_id)-1]=0; + break; + case 1: + case 2: + break; + case 3: + parser->texture_done = 1; + break; + default: + fprintf (stderr, "!!%i\n", parser->n_numbers); + break; + } + } + parser->command = CTX_DEFINE_TEXTURE; + break; + + + case CTX_DEFINE_GLYPH: + /* XXX : reuse n_args logic - to enforce order */ + if (parser->n_numbers == 1) + { + CtxEntry e = {CTX_DEFINE_GLYPH, }; + e.data.u32[0] = parser->color_space_slot; + e.data.u32[1] = arg(0) * 256; + ctx_process (ctx, &e); + } + else + { + int unichar = ctx_utf8_to_unichar ((char*)parser->holding); + parser->color_space_slot = (CtxColorSpace)unichar; + } + parser->command = CTX_DEFINE_GLYPH; + break; + + case CTX_COLOR: + { + switch (parser->color_model) + { + case CTX_GRAY: + case CTX_GRAYA: + case CTX_RGB: + case CTX_RGBA: + case CTX_DRGB: + case CTX_DRGBA: + ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYK: + case CTX_CMYKA: + ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke); + break; +#else + /* when there is no cmyk support at all in rasterizer + * do a naive mapping to RGB on input. + */ + case CTX_CMYK: + case CTX_CMYKA: + case CTX_DCMYKA: + { + float rgba[4] = {1,1,1,1.0f}; + + ctx_cmyk_to_rgb (arg(0), arg(1), arg(2), arg(3), &rgba[0], &rgba[1], &rgba[2]); + if (parser->color_model == CTX_CMYKA) + { rgba[3] = arg(4); } + ctx_color_raw (ctx, CTX_RGBA, rgba, parser->color_stroke); + } + break; +#endif + case CTX_LAB: + case CTX_LCH: + default: + break; + } + } + break; + case CTX_LINE_DASH: + if (parser->n_numbers) + { + ctx_line_dash (ctx, parser->numbers, parser->n_numbers); + } + else + { + ctx_line_dash (ctx, NULL, 0); + } + //append_dash_val (ctx, arg(0)); + break; + case CTX_ARC_TO: + ctx_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4)); + break; + case CTX_REL_ARC_TO: + ctx_rel_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4) ); + break; + case CTX_REL_SMOOTH_TO: + { + float cx = parser->pcx; + float cy = parser->pcy; + float ax = 2 * ctx_x (ctx) - cx; + float ay = 2 * ctx_y (ctx) - cy; + ctx_curve_to (ctx, ax, ay, arg(0) + cx, arg(1) + cy, + arg(2) + cx, arg(3) + cy); + parser->pcx = arg(0) + cx; + parser->pcy = arg(1) + cy; + } + break; + case CTX_SMOOTH_TO: + { + float ax = 2 * ctx_x (ctx) - parser->pcx; + float ay = 2 * ctx_y (ctx) - parser->pcy; + ctx_curve_to (ctx, ax, ay, arg(0), arg(1), + arg(2), arg(3) ); + parser->pcx = arg(0); + parser->pcx = arg(1); + } + break; + case CTX_SMOOTHQ_TO: + ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0), arg(1) ); + break; + case CTX_REL_SMOOTHQ_TO: + { + float cx = parser->pcx; + float cy = parser->pcy; + parser->pcx = 2 * ctx_x (ctx) - parser->pcx; + parser->pcy = 2 * ctx_y (ctx) - parser->pcy; + ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0) + cx, arg(1) + cy); + } + break; + case CTX_VER_LINE_TO: + ctx_line_to (ctx, ctx_x (ctx), arg(0) ); + parser->command = CTX_VER_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_HOR_LINE_TO: + ctx_line_to (ctx, arg(0), ctx_y (ctx) ); + parser->command = CTX_HOR_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_REL_HOR_LINE_TO: + ctx_rel_line_to (ctx, arg(0), 0.0f); + parser->command = CTX_REL_HOR_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_REL_VER_LINE_TO: + ctx_rel_line_to (ctx, 0.0f, arg(0) ); + parser->command = CTX_REL_VER_LINE_TO; + parser->pcx = ctx_x (ctx); + parser->pcy = ctx_y (ctx); + break; + case CTX_ARC: + ctx_arc (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_APPLY_TRANSFORM: + ctx_apply_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_SOURCE_TRANSFORM: + ctx_source_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_CURVE_TO: + ctx_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + parser->pcx = arg(2); + parser->pcy = arg(3); + parser->command = CTX_CURVE_TO; + break; + case CTX_REL_CURVE_TO: + parser->pcx = arg(2) + ctx_x (ctx); + parser->pcy = arg(3) + ctx_y (ctx); + ctx_rel_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + parser->command = CTX_REL_CURVE_TO; + break; + case CTX_LINE_TO: + ctx_line_to (ctx, arg(0), arg(1) ); + parser->command = CTX_LINE_TO; + parser->pcx = arg(0); + parser->pcy = arg(1); + break; + case CTX_MOVE_TO: + ctx_move_to (ctx, arg(0), arg(1) ); + parser->command = CTX_LINE_TO; + parser->pcx = arg(0); + parser->pcy = arg(1); + parser->left_margin = parser->pcx; + break; + case CTX_FONT_SIZE: + ctx_font_size (ctx, arg(0) ); + break; + case CTX_MITER_LIMIT: + ctx_miter_limit (ctx, arg(0) ); + break; + case CTX_SCALE: + ctx_scale (ctx, arg(0), arg(1) ); + break; + case CTX_QUAD_TO: + parser->pcx = arg(0); + parser->pcy = arg(1); + ctx_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) ); + parser->command = CTX_QUAD_TO; + break; + case CTX_REL_QUAD_TO: + parser->pcx = arg(0) + ctx_x (ctx); + parser->pcy = arg(1) + ctx_y (ctx); + ctx_rel_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) ); + parser->command = CTX_REL_QUAD_TO; + break; + case CTX_CLIP: + ctx_clip (ctx); + break; + case CTX_TRANSLATE: + ctx_translate (ctx, arg(0), arg(1) ); + break; + case CTX_ROTATE: + ctx_rotate (ctx, arg(0) ); + break; + case CTX_FONT: + ctx_font (ctx, (char *) parser->holding); + break; + + case CTX_STROKE_TEXT: + case CTX_TEXT: + if (parser->n_numbers == 1) + { ctx_rel_move_to (ctx, -parser->numbers[0], 0.0); } // XXX : scale by font(size) + else + { + for (char *c = (char *) parser->holding; c; ) + { + char *next_nl = ctx_strchr (c, '\n'); + if (next_nl) + { *next_nl = 0; } + /* do our own layouting on a per-word basis?, to get justified + * margins? then we'd want explict margins rather than the + * implicit ones from move_to's .. making move_to work within + * margins. + */ + if (cmd == CTX_STROKE_TEXT) + { ctx_text_stroke (ctx, c); } + else + { ctx_text (ctx, c); } + if (next_nl) + { + *next_nl = '\n'; // swap it newline back in + ctx_move_to (ctx, parser->left_margin, ctx_y (ctx) + + ctx_get_font_size (ctx) ); + c = next_nl + 1; + if (c[0] == 0) + { c = NULL; } + } + else + { + c = NULL; + } + } + } + if (cmd == CTX_STROKE_TEXT) + { parser->command = CTX_STROKE_TEXT; } + else + { parser->command = CTX_TEXT; } + break; + case CTX_REL_LINE_TO: + ctx_rel_line_to (ctx, arg(0), arg(1) ); + parser->pcx += arg(0); + parser->pcy += arg(1); + break; + case CTX_REL_MOVE_TO: + ctx_rel_move_to (ctx, arg(0), arg(1) ); + parser->pcx += arg(0); + parser->pcy += arg(1); + parser->left_margin = ctx_x (ctx); + break; + case CTX_LINE_WIDTH: + ctx_line_width (ctx, arg(0)); + break; + case CTX_LINE_DASH_OFFSET: + ctx_line_dash_offset (ctx, arg(0)); + break; + case CTX_IMAGE_SMOOTHING: + ctx_image_smoothing (ctx, arg(0)); + break; + case CTX_SHADOW_COLOR: + ctx_shadow_rgba (ctx, arg(0), arg(1), arg(2), arg(3)); + break; + case CTX_SHADOW_BLUR: + ctx_shadow_blur (ctx, arg(0) ); + break; + case CTX_SHADOW_OFFSET_X: + ctx_shadow_offset_x (ctx, arg(0) ); + break; + case CTX_SHADOW_OFFSET_Y: + ctx_shadow_offset_y (ctx, arg(0) ); + break; + case CTX_LINE_JOIN: + ctx_line_join (ctx, (CtxLineJoin) arg(0) ); + break; + case CTX_LINE_CAP: + ctx_line_cap (ctx, (CtxLineCap) arg(0) ); + break; + case CTX_COMPOSITING_MODE: + ctx_compositing_mode (ctx, (CtxCompositingMode) arg(0) ); + break; + case CTX_BLEND_MODE: + { + int blend_mode = arg(0); + if (blend_mode == CTX_COLOR) blend_mode = CTX_BLEND_COLOR; + ctx_blend_mode (ctx, (CtxBlend)blend_mode); + } + break; + case CTX_FILL_RULE: + ctx_fill_rule (ctx, (CtxFillRule) arg(0) ); + break; + case CTX_TEXT_ALIGN: + ctx_text_align (ctx, (CtxTextAlign) arg(0) ); + break; + case CTX_TEXT_BASELINE: + ctx_text_baseline (ctx, (CtxTextBaseline) arg(0) ); + break; + case CTX_TEXT_DIRECTION: + ctx_text_direction (ctx, (CtxTextDirection) arg(0) ); + break; + case CTX_IDENTITY: + ctx_identity (ctx); + break; + case CTX_RECTANGLE: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_FILL_RECT: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + ctx_fill (ctx); + break; + case CTX_STROKE_RECT: + ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) ); + ctx_stroke (ctx); + break; + case CTX_ROUND_RECTANGLE: + ctx_round_rectangle (ctx, arg(0), arg(1), arg(2), arg(3), arg(4)); + break; + case CTX_VIEW_BOX: + ctx_view_box (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_LINEAR_GRADIENT: + ctx_linear_gradient (ctx, arg(0), arg(1), arg(2), arg(3) ); + break; + case CTX_RADIAL_GRADIENT: + ctx_radial_gradient (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) ); + break; + case CTX_GRADIENT_STOP: + { + float red, green, blue, alpha; + ctx_parser_get_color_rgba (parser, 1, &red, &green, &blue, &alpha); + ctx_gradient_add_stop (ctx, arg(0), red, green, blue, alpha); + } + break; + case CTX_GLOBAL_ALPHA: + ctx_global_alpha (ctx, arg(0) ); + break; + case CTX_BEGIN_PATH: + ctx_begin_path (ctx); + break; + case CTX_GLYPH: + ctx_glyph (ctx, arg(0), 0); + break; + case CTX_CLOSE_PATH: + ctx_close_path (ctx); + break; + case CTX_EXIT: + if (parser->exit) + { parser->exit (parser->exit_data); + return; + } + break; + case CTX_FLUSH: + //ctx_flush (ctx); + break; + case CTX_RESET: + ctx_reset (ctx); + if (parser->translate_origin) + { + ctx_translate (ctx, + (parser->cursor_x-1) * parser->cell_width * 1.0, + (parser->cursor_y-1) * parser->cell_height * 1.0); + } + break; + } +#undef arg +// parser->n_numbers = 0; +} + +static inline void ctx_parser_holding_append (CtxParser *parser, int byte) +{ +#if !CTX_PARSER_FIXED_TEMP + if (CTX_UNLIKELY(parser->hold_len < parser->pos + 1 + 1)) + { + int new_len = parser->hold_len * 2; + if (new_len < 512) new_len = 512; + parser->holding = (uint8_t*)realloc (parser->holding, new_len); + parser->hold_len = new_len; + } +#endif + + parser->holding[parser->pos++]=byte; +#if CTX_PARSER_FIXED_TEMP + if (CTX_UNLIKELY(parser->pos > (int) sizeof (parser->holding)-2)) + { parser->pos = sizeof (parser->holding)-2; } +#endif + parser->holding[parser->pos]=0; +} + +static void ctx_parser_transform_percent (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + int big = parser->width; + int small = parser->height; + if (big < small) + { + small = parser->width; + big = parser->height; + } + switch (code) + { + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + switch (arg_no) + { + case 0: + case 3: + *value *= (parser->width/100.0); + break; + case 1: + case 4: + *value *= (parser->height/100.0); + break; + case 2: + case 5: + *value *= small/100.0; + break; + } + break; + case CTX_FONT_SIZE: + case CTX_MITER_LIMIT: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + { + *value *= (small/100.0); + } + break; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + if (arg_no > 3) + { + *value *= (small/100.0); + } + else + { + if (arg_no % 2 == 0) + { *value *= ( (parser->width) /100.0); } + else + { *value *= ( (parser->height) /100.0); } + } + break; + case CTX_ROUND_RECTANGLE: + if (arg_no == 4) + { + { *value *= ((parser->height)/100.0); } + return; + } + /* FALLTHROUGH */ + default: // even means x coord + if (arg_no % 2 == 0) + { *value *= ((parser->width)/100.0); } + else + { *value *= ((parser->height)/100.0); } + break; + } +} + +static void ctx_parser_transform_percent_height (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + *value *= (parser->height/100.0); +} + +static void ctx_parser_transform_percent_width (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + *value *= (parser->height/100.0); +} + +static void ctx_parser_transform_cell (CtxParser *parser, CtxCode code, int arg_no, float *value) +{ + float small = parser->cell_width; + if (small > parser->cell_height) + { small = parser->cell_height; } + switch (code) + { + case CTX_RADIAL_GRADIENT: + case CTX_ARC: + switch (arg_no) + { + case 0: + case 3: + *value *= parser->cell_width; + break; + case 1: + case 4: + *value *= parser->cell_height; + break; + case 2: + case 5: + *value *= small; // use height? + break; + } + break; + case CTX_MITER_LIMIT: + case CTX_FONT_SIZE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + { + *value *= parser->cell_height; + } + break; + case CTX_ARC_TO: + case CTX_REL_ARC_TO: + if (arg_no > 3) + { + *value *= small; + } + else + { + *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height; + } + break; + case CTX_RECTANGLE: + if (arg_no % 2 == 0) + { *value *= parser->cell_width; } + else + { + if (! (arg_no > 1) ) + { (*value) -= 1.0f; } + *value *= parser->cell_height; + } + break; + default: // even means x coord odd means y coord + *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height; + break; + } +} + +// %h %v %m %M + +static void ctx_parser_number_done (CtxParser *parser) +{ + +} + +static void ctx_parser_word_done (CtxParser *parser) +{ + parser->holding[parser->pos]=0; + //int old_args = parser->expected_args; + int command = ctx_parser_resolve_command (parser, parser->holding); + if ((command >= 0 && command < 32) + || (command > 150) || (command < 0) + ) // special case low enum values + { // and enum values too high to be + // commands - permitting passing words + // for strings in some cases + parser->numbers[parser->n_numbers] = command; + + // trigger transition from number + parser->state = CTX_PARSER_NUMBER; + char c = ','; + ctx_parser_feed_bytes (parser, &c, 1); + } + else if (command > 0) + { +#if 0 + if (old_args == CTX_ARG_COLLECT_NUMBERS || + old_args == CTX_ARG_STRING_OR_NUMBER) + { + int tmp1 = parser->command; + int tmp2 = parser->expected_args; + int tmp3 = parser->n_numbers; + // int tmp4 = parser->n_args; + ctx_parser_dispatch_command (parser); + parser->command = (CtxCode)tmp1; + parser->expected_args = tmp2; + parser->n_numbers = tmp3; + // parser->n_args = tmp4; + } +#endif + + parser->command = (CtxCode) command; + parser->n_numbers = 0; + parser->n_args = 0; + if (parser->expected_args == 0) + { + ctx_parser_dispatch_command (parser); + } + } + else + { + /* interpret char by char */ + uint8_t buf[16]=" "; + for (int i = 0; parser->pos && parser->holding[i] > ' '; i++) + { + buf[0] = parser->holding[i]; + parser->command = (CtxCode) ctx_parser_resolve_command (parser, buf); + parser->n_numbers = 0; + parser->n_args = 0; + if (parser->command > 0) + { + if (parser->expected_args == 0) + { + ctx_parser_dispatch_command (parser); + } + } + else + { + ctx_log ("unhandled command '%c'\n", buf[0]); + } + } + } +} + +static void ctx_parser_string_done (CtxParser *parser) +{ + if (parser->expected_args == CTX_ARG_STRING_OR_NUMBER) + { + /* + if (parser->state != CTX_PARSER_NUMBER && + parser->state != CTX_PARSER_NEGATIVE_NUMBER && + parser->state != CTX_PARSER_STRING_A85 && + parser->state != CTX_PARSER_STRING_APOS && + parser->state != CTX_PARSER_STRING_QUOT + ) + */ + { + int tmp1 = parser->command; + int tmp2 = parser->expected_args; + int tmp3 = parser->n_numbers; + int tmp4 = parser->n_args; + ctx_parser_dispatch_command (parser); + parser->command = (CtxCode)tmp1; + parser->expected_args = tmp2; + parser->n_numbers = tmp3; + parser->n_args = tmp4; + } + } + else + { + ctx_parser_dispatch_command (parser); + } +} + +static inline void ctx_parser_feed_byte (CtxParser *parser, char byte) +{ +#if CTX_REPORT_COL_ROW + if (CTX_UNLIKELY(byte == '\n')) + { + parser->col=0; + parser->line++; + } + else + { + parser->col++; + } +#endif + + if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_YENC)) + { + if (CTX_UNLIKELY((parser->prev_byte == '=') && (byte == 'y'))) + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_ydec ((char*)parser->holding, (char*)parser->holding, parser->pos) - 1; +#if 0 + if (parser->pos > 5) + fprintf (stderr, "dec got %i %c %c %c %c\n", parser->pos, + parser->holding[0], + parser->holding[1], + parser->holding[2], + parser->holding[3] + ); +#endif + ctx_parser_string_done (parser); + } + else + { + ctx_parser_holding_append (parser, byte); + } + parser->prev_byte = byte; + return; + } + else if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_A85)) + { + /* since these are our largest bulk transfers, minimize + * overhead for this case. */ + if (CTX_LIKELY(byte!='~')) + { + ctx_parser_holding_append (parser, byte); + } + else + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos); + // fprintf (stderr, "dec got %i\n", parser->pos); + ctx_parser_string_done (parser); + } + return; + } + switch (parser->state) + { + case CTX_PARSER_NEUTRAL: + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 11: case 12: case 14: + case 15: case 16: case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: case 25: case 26: + case 27: case 28: case 29: case 30: case 31: + break; + case ' ': case '\t': case '\r': case '\n': + case ';': case ',': + case '(': case ')': + case '{': case '}': + //case '=': + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '\'': + parser->state = CTX_PARSER_STRING_APOS; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '=': + parser->state = CTX_PARSER_STRING_YENC; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '~': + parser->state = CTX_PARSER_STRING_A85; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '"': + parser->state = CTX_PARSER_STRING_QUOT; + parser->pos = 0; + parser->holding[0] = 0; + break; + case '-': + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 0; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->numbers[parser->n_numbers] += (byte - '0'); + parser->decimal = 0; + break; + case '.': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 1; + break; + default: + parser->state = CTX_PARSER_WORD; + parser->pos = 0; + ctx_parser_holding_append (parser, byte); + break; + } + break; + case CTX_PARSER_NUMBER: + case CTX_PARSER_NEGATIVE_NUMBER: + { + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: + case 11: case 12: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: + case 22: case 23: case 24: case 25: case 26: + case 27: case 28: case 29: case 30: case 31: + parser->state = CTX_PARSER_NEUTRAL; + break; + case ' ': + case '\t': + case '\r': + case '\n': + case ';': + case ',': + case '(': + case ')': + case '{': + case '}': + case '=': + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '-': + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers+1] = 0; + parser->n_numbers ++; + parser->decimal = 0; + break; + case '.': + //if (parser->decimal) // TODO permit .13.32.43 to equivalent to .12 .32 .43 + parser->decimal = 1; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (parser->decimal) + { + parser->decimal *= 10; + parser->numbers[parser->n_numbers] += (byte - '0') / (1.0 * parser->decimal); + } + else + { + parser->numbers[parser->n_numbers] *= 10; + parser->numbers[parser->n_numbers] += (byte - '0'); + } + break; + case '@': // cells + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_cell (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '%': // percent of width/height + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '^': // percent of height + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent_height (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + case '~': // percent of width + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + { + float fval = parser->numbers[parser->n_numbers]; + ctx_parser_transform_percent_width (parser, parser->command, parser->n_numbers, &fval); + parser->numbers[parser->n_numbers]= fval; + } + parser->state = CTX_PARSER_NEUTRAL; + break; + default: + if (parser->state == CTX_PARSER_NEGATIVE_NUMBER) + { parser->numbers[parser->n_numbers] *= -1; } + parser->state = CTX_PARSER_WORD; + parser->pos = 0; + ctx_parser_holding_append (parser, byte); + break; + } + if ( (parser->state != CTX_PARSER_NUMBER) && + (parser->state != CTX_PARSER_NEGATIVE_NUMBER)) + { + parser->n_numbers ++; + ctx_parser_number_done (parser); + + if (parser->n_numbers == parser->expected_args || + parser->expected_args == CTX_ARG_COLLECT_NUMBERS || + parser->expected_args == CTX_ARG_STRING_OR_NUMBER) + { + int tmp1 = parser->n_numbers; + int tmp2 = parser->n_args; + CtxCode tmp3 = parser->command; + int tmp4 = parser->expected_args; + ctx_parser_dispatch_command (parser); + parser->command = tmp3; + switch (parser->command) + { + case CTX_DEFINE_TEXTURE: + case CTX_TEXTURE: + parser->n_numbers = tmp1; + parser->n_args = tmp2; + break; + default: + parser->n_numbers = 0; + parser->n_args = 0; + break; + } + parser->expected_args = tmp4; + } + if (parser->n_numbers > CTX_PARSER_MAX_ARGS) + { parser->n_numbers = CTX_PARSER_MAX_ARGS; + } + } + } + break; + case CTX_PARSER_WORD: + switch (byte) + { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + case 8: case 11: case 12: case 14: case 15: case 16: case 17: + case 18: case 19: case 20: case 21: case 22: case 23: case 24: + case 25: case 26: case 27: case 28: case 29: case 30: case 31: + case ' ': case '\t': case '\r': case '\n': + case ';': case ',': + case '(': case ')': case '=': case '{': case '}': + parser->state = CTX_PARSER_NEUTRAL; + break; + case '#': + parser->state = CTX_PARSER_COMMENT; + break; + case '-': + parser->state = CTX_PARSER_NEGATIVE_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 0; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->numbers[parser->n_numbers] += (byte - '0'); + parser->decimal = 0; + break; + case '.': + parser->state = CTX_PARSER_NUMBER; + parser->numbers[parser->n_numbers] = 0; + parser->decimal = 1; + break; + default: + ctx_parser_holding_append (parser, byte); + break; + } + if (parser->state != CTX_PARSER_WORD) + { + ctx_parser_word_done (parser); + } + break; + case CTX_PARSER_STRING_A85: + if (CTX_LIKELY(byte!='~')) + { + ctx_parser_holding_append (parser, byte); + } + else + { + parser->state = CTX_PARSER_NEUTRAL; + // fprintf (stderr, "got %i\n", parser->pos); + parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos); + // fprintf (stderr, "dec got %i\n", parser->pos); + ctx_parser_string_done (parser); + } + break; + case CTX_PARSER_STRING_APOS: + switch (byte) + { + case '\\': parser->state = CTX_PARSER_STRING_APOS_ESCAPED; break; + case '\'': parser->state = CTX_PARSER_NEUTRAL; + ctx_parser_string_done (parser); + break; + default: + ctx_parser_holding_append (parser, byte); break; + } + break; + case CTX_PARSER_STRING_APOS_ESCAPED: + switch (byte) + { + case '0': byte = '\0'; break; + case 'b': byte = '\b'; break; + case 'f': byte = '\f'; break; + case 'n': byte = '\n'; break; + case 'r': byte = '\r'; break; + case 't': byte = '\t'; break; + case 'v': byte = '\v'; break; + default: break; + } + ctx_parser_holding_append (parser, byte); + parser->state = CTX_PARSER_STRING_APOS; + break; + case CTX_PARSER_STRING_QUOT_ESCAPED: + switch (byte) + { + case '0': byte = '\0'; break; + case 'b': byte = '\b'; break; + case 'f': byte = '\f'; break; + case 'n': byte = '\n'; break; + case 'r': byte = '\r'; break; + case 't': byte = '\t'; break; + case 'v': byte = '\v'; break; + default: break; + } + ctx_parser_holding_append (parser, byte); + parser->state = CTX_PARSER_STRING_QUOT; + break; + case CTX_PARSER_STRING_QUOT: + switch (byte) + { + case '\\': + parser->state = CTX_PARSER_STRING_QUOT_ESCAPED; + break; + case '"': + parser->state = CTX_PARSER_NEUTRAL; + ctx_parser_string_done (parser); + break; + default: + ctx_parser_holding_append (parser, byte); + break; + } + break; + case CTX_PARSER_COMMENT: + switch (byte) + { + case '\r': + case '\n': + parser->state = CTX_PARSER_NEUTRAL; + default: + break; + } + break; + } +} + +void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count) +{ + for (int i = 0; i < count; i++) + ctx_parser_feed_byte (parser, data[i]); +} + +void ctx_parse (Ctx *ctx, const char *string) +{ + if (!string) + return; + CtxParser *parser = ctx_parser_new (ctx, ctx_width(ctx), + ctx_height(ctx), + ctx_get_font_size(ctx), + ctx_get_font_size(ctx), + 0, 0, NULL, NULL, NULL, NULL, NULL); + ctx_parser_feed_bytes (parser, string, strlen (string)); + ctx_parser_free (parser); +} + +#endif + +static CtxFont ctx_fonts[CTX_MAX_FONTS]; +static int ctx_font_count = 0; + +#if CTX_FONT_ENGINE_STB +static float +ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar); +static float +ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB); +static int +ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke); + +CtxFontEngine ctx_font_engine_stb = +{ +#if CTX_FONTS_FROM_FILE + ctx_load_font_ttf_file, +#endif + ctx_load_font_ttf, + ctx_glyph_stb, + ctx_glyph_width_stb, + ctx_glyph_kern_stb, +}; + +int +ctx_load_font_ttf (const char *name, const void *ttf_contents, int length) +{ + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + ctx_fonts[ctx_font_count].type = 1; + ctx_fonts[ctx_font_count].name = (char *) malloc (strlen (name) + 1); + ctx_strcpy ( (char *) ctx_fonts[ctx_font_count].name, name); + if (!stbtt_InitFont (&ctx_fonts[ctx_font_count].stb.ttf_info, ttf_contents, 0) ) + { + ctx_log ( "Font init failed\n"); + return -1; + } + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_stb; + ctx_font_count ++; + return ctx_font_count-1; +} + +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ttf_file (const char *name, const char *path) +{ + uint8_t *contents = NULL; + long length = 0; + ctx_get_contents (path, &contents, &length); + if (!contents) + { + ctx_log ( "File load failed\n"); + return -1; + } + return ctx_load_font_ttf (name, contents, length); +} +#endif + +static int +ctx_glyph_stb_find (CtxFont *font, uint32_t unichar) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + int index = font->stb.cache_index; + if (font->stb.cache_unichar == unichar) + { + return index; + } + font->stb.cache_unichar = 0; + index = font->stb.cache_index = stbtt_FindGlyphIndex (ttf_info, unichar); + font->stb.cache_unichar = unichar; + return index; +} + +static float +ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + float font_size = ctx->state.gstate.font_size; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size); + int advance, lsb; + int glyph = ctx_glyph_stb_find (font, unichar); + if (glyph==0) + { return 0.0f; } + stbtt_GetGlyphHMetrics (ttf_info, glyph, &advance, &lsb); + return (advance * scale); +} + +static float +ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + float font_size = ctx->state.gstate.font_size; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size); + int glyphA = ctx_glyph_stb_find (font, unicharA); + int glyphB = ctx_glyph_stb_find (font, unicharB); + return stbtt_GetGlyphKernAdvance (ttf_info, glyphA, glyphB) * scale; +} + +static int +ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + stbtt_fontinfo *ttf_info = &font->stb.ttf_info; + int glyph = ctx_glyph_stb_find (font, unichar); + if (glyph==0) + { return -1; } + float font_size = ctx->state.gstate.font_size; + int baseline = ctx->state.y; + float origin_x = ctx->state.x; + float origin_y = baseline; + float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size);; + stbtt_vertex *vertices = NULL; + ctx_begin_path (ctx); + int num_verts = stbtt_GetGlyphShape (ttf_info, glyph, &vertices); + for (int i = 0; i < num_verts; i++) + { + stbtt_vertex *vertex = &vertices[i]; + switch (vertex->type) + { + case STBTT_vmove: + ctx_move_to (ctx, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vline: + ctx_line_to (ctx, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vcubic: + ctx_curve_to (ctx, + origin_x + vertex->cx * scale, origin_y - vertex->cy * scale, + origin_x + vertex->cx1 * scale, origin_y - vertex->cy1 * scale, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + case STBTT_vcurve: + ctx_quad_to (ctx, + origin_x + vertex->cx * scale, origin_y - vertex->cy * scale, + origin_x + vertex->x * scale, origin_y - vertex->y * scale); + break; + } + } + stbtt_FreeShape (ttf_info, vertices); + if (stroke) + { + ctx_stroke (ctx); + } + else + { ctx_fill (ctx); } + return 0; +} +#endif + +#if CTX_FONT_ENGINE_CTX + +/* XXX: todo remove this, and rely on a binary search instead + */ +static int ctx_font_find_glyph_cached (CtxFont *font, uint32_t glyph) +{ + for (int i = 0; i < font->ctx.glyphs; i++) + { + if (font->ctx.index[i * 2] == glyph) + { return font->ctx.index[i * 2 + 1]; } + } + return -1; +} + +static int ctx_glyph_find_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + int ret = ctx_font_find_glyph_cached (font, unichar); + if (ret >= 0) return ret; + + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH && + entry->data.u32[0] == unichar) + { + return i; + // XXX this could be prone to insertion of valid header + // data in included bitmaps.. is that an issue? + // + } + } + return -1; +} + + +static float +ctx_glyph_kern_ctx (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ + float font_size = ctx->state.gstate.font_size; + int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA); + if (first_kern < 0) return 0.0; + for (int i = first_kern + 1; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_KERNING_PAIR) + { + if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB) + { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; } + } + if (entry->code == CTX_DEFINE_GLYPH) + return 0.0; + } + return 0.0; +} +#if 0 +static int ctx_glyph_find (Ctx *ctx, CtxFont *font, uint32_t unichar) +{ + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar) + { return i; } + } + return 0; +} +#endif + + +static float +ctx_glyph_width_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + CtxState *state = &ctx->state; + float font_size = state->gstate.font_size; + int start = ctx_glyph_find_ctx (font, ctx, unichar); + if (start < 0) + { return 0.0; } // XXX : fallback + for (int i = start; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + if (entry->data.u32[0] == (unsigned) unichar) + { return (entry->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE); } + } + return 0.0; +} + +static int +ctx_glyph_drawlist (CtxFont *font, Ctx *ctx, CtxDrawlist *drawlist, uint32_t unichar, int stroke) +{ + CtxState *state = &ctx->state; + CtxIterator iterator; + float origin_x = state->x; + float origin_y = state->y; + ctx_current_point (ctx, &origin_x, &origin_y); + int in_glyph = 0; + float font_size = state->gstate.font_size; + int start = 0; + if (font->type == 0) + { + start = ctx_glyph_find_ctx (font, ctx, unichar); + if (start < 0) + { return -1; } // XXX : fallback glyph + } + ctx_iterator_init (&iterator, drawlist, start, CTX_ITERATOR_EXPAND_BITPACK); + CtxCommand *command; + + /* XXX : do a binary search instead of a linear search */ + while ( (command= ctx_iterator_next (&iterator) ) ) + { + CtxEntry *entry = &command->entry; + if (in_glyph) + { + if (entry->code == CTX_DEFINE_GLYPH) + { + if (stroke) + { ctx_stroke (ctx); } + else + { +#if CTX_RASTERIZER +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow) + { + ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer); + ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1; + } + else +#endif +#endif + ctx_fill (ctx); + + } + ctx_restore (ctx); + return 0; + } + ctx_process (ctx, entry); + } + else if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar) + { + in_glyph = 1; + ctx_save (ctx); + ctx_translate (ctx, origin_x, origin_y); + ctx_move_to (ctx, 0, 0); + ctx_begin_path (ctx); + ctx_scale (ctx, font_size / CTX_BAKE_FONT_SIZE, + font_size / CTX_BAKE_FONT_SIZE); + } + } + if (stroke) + { ctx_stroke (ctx); + } + else + { + +#if CTX_RASTERIZER +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow) + { + ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer); + ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1; + } + else +#endif +#endif + { + ctx_fill (ctx); + } + } + ctx_restore (ctx); + return -1; +} + +static int +ctx_glyph_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + CtxDrawlist drawlist = { (CtxEntry *) font->ctx.data, + font->ctx.length, + font->ctx.length, 0, 0 + }; + return ctx_glyph_drawlist (font, ctx, &drawlist, unichar, stroke); +} + +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]; +} + +static void ctx_font_init_ctx (CtxFont *font) +{ + int glyph_count = 0; + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + { glyph_count ++; } + } + font->ctx.glyphs = glyph_count; +#if CTX_DRAWLIST_STATIC + static uint32_t idx[512]; // one might have to adjust this for + // larger fonts XXX + // should probably be made a #define + font->ctx.index = &idx[0]; +#else + font->ctx.index = (uint32_t *) malloc (sizeof (uint32_t) * 2 * glyph_count); +#endif + int no = 0; + for (int i = 0; i < font->ctx.length; i++) + { + CtxEntry *entry = &font->ctx.data[i]; + if (entry->code == CTX_DEFINE_GLYPH) + { + font->ctx.index[no*2] = entry->data.u32[0]; + font->ctx.index[no*2+1] = i; + no++; + } + } +} + +int +ctx_load_font_ctx (const char *name, const void *data, int length); +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ctx_file (const char *name, const char *path); +#endif + +static CtxFontEngine ctx_font_engine_ctx = +{ +#if CTX_FONTS_FROM_FILE + ctx_load_font_ctx_file, +#endif + ctx_load_font_ctx, + ctx_glyph_ctx, + ctx_glyph_width_ctx, + ctx_glyph_kern_ctx, +}; + +int +ctx_load_font_ctx (const char *name, const void *data, int length) +{ + if (length % sizeof (CtxEntry) ) + { return -1; } + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + ctx_fonts[ctx_font_count].type = 0; + ctx_fonts[ctx_font_count].name = name; + ctx_fonts[ctx_font_count].ctx.data = (CtxEntry *) data; + ctx_fonts[ctx_font_count].ctx.length = length / sizeof (CtxEntry); + ctx_font_init_ctx (&ctx_fonts[ctx_font_count]); + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx; + ctx_font_count++; + return ctx_font_count-1; +} + +#if CTX_FONTS_FROM_FILE +int +ctx_load_font_ctx_file (const char *name, const char *path) +{ + uint8_t *contents = NULL; + long length = 0; + ctx_get_contents (path, &contents, &length); + if (!contents) + { + ctx_log ( "File load failed\n"); + return -1; + } + return ctx_load_font_ctx (name, contents, length); +} +#endif +#endif + +#if CTX_FONT_ENGINE_CTX_FS + +static float +ctx_glyph_kern_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB) +{ +#if 0 + float font_size = ctx->state.gstate.font_size; + int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA); + if (first_kern < 0) return 0.0; + for (int i = first_kern + 1; i < font->ctx.length; i++) + { + CtxEntry *entry = (CtxEntry *) &font->ctx.data[i]; + if (entry->code == CTX_KERNING_PAIR) + { + if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB) + { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; } + } + if (entry->code == CTX_DEFINE_GLYPH) + return 0.0; + } +#endif + return 0.0; +} + +static float +ctx_glyph_width_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar) +{ + CtxState *state = &ctx->state; + char path[1024]; + sprintf (path, "%s/%010p", font->ctx_fs.path, unichar); + uint8_t *data = NULL; + long int len_bytes = 0; + ctx_get_contents (path, &data, &len_bytes); + float ret = 0.0; + float font_size = state->gstate.font_size; + if (data){ + Ctx *glyph_ctx = ctx_new (); + ctx_parse (glyph_ctx, data); + for (int i = 0; i < glyph_ctx->drawlist.count; i++) + { + CtxEntry *e = &glyph_ctx->drawlist.entries[i]; + if (e->code == CTX_DEFINE_GLYPH) + ret = e->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; + } + free (data); + ctx_free (glyph_ctx); + } + return ret; +} + +static int +ctx_glyph_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke) +{ + char path[1024]; + sprintf (path, "file://%s/%010p", font->ctx_fs.path, unichar); + uint8_t *data = NULL; + long int len_bytes = 0; + ctx_get_contents (path, &data, &len_bytes); + + if (data){ + Ctx *glyph_ctx = ctx_new (); + ctx_parse (glyph_ctx, data); + int ret = ctx_glyph_drawlist (font, ctx, &(glyph_ctx->drawlist), + unichar, stroke); + free (data); + ctx_free (glyph_ctx); + return ret; + } + return -1; +} + +int +ctx_load_font_ctx_fs (const char *name, const void *data, int length); + +static CtxFontEngine ctx_font_engine_ctx_fs = +{ +#if CTX_FONTS_FROM_FILE + NULL, +#endif + ctx_load_font_ctx_fs, + ctx_glyph_ctx_fs, + ctx_glyph_width_ctx_fs, + ctx_glyph_kern_ctx_fs, +}; + +int +ctx_load_font_ctx_fs (const char *name, const void *path, int length) // length is ignored +{ + if (ctx_font_count >= CTX_MAX_FONTS) + { return -1; } + + ctx_fonts[ctx_font_count].type = 42; + ctx_fonts[ctx_font_count].name = name; + ctx_fonts[ctx_font_count].ctx_fs.path = strdup (path); + int path_len = strlen (path); + if (ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] == '/') + ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] = 0; + ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx_fs; + ctx_font_count++; + return ctx_font_count-1; +} + +#endif + +int +_ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + // a begin-path here did not remove stray spikes in terminal + return font->engine->glyph (font, ctx, unichar, stroke); +} + +int +ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke) +{ +#if CTX_BACKEND_TEXT + CtxEntry commands[3]; // 3 to silence incorrect warning from static analysis + ctx_memset (commands, 0, sizeof (commands) ); + commands[0] = ctx_u32 (CTX_GLYPH, unichar, 0); + commands[0].data.u8[4] = stroke; + ctx_process (ctx, commands); + return 0; // XXX is return value used? +#else + return _ctx_glyph (ctx, unichar, stroke); +#endif +} + +float +ctx_glyph_width (Ctx *ctx, int unichar) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + + return font->engine->glyph_width (font, ctx, unichar); +} + +static float +ctx_glyph_kern (Ctx *ctx, int unicharA, int unicharB) +{ + CtxFont *font = &ctx_fonts[ctx->state.gstate.font]; + return font->engine->glyph_kern (font, ctx, unicharA, unicharB); +} + +float +ctx_text_width (Ctx *ctx, + const char *string) +{ + float sum = 0.0; + if (!string) + return 0.0f; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) ) + { + sum += ctx_glyph_width (ctx, ctx_utf8_to_unichar (utf8) ); + } + return sum; +} + +static void +_ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs, + int stroke) +{ + for (int i = 0; i < n_glyphs; i++) + { + { + uint32_t unichar = glyphs[i].index; + ctx_move_to (ctx, glyphs[i].x, glyphs[i].y); + ctx_glyph (ctx, unichar, stroke); + } + } +} + +static void +_ctx_text (Ctx *ctx, + const char *string, + int stroke, + int visible) +{ + CtxState *state = &ctx->state; + float x = ctx->state.x; + switch ( (int) ctx_state_get (state, CTX_text_align) ) + //switch (state->gstate.text_align) + { + case CTX_TEXT_ALIGN_START: + case CTX_TEXT_ALIGN_LEFT: + break; + case CTX_TEXT_ALIGN_CENTER: + x -= ctx_text_width (ctx, string) /2; + break; + case CTX_TEXT_ALIGN_END: + case CTX_TEXT_ALIGN_RIGHT: + x -= ctx_text_width (ctx, string); + break; + } + float y = ctx->state.y; + float baseline_offset = 0.0f; + switch ( (int) ctx_state_get (state, CTX_text_baseline) ) + { + case CTX_TEXT_BASELINE_HANGING: + /* XXX : crude */ + baseline_offset = ctx->state.gstate.font_size * 0.55; + break; + case CTX_TEXT_BASELINE_TOP: + /* XXX : crude */ + baseline_offset = ctx->state.gstate.font_size * 0.7; + break; + case CTX_TEXT_BASELINE_BOTTOM: + baseline_offset = -ctx->state.gstate.font_size * 0.1; + break; + case CTX_TEXT_BASELINE_ALPHABETIC: + case CTX_TEXT_BASELINE_IDEOGRAPHIC: + baseline_offset = 0.0f; + break; + case CTX_TEXT_BASELINE_MIDDLE: + baseline_offset = ctx->state.gstate.font_size * 0.25; + break; + } + float x0 = x; + for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) ) + { + if (*utf8 == '\n') + { + y += ctx->state.gstate.font_size * ctx_state_get (state, CTX_line_spacing); + x = x0; + if (visible) + { ctx_move_to (ctx, x, y); } + } + else + { + uint32_t unichar = ctx_utf8_to_unichar (utf8); + if (visible) + { + ctx_move_to (ctx, x, y + baseline_offset); + _ctx_glyph (ctx, unichar, stroke); + } + const char *next_utf8 = ctx_utf8_skip (utf8, 1); + if (next_utf8) + { + x += ctx_glyph_width (ctx, unichar); + x += ctx_glyph_kern (ctx, unichar, ctx_utf8_to_unichar (next_utf8) ); + } + if (visible) + { ctx_move_to (ctx, x, y); } + } + } + if (!visible) + { ctx_move_to (ctx, x, y); } +} + + +CtxGlyph * +ctx_glyph_allocate (int n_glyphs) +{ + return (CtxGlyph *) malloc (sizeof (CtxGlyph) * n_glyphs); +} +void +gtx_glyph_free (CtxGlyph *glyphs) +{ + free (glyphs); +} + +void +ctx_glyphs (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs) +{ + _ctx_glyphs (ctx, glyphs, n_glyphs, 0); +} + +void +ctx_glyphs_stroke (Ctx *ctx, + CtxGlyph *glyphs, + int n_glyphs) +{ + _ctx_glyphs (ctx, glyphs, n_glyphs, 1); +} + +void +ctx_text (Ctx *ctx, + const char *string) +{ + if (!string) + return; +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_TEXT, string, 0, 0); + _ctx_text (ctx, string, 0, 0); +#else + _ctx_text (ctx, string, 0, 1); +#endif +} + + +void +ctx_fill_text (Ctx *ctx, const char *string, + float x, float y) +{ + ctx_move_to (ctx, x, y); + ctx_text (ctx, string); +} + +void +ctx_text_stroke (Ctx *ctx, + const char *string) +{ + if (!string) + return; +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_STROKE_TEXT, string, 0, 0); + _ctx_text (ctx, string, 1, 0); +#else + _ctx_text (ctx, string, 1, 1); +#endif +} + +void +ctx_stroke_text (Ctx *ctx, const char *string, + float x, float y) +{ + ctx_move_to (ctx, x, y); + ctx_text_stroke (ctx, string); +} + +static int _ctx_resolve_font (const char *name) +{ + for (int i = 0; i < ctx_font_count; i ++) + { + if (!ctx_strcmp (ctx_fonts[i].name, name) ) + { return i; } + } + for (int i = 0; i < ctx_font_count; i ++) + { + if (ctx_strstr (ctx_fonts[i].name, name) ) + { return i; } + } + return -1; +} + +int ctx_resolve_font (const char *name) +{ + int ret = _ctx_resolve_font (name); + if (ret >= 0) + { return ret; } + if (!ctx_strcmp (name, "regular") ) + { + int ret = _ctx_resolve_font ("sans"); + if (ret >= 0) { return ret; } + ret = _ctx_resolve_font ("serif"); + if (ret >= 0) { return ret; } + } + return 0; +} + +static void ctx_font_setup () +{ + static int initialized = 0; + if (initialized) { return; } + initialized = 1; +#if CTX_FONT_ENGINE_CTX + ctx_font_count = 0; // oddly - this is needed in arduino + +#if CTX_FONT_ENGINE_CTX_FS + ctx_load_font_ctx_fs ("sans-ctx", "/tmp/ctx-regular", 0); +#else +#if CTX_FONT_ascii + ctx_load_font_ctx ("sans-ctx", ctx_font_ascii, sizeof (ctx_font_ascii) ); +#endif +#if CTX_FONT_regular + ctx_load_font_ctx ("sans-ctx", ctx_font_regular, sizeof (ctx_font_regular) ); +#endif +#endif + +#if CTX_FONT_mono + ctx_load_font_ctx ("mono-ctx", ctx_font_mono, sizeof (ctx_font_mono) ); +#endif +#if CTX_FONT_bold + ctx_load_font_ctx ("bold-ctx", ctx_font_bold, sizeof (ctx_font_bold) ); +#endif +#if CTX_FONT_italic + ctx_load_font_ctx ("italic-ctx", ctx_font_italic, sizeof (ctx_font_italic) ); +#endif +#if CTX_FONT_sans + ctx_load_font_ctx ("sans-ctx", ctx_font_sans, sizeof (ctx_font_sans) ); +#endif +#if CTX_FONT_serif + ctx_load_font_ctx ("serif-ctx", ctx_font_serif, sizeof (ctx_font_serif) ); +#endif +#if CTX_FONT_symbol + ctx_load_font_ctx ("symbol-ctx", ctx_font_symbol, sizeof (ctx_font_symbol) ); +#endif +#if CTX_FONT_emoji + ctx_load_font_ctx ("emoji-ctx", ctx_font_emoji, sizeof (ctx_font_emoji) ); +#endif +#endif + +#if NOTO_EMOJI_REGULAR + ctx_load_font_ttf ("sans-NotoEmoji_Regular", ttf_NotoEmoji_Regular_ttf, ttf_NotoEmoji_Regular_ttf_len); +#endif +#if ROBOTO_LIGHT + ctx_load_font_ttf ("sans-light-Roboto_Light", ttf_Roboto_Light_ttf, ttf_Roboto_Light_ttf_len); +#endif +#if ROBOTO_REGULAR + ctx_load_font_ttf ("sans-Roboto_Regular", ttf_Roboto_Regular_ttf, ttf_Roboto_Regular_ttf_len); +#endif +#if ROBOTO_BOLD + ctx_load_font_ttf ("sans-bold-Roboto_Bold", ttf_Roboto_Bold_ttf, ttf_Roboto_Bold_ttf_len); +#endif +#if DEJAVU_SANS + ctx_load_font_ttf ("sans-DejaVuSans", ttf_DejaVuSans_ttf, ttf_DejaVuSans_ttf_len); +#endif +#if VERA + ctx_load_font_ttf ("sans-Vera", ttf_Vera_ttf, ttf_Vera_ttf_len); +#endif +#if UNSCII_16 + ctx_load_font_ttf ("mono-unscii16", ttf_unscii_16_ttf, ttf_unscii_16_ttf_len); +#endif +#if XA000_MONO + ctx_load_font_ttf ("mono-0xA000", ttf_0xA000_Mono_ttf, ttf_0xA000_Mono_ttf_len); +#endif +#if DEJAVU_SANS_MONO + ctx_load_font_ttf ("mono-DejaVuSansMono", ttf_DejaVuSansMono_ttf, ttf_DejaVuSansMono_ttf_len); +#endif +#if NOTO_MONO_REGULAR + ctx_load_font_ttf ("mono-NotoMono_Regular", ttf_NotoMono_Regular_ttf, ttf_NotoMono_Regular_ttf_len); +#endif +} + + + +#if !__COSMOPOLITAN__ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#endif + +//#include "ctx.h" +/* instead of including ctx.h we declare the few utf8 + * functions we use + */ +uint32_t ctx_utf8_to_unichar (const char *input); +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +int ctx_utf8_strlen (const char *s); + + +void ctx_string_init (CtxString *string, int initial_size) +{ + string->allocated_length = initial_size; + string->length = 0; + string->utf8_length = 0; + string->str = (char*)malloc (string->allocated_length + 1); + string->str[0]='\0'; +} + +static void ctx_string_destroy (CtxString *string) +{ + if (string->str) + { + free (string->str); + string->str = NULL; + } +} + +void ctx_string_clear (CtxString *string) +{ + string->length = 0; + string->utf8_length = 0; + string->str[string->length]=0; +} + +static inline void _ctx_string_append_byte (CtxString *string, char val) +{ + if (CTX_LIKELY((val & 0xC0) != 0x80)) + { string->utf8_length++; } + if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length)) + { + char *old = string->str; + string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2); + string->str = (char*)realloc (old, string->allocated_length); + } + string->str[string->length++] = val; + string->str[string->length] = '\0'; +} + +void ctx_string_append_unichar (CtxString *string, unsigned int unichar) +{ + char *str; + char utf8[5]; + utf8[ctx_unichar_to_utf8 (unichar, (unsigned char *) utf8)]=0; + str = utf8; + while (str && *str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +static inline void _ctx_string_append_str (CtxString *string, const char *str) +{ + if (!str) { return; } + while (*str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +void ctx_string_append_utf8char (CtxString *string, const char *str) +{ + if (!str) { return; } + int len = ctx_utf8_len (*str); + for (int i = 0; i < len && *str; i++) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +void ctx_string_append_str (CtxString *string, const char *str) +{ + _ctx_string_append_str (string, str); +} + +CtxString *ctx_string_new_with_size (const char *initial, int initial_size) +{ + CtxString *string = (CtxString*)ctx_calloc (sizeof (CtxString), 1); + ctx_string_init (string, initial_size); + if (initial) + { _ctx_string_append_str (string, initial); } + return string; +} + +CtxString *ctx_string_new (const char *initial) +{ + return ctx_string_new_with_size (initial, 8); +} + +void ctx_string_append_data (CtxString *string, const char *str, int len) +{ + int i; + for (i = 0; i<len; i++) + { _ctx_string_append_byte (string, str[i]); } +} + +void ctx_string_append_string (CtxString *string, CtxString *string2) +{ + const char *str = ctx_string_get (string2); + while (str && *str) + { + _ctx_string_append_byte (string, *str); + str++; + } +} + +const char *ctx_string_get (CtxString *string) +{ + return string->str; +} + +int ctx_string_get_utf8length (CtxString *string) +{ + return string->utf8_length; +} + +int ctx_string_get_length (CtxString *string) +{ + return string->length; +} + +void +ctx_string_free (CtxString *string, int freealloc) +{ + if (freealloc) + { + ctx_string_destroy (string); + } +#if 0 + if (string->is_line) + { + VtLine *line = (VtLine*)string; + if (line->style) + { free (line->style); } + if (line->ctx) + { ctx_free (line->ctx); } + if (line->ctx_copy) + { ctx_free (line->ctx_copy); } + } +#endif + free (string); +} + +void +ctx_string_set (CtxString *string, const char *new_string) +{ + ctx_string_clear (string); + _ctx_string_append_str (string, new_string); +} + +static char *ctx_strdup (const char *str) +{ + int len = strlen (str); + char *ret = (char*)malloc (len + 1); + memcpy (ret, str, len); + ret[len]=0; + return ret; +} + +void ctx_string_replace_utf8 (CtxString *string, int pos, const char *new_glyph) +{ +#if 1 + int old_len = string->utf8_length; +#else + int old_len = ctx_utf8_strlen (string->str);// string->utf8_length; +#endif + if (CTX_LIKELY(pos == old_len)) + { + _ctx_string_append_str (string, new_glyph); + return; + } + + char tmpg[3]=" "; + int new_len = ctx_utf8_len (*new_glyph); + if (new_len <= 1 && new_glyph[0] < 32) + { + new_len = 1; + tmpg[0]=new_glyph[0]+64; + new_glyph = tmpg; + } + { + for (int i = old_len; i <= pos + 2; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + if (string->length + new_len >= string->allocated_length - 2) + { + char *tmp; + char *defer; + string->allocated_length = string->length + new_len + 2; + tmp = (char*) ctx_calloc (string->allocated_length + 1 + 8, 1); + strcpy (tmp, string->str); + defer = string->str; + string->str = tmp; + free (defer); + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if (*p == 0 || * (p+prev_len) == 0) + { + rest = ctx_strdup (""); + } + else + { + if (p + prev_len >= string->length + string->str) + { rest = ctx_strdup (""); } + else + { rest = ctx_strdup (p + prev_len); } + } + memcpy (p, new_glyph, new_len); + memcpy (p + new_len, rest, strlen (rest) + 1); + string->length += new_len; + string->length -= prev_len; + free (rest); + //string->length = strlen (string->str); + //string->utf8_length = ctx_utf8_strlen (string->str); +} + +void ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar) +{ + uint8_t utf8[8]; + ctx_unichar_to_utf8 (unichar, utf8); + ctx_string_replace_utf8 (string, pos, (char *) utf8); +} + +uint32_t ctx_string_get_unichar (CtxString *string, int pos) +{ + char *p = (char *) ctx_utf8_skip (string->str, pos); + if (!p) + { return 0; } + return ctx_utf8_to_unichar (p); +} + + +void ctx_string_insert_utf8 (CtxString *string, int pos, const char *new_glyph) +{ + int new_len = ctx_utf8_len (*new_glyph); + int old_len = string->utf8_length; + char tmpg[3]=" "; + if (old_len == pos && 0) + { + ctx_string_append_str (string, new_glyph); + return; + } + if (new_len <= 1 && new_glyph[0] < 32) + { + tmpg[0]=new_glyph[0]+64; + new_glyph = tmpg; + } + { + for (int i = old_len; i <= pos; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + if (string->length + new_len + 1 > string->allocated_length) + { + char *tmp; + char *defer; + string->allocated_length = string->length + new_len + 1; + tmp = (char*) ctx_calloc (string->allocated_length + 1, 1); + strcpy (tmp, string->str); + defer = string->str; + string->str = tmp; + free (defer); + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if ( (*p == 0 || * (p+prev_len) == 0) && pos != 0) + { + rest = ctx_strdup (""); + } + else + { + rest = ctx_strdup (p); + } + memcpy (p, new_glyph, new_len); + memcpy (p + new_len, rest, strlen (rest) + 1); + free (rest); + string->length = strlen (string->str); + string->utf8_length = ctx_utf8_strlen (string->str); +} + +void ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar) +{ + uint8_t utf8[5]=""; + utf8[ctx_unichar_to_utf8(unichar, utf8)]=0; + ctx_string_insert_utf8 (string, pos, (char*)utf8); +} + +void ctx_string_remove (CtxString *string, int pos) +{ + int old_len = string->utf8_length; + { + for (int i = old_len; i <= pos; i++) + { + _ctx_string_append_byte (string, ' '); + old_len++; + } + } + char *p = (char *) ctx_utf8_skip (string->str, pos); + int prev_len = ctx_utf8_len (*p); + char *rest; + if (!p || *p == 0) + { + return; + rest = ctx_strdup (""); + prev_len = 0; + } + else if (* (p+prev_len) == 0) + { + rest = ctx_strdup (""); + } + else + { + rest = ctx_strdup (p + prev_len); + } + strcpy (p, rest); + string->str[string->length - prev_len] = 0; + free (rest); + string->length = strlen (string->str); + string->utf8_length = ctx_utf8_strlen (string->str); +} + +char *ctx_strdup_printf (const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*)malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + return buffer; +} + +void ctx_string_append_printf (CtxString *string, const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*)malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + ctx_string_append_str (string, buffer); + free (buffer); +} + +#if CTX_CAIRO + +typedef struct _CtxCairo CtxCairo; +struct + _CtxCairo +{ + CtxImplementation vfuncs; + Ctx *ctx; + cairo_t *cr; + cairo_pattern_t *pat; + cairo_surface_t *image; + int preserve; +}; + +static void +ctx_cairo_process (CtxCairo *ctx_cairo, CtxCommand *c) +{ + CtxEntry *entry = (CtxEntry *) &c->entry; + cairo_t *cr = ctx_cairo->cr; + switch (entry->code) + { + case CTX_LINE_TO: + cairo_line_to (cr, c->line_to.x, c->line_to.y); + break; + case CTX_REL_LINE_TO: + cairo_rel_line_to (cr, c->rel_line_to.x, c->rel_line_to.y); + break; + case CTX_MOVE_TO: + cairo_move_to (cr, c->move_to.x, c->move_to.y); + break; + case CTX_REL_MOVE_TO: + cairo_rel_move_to (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_CURVE_TO: + cairo_curve_to (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + break; + case CTX_REL_CURVE_TO: + cairo_rel_curve_to (cr,ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + break; + case CTX_PRESERVE: + ctx_cairo->preserve = 1; + break; + case CTX_QUAD_TO: + { + double x0, y0; + cairo_get_current_point (cr, &x0, &y0); + float cx = ctx_arg_float (0); + float cy = ctx_arg_float (1); + float x = ctx_arg_float (2); + float y = ctx_arg_float (3); + cairo_curve_to (cr, + (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); + } + break; + case CTX_REL_QUAD_TO: + { + double x0, y0; + cairo_get_current_point (cr, &x0, &y0); + float cx = ctx_arg_float (0) + x0; + float cy = ctx_arg_float (1) + y0; + float x = ctx_arg_float (2) + x0; + float y = ctx_arg_float (3) + y0; + cairo_curve_to (cr, + (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f, + (cx * 2 + x) / 3.0f, (cy * 2 + y) / 3.0f, + x, y); + } + break; + /* rotate/scale/translate does not occur in fully minified data stream */ + case CTX_ROTATE: + cairo_rotate (cr, ctx_arg_float (0) ); + break; + case CTX_SCALE: + cairo_scale (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_TRANSLATE: + cairo_translate (cr, ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_LINE_WIDTH: + cairo_set_line_width (cr, ctx_arg_float (0) ); + break; + case CTX_ARC: +#if 0 + fprintf (stderr, "F %2.1f %2.1f %2.1f %2.1f %2.1f %2.1f\n", + ctx_arg_float(0), + ctx_arg_float(1), + ctx_arg_float(2), + ctx_arg_float(3), + ctx_arg_float(4), + ctx_arg_float(5), + ctx_arg_float(6)); +#endif + if (ctx_arg_float (5) == 1) + cairo_arc (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4) ); + else + cairo_arc_negative (cr, ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4) ); + break; + case CTX_SET_RGBA_U8: + cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ), + ctx_u8_to_float (ctx_arg_u8 (1) ), + ctx_u8_to_float (ctx_arg_u8 (2) ), + ctx_u8_to_float (ctx_arg_u8 (3) ) ); + break; +#if 0 + case CTX_SET_RGBA_STROKE: // XXX : we need to maintain + // state for the two kinds + cairo_set_source_rgba (cr, ctx_arg_u8 (0) /255.0, + ctx_arg_u8 (1) /255.0, + ctx_arg_u8 (2) /255.0, + ctx_arg_u8 (3) /255.0); + break; +#endif + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: // XXX - arcs + cairo_rectangle (cr, c->rectangle.x, c->rectangle.y, + c->rectangle.width, c->rectangle.height); + break; + case CTX_SET_PIXEL: + cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ), + ctx_u8_to_float (ctx_arg_u8 (1) ), + ctx_u8_to_float (ctx_arg_u8 (2) ), + ctx_u8_to_float (ctx_arg_u8 (3) ) ); + cairo_rectangle (cr, ctx_arg_u16 (2), ctx_arg_u16 (3), 1, 1); + cairo_fill (cr); + break; + case CTX_FILL: + if (ctx_cairo->preserve) + { + cairo_fill_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_fill (cr); + } + break; + case CTX_STROKE: + if (ctx_cairo->preserve) + { + cairo_stroke_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_stroke (cr); + } + break; + case CTX_IDENTITY: + cairo_identity_matrix (cr); + break; + case CTX_CLIP: + if (ctx_cairo->preserve) + { + cairo_clip_preserve (cr); + ctx_cairo->preserve = 0; + } + else + { + cairo_clip (cr); + } + break; + break; + case CTX_BEGIN_PATH: + cairo_new_path (cr); + break; + case CTX_CLOSE_PATH: + cairo_close_path (cr); + break; + case CTX_SAVE: + cairo_save (cr); + break; + case CTX_RESTORE: + cairo_restore (cr); + break; + case CTX_FONT_SIZE: + cairo_set_font_size (cr, ctx_arg_float (0) ); + break; + case CTX_MITER_LIMIT: + cairo_set_miter_limit (cr, ctx_arg_float (0) ); + break; + case CTX_LINE_CAP: + { + int cairo_val = CAIRO_LINE_CAP_SQUARE; + switch (ctx_arg_u8 (0) ) + { + case CTX_CAP_ROUND: + cairo_val = CAIRO_LINE_CAP_ROUND; + break; + case CTX_CAP_SQUARE: + cairo_val = CAIRO_LINE_CAP_SQUARE; + break; + case CTX_CAP_NONE: + cairo_val = CAIRO_LINE_CAP_BUTT; + break; + } + cairo_set_line_cap (cr, cairo_val); + } + break; + case CTX_BLEND_MODE: + { + // does not map to cairo + } + break; + case CTX_COMPOSITING_MODE: + { + int cairo_val = CAIRO_OPERATOR_OVER; + switch (ctx_arg_u8 (0) ) + { + case CTX_COMPOSITE_SOURCE_OVER: + cairo_val = CAIRO_OPERATOR_OVER; + break; + case CTX_COMPOSITE_COPY: + cairo_val = CAIRO_OPERATOR_SOURCE; + break; + } + cairo_set_operator (cr, cairo_val); + } + case CTX_LINE_JOIN: + { + int cairo_val = CAIRO_LINE_JOIN_ROUND; + switch (ctx_arg_u8 (0) ) + { + case CTX_JOIN_ROUND: + cairo_val = CAIRO_LINE_JOIN_ROUND; + break; + case CTX_JOIN_BEVEL: + cairo_val = CAIRO_LINE_JOIN_BEVEL; + break; + case CTX_JOIN_MITER: + cairo_val = CAIRO_LINE_JOIN_MITER; + break; + } + cairo_set_line_join (cr, cairo_val); + } + break; + case CTX_LINEAR_GRADIENT: + { + if (ctx_cairo->pat) + { + cairo_pattern_destroy (ctx_cairo->pat); + ctx_cairo->pat = NULL; + } + ctx_cairo->pat = cairo_pattern_create_linear (ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3) ); + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 0, 0, 0, 0, 1); + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 1, 1, 1, 1, 1); + cairo_set_source (cr, ctx_cairo->pat); + } + break; + case CTX_RADIAL_GRADIENT: + { + if (ctx_cairo->pat) + { + cairo_pattern_destroy (ctx_cairo->pat); + ctx_cairo->pat = NULL; + } + ctx_cairo->pat = cairo_pattern_create_radial (ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + cairo_set_source (cr, ctx_cairo->pat); + } + break; + case CTX_GRADIENT_STOP: + cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, + ctx_arg_float (0), + ctx_u8_to_float (ctx_arg_u8 (4) ), + ctx_u8_to_float (ctx_arg_u8 (5) ), + ctx_u8_to_float (ctx_arg_u8 (6) ), + ctx_u8_to_float (ctx_arg_u8 (7) ) ); + break; + // XXX implement TEXTURE +#if 0 + case CTX_LOAD_IMAGE: + { + if (image) + { + cairo_surface_destroy (image); + image = NULL; + } + if (pat) + { + cairo_pattern_destroy (pat); + pat = NULL; + } + image = cairo_image_surface_create_from_png (ctx_arg_string() ); + cairo_set_source_surface (cr, image, ctx_arg_float (0), ctx_arg_float (1) ); + } + break; +#endif + case CTX_TEXT: + /* XXX: implement some linebreaking/wrap, positioning + * behavior here + */ + cairo_show_text (cr, ctx_arg_string () ); + break; + case CTX_CONT: + case CTX_EDGE: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_FLUSH: + break; + } + ctx_process (ctx_cairo->ctx, entry); +} + +void ctx_cairo_free (CtxCairo *ctx_cairo) +{ + if (ctx_cairo->pat) + { cairo_pattern_destroy (ctx_cairo->pat); } + if (ctx_cairo->image) + { cairo_surface_destroy (ctx_cairo->image); } + free (ctx_cairo); +} + +void +ctx_render_cairo (Ctx *ctx, cairo_t *cr) +{ + CtxIterator iterator; + CtxCommand *command; + CtxCairo ctx_cairo = {{(void*)ctx_cairo_process, NULL, NULL}, ctx, cr, NULL, NULL}; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_cairo_process (&ctx_cairo, command); } +} + +Ctx * +ctx_new_for_cairo (cairo_t *cr) +{ + Ctx *ctx = ctx_new (); + CtxCairo *ctx_cairo = calloc(sizeof(CtxCairo),1); + ctx_cairo->vfuncs.free = (void*)ctx_cairo_free; + ctx_cairo->vfuncs.process = (void*)ctx_cairo_process; + ctx_cairo->ctx = ctx; + ctx_cairo->cr = cr; + + ctx_set_renderer (ctx, (void*)ctx_cairo); + return ctx; +} + +#endif + +#if CTX_EVENTS + +static int ctx_find_largest_matching_substring + (const char *X, const char *Y, int m, int n, int *offsetY, int *offsetX) +{ + int longest_common_suffix[2][n+1]; + int best_length = 0; + for (int i=0; i<=m; i++) + { + for (int j=0; j<=n; j++) + { + if (i == 0 || j == 0 || !(X[i-1] == Y[j-1])) + { + longest_common_suffix[i%2][j] = 0; + } + else + { + longest_common_suffix[i%2][j] = longest_common_suffix[(i-1)%2][j-1] + 1; + if (best_length < longest_common_suffix[i%2][j]) + { + best_length = longest_common_suffix[i%2][j]; + if (offsetY) *offsetY = j - best_length; + if (offsetX) *offsetX = i - best_length; + } + } + } + } + return best_length; +} + +typedef struct CtxSpan { + int from_prev; + int start; + int length; +} CtxSpan; + +#define CHUNK_SIZE 32 +#define MIN_MATCH 7 // minimum match length to be encoded +#define WINDOW_PADDING 16 // look-aside amount + +#if 0 +static void _dassert(int line, int condition, const char *str, int foo, int bar, int baz) +{ + if (!condition) + { + FILE *f = fopen ("/tmp/cdebug", "a"); + fprintf (f, "%i: %s %i %i %i\n", line, str, foo, bar, baz); + fclose (f); + } +} +#define dassert(cond, foo, bar, baz) _dassert(__LINE__, cond, #cond, foo, bar ,baz) +#endif +#define dassert(cond, foo, bar, baz) + +/* XXX repeated substring matching is slow, we'll be + * better off with a hash-table with linked lists of + * matching 3-4 characters in previous.. or even + * a naive approach that expects rough alignment.. + */ +static char *encode_in_terms_of_previous ( + const char *src, int src_len, + const char *prev, int prev_len, + int *out_len, + int max_ticks) +{ + CtxString *string = ctx_string_new (""); + CtxList *encoded_list = NULL; + + /* TODO : make expected position offset in prev slide based on + * matches and not be constant */ + + long ticks_start = ctx_ticks (); + int start = 0; + int length = CHUNK_SIZE; + for (start = 0; start < src_len; start += length) + { + CtxSpan *span = calloc (sizeof (CtxSpan), 1); + span->start = start; + if (start + length > src_len) + span->length = src_len - start; + else + span->length = length; + span->from_prev = 0; + ctx_list_append (&encoded_list, span); + } + + for (CtxList *l = encoded_list; l; l = l->next) + { + CtxSpan *span = l->data; + if (!span->from_prev) + { + if (span->length >= MIN_MATCH) + { + int prev_pos = 0; + int curr_pos = 0; + assert(1); +#if 0 + int prev_start = 0; + int prev_window_length = prev_len; +#else + int window_padding = WINDOW_PADDING; + int prev_start = span->start - window_padding; + if (prev_start < 0) + prev_start = 0; + + dassert(span->start>=0 , 0,0,0); + + int prev_window_length = prev_len - prev_start; + if (prev_window_length > span->length + window_padding * 2 + span->start) + prev_window_length = span->length + window_padding * 2 + span->start; +#endif + int match_len = 0; + if (prev_window_length > 0) + match_len = ctx_find_largest_matching_substring(prev + prev_start, src + span->start, prev_window_length, span->length, &curr_pos, &prev_pos); +#if 1 + prev_pos += prev_start; +#endif + + if (match_len >= MIN_MATCH) + { + int start = span->start; + int length = span->length; + + span->from_prev = 1; + span->start = prev_pos; + span->length = match_len; + dassert (span->start >= 0, prev_pos, prev_start, span->start); + dassert (span->length > 0, prev_pos, prev_start, span->length); + + if (curr_pos) + { + CtxSpan *prev = calloc (sizeof (CtxSpan), 1); + prev->start = start; + prev->length = curr_pos; + dassert (prev->start >= 0, prev_pos, prev_start, prev->start); + dassert (prev->length > 0, prev_pos, prev_start, prev->length); + prev->from_prev = 0; + ctx_list_insert_before (&encoded_list, l, prev); + } + + + if (match_len + curr_pos < start + length) + { + CtxSpan *next = calloc (sizeof (CtxSpan), 1); + next->start = start + curr_pos + match_len; + next->length = (start + length) - next->start; + dassert (next->start >= 0, prev_pos, prev_start, next->start); + // dassert (next->length > 0, prev_pos, prev_start, next->length); + next->from_prev = 0; + if (next->length) + { + if (l->next) + ctx_list_insert_before (&encoded_list, l->next, next); + else + ctx_list_append (&encoded_list, next); + } + else + free (next); + } + + if (curr_pos) // step one item back for forloop + { + CtxList *tmp = encoded_list; + int found = 0; + while (!found && tmp && tmp->next) + { + if (tmp->next == l) + { + l = tmp; + break; + } + tmp = tmp->next; + } + } + } + } + } + + if (ctx_ticks ()-ticks_start > (unsigned long)max_ticks) + break; + } + + /* merge adjecant prev span references */ + { + for (CtxList *l = encoded_list; l; l = l->next) + { + CtxSpan *span = l->data; +again: + if (l->next) + { + CtxSpan *next_span = l->next->data; + if (span->from_prev && next_span->from_prev && + span->start + span->length == + next_span->start) + { + span->length += next_span->length; + ctx_list_remove (&encoded_list, next_span); + goto again; + } + } + } + } + + while (encoded_list) + { + CtxSpan *span = encoded_list->data; + if (span->from_prev) + { + char ref[128]; + sprintf (ref, "%c%i %i%c", CTX_CODEC_CHAR, span->start, span->length, CTX_CODEC_CHAR); + ctx_string_append_data (string, ref, strlen(ref)); + } + else + { + for (int i = span->start; i< span->start+span->length; i++) + { + if (src[i] == CTX_CODEC_CHAR) + { + char bytes[2]={CTX_CODEC_CHAR, CTX_CODEC_CHAR}; + ctx_string_append_data (string, bytes, 2); + } + else + { + ctx_string_append_data (string, &src[i], 1); + } + } + } + free (span); + ctx_list_remove (&encoded_list, span); + } + + char *ret = string->str; + if (out_len) *out_len = string->length; + ctx_string_free (string, 0); + return ret; +} + +#if 0 // for documentation/reference purposes +static char *decode_ctx (const char *encoded, int enc_len, const char *prev, int prev_len, int *out_len) +{ + CtxString *string = ctx_string_new (""); + char reference[32]=""; + int ref_len = 0; + int in_ref = 0; + for (int i = 0; i < enc_len; i++) + { + if (encoded[i] == CTX_CODEC_CHAR) + { + if (!in_ref) + { + in_ref = 1; + } + else + { + int start = atoi (reference); + int len = 0; + if (strchr (reference, ' ')) + len = atoi (strchr (reference, ' ')+1); + + if (start < 0)start = 0; + if (start >= prev_len)start = prev_len-1; + if (len + start > prev_len) + len = prev_len - start; + + if (start == 0 && len == 0) + ctx_string_append_byte (string, CTX_CODEC_CHAR); + else + ctx_string_append_data (string, prev + start, len); + ref_len = 0; + in_ref = 0; + } + } + else + { + if (in_ref) + { + if (ref_len < 16) + { + reference[ref_len++] = encoded[i]; + reference[ref_len] = 0; + } + } + else + ctx_string_append_data (string, &encoded[i], 1); + } + } + char *ret = string->str; + if (out_len) *out_len = string->length; + ctx_string_free (string, 0); + return ret; +} +#endif + +#define CTX_START_STRING "U\n" // or " reset " +#define CTX_END_STRING "\nX" // or "\ndone" +#define CTX_END_STRING2 "\n\e" + +int ctx_frame_ack = -1; +static char *prev_frame_contents = NULL; +static int prev_frame_len = 0; + +static void ctx_ctx_flush (CtxCtx *ctxctx) +{ +#if 0 + FILE *debug = fopen ("/tmp/ctx-debug", "a"); + fprintf (debug, "------\n"); +#endif + + if (ctx_native_events) + fprintf (stdout, "\e[?201h"); + fprintf (stdout, "\e[H\e[?25l\e[?200h"); +#if 1 + fprintf (stdout, CTX_START_STRING); + ctx_render_stream (ctxctx->ctx, stdout, 0); + fprintf (stdout, CTX_END_STRING); +#else + { + int cur_frame_len = 0; + char *rest = ctx_render_string (ctxctx->ctx, 0, &cur_frame_len); + char *cur_frame_contents = malloc (cur_frame_len + strlen(CTX_START_STRING) + strlen (CTX_END_STRING) + 1); + + cur_frame_contents[0]=0; + strcat (cur_frame_contents, CTX_START_STRING); + strcat (cur_frame_contents, rest); + strcat (cur_frame_contents, CTX_END_STRING); + free (rest); + cur_frame_len += strlen (CTX_START_STRING) + strlen (CTX_END_STRING); + + if (prev_frame_contents && 0) // XXX : + { + char *encoded; + int encoded_len = 0; + //uint64_t ticks_start = ctx_ticks (); + + encoded = encode_in_terms_of_previous (cur_frame_contents, cur_frame_len, prev_frame_contents, prev_frame_len, &encoded_len, 1000 * 10); +// encoded = strdup (cur_frame_contents); +// encoded_len = strlen (encoded); + //uint64_t ticks_end = ctx_ticks (); + + fwrite (encoded, encoded_len, 1, stdout); +// fwrite (encoded, cur_frame_len, 1, stdout); +#if 0 + fprintf (debug, "---prev-frame(%i)\n%s", (int)strlen(prev_frame_contents), prev_frame_contents); + fprintf (debug, "---cur-frame(%i)\n%s", (int)strlen(cur_frame_contents), cur_frame_contents); + fprintf (debug, "---encoded(%.4f %i)---\n%s--------\n", + (ticks_end-ticks_start)/1000.0, + (int)strlen(encoded), encoded); +#endif + free (encoded); + } + else + { + fwrite (cur_frame_contents, cur_frame_len, 1, stdout); + } + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = cur_frame_contents; + prev_frame_len = cur_frame_len; + } +#endif +#if 0 + fclose (debug); +#endif + fprintf (stdout, CTX_END_STRING2); + + fprintf (stdout, "\e[5n"); + fflush (stdout); + + ctx_frame_ack = 0; + do { + ctx_consume_events (ctxctx->ctx); + } while (ctx_frame_ack != 1); +} + +void ctx_ctx_free (CtxCtx *ctx) +{ + nc_at_exit (); + free (ctx); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +Ctx *ctx_new_ctx (int width, int height) +{ + float font_size = 12.0; + Ctx *ctx = ctx_new (); + CtxCtx *ctxctx = (CtxCtx*)calloc (sizeof (CtxCtx), 1); + fprintf (stdout, "\e[?1049h"); + fflush (stdout); + //fprintf (stderr, "\e[H"); + //fprintf (stderr, "\e[2J"); + ctx_native_events = 1; + if (width <= 0 || height <= 0) + { + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + width = ctxctx->width = ctx_terminal_width (); + height = ctxctx->height = ctx_terminal_height (); + font_size = height / ctxctx->rows; + ctx_font_size (ctx, font_size); + } + else + { + ctxctx->width = width; + ctxctx->height = height; + ctxctx->cols = width / 80; + ctxctx->rows = height / 24; + } + ctxctx->ctx = ctx; + if (!ctx_native_events) + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, ctxctx); + ctx_set_size (ctx, width, height); + ctxctx->flush = (void(*)(void *))ctx_ctx_flush; + ctxctx->free = (void(*)(void *))ctx_ctx_free; + return ctx; +} + +void ctx_ctx_pcm (Ctx *ctx); + +int ctx_ctx_consume_events (Ctx *ctx) +{ + int ix, iy; + CtxCtx *ctxctx = (CtxCtx*)ctx->renderer; + const char *event = NULL; +#if CTX_AUDIO + ctx_ctx_pcm (ctx); +#endif + if (ctx_native_events) + { + float x = 0, y = 0; + int b = 0; + char event_type[128]=""; + event = ctx_native_get_event (ctx, 1000/120); +#if 0 + if(event){ + FILE *file = fopen ("/tmp/log", "a"); + fprintf (file, "[%s]\n", event); + fclose (file); + } +#endif + if (event) + { + sscanf (event, "%s %f %f %i", event_type, &x, &y, &b); + if (!strcmp (event_type, "idle")) + { + } + else if (!strcmp (event_type, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "mouse-drag")|| + !strcmp (event_type, "mouse-motion")) + { + ctx_pointer_motion (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, b, 0); + } + else if (!strcmp (event_type, "message")) + { + ctx_incoming_message (ctx, event + strlen ("message"), 0); + } else if (!strcmp (event, "size-changed")) + { + fprintf (stdout, "\e[H\e[2J\e[?25l"); + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + ctxctx->width = ctx_terminal_width (); + ctxctx->height = ctx_terminal_height (); + ctx_set_size (ctx, ctxctx->width, ctxctx->height); + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = NULL; + prev_frame_len = 0; + ctx_set_dirty (ctx, 1); + //ctx_key_press (ctx, 0, "size-changed", 0); + } + else if (!strcmp (event_type, "keyup")) + { + char buf[4]={ x, 0 }; + ctx_key_up (ctx, (int)x, buf, 0); + } + else if (!strcmp (event_type, "keydown")) + { + char buf[4]={ x, 0 }; + ctx_key_down (ctx, (int)x, buf, 0); + } + else + { + ctx_key_press (ctx, 0, event, 0); + } + } + } + else + { + float x, y; + event = ctx_nct_get_event (ctx, 20, &ix, &iy); + + x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width; + y = (iy - 1.0) / ctxctx->rows * ctx->events.height; + + if (!strcmp (event, "mouse-press")) + { + ctx_pointer_press (ctx, x, y, 0, 0); + ctxctx->was_down = 1; + } else if (!strcmp (event, "mouse-release")) + { + ctx_pointer_release (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-motion")) + { + //nct_set_cursor_pos (backend->term, ix, iy); + //nct_flush (backend->term); + if (ctxctx->was_down) + { + ctx_pointer_release (ctx, x, y, 0, 0); + ctxctx->was_down = 0; + } + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "mouse-drag")) + { + ctx_pointer_motion (ctx, x, y, 0, 0); + } else if (!strcmp (event, "size-changed")) + { + fprintf (stdout, "\e[H\e[2J\e[?25l"); + ctxctx->cols = ctx_terminal_cols (); + ctxctx->rows = ctx_terminal_rows (); + ctxctx->width = ctx_terminal_width (); + ctxctx->height = ctx_terminal_height (); + ctx_set_size (ctx, ctxctx->width, ctxctx->height); + + if (prev_frame_contents) + free (prev_frame_contents); + prev_frame_contents = NULL; + prev_frame_len = 0; + ctx_set_dirty (ctx, 1); + //ctx_key_press (ctx, 0, "size-changed", 0); + } + else + { + if (!strcmp (event, "esc")) + ctx_key_press (ctx, 0, "escape", 0); + else if (!strcmp (event, "space")) + ctx_key_press (ctx, 0, "space", 0); + else if (!strcmp (event, "enter")|| + !strcmp (event, "return")) + ctx_key_press (ctx, 0, "\n", 0); + else + ctx_key_press (ctx, 0, event, 0); + } + } + + return 1; +} + +int ctx_renderer_is_ctx (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_ctx_free) + return 1; + return 0; +} + +#endif + +#if CTX_TILED +static inline int +ctx_tiled_threads_done (CtxTiled *tiled) +{ + int sum = 0; + for (int i = 0; i < _ctx_max_threads; i++) + { + if (tiled->rendered_frame[i] == tiled->render_frame) + sum ++; + } + return sum; +} + +int _ctx_damage_control = 0; + +void ctx_tiled_free (CtxTiled *tiled) +{ + tiled->quit = 1; + mtx_lock (&tiled->mtx); + cnd_broadcast (&tiled->cond); + mtx_unlock (&tiled->mtx); + + while (tiled->thread_quit < _ctx_max_threads) + usleep (1000); + + if (tiled->pixels) + { + free (tiled->pixels); + tiled->pixels = NULL; + for (int i = 0 ; i < _ctx_max_threads; i++) + { + ctx_free (tiled->host[i]); + tiled->host[i]=NULL; + } + + ctx_free (tiled->ctx_copy); + } + // leak? +} +static unsigned char *sdl_icc = NULL; +static long sdl_icc_length = 0; + +inline static void ctx_tiled_flush (CtxTiled *tiled) +{ + if (tiled->shown_frame == tiled->render_frame) + { + int dirty_tiles = 0; + ctx_set_drawlist (tiled->ctx_copy, &tiled->ctx->drawlist.entries[0], + tiled->ctx->drawlist.count * 9); + if (_ctx_enable_hash_cache) + { + Ctx *hasher = ctx_hasher_new (tiled->width, tiled->height, + CTX_HASH_COLS, CTX_HASH_ROWS); + ctx_render_ctx (tiled->ctx_copy, hasher); + + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + uint8_t *new_hash = ctx_hasher_get_hash (hasher, col, row); + if (new_hash && memcmp (new_hash, &tiled->hashes[(row * CTX_HASH_COLS + col) * 20], 20)) + { + memcpy (&tiled->hashes[(row * CTX_HASH_COLS + col)*20], new_hash, 20); + tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1; + dirty_tiles++; + } + else + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = -1; + } + } + free (((CtxHasher*)(hasher->renderer))->hashes); + ctx_free (hasher); + } + else + { + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1; + dirty_tiles++; + } + } + int dirty_no = 0; + if (dirty_tiles) + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + if (tiled->tile_affinity[row * CTX_HASH_COLS + col] != -1) + { + tiled->tile_affinity[row * CTX_HASH_COLS + col] = dirty_no * (_ctx_max_threads) / dirty_tiles; + dirty_no++; + if (col > tiled->max_col) tiled->max_col = col; + if (col < tiled->min_col) tiled->min_col = col; + if (row > tiled->max_row) tiled->max_row = row; + if (row < tiled->min_row) tiled->min_row = row; + } + } + + if (_ctx_damage_control) + { + for (int i = 0; i < tiled->width * tiled->height; i++) + { + tiled->pixels[i*4+2] = (tiled->pixels[i*4+2] + 255)/2; + } + } + + tiled->render_frame = ++tiled->frame; + +#if 0 + + //if (tiled->tile_affinity[hno]==no) + { + int x0 = ((tiled->width)/CTX_HASH_COLS) * 0; + int y0 = ((tiled->height)/CTX_HASH_ROWS) * 0; + int width = tiled->width / CTX_HASH_COLS; + int height = tiled->height / CTX_HASH_ROWS; + Ctx *host = tiled->host[0]; + + CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer; + int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green; + ctx_rasterizer_init (rasterizer, + host, tiled->ctx, &host->state, + &tiled->pixels[tiled->width * 4 * y0 + x0 * 4], + 0, 0, 1, 1, + tiled->width*4, CTX_FORMAT_BGRA8, + tiled->antialias); + ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green; + if (sdl_icc_length) + ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length); + + ctx_translate (host, -x0, -y0); + ctx_render_ctx (tiled->ctx_copy, host); + } +#endif + + + mtx_lock (&tiled->mtx); + cnd_broadcast (&tiled->cond); + mtx_unlock (&tiled->mtx); + } +} + +static +void ctx_tiled_render_fun (void **data) +{ + int no = (size_t)data[0]; + CtxTiled *tiled = data[1]; + + while (!tiled->quit) + { + Ctx *host = tiled->host[no]; + + mtx_lock (&tiled->mtx); + cnd_wait(&tiled->cond, &tiled->mtx); + mtx_unlock (&tiled->mtx); + + if (tiled->render_frame != tiled->rendered_frame[no]) + { + int hno = 0; + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++, hno++) + { + if (tiled->tile_affinity[hno]==no) + { + int x0 = ((tiled->width)/CTX_HASH_COLS) * col; + int y0 = ((tiled->height)/CTX_HASH_ROWS) * row; + int width = tiled->width / CTX_HASH_COLS; + int height = tiled->height / CTX_HASH_ROWS; + + CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer; +#if 1 // merge horizontally adjecant tiles of same affinity into one job + while (col + 1 < CTX_HASH_COLS && + tiled->tile_affinity[hno+1] == no) + { + width += tiled->width / CTX_HASH_COLS; + col++; + hno++; + } +#endif + int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green; + ctx_rasterizer_init (rasterizer, + host, tiled->ctx, &host->state, + &tiled->pixels[tiled->width * 4 * y0 + x0 * 4], + 0, 0, width, height, + tiled->width*4, CTX_FORMAT_BGRA8, + tiled->antialias); + ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green; + if (sdl_icc_length) + ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length); + + ctx_translate (host, -x0, -y0); + ctx_render_ctx (tiled->ctx_copy, host); + } + } + tiled->rendered_frame[no] = tiled->render_frame; + } + } + tiled->thread_quit++; // need atomic? +} + +#endif + + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> +#endif + + +#if CTX_FB + #include <linux/fb.h> + #include <linux/vt.h> + #include <linux/kd.h> + #include <sys/mman.h> + #include <threads.h> + #include <libdrm/drm.h> + #include <libdrm/drm_mode.h> + +typedef struct _EvSource EvSource; + + +struct _EvSource +{ + void *priv; /* private storage */ + + /* returns non 0 if there is events waiting */ + int (*has_event) (EvSource *ev_source); + + /* get an event, the returned event should be freed by the caller */ + char *(*get_event) (EvSource *ev_source); + + /* destroy/unref this instance */ + void (*destroy) (EvSource *ev_source); + + /* get the underlying fd, useful for using select on */ + int (*get_fd) (EvSource *ev_source); + + + void (*set_coord) (EvSource *ev_source, double x, double y); + /* set_coord is needed to warp relative cursors into normalized range, + * like normal mice/trackpads/nipples - to obey edges and more. + */ + + /* if this returns non-0 select can be used for non-blocking.. */ +}; + + +typedef struct _CtxFb CtxFb; +struct _CtxFb +{ + CtxTiled tiled; +#if 0 + void (*render) (void *fb, CtxCommand *command); + void (*reset) (void *fb); + void (*flush) (void *fb); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *fb); + Ctx *ctx; + int width; + int height; + int cols; // unused + int rows; // unused + int was_down; + uint8_t *pixels; + Ctx *ctx_copy; + Ctx *host[CTX_MAX_THREADS]; + CtxAntialias antialias; + int quit; + _Atomic int thread_quit; + int shown_frame; + int render_frame; + int rendered_frame[CTX_MAX_THREADS]; + int frame; + int min_col; // hasher cols and rows + int min_row; + int max_col; + int max_row; + uint8_t hashes[CTX_HASH_ROWS * CTX_HASH_COLS * 20]; + int8_t tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is + // responsible for a tile + // + + + int pointer_down[3]; +#endif + int key_balance; + int key_repeat; + int lctrl; + int lalt; + int rctrl; + + uint8_t *fb; + + int fb_fd; + char *fb_path; + int fb_bits; + int fb_bpp; + int fb_mapped_size; + struct fb_var_screeninfo vinfo; + struct fb_fix_screeninfo finfo; + int vt; + int tty; + int vt_active; + EvSource *evsource[4]; + int evsource_count; + int is_drm; + cnd_t cond; + mtx_t mtx; + struct drm_mode_crtc crtc; +}; + +static char *ctx_fb_clipboard = NULL; +static void ctx_fb_set_clipboard (CtxFb *fb, const char *text) +{ + if (ctx_fb_clipboard) + free (ctx_fb_clipboard); + ctx_fb_clipboard = NULL; + if (text) + { + ctx_fb_clipboard = strdup (text); + } +} + +static char *ctx_fb_get_clipboard (CtxFb *sdl) +{ + if (ctx_fb_clipboard) return strdup (ctx_fb_clipboard); + return strdup (""); +} + +#if UINTPTR_MAX == 0xffFFffFF + #define fbdrmuint_t uint32_t +#elif UINTPTR_MAX == 0xffFFffFFffFFffFF + #define fbdrmuint_t uint64_t +#endif + +void *ctx_fbdrm_new (CtxFb *fb, int *width, int *height) +{ + int got_master = 0; + fb->fb_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); + if (!fb->fb_fd) + return NULL; + static fbdrmuint_t res_conn_buf[20]={0}; // this is static since its contents + // are used by the flip callback + fbdrmuint_t res_fb_buf[20]={0}; + fbdrmuint_t res_crtc_buf[20]={0}; + fbdrmuint_t res_enc_buf[20]={0}; + struct drm_mode_card_res res={0}; + + if (ioctl(fb->fb_fd, DRM_IOCTL_SET_MASTER, 0)) + goto cleanup; + got_master = 1; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) + goto cleanup; + res.fb_id_ptr=(fbdrmuint_t)res_fb_buf; + res.crtc_id_ptr=(fbdrmuint_t)res_crtc_buf; + res.connector_id_ptr=(fbdrmuint_t)res_conn_buf; + res.encoder_id_ptr=(fbdrmuint_t)res_enc_buf; + if(ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) + goto cleanup; + + + unsigned int i; + for (i=0;i<res.count_connectors;i++) + { + struct drm_mode_modeinfo conn_mode_buf[20]={0}; + fbdrmuint_t conn_prop_buf[20]={0}, + conn_propval_buf[20]={0}, + conn_enc_buf[20]={0}; + + struct drm_mode_get_connector conn={0}; + + conn.connector_id=res_conn_buf[i]; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) + goto cleanup; + + conn.modes_ptr=(fbdrmuint_t)conn_mode_buf; + conn.props_ptr=(fbdrmuint_t)conn_prop_buf; + conn.prop_values_ptr=(fbdrmuint_t)conn_propval_buf; + conn.encoders_ptr=(fbdrmuint_t)conn_enc_buf; + + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) + goto cleanup; + + //Check if the connector is OK to use (connected to something) + if (conn.count_encoders<1 || conn.count_modes<1 || !conn.encoder_id || !conn.connection) + continue; + +//------------------------------------------------------------------------------ +//Creating a dumb buffer +//------------------------------------------------------------------------------ + struct drm_mode_create_dumb create_dumb={0}; + struct drm_mode_map_dumb map_dumb={0}; + struct drm_mode_fb_cmd cmd_dumb={0}; + create_dumb.width = conn_mode_buf[0].hdisplay; + create_dumb.height = conn_mode_buf[0].vdisplay; + create_dumb.bpp = 32; + create_dumb.flags = 0; + create_dumb.pitch = 0; + create_dumb.size = 0; + create_dumb.handle = 0; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) || + !create_dumb.handle) + goto cleanup; + + cmd_dumb.width =create_dumb.width; + cmd_dumb.height=create_dumb.height; + cmd_dumb.bpp =create_dumb.bpp; + cmd_dumb.pitch =create_dumb.pitch; + cmd_dumb.depth =24; + cmd_dumb.handle=create_dumb.handle; + if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb)) + goto cleanup; + + map_dumb.handle=create_dumb.handle; + if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb)) + goto cleanup; + + void *base = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, + fb->fb_fd, map_dumb.offset); + if (!base) + { + goto cleanup; + } + *width = create_dumb.width; + *height = create_dumb.height; + + struct drm_mode_get_encoder enc={0}; + enc.encoder_id=conn.encoder_id; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETENCODER, &enc)) + goto cleanup; + + fb->crtc.crtc_id=enc.crtc_id; + if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCRTC, &fb->crtc)) + goto cleanup; + + fb->crtc.fb_id=cmd_dumb.fb_id; + fb->crtc.set_connectors_ptr=(fbdrmuint_t)&res_conn_buf[i]; + fb->crtc.count_connectors=1; + fb->crtc.mode=conn_mode_buf[0]; + fb->crtc.mode_valid=1; + return base; + } +cleanup: + if (got_master) + ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + fb->fb_fd = 0; + return NULL; +} + +void ctx_fbdrm_flip (CtxFb *fb) +{ + if (!fb->fb_fd) + return; + ioctl(fb->fb_fd, DRM_IOCTL_MODE_SETCRTC, &fb->crtc); +} + +void ctx_fbdrm_close (CtxFb *fb) +{ + if (!fb->fb_fd) + return; + ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + close (fb->fb_fd); + fb->fb_fd = 0; +} + +static void ctx_fb_flip (CtxFb *fb) +{ + if (fb->is_drm) + ctx_fbdrm_flip (fb); + else + ioctl (fb->fb_fd, FBIOPAN_DISPLAY, &fb->vinfo); +} + +inline static uint32_t +ctx_swap_red_green2 (uint32_t orig) +{ + uint32_t green_alpha = (orig & 0xff00ff00); + uint32_t red_blue = (orig & 0x00ff00ff); + uint32_t red = red_blue << 16; + uint32_t blue = red_blue >> 16; + return green_alpha | red | blue; +} + +static int ctx_fb_cursor_drawn = 0; +static int ctx_fb_cursor_drawn_x = 0; +static int ctx_fb_cursor_drawn_y = 0; +static CtxCursor ctx_fb_cursor_drawn_shape = 0; + + +#define CTX_FB_HIDE_CURSOR_FRAMES 200 + +static int ctx_fb_cursor_same_pos = CTX_FB_HIDE_CURSOR_FRAMES; + +static inline int ctx_is_in_cursor (int x, int y, int size, CtxCursor shape) +{ + switch (shape) + { + case CTX_CURSOR_ARROW: + if (x > ((size * 4)-y*4)) return 0; + if (x < y && x > y / 16) + return 1; + return 0; + + case CTX_CURSOR_RESIZE_SE: + case CTX_CURSOR_RESIZE_NW: + case CTX_CURSOR_RESIZE_SW: + case CTX_CURSOR_RESIZE_NE: + { + float theta = -45.0/180 * M_PI; + float cos_theta; + float sin_theta; + + if ((shape == CTX_CURSOR_RESIZE_SW) || + (shape == CTX_CURSOR_RESIZE_NE)) + { + theta = -theta; + cos_theta = cos (theta); + sin_theta = sin (theta); + } + else + { + cos_theta = cos (theta); + sin_theta = sin (theta); + } + int rot_x = x * cos_theta - y * sin_theta; + int rot_y = y * cos_theta + x * sin_theta; + x = rot_x; + y = rot_y; + } + /*FALLTHROUGH*/ + case CTX_CURSOR_RESIZE_W: + case CTX_CURSOR_RESIZE_E: + case CTX_CURSOR_RESIZE_ALL: + if (abs (x) < size/2 && abs (y) < size/2) + { + if (abs(y) < size/10) + { + return 1; + } + } + if ((abs (x) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0) + { + if (abs(y) < (size/2.8)-(abs(x) - (size/2))) + return 1; + } + if (shape != CTX_CURSOR_RESIZE_ALL) + break; + /* FALLTHROUGH */ + case CTX_CURSOR_RESIZE_S: + case CTX_CURSOR_RESIZE_N: + if (abs (y) < size/2 && abs (x) < size/2) + { + if (abs(x) < size/10) + { + return 1; + } + } + if ((abs (y) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0) + { + if (abs(x) < (size/2.8)-(abs(y) - (size/2))) + return 1; + } + break; +#if 0 + case CTX_CURSOR_RESIZE_ALL: + if (abs (x) < size/2 && abs (y) < size/2) + { + if (abs (x) < size/10 || abs(y) < size/10) + return 1; + } + break; +#endif + default: + return (x ^ y) & 1; + } + return 0; +} + +static void ctx_fb_undraw_cursor (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int cursor_size = ctx_height (tiled->ctx) / 28; + + if (ctx_fb_cursor_drawn) + { + int no = 0; + int startx = -cursor_size; + int starty = -cursor_size; + if (ctx_fb_cursor_drawn_shape == CTX_CURSOR_ARROW) + startx = starty = 0; + + for (int y = starty; y < cursor_size; y++) + for (int x = startx; x < cursor_size; x++, no+=4) + { + if (x + ctx_fb_cursor_drawn_x < tiled->width && y + ctx_fb_cursor_drawn_y < tiled->height) + { + if (ctx_is_in_cursor (x, y, cursor_size, ctx_fb_cursor_drawn_shape)) + { + int o = ((ctx_fb_cursor_drawn_y + y) * tiled->width + (ctx_fb_cursor_drawn_x + x)) * 4; + fb->fb[o+0]^=0x88; + fb->fb[o+1]^=0x88; + fb->fb[o+2]^=0x88; + } + } + } + + ctx_fb_cursor_drawn = 0; + } +} + +static void ctx_fb_draw_cursor (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int cursor_x = ctx_pointer_x (tiled->ctx); + int cursor_y = ctx_pointer_y (tiled->ctx); + int cursor_size = ctx_height (tiled->ctx) / 28; + CtxCursor cursor_shape = tiled->ctx->cursor; + int no = 0; + + if (cursor_x == ctx_fb_cursor_drawn_x && + cursor_y == ctx_fb_cursor_drawn_y && + cursor_shape == ctx_fb_cursor_drawn_shape) + ctx_fb_cursor_same_pos ++; + else + ctx_fb_cursor_same_pos = 0; + + if (ctx_fb_cursor_same_pos >= CTX_FB_HIDE_CURSOR_FRAMES) + { + if (ctx_fb_cursor_drawn) + ctx_fb_undraw_cursor (fb); + return; + } + + /* no need to flicker when stationary, motion flicker can also be removed + * by combining the previous and next position masks when a motion has + * occured.. + */ + if (ctx_fb_cursor_same_pos && ctx_fb_cursor_drawn) + return; + + ctx_fb_undraw_cursor (fb); + + no = 0; + + int startx = -cursor_size; + int starty = -cursor_size; + + if (cursor_shape == CTX_CURSOR_ARROW) + startx = starty = 0; + + for (int y = starty; y < cursor_size; y++) + for (int x = startx; x < cursor_size; x++, no+=4) + { + if (x + cursor_x < tiled->width && y + cursor_y < tiled->height) + { + if (ctx_is_in_cursor (x, y, cursor_size, cursor_shape)) + { + int o = ((cursor_y + y) * tiled->width + (cursor_x + x)) * 4; + fb->fb[o+0]^=0x88; + fb->fb[o+1]^=0x88; + fb->fb[o+2]^=0x88; + } + } + } + ctx_fb_cursor_drawn = 1; + ctx_fb_cursor_drawn_x = cursor_x; + ctx_fb_cursor_drawn_y = cursor_y; + ctx_fb_cursor_drawn_shape = cursor_shape; +} + +static void ctx_fb_show_frame (CtxFb *fb, int block) +{ + CtxTiled *tiled = (void*)fb; + if (tiled->shown_frame == tiled->render_frame) + { + if (block == 0) // consume event call + { + ctx_fb_draw_cursor (fb); + ctx_fb_flip (fb); + } + return; + } + + if (block) + { + int count = 0; + while (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + { + usleep (500); + count ++; + if (count > 2000) + { + tiled->shown_frame = tiled->render_frame; + return; + } + } + } + else + { + if (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + return; + } + + if (fb->vt_active) + { + int pre_skip = tiled->min_row * tiled->height/CTX_HASH_ROWS * tiled->width; + int post_skip = (CTX_HASH_ROWS-tiled->max_row-1) * tiled->height/CTX_HASH_ROWS * tiled->width; + + int rows = ((tiled->width * tiled->height) - pre_skip - post_skip)/tiled->width; + + int col_pre_skip = tiled->min_col * tiled->width/CTX_HASH_COLS; + int col_post_skip = (CTX_HASH_COLS-tiled->max_col-1) * tiled->width/CTX_HASH_COLS; + if (_ctx_damage_control) + { + pre_skip = post_skip = col_pre_skip = col_post_skip = 0; + } + + if (pre_skip < 0) pre_skip = 0; + if (post_skip < 0) post_skip = 0; + + __u32 dummy = 0; + + if (tiled->min_row == 100){ + pre_skip = 0; + post_skip = 0; + // not when drm ? + ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy); + ctx_fb_undraw_cursor (fb); + } + else + { + + tiled->min_row = 100; + tiled->max_row = 0; + tiled->min_col = 100; + tiled->max_col = 0; + + // not when drm ? + ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy); + ctx_fb_undraw_cursor (fb); + switch (fb->fb_bits) + { + case 32: +#if 1 + { + uint8_t *dst = fb->fb + pre_skip * 4; + uint8_t *src = tiled->pixels + pre_skip * 4; + int pre = col_pre_skip * 4; + int post = col_post_skip * 4; + int core = tiled->width * 4 - pre - post; + for (int i = 0; i < rows; i++) + { + dst += pre; + src += pre; + memcpy (dst, src, core); + src += core; + dst += core; + dst += post; + src += post; + } + } +#else + { int count = tiled->width * tiled->height; + const uint32_t *src = (void*)tiled->pixels; + uint32_t *dst = (void*)fb->fb; + count-= pre_skip; + src+= pre_skip; + dst+= pre_skip; + count-= post_skip; + while (count -- > 0) + { + dst[0] = ctx_swap_red_green2 (src[0]); + src++; + dst++; + } + } +#endif + break; + /* XXX : note: converting a scanline (or all) to target and + * then doing a bulk memcpy be faster (at least with som /dev/fbs) */ + case 24: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 3; + count-= post_skip; + while (count -- > 0) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst+=3; + src+=4; + } + } + break; + case 16: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 2; + while (count -- > 0) + { + int big = ((src[0] >> 3)) + + ((src[1] >> 2)<<5) + + ((src[2] >> 3)<<11); + dst[0] = big & 255; + dst[1] = big >> 8; + dst+=2; + src+=4; + } + } + break; + case 15: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip * 2; + while (count -- > 0) + { + int big = ((src[2] >> 3)) + + ((src[1] >> 2)<<5) + + ((src[0] >> 3)<<10); + dst[0] = big & 255; + dst[1] = big >> 8; + dst+=2; + src+=4; + } + } + break; + case 8: + { int count = tiled->width * tiled->height; + const uint8_t *src = tiled->pixels; + uint8_t *dst = fb->fb; + count-= post_skip; + count-= pre_skip; + src+= pre_skip * 4; + dst+= pre_skip; + while (count -- > 0) + { + dst[0] = ((src[0] >> 5)) + + ((src[1] >> 5)<<3) + + ((src[2] >> 6)<<6); + dst+=1; + src+=4; + } + } + break; + } + } + ctx_fb_cursor_drawn = 0; + ctx_fb_draw_cursor (fb); + ctx_fb_flip (fb); + tiled->shown_frame = tiled->render_frame; + } +} + + +#define evsource_has_event(es) (es)->has_event((es)) +#define evsource_get_event(es) (es)->get_event((es)) +#define evsource_destroy(es) do{if((es)->destroy)(es)->destroy((es));}while(0) +#define evsource_set_coord(es,x,y) do{if((es)->set_coord)(es)->set_coord((es),(x),(y));}while(0) +#define evsource_get_fd(es) ((es)->get_fd?(es)->get_fd((es)):0) + + + +static int mice_has_event (); +static char *mice_get_event (); +static void mice_destroy (); +static int mice_get_fd (EvSource *ev_source); +static void mice_set_coord (EvSource *ev_source, double x, double y); + +static EvSource ctx_ev_src_mice = { + NULL, + (void*)mice_has_event, + (void*)mice_get_event, + (void*)mice_destroy, + mice_get_fd, + mice_set_coord +}; + +typedef struct Mice +{ + int fd; + double x; + double y; + int button; + int prev_state; +} Mice; + +Mice *_mrg_evsrc_coord = NULL; +static int _ctx_mice_fd = 0; + +void _mmm_get_coords (Ctx *ctx, double *x, double *y) +{ + if (!_mrg_evsrc_coord) + return; + if (x) + *x = _mrg_evsrc_coord->x; + if (y) + *y = _mrg_evsrc_coord->y; +} + +static Mice mice; +static Mice* mrg_mice_this = &mice; + +static int mmm_evsource_mice_init () +{ + unsigned char reset[]={0xff}; + /* need to detect which event */ + + mrg_mice_this->prev_state = 0; + mrg_mice_this->fd = open ("/dev/input/mice", O_RDONLY | O_NONBLOCK); + if (mrg_mice_this->fd == -1) + { + fprintf (stderr, "error opening /dev/input/mice device, maybe add user to input group if such group exist, or otherwise make the rights be satisfied.\n"); + return -1; + } + if (write (mrg_mice_this->fd, reset, 1) == -1) + { + // might happen if we're a regular user with only read permission + } + _ctx_mice_fd = mrg_mice_this->fd; + _mrg_evsrc_coord = mrg_mice_this; + return 0; +} + +static void mice_destroy () +{ + if (mrg_mice_this->fd != -1) + close (mrg_mice_this->fd); +} + +static int mice_has_event () +{ + struct timeval tv; + int retval; + + if (mrg_mice_this->fd == -1) + return 0; + + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(mrg_mice_this->fd, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (mrg_mice_this->fd+1, &rfds, NULL, NULL, &tv); + if (retval == 1) + return FD_ISSET (mrg_mice_this->fd, &rfds); + return 0; +} + +static char *mice_get_event () +{ + const char *ret = "mouse-motion"; + double relx, rely; + signed char buf[3]; + int n_read = 0; + CtxFb *fb = ctx_ev_src_mice.priv; + CtxTiled *tiled = (void*)fb; + n_read = read (mrg_mice_this->fd, buf, 3); + if (n_read == 0) + return strdup (""); + relx = buf[1]; + rely = -buf[2]; + + if (relx < 0) + { + if (relx > -6) + relx = - relx*relx; + else + relx = -36; + } + else + { + if (relx < 6) + relx = relx*relx; + else + relx = 36; + } + + if (rely < 0) + { + if (rely > -6) + rely = - rely*rely; + else + rely = -36; + } + else + { + if (rely < 6) + rely = rely*rely; + else + rely = 36; + } + + mrg_mice_this->x += relx; + mrg_mice_this->y += rely; + + if (mrg_mice_this->x < 0) + mrg_mice_this->x = 0; + if (mrg_mice_this->y < 0) + mrg_mice_this->y = 0; + if (mrg_mice_this->x >= tiled->width) + mrg_mice_this->x = tiled->width -1; + if (mrg_mice_this->y >= tiled->height) + mrg_mice_this->y = tiled->height -1; + int button = 0; + + if ((mrg_mice_this->prev_state & 1) != (buf[0] & 1)) + { + if (buf[0] & 1) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 1; + } + else if (buf[0] & 1) + { + ret = "mouse-drag"; + button = 1; + } + + if (!button) + { + if ((mrg_mice_this->prev_state & 2) != (buf[0] & 2)) + { + if (buf[0] & 2) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 3; + } + else if (buf[0] & 2) + { + ret = "mouse-drag"; + button = 3; + } + } + + if (!button) + { + if ((mrg_mice_this->prev_state & 4) != (buf[0] & 4)) + { + if (buf[0] & 4) + { + ret = "mouse-press"; + } + else + { + ret = "mouse-release"; + } + button = 2; + } + else if (buf[0] & 4) + { + ret = "mouse-drag"; + button = 2; + } + } + + mrg_mice_this->prev_state = buf[0]; + + { + char *r = malloc (64); + sprintf (r, "%s %.0f %.0f %i", ret, mrg_mice_this->x, mrg_mice_this->y, button); + return r; + } + + return NULL; +} + +static int mice_get_fd (EvSource *ev_source) +{ + return mrg_mice_this->fd; +} + +static void mice_set_coord (EvSource *ev_source, double x, double y) +{ + mrg_mice_this->x = x; + mrg_mice_this->y = y; +} + +static EvSource *evsource_mice_new (void) +{ + if (mmm_evsource_mice_init () == 0) + { + mrg_mice_this->x = 0; + mrg_mice_this->y = 0; + return &ctx_ev_src_mice; + } + return NULL; +} + +static int evsource_kb_has_event (void); +static char *evsource_kb_get_event (void); +static void evsource_kb_destroy (int sign); +static int evsource_kb_get_fd (void); + +/* kept out of struct to be reachable by atexit */ +static EvSource ctx_ev_src_kb = { + NULL, + (void*)evsource_kb_has_event, + (void*)evsource_kb_get_event, + (void*)evsource_kb_destroy, + (void*)evsource_kb_get_fd, + NULL +}; + +static struct termios orig_attr; + +static void real_evsource_kb_destroy (int sign) +{ + static int done = 0; + + if (sign == 0) + return; + + if (done) + return; + done = 1; + + switch (sign) + { + case -11:break; /* will be called from atexit with sign==-11 */ + case SIGSEGV: break;//fprintf (stderr, " SIGSEGV\n");break; + case SIGABRT: fprintf (stderr, " SIGABRT\n");break; + case SIGBUS: fprintf (stderr, " SIGBUS\n");break; + case SIGKILL: fprintf (stderr, " SIGKILL\n");break; + case SIGINT: fprintf (stderr, " SIGINT\n");break; + case SIGTERM: fprintf (stderr, " SIGTERM\n");break; + case SIGQUIT: fprintf (stderr, " SIGQUIT\n");break; + default: fprintf (stderr, "sign: %i\n", sign); + fprintf (stderr, "%i %i %i %i %i %i %i\n", SIGSEGV, SIGABRT, SIGBUS, SIGKILL, SIGINT, SIGTERM, SIGQUIT); + } + tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr); + //fprintf (stderr, "evsource kb destroy\n"); +} + +static void evsource_kb_destroy (int sign) +{ + real_evsource_kb_destroy (-11); +} + +static int evsource_kb_init () +{ +// ioctl(STDIN_FILENO, KDSKBMODE, K_RAW); + atexit ((void*) real_evsource_kb_destroy); + signal (SIGSEGV, (void*) real_evsource_kb_destroy); + signal (SIGABRT, (void*) real_evsource_kb_destroy); + signal (SIGBUS, (void*) real_evsource_kb_destroy); + signal (SIGKILL, (void*) real_evsource_kb_destroy); + signal (SIGINT, (void*) real_evsource_kb_destroy); + signal (SIGTERM, (void*) real_evsource_kb_destroy); + signal (SIGQUIT, (void*) real_evsource_kb_destroy); + + struct termios raw; + if (tcgetattr (STDIN_FILENO, &orig_attr) == -1) + { + fprintf (stderr, "error initializing keyboard\n"); + return -1; + } + raw = orig_attr; + + cfmakeraw (&raw); + + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) + return 0; // XXX? return other value? + + return 0; +} +static int evsource_kb_has_event (void) +{ + struct timeval tv; + int retval; + + fd_set rfds; + FD_ZERO (&rfds); + FD_SET(STDIN_FILENO, &rfds); + tv.tv_sec = 0; tv.tv_usec = 0; + retval = select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv); + return retval == 1; +} + +/* note that a nick can have multiple occurences, the labels + * should be kept the same for all occurences of a combination. + * + * this table is taken from nchanterm. + */ +typedef struct MmmKeyCode { + char *nick; /* programmers name for key */ + char sequence[10]; /* terminal sequence */ +} MmmKeyCode; +static const MmmKeyCode ufb_keycodes[]={ + {"up", "\e[A"}, + {"down", "\e[B"}, + {"right", "\e[C"}, + {"left", "\e[D"}, + + {"shift-up", "\e[1;2A"}, + {"shift-down", "\e[1;2B"}, + {"shift-right", "\e[1;2C"}, + {"shift-left", "\e[1;2D"}, + + {"alt-up", "\e[1;3A"}, + {"alt-down", "\e[1;3B"}, + {"alt-right", "\e[1;3C"}, + {"alt-left", "\e[1;3D"}, + {"alt-shift-up", "\e[1;4A"}, + {"alt-shift-down", "\e[1;4B"}, + {"alt-shift-right", "\e[1;4C"}, + {"alt-shift-left", "\e[1;4D"}, + + {"control-up", "\e[1;5A"}, + {"control-down", "\e[1;5B"}, + {"control-right", "\e[1;5C"}, + {"control-left", "\e[1;5D"}, + + /* putty */ + {"control-up", "\eOA"}, + {"control-down", "\eOB"}, + {"control-right", "\eOC"}, + {"control-left", "\eOD"}, + + {"control-shift-up", "\e[1;6A"}, + {"control-shift-down", "\e[1;6B"}, + {"control-shift-right", "\e[1;6C"}, + {"control-shift-left", "\e[1;6D"}, + + {"control-up", "\eOa"}, + {"control-down", "\eOb"}, + {"control-right", "\eOc"}, + {"control-left", "\eOd"}, + + {"shift-up", "\e[a"}, + {"shift-down", "\e[b"}, + {"shift-right", "\e[c"}, + {"shift-left", "\e[d"}, + + {"insert", "\e[2~"}, + {"delete", "\e[3~"}, + {"page-up", "\e[5~"}, + {"page-down", "\e[6~"}, + {"home", "\eOH"}, + {"end", "\eOF"}, + {"home", "\e[H"}, + {"end", "\e[F"}, + {"control-delete", "\e[3;5~"}, + {"shift-delete", "\e[3;2~"}, + {"control-shift-delete","\e[3;6~"}, + + {"F1", "\e[25~"}, + {"F2", "\e[26~"}, + {"F3", "\e[27~"}, + {"F4", "\e[26~"}, + + + {"F1", "\e[11~"}, + {"F2", "\e[12~"}, + {"F3", "\e[13~"}, + {"F4", "\e[14~"}, + {"F1", "\eOP"}, + {"F2", "\eOQ"}, + {"F3", "\eOR"}, + {"F4", "\eOS"}, + {"F5", "\e[15~"}, + {"F6", "\e[16~"}, + {"F7", "\e[17~"}, + {"F8", "\e[18~"}, + {"F9", "\e[19~"}, + {"F9", "\e[20~"}, + {"F10", "\e[21~"}, + {"F11", "\e[22~"}, + {"F12", "\e[23~"}, + {"tab", {9, '\0'}}, + {"shift-tab", {27, 9, '\0'}}, // also generated by alt-tab in linux console + {"alt-space", {27, ' ', '\0'}}, + {"shift-tab", "\e[Z"}, + {"backspace", {127, '\0'}}, + {"space", " "}, + {"\e", "\e"}, + {"return", {10,0}}, + {"return", {13,0}}, + /* this section could be autogenerated by code */ + {"control-a", {1,0}}, + {"control-b", {2,0}}, + {"control-c", {3,0}}, + {"control-d", {4,0}}, + {"control-e", {5,0}}, + {"control-f", {6,0}}, + {"control-g", {7,0}}, + {"control-h", {8,0}}, /* backspace? */ + {"control-i", {9,0}}, + {"control-j", {10,0}}, + {"control-k", {11,0}}, + {"control-l", {12,0}}, + {"control-n", {14,0}}, + {"control-o", {15,0}}, + {"control-p", {16,0}}, + {"control-q", {17,0}}, + {"control-r", {18,0}}, + {"control-s", {19,0}}, + {"control-t", {20,0}}, + {"control-u", {21,0}}, + {"control-v", {22,0}}, + {"control-w", {23,0}}, + {"control-x", {24,0}}, + {"control-y", {25,0}}, + {"control-z", {26,0}}, + {"alt-`", "\e`"}, + {"alt-0", "\e0"}, + {"alt-1", "\e1"}, + {"alt-2", "\e2"}, + {"alt-3", "\e3"}, + {"alt-4", "\e4"}, + {"alt-5", "\e5"}, + {"alt-6", "\e6"}, + {"alt-7", "\e7"}, /* backspace? */ + {"alt-8", "\e8"}, + {"alt-9", "\e9"}, + {"alt-+", "\e+"}, + {"alt--", "\e-"}, + {"alt-/", "\e/"}, + {"alt-a", "\ea"}, + {"alt-b", "\eb"}, + {"alt-c", "\ec"}, + {"alt-d", "\ed"}, + {"alt-e", "\ee"}, + {"alt-f", "\ef"}, + {"alt-g", "\eg"}, + {"alt-h", "\eh"}, /* backspace? */ + {"alt-i", "\ei"}, + {"alt-j", "\ej"}, + {"alt-k", "\ek"}, + {"alt-l", "\el"}, + {"alt-n", "\em"}, + {"alt-n", "\en"}, + {"alt-o", "\eo"}, + {"alt-p", "\ep"}, + {"alt-q", "\eq"}, + {"alt-r", "\er"}, + {"alt-s", "\es"}, + {"alt-t", "\et"}, + {"alt-u", "\eu"}, + {"alt-v", "\ev"}, + {"alt-w", "\ew"}, + {"alt-x", "\ex"}, + {"alt-y", "\ey"}, + {"alt-z", "\ez"}, + /* Linux Console */ + {"home", "\e[1~"}, + {"end", "\e[4~"}, + {"F1", "\e[[A"}, + {"F2", "\e[[B"}, + {"F3", "\e[[C"}, + {"F4", "\e[[D"}, + {"F5", "\e[[E"}, + {"F6", "\e[[F"}, + {"F7", "\e[[G"}, + {"F8", "\e[[H"}, + {"F9", "\e[[I"}, + {"F10", "\e[[J"}, + {"F11", "\e[[K"}, + {"F12", "\e[[L"}, + {NULL, } +}; +static int fb_keyboard_match_keycode (const char *buf, int length, const MmmKeyCode **ret) +{ + int i; + int matches = 0; + + if (!strncmp (buf, "\e[M", MIN(length,3))) + { + if (length >= 6) + return 9001; + return 2342; + } + for (i = 0; ufb_keycodes[i].nick; i++) + if (!strncmp (buf, ufb_keycodes[i].sequence, length)) + { + matches ++; + if ((int)strlen (ufb_keycodes[i].sequence) == length && ret) + { + *ret = &ufb_keycodes[i]; + return 1; + } + } + if (matches != 1 && ret) + *ret = NULL; + return matches==1?2:matches; +} + +//int is_active (void *host) +//{ +// return 1; +//} + +static char *evsource_kb_get_event (void) +{ + unsigned char buf[20]; + int length; + + + for (length = 0; length < 10; length ++) + if (read (STDIN_FILENO, &buf[length], 1) != -1) + { + const MmmKeyCode *match = NULL; + + //if (!is_active (ctx_ev_src_kb.priv)) + // return NULL; + + /* special case ESC, so that we can use it alone in keybindings */ + if (length == 0 && buf[0] == 27) + { + struct timeval tv; + fd_set rfds; + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1000 * 120; + if (select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv) == 0) + return strdup ("escape"); + } + + switch (fb_keyboard_match_keycode ((void*)buf, length + 1, &match)) + { + case 1: /* unique match */ + if (!match) + return NULL; + return strdup (match->nick); + break; + case 0: /* no matches, bail*/ + { + static char ret[256]=""; + if (length == 0 && ctx_utf8_len (buf[0])>1) /* read a + * single unicode + * utf8 character + */ + { + int bytes = read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1); + if (bytes) + { + buf[ctx_utf8_len(buf[0])]=0; + strcpy (ret, (void*)buf); + } + return strdup(ret); //XXX: simplify + } + if (length == 0) /* ascii */ + { + buf[1]=0; + strcpy (ret, (void*)buf); + return strdup(ret); + } + sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'", + length >=0 ? buf[0] : 0, + length >=0 ? buf[0]>31?buf[0]:'?' : ' ', + length >=1 ? buf[1] : 0, + length >=1 ? buf[1]>31?buf[1]:'?' : ' ', + length >=2 ? buf[2] : 0, + length >=2 ? buf[2]>31?buf[2]:'?' : ' ', + length >=3 ? buf[3] : 0, + length >=3 ? buf[3]>31?buf[3]:'?' : ' ', + length >=4 ? buf[4] : 0, + length >=4 ? buf[4]>31?buf[4]:'?' : ' ', + length >=5 ? buf[5] : 0, + length >=5 ? buf[5]>31?buf[5]:'?' : ' ', + length >=6 ? buf[6] : 0, + length >=6 ? buf[6]>31?buf[6]:'?' : ' ' + ); + return strdup(ret); + } + return NULL; + default: /* continue */ + break; + } + } + else + return strdup("key read eek"); + return strdup("fail"); +} + +static int evsource_kb_get_fd (void) +{ + return STDIN_FILENO; +} + + +static EvSource *evsource_kb_new (void) +{ + if (evsource_kb_init() == 0) + { + return &ctx_ev_src_kb; + } + return NULL; +} + +static int event_check_pending (CtxFb *fb) +{ + CtxTiled *tiled = (void*)fb; + int events = 0; + for (int i = 0; i < fb->evsource_count; i++) + { + while (evsource_has_event (fb->evsource[i])) + { + char *event = evsource_get_event (fb->evsource[i]); + if (event) + { + if (fb->vt_active) + { + ctx_key_press (tiled->ctx, 0, event, 0); // we deliver all events as key-press, the key_press handler disambiguates + events++; + } + free (event); + } + } + } + return events; +} + +int ctx_fb_consume_events (Ctx *ctx) +{ + CtxFb *fb = (void*)ctx->renderer; + ctx_fb_show_frame (fb, 0); + event_check_pending (fb); + return 0; +} + +inline static void ctx_fb_reset (CtxFb *fb) +{ + ctx_fb_show_frame (fb, 1); +} + +inline static void ctx_fb_flush (CtxFb *fb) +{ + ctx_tiled_flush ((CtxTiled*)fb); +} + +void ctx_fb_free (CtxFb *fb) +{ + if (fb->is_drm) + { + ctx_fbdrm_close (fb); + } + + ioctl (0, KDSETMODE, KD_TEXT); + if (system("stty sane")){}; + ctx_tiled_free ((CtxTiled*)fb); + //free (fb); +#if CTX_BABL + babl_exit (); +#endif +} + +//static unsigned char *fb_icc = NULL; +//static long fb_icc_length = 0; + +int ctx_renderer_is_fb (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_fb_free) + return 1; + return 0; +} + +static CtxFb *ctx_fb = NULL; +static void vt_switch_cb (int sig) +{ + CtxTiled *tiled = (void*)ctx_fb; + if (sig == SIGUSR1) + { + if (ctx_fb->is_drm) + ioctl(ctx_fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0); + ioctl (0, VT_RELDISP, 1); + ctx_fb->vt_active = 0; + ioctl (0, KDSETMODE, KD_TEXT); + } + else + { + ioctl (0, VT_RELDISP, VT_ACKACQ); + ctx_fb->vt_active = 1; + // queue draw + tiled->render_frame = ++tiled->frame; + ioctl (0, KDSETMODE, KD_GRAPHICS); + if (ctx_fb->is_drm) + { + ioctl(ctx_fb->fb_fd, DRM_IOCTL_SET_MASTER, 0); + ctx_fb_flip (ctx_fb); + } + else + { + tiled->ctx->dirty=1; + + for (int row = 0; row < CTX_HASH_ROWS; row++) + for (int col = 0; col < CTX_HASH_COLS; col++) + { + tiled->hashes[(row * CTX_HASH_COLS + col) * 20] += 1; + } + } + } +} + +static int ctx_fb_get_mice_fd (Ctx *ctx) +{ + //CtxFb *fb = (void*)ctx->renderer; + return _ctx_mice_fd; +} + +Ctx *ctx_new_fb (int width, int height, int drm) +{ +#if CTX_RASTERIZER + CtxFb *fb = calloc (sizeof (CtxFb), 1); + + CtxTiled *tiled = (void*)fb; + ctx_fb = fb; + if (drm) + fb->fb = ctx_fbdrm_new (fb, &tiled->width, &tiled->height); + if (fb->fb) + { + fb->is_drm = 1; + width = tiled->width; + height = tiled->height; + /* + we're ignoring the input width and height , + maybe turn them into properties - for + more generic handling. + */ + fb->fb_mapped_size = tiled->width * tiled->height * 4; + fb->fb_bits = 32; + fb->fb_bpp = 4; + } + else + { + fb->fb_fd = open ("/dev/fb0", O_RDWR); + if (fb->fb_fd > 0) + fb->fb_path = strdup ("/dev/fb0"); + else + { + fb->fb_fd = open ("/dev/graphics/fb0", O_RDWR); + if (fb->fb_fd > 0) + { + fb->fb_path = strdup ("/dev/graphics/fb0"); + } + else + { + free (fb); + return NULL; + } + } + + if (ioctl(fb->fb_fd, FBIOGET_FSCREENINFO, &fb->finfo)) + { + fprintf (stderr, "error getting fbinfo\n"); + close (fb->fb_fd); + free (fb->fb_path); + free (fb); + return NULL; + } + + if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &fb->vinfo)) + { + fprintf (stderr, "error getting fbinfo\n"); + close (fb->fb_fd); + free (fb->fb_path); + free (fb); + return NULL; + } + +//fprintf (stderr, "%s\n", fb->fb_path); + width = tiled->width = fb->vinfo.xres; + height = tiled->height = fb->vinfo.yres; + + fb->fb_bits = fb->vinfo.bits_per_pixel; +//fprintf (stderr, "fb bits: %i\n", fb->fb_bits); + + if (fb->fb_bits == 16) + fb->fb_bits = + fb->vinfo.red.length + + fb->vinfo.green.length + + fb->vinfo.blue.length; + + else if (fb->fb_bits == 8) + { + unsigned short red[256], green[256], blue[256]; + unsigned short original_red[256]; + unsigned short original_green[256]; + unsigned short original_blue[256]; + struct fb_cmap cmap = {0, 256, red, green, blue, NULL}; + struct fb_cmap original_cmap = {0, 256, original_red, original_green, original_blue, NULL}; + int i; + + /* do we really need to restore it ? */ + if (ioctl (fb->fb_fd, FBIOPUTCMAP, &original_cmap) == -1) + { + fprintf (stderr, "palette initialization problem %i\n", __LINE__); + } + + for (i = 0; i < 256; i++) + { + red[i] = ((( i >> 5) & 0x7) << 5) << 8; + green[i] = ((( i >> 2) & 0x7) << 5) << 8; + blue[i] = ((( i >> 0) & 0x3) << 6) << 8; + } + + if (ioctl (fb->fb_fd, FBIOPUTCMAP, &cmap) == -1) + { + fprintf (stderr, "palette initialization problem %i\n", __LINE__); + } + } + + fb->fb_bpp = fb->vinfo.bits_per_pixel / 8; + fb->fb_mapped_size = fb->finfo.smem_len; + + fb->fb = mmap (NULL, fb->fb_mapped_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb->fb_fd, 0); + } + if (!fb->fb) + return NULL; + tiled->pixels = calloc (fb->fb_mapped_size, 1); + ctx_fb_events = 1; + +#if CTX_BABL + babl_init (); +#endif + + ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length); + + tiled->ctx = ctx_new (); + tiled->ctx_copy = ctx_new (); + tiled->width = width; + tiled->height = height; + + ctx_set_renderer (tiled->ctx, fb); + ctx_set_renderer (tiled->ctx_copy, fb); + ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx); + + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + + tiled->flush = (void*)ctx_fb_flush; + tiled->reset = (void*)ctx_fb_reset; + tiled->free = (void*)ctx_fb_free; + tiled->set_clipboard = (void*)ctx_fb_set_clipboard; + tiled->get_clipboard = (void*)ctx_fb_get_clipboard; + + for (int i = 0; i < _ctx_max_threads; i++) + { + tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels, + tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS, + tiled->width * 4, CTX_FORMAT_BGRA8); // this format + // is overriden in thread + ((CtxRasterizer*)(tiled->host[i]->renderer))->swap_red_green = 1; + ctx_set_texture_source (tiled->host[i], tiled->ctx); + } + + mtx_init (&tiled->mtx, mtx_plain); + cnd_init (&tiled->cond); + +#define start_thread(no)\ + if(_ctx_max_threads>no){ \ + static void *args[2]={(void*)no, };\ + thrd_t tid;\ + args[1]=fb;\ + thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\ + } + start_thread(0); + start_thread(1); + start_thread(2); + start_thread(3); + start_thread(4); + start_thread(5); + start_thread(6); + start_thread(7); + start_thread(8); + start_thread(9); + start_thread(10); + start_thread(11); + start_thread(12); + start_thread(13); + start_thread(14); + start_thread(15); +#undef start_thread + + ctx_flush (tiled->ctx); + + EvSource *kb = evsource_kb_new (); + if (kb) + { + fb->evsource[fb->evsource_count++] = kb; + kb->priv = fb; + } + EvSource *mice = evsource_mice_new (); + if (mice) + { + fb->evsource[fb->evsource_count++] = mice; + mice->priv = fb; + } + + fb->vt_active = 1; + ioctl(0, KDSETMODE, KD_GRAPHICS); + signal (SIGUSR1, vt_switch_cb); + signal (SIGUSR2, vt_switch_cb); + struct vt_stat st; + if (ioctl (0, VT_GETSTATE, &st) == -1) + { + ctx_log ("VT_GET_MODE on vt %i failed\n", fb->vt); + return NULL; + } + + fb->vt = st.v_active; + + struct vt_mode mode; + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR2; + if (ioctl (0, VT_SETMODE, &mode) < 0) + { + ctx_log ("VT_SET_MODE on vt %i failed\n", fb->vt); + return NULL; + } + + return tiled->ctx; +#else + return NULL; +#endif +} +#else + +int ctx_renderer_is_fb (Ctx *ctx) +{ + return 0; +} +#endif +#endif + +#if CTX_SDL + +/**/ + +typedef struct _CtxSDL CtxSDL; +struct _CtxSDL +{ + CtxTiled tiled; + /* where we diverge from fb*/ + int key_balance; + int key_repeat; + int lctrl; + int lalt; + int rctrl; + int lshift; + int rshift; + + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + +// cnd_t cond; +// mtx_t mtx; + int fullscreen; +}; + +#include "stb_image_write.h" + +void ctx_screenshot (Ctx *ctx, const char *output_path) +{ +#if CTX_SCREENSHOT + int valid = 0; + CtxSDL *sdl = (void*)ctx->renderer; + + if (ctx_renderer_is_sdl (ctx)) valid = 1; +#if CTX_FB + if (ctx_renderer_is_fb (ctx)) valid = 1; +#endif + + if (!valid) + return; + +#if CTX_FB + // we rely on the same layout + for (int i = 0; i < sdl->width * sdl->height; i++) + { + int tmp = sdl->pixels[i*4]; + sdl->pixels[i*4] = sdl->pixels[i*4 + 2]; + sdl->pixels[i*4 + 2] = tmp; + } +#endif + + stbi_write_png (output_path, sdl->width, sdl->height, 4, sdl->pixels, sdl->width*4); + +#if CTX_FB + for (int i = 0; i < sdl->width * sdl->height; i++) + { + int tmp = sdl->pixels[i*4]; + sdl->pixels[i*4] = sdl->pixels[i*4 + 2]; + sdl->pixels[i*4 + 2] = tmp; + } +#endif +#endif +} + +int ctx_show_fps = 1; +void ctx_sdl_set_title (void *self, const char *new_title) +{ + CtxSDL *sdl = self; + if (!ctx_show_fps) + SDL_SetWindowTitle (sdl->window, new_title); +} + +static void ctx_sdl_show_frame (CtxSDL *sdl, int block) +{ + CtxTiled *tiled = &sdl->tiled; + if (tiled->shown_cursor != tiled->ctx->cursor) + { + tiled->shown_cursor = tiled->ctx->cursor; + SDL_Cursor *new_cursor = NULL; + switch (tiled->shown_cursor) + { + case CTX_CURSOR_UNSET: // XXX: document how this differs from none + // perhaps falling back to arrow? + break; + case CTX_CURSOR_NONE: + new_cursor = NULL; + break; + case CTX_CURSOR_ARROW: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + break; + case CTX_CURSOR_CROSSHAIR: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + break; + case CTX_CURSOR_WAIT: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + break; + case CTX_CURSOR_HAND: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + break; + case CTX_CURSOR_IBEAM: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + break; + case CTX_CURSOR_MOVE: + case CTX_CURSOR_RESIZE_ALL: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + break; + case CTX_CURSOR_RESIZE_N: + case CTX_CURSOR_RESIZE_S: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + break; + case CTX_CURSOR_RESIZE_E: + case CTX_CURSOR_RESIZE_W: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + break; + case CTX_CURSOR_RESIZE_NE: + case CTX_CURSOR_RESIZE_SW: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + break; + case CTX_CURSOR_RESIZE_NW: + case CTX_CURSOR_RESIZE_SE: + new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + break; + } + if (new_cursor) + { + SDL_Cursor *old_cursor = SDL_GetCursor(); + SDL_SetCursor (new_cursor); + SDL_ShowCursor (1); + if (old_cursor) + SDL_FreeCursor (old_cursor); + } + else + { + SDL_ShowCursor (0); + } + } + + if (tiled->shown_frame == tiled->render_frame) + { + return; + } + + if (block) + { + int count = 0; + while (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + { + usleep (50); + count ++; + if (count > 2000) + { + tiled->shown_frame = tiled->render_frame; + return; + } + } + } + else + { + if (ctx_tiled_threads_done (tiled) != _ctx_max_threads) + return; + } + + + if (tiled->min_row == 100) + { + } + else + { +#if 1 + int x = tiled->min_col * tiled->width/CTX_HASH_COLS; + int y = tiled->min_row * tiled->height/CTX_HASH_ROWS; + int x1 = (tiled->max_col+1) * tiled->width/CTX_HASH_COLS; + int y1 = (tiled->max_row+1) * tiled->height/CTX_HASH_ROWS; + int width = x1 - x; + int height = y1 - y; +#endif + tiled->min_row = 100; + tiled->max_row = 0; + tiled->min_col = 100; + tiled->max_col = 0; + + SDL_Rect r = {x, y, width, height}; + SDL_UpdateTexture (sdl->texture, &r, + //(void*)sdl->pixels, + (void*)(tiled->pixels + y * tiled->width * 4 + x * 4), + + tiled->width * 4); + SDL_RenderClear (sdl->renderer); + SDL_RenderCopy (sdl->renderer, sdl->texture, NULL, NULL); + SDL_RenderPresent (sdl->renderer); + + + if (ctx_show_fps) + { + static uint64_t prev_time = 0; + static char tmp_title[1024]; + uint64_t time = ctx_ticks (); + sprintf (tmp_title, "FPS: %.1f", 1000000.0/ (time - prev_time)); + prev_time = time; + SDL_SetWindowTitle (sdl->window, tmp_title); + } + } + tiled->shown_frame = tiled->render_frame; +} + +static const char *ctx_sdl_keysym_to_name (unsigned int sym, int *r_keycode) +{ + static char buf[16]=""; + buf[ctx_unichar_to_utf8 (sym, (void*)buf)]=0; + int code = sym; + const char *name = &buf[0]; + switch (sym) + { + case SDLK_RSHIFT: code = 16 ; break; + case SDLK_LSHIFT: code = 16 ; break; + case SDLK_LCTRL: code = 17 ; break; + case SDLK_RCTRL: code = 17 ; break; + case SDLK_LALT: code = 18 ; break; + case SDLK_RALT: code = 18 ; break; + case SDLK_CAPSLOCK: name = "capslock"; code = 20 ; break; + //case SDLK_NUMLOCK: name = "numlock"; code = 144 ; break; + //case SDLK_SCROLLLOCK: name = "scrollock"; code = 145 ; break; + + case SDLK_F1: name = "F1"; code = 112; break; + case SDLK_F2: name = "F2"; code = 113; break; + case SDLK_F3: name = "F3"; code = 114; break; + case SDLK_F4: name = "F4"; code = 115; break; + case SDLK_F5: name = "F5"; code = 116; break; + case SDLK_F6: name = "F6"; code = 117; break; + case SDLK_F7: name = "F7"; code = 118; break; + case SDLK_F8: name = "F8"; code = 119; break; + case SDLK_F9: name = "F9"; code = 120; break; + case SDLK_F10: name = "F10"; code = 121; break; + case SDLK_F11: name = "F11"; code = 122; break; + case SDLK_F12: name = "F12"; code = 123; break; + case SDLK_ESCAPE: name = "escape"; break; + case SDLK_DOWN: name = "down"; code = 40; break; + case SDLK_LEFT: name = "left"; code = 37; break; + case SDLK_UP: name = "up"; code = 38; break; + case SDLK_RIGHT: name = "right"; code = 39; break; + case SDLK_BACKSPACE: name = "backspace"; break; + case SDLK_SPACE: name = "space"; break; + case SDLK_TAB: name = "tab"; break; + case SDLK_DELETE: name = "delete"; code = 46; break; + case SDLK_INSERT: name = "insert"; code = 45; break; + case SDLK_RETURN: + //if (key_repeat == 0) // return never should repeat + name = "return"; // on a DEC like terminal + break; + case SDLK_HOME: name = "home"; code = 36; break; + case SDLK_END: name = "end"; code = 35; break; + case SDLK_PAGEDOWN: name = "page-down"; code = 34; break; + case SDLK_PAGEUP: name = "page-up"; code = 33; break; + case ',': code = 188; break; + case '.': code = 190; break; + case '/': code = 191; break; + case '`': code = 192; break; + case '[': code = 219; break; + case '\\': code = 220; break; + case ']': code = 221; break; + case '\'': code = 222; break; + default: + ; + } + if (sym >= 'a' && sym <='z') code -= 32; + if (r_keycode) + { + *r_keycode = code; + } + return name; +} + +int ctx_sdl_consume_events (Ctx *ctx) +{ + CtxTiled *tiled = (void*)ctx->renderer; + CtxSDL *sdl = (void*)ctx->renderer; + SDL_Event event; + int got_events = 0; + + ctx_sdl_show_frame (sdl, 0); + + while (SDL_PollEvent (&event)) + { + got_events ++; + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + SDL_CaptureMouse (1); + ctx_pointer_press (ctx, event.button.x, event.button.y, event.button.button, 0); + break; + case SDL_MOUSEBUTTONUP: + SDL_CaptureMouse (0); + ctx_pointer_release (ctx, event.button.x, event.button.y, event.button.button, 0); + break; + case SDL_MOUSEMOTION: + // XXX : look at mask and generate motion for each pressed + // button + ctx_pointer_motion (ctx, event.motion.x, event.motion.y, 1, 0); + break; + case SDL_FINGERMOTION: + ctx_pointer_motion (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + break; + case SDL_FINGERDOWN: + { + static int fdowns = 0; + fdowns ++; + if (fdowns > 1) // the very first finger down from SDL seems to be + // mirrored as mouse events, later ones not - at + // least under wayland + { + ctx_pointer_press (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + } + } + break; + case SDL_FINGERUP: + ctx_pointer_release (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, + (event.tfinger.fingerId%10) + 4, 0); + break; +#if 1 + case SDL_TEXTINPUT: + // if (!active) + // break; + if (!sdl->lctrl && !sdl->rctrl && !sdl->lalt + //&& ( (vt && vt_keyrepeat (vt) ) || (key_repeat==0) ) + ) + { + const char *name = event.text.text; + int keycode = 0; + if (!strcmp (name, " ") ) { name = "space"; } + if (name[0] && name[1] == 0) + { + keycode = name[0]; + keycode = toupper (keycode); + switch (keycode) + { + case '.': keycode = 190; break; + case ';': keycode = 59; break; + case ',': keycode = 188; break; + case '/': keycode = 191; break; + case '\'': keycode = 222; break; + case '`': keycode = 192; break; + case '[': keycode = 219; break; + case ']': keycode = 221; break; + case '\\': keycode = 220; break; + } + } + ctx_key_press (ctx, keycode, name, 0); + //got_event = 1; + } + break; +#endif + case SDL_KEYDOWN: + { + char buf[32] = ""; + const char *name = buf; + if (!event.key.repeat) + { + sdl->key_balance ++; + sdl->key_repeat = 0; + } + else + { + sdl->key_repeat ++; + } + switch (event.key.keysym.sym) + { + case SDLK_LSHIFT: sdl->lshift = 1; break; + case SDLK_RSHIFT: sdl->rshift = 1; break; + case SDLK_LCTRL: sdl->lctrl = 1; break; + case SDLK_LALT: sdl->lalt = 1; break; + case SDLK_RCTRL: sdl->rctrl = 1; break; + } + if (sdl->lshift | sdl->rshift | sdl->lctrl | sdl->lalt | sdl->rctrl) + { + ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL| + CTX_MODIFIER_STATE_ALT| + CTX_MODIFIER_STATE_SHIFT); + if (sdl->lshift | sdl->rshift) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT; + if (sdl->lctrl | sdl->rctrl) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL; + if (sdl->lalt) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT; + } + int keycode; + name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode); + ctx_key_down (ctx, keycode, name, 0); + + if (strlen (name) + &&(event.key.keysym.mod & (KMOD_CTRL) || + event.key.keysym.mod & (KMOD_ALT) || + strlen (name) >= 2)) + { + if (event.key.keysym.mod & (KMOD_CTRL) ) + { + static char buf[64] = ""; + sprintf (buf, "control-%s", name); + name = buf; + } + if (event.key.keysym.mod & (KMOD_ALT) ) + { + static char buf[128] = ""; + sprintf (buf, "alt-%s", name); + name = buf; + } + if (event.key.keysym.mod & (KMOD_SHIFT) ) + { + static char buf[196] = ""; + sprintf (buf, "shift-%s", name); + name = buf; + } + if (strcmp (name, "space")) + { + ctx_key_press (ctx, keycode, name, 0); + } + } + else + { +#if 0 + ctx_key_press (ctx, 0, buf, 0); +#endif + } + } + break; + case SDL_KEYUP: + { + sdl->key_balance --; + switch (event.key.keysym.sym) + { + case SDLK_LSHIFT: sdl->lshift = 0; break; + case SDLK_RSHIFT: sdl->rshift = 0; break; + case SDLK_LCTRL: sdl->lctrl = 0; break; + case SDLK_RCTRL: sdl->rctrl = 0; break; + case SDLK_LALT: sdl->lalt = 0; break; + } + + { + ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL| + CTX_MODIFIER_STATE_ALT| + CTX_MODIFIER_STATE_SHIFT); + if (sdl->lshift | sdl->rshift) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT; + if (sdl->lctrl | sdl->rctrl) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL; + if (sdl->lalt) + ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT; + } + + int keycode; + const char *name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode); + ctx_key_up (ctx, keycode, name, 0); + } + break; + case SDL_QUIT: + ctx_quit (ctx); + break; + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_RESIZED) + { + ctx_sdl_show_frame (sdl, 1); + int width = event.window.data1; + int height = event.window.data2; + SDL_DestroyTexture (sdl->texture); + sdl->texture = SDL_CreateTexture (sdl->renderer, SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STREAMING, width, height); + free (tiled->pixels); + tiled->pixels = calloc (4, width * height); + + tiled->width = width; + tiled->height = height; + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + } + break; + } + } + return 1; +} +#else +void ctx_screenshot (Ctx *ctx, const char *path) +{ +} +#endif + +#if CTX_SDL + +static void ctx_sdl_set_clipboard (CtxSDL *sdl, const char *text) +{ + if (text) + SDL_SetClipboardText (text); +} + +static char *ctx_sdl_get_clipboard (CtxSDL *sdl) +{ + return SDL_GetClipboardText (); +} + +inline static void ctx_sdl_reset (CtxSDL *sdl) +{ + ctx_sdl_show_frame (sdl, 1); +} + +inline static void ctx_sdl_flush (CtxSDL *sdl) +{ + ctx_tiled_flush ((void*)sdl); + //CtxTiled *tiled = (void*)sdl; +} + +void ctx_sdl_free (CtxSDL *sdl) +{ + + if (sdl->texture) + SDL_DestroyTexture (sdl->texture); + if (sdl->renderer) + SDL_DestroyRenderer (sdl->renderer); + if (sdl->window) + SDL_DestroyWindow (sdl->window); + + ctx_tiled_free ((CtxTiled*)sdl); +#if CTX_BABL + babl_exit (); +#endif +} + + +int ctx_renderer_is_sdl (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_sdl_free) + return 1; + return 0; +} + +void ctx_sdl_set_fullscreen (Ctx *ctx, int val) +{ + CtxSDL *sdl = (void*)ctx->renderer; + + if (val) + { + SDL_SetWindowFullscreen (sdl->window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else + { + SDL_SetWindowFullscreen (sdl->window, 0); + } + // XXX we're presuming success + sdl->fullscreen = val; +} +int ctx_sdl_get_fullscreen (Ctx *ctx) +{ + CtxSDL *sdl = (void*)ctx->renderer; + return sdl->fullscreen; +} + + +Ctx *ctx_new_sdl (int width, int height) +{ +#if CTX_RASTERIZER + + CtxSDL *sdl = (CtxSDL*)calloc (sizeof (CtxSDL), 1); + CtxTiled *tiled = (void*)sdl; + + ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length); + if (width <= 0 || height <= 0) + { + width = 1920; + height = 1080; + } + sdl->window = SDL_CreateWindow("ctx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE); + //sdl->renderer = SDL_CreateRenderer (sdl->window, -1, SDL_RENDERER_SOFTWARE); + sdl->renderer = SDL_CreateRenderer (sdl->window, -1, 0); + if (!sdl->renderer) + { + ctx_free (tiled->ctx); + free (sdl); + return NULL; + } +#if CTX_BABL + babl_init (); +#endif + sdl->fullscreen = 0; + + + ctx_show_fps = getenv ("CTX_SHOW_FPS")!=NULL; + + ctx_sdl_events = 1; + sdl->texture = SDL_CreateTexture (sdl->renderer, + SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STREAMING, + width, height); + + SDL_StartTextInput (); + SDL_EnableScreenSaver (); + + tiled->ctx = ctx_new (); + tiled->ctx_copy = ctx_new (); + tiled->width = width; + tiled->height = height; + tiled->cols = 80; + tiled->rows = 20; + ctx_set_renderer (tiled->ctx, sdl); + ctx_set_renderer (tiled->ctx_copy, sdl); + ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx); + + tiled->pixels = (uint8_t*)malloc (width * height * 4); + + ctx_set_size (tiled->ctx, width, height); + ctx_set_size (tiled->ctx_copy, width, height); + + tiled->flush = (void*)ctx_sdl_flush; + tiled->reset = (void*)ctx_sdl_reset; + tiled->free = (void*)ctx_sdl_free; + tiled->set_clipboard = (void*)ctx_sdl_set_clipboard; + tiled->get_clipboard = (void*)ctx_sdl_get_clipboard; + + for (int i = 0; i < _ctx_max_threads; i++) + { + tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels, + tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS, + tiled->width * 4, CTX_FORMAT_RGBA8); + ctx_set_texture_source (tiled->host[i], tiled->ctx); + } + + mtx_init (&tiled->mtx, mtx_plain); + cnd_init (&tiled->cond); + +#define start_thread(no)\ + if(_ctx_max_threads>no){ \ + static void *args[2]={(void*)no, };\ + thrd_t tid;\ + args[1]=sdl;\ + thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\ + } + start_thread(0); + start_thread(1); + start_thread(2); + start_thread(3); + start_thread(4); + start_thread(5); + start_thread(6); + start_thread(7); + start_thread(8); + start_thread(9); + start_thread(10); + start_thread(11); + start_thread(12); + start_thread(13); + start_thread(14); + start_thread(15); +#undef start_thread + + ctx_flush (tiled->ctx); + return tiled->ctx; +#else + return NULL; +#endif +} +#else + +int ctx_renderer_is_sdl (Ctx *ctx) +{ + return 0; +} +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +typedef struct CtxTermCell +{ + char utf8[5]; + uint8_t fg[4]; + uint8_t bg[4]; + + char prev_utf8[5]; + uint8_t prev_fg[4]; + uint8_t prev_bg[4]; +} CtxTermCell; + +typedef struct CtxTermLine +{ + CtxTermCell *cells; + int maxcol; + int size; +} CtxTermLine; + +typedef enum +{ + CTX_TERM_ASCII, + CTX_TERM_ASCII_MONO, + CTX_TERM_SEXTANT, + CTX_TERM_BRAILLE_MONO, + CTX_TERM_BRAILLE, + CTX_TERM_QUARTER, +} CtxTermMode; + +typedef struct _CtxTerm CtxTerm; +struct _CtxTerm +{ + void (*render) (void *term, CtxCommand *command); + void (*reset) (void *term); + void (*flush) (void *term); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *term); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + + uint8_t *pixels; + + Ctx *host; + CtxList *lines; + CtxTermMode mode; +}; + +static int ctx_term_ch = 8; +static int ctx_term_cw = 8; + +void ctx_term_set (CtxTerm *term, + int col, int row, const char *utf8, + uint8_t *fg, uint8_t *bg) +{ + if (col < 1 || row < 1 || col > term->cols || row > term->rows) return; + while (ctx_list_length (term->lines) < row) + { + ctx_list_append (&term->lines, calloc (sizeof (CtxTermLine), 1)); + } + CtxTermLine *line = ctx_list_nth_data (term->lines, row-1); + assert (line); + if (line->size < col) + { + int new_size = ((col + 128)/128)*128; + line->cells = realloc (line->cells, sizeof (CtxTermCell) * new_size); + memset (&line->cells[line->size], 0, sizeof (CtxTermCell) * (new_size - line->size) ); + line->size = new_size; + } + if (col > line->maxcol) line->maxcol = col; + strncpy (line->cells[col-1].utf8, (char*)utf8, 4); + memcpy (line->cells[col-1].fg, fg, 4); + memcpy (line->cells[col-1].bg, bg, 4); +} + +static int _ctx_term256 = 0; // XXX TODO implement autodetect for this +static long _ctx_curfg = -1; +static long _ctx_curbg = -1; + +static long ctx_rgb_to_long (int r,int g, int b) +{ + return r * 256 * 256 + g * 256 + b; +} + + +static void ctx_term_set_fg (int red, int green, int blue) +{ + long lc = ctx_rgb_to_long (red, green, blue); + if (lc == _ctx_curfg) + return; + _ctx_curfg=lc; + if (_ctx_term256 == 0) + { + printf("\e[38;2;%i;%i;%im", red,green,blue); + } + else + { + int gray = (green /255.0) * 24 + 0.5; + int r = (red/255.0) * 6 + 0.5; + int g = (green/255.0) * 6 + 0.5; + int b = (blue/255.0) * 6 + 0.5; + if (gray > 23) gray = 23; + + if (r > 5) r = 5; + if (g > 5) g = 5; + if (b > 5) b = 5; + + if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66)))) + { + printf("\e[38;5;%im", 16 + 216 + gray); + } + else + printf("\e[38;5;%im", 16 + r * 6 * 6 + g * 6 + b); + } +} + +static void ctx_term_set_bg(int red, int green, int blue) +{ + long lc = ctx_rgb_to_long (red, green, blue); +//if (lc == _ctx_curbg) +// return; + _ctx_curbg=lc; + if (_ctx_term256 == 0) + { + printf("\e[48;2;%i;%i;%im", red,green,blue); + } + else + { + int gray = (green /255.0) * 24 + 0.5; + int r = (red/255.0) * 6 + 0.5; + int g = (green/255.0) * 6 + 0.5; + int b = (blue/255.0) * 6 + 0.5; + if (gray > 23) gray = 23; + + if (r > 5) r = 5; + if (g > 5) g = 5; + if (b > 5) b = 5; + + if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66)))) + { + printf("\e[48;5;%im", 16 + 216 + gray); + } + else + printf("\e[48;5;%im", 16 + r * 6 * 6 + g * 6 + b); + } +} + +static int _ctx_term_force_full = 0; + +void ctx_term_scanout (CtxTerm *term) +{ + int row = 1; + printf ("\e[H"); +// printf ("\e[?25l"); + printf ("\e[0m"); + for (CtxList *l = term->lines; l; l = l->next) + { + CtxTermLine *line = l->data; + for (int col = 1; col <= line->maxcol; col++) + { + CtxTermCell *cell = &line->cells[col-1]; + + if (strcmp(cell->utf8, cell->prev_utf8) || + memcmp(cell->fg, cell->prev_fg, 3) || + memcmp(cell->bg, cell->prev_bg, 3) || _ctx_term_force_full) + { + ctx_term_set_fg (cell->fg[0], cell->fg[1], cell->fg[2]); + ctx_term_set_bg (cell->bg[0], cell->bg[1], cell->bg[2]); + printf ("%s", cell->utf8); + } + else + { + // TODO: accumulate succesive such, and compress them + // into one + printf ("\e[C"); + } + strcpy (cell->prev_utf8, cell->utf8); + memcpy (cell->prev_fg, cell->fg, 3); + memcpy (cell->prev_bg, cell->bg, 3); + } + if (row != term->rows) + printf ("\n\r"); + row ++; + } + printf ("\e[0m"); + //printf ("\e[?25h"); + // +} + +// xx +// xx +// xx +// + +static inline int _ctx_rgba8_manhattan_diff (const uint8_t *a, const uint8_t *b) +{ + int c; + int diff = 0; + for (c = 0; c<3;c++) + diff += ctx_pow2(a[c]-b[c]); + return sqrtf(diff); + return diff; +} + +static void ctx_term_output_buf_half (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + const char *sextants[]={ + " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█", + + }; + for (int row = 0; row < height/2; row++) + { + for (int col = 0; col < width-3; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + int i = 0; + + int rgbasum[2][4] = {0,}; + int sumcount[2]; + + int curdiff = 0; + /* first find starting point colors */ + for (int yi = 0; yi < ctx_term_ch; yi++) + for (int xi = 0; xi < ctx_term_cw; xi++, i++) + { + int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4; + + if (rgba[0][3] == 0) + { + for (int c = 0; c < 3; c++) + rgba[0][c] = pixels[noi + c]; + rgba[0][3] = 255; // used only as mark of in-use + } + else + { + int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + if (diff > curdiff) + { + curdiff = diff; + for (int c = 0; c < 3; c++) + rgba[1][c] = pixels[noi + c]; + } + } + + } + + for (int iters = 0; iters < 1; iters++) + { + i= 0; + for (int i = 0; i < 4; i ++) + rgbasum[0][i] = rgbasum[1][i]=0; + sumcount[0] = sumcount[1] = 0; + + for (int yi = 0; yi < ctx_term_ch; yi++) + for (int xi = 0; xi < ctx_term_cw; xi++, i++) + { + int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4; + + int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]); + int cluster = 0; + if (diff1 <= diff2) + cluster = 0; + else + cluster = 1; + sumcount[cluster]++; + for (int c = 0; c < 3; c++) + rgbasum[cluster][c] += pixels[noi+c]; + } + + + if (sumcount[0]) + for (int c = 0; c < 3; c++) + { + rgba[0][c] = rgbasum[0][c] / sumcount[0]; + } + if (sumcount[1]) + for (int c = 0; c < 3; c++) + { + rgba[1][c] = rgbasum[1][c] / sumcount[1]; + } + } + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + if (pixels_set == 4) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + +void ctx_term_find_color_pair (CtxTerm *term, int x0, int y0, int w, int h, + uint8_t rgba[2][4]) + //uint8_t *rgba0, uint8_t *rgba1) +{ +int curdiff = 0; +int stride = term->width * 4; +uint8_t *pixels = term->pixels; +/* first find starting point colors */ +for (int y = y0; y < y0 + h; y++) + for (int x = x0; x < x0 + w; x++) + { + int noi = (y) * stride + (x) * 4; + + if (rgba[0][3] == 0) + { + for (int c = 0; c < 3; c++) + rgba[0][c] = pixels[noi + c]; + rgba[0][3] = 255; // used only as mark of in-use + } + else + { + int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], &rgba[0][0]); + if (diff > curdiff) + { + curdiff = diff; + for (int c = 0; c < 3; c++) + rgba[1][c] = pixels[noi + c]; + } + } + } + int rgbasum[2][4] = {0,}; + int sumcount[2]; + + for (int iters = 0; iters < 1; iters++) + { + for (int i = 0; i < 4; i ++) + rgbasum[0][i] = rgbasum[1][i]=0; + sumcount[0] = sumcount[1] = 0; + + for (int y = y0; y < y0 + h; y++) + for (int x = x0; x < x0 + w; x++) + { + int noi = (y) * stride + (x) * 4; + + int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]); + int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]); + int cluster = 0; + if (diff1 <= diff2) + cluster = 0; + else + cluster = 1; + sumcount[cluster]++; + for (int c = 0; c < 3; c++) + rgbasum[cluster][c] += pixels[noi+c]; + } + + + if (sumcount[0]) + for (int c = 0; c < 3; c++) + { + rgba[0][c] = rgbasum[0][c] / sumcount[0]; + } + if (sumcount[1]) + for (int c = 0; c < 3; c++) + { + rgba[1][c] = rgbasum[1][c] / sumcount[1]; + } + } + +} + + + +static void ctx_term_output_buf_quarter (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + const char *sextants[]={ + " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█" + + }; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + int pixels_set = 0; + for (int y = 0; y < 2; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + if (pixels_set == 4) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + + +static void ctx_term_output_buf_sextant (uint8_t *pixels, + int width, + int height, + CtxTerm *term) +{ + int stride = width * 4; + + const char *sextants[]={ + " ","🬀","🬁","🬂","🬃","🬄","🬅","🬆","🬇","🬈","🬉","🬊","🬋","🬌","🬍","🬎","🬏","🬐","🬑","🬒","🬓","▌","🬔","🬕","🬖","🬗","🬘","🬙","🬚","🬛","🬜","🬝","🬞","🬟","🬠","🬡","🬢","🬣","🬤","🬥","🬦","🬧","▐","🬨","🬩","🬪","🬫","🬬","🬭","🬮","🬯","🬰","🬱","🬲","🬳","🬴","🬵","🬶","🬷","🬸","🬹","🬺","🬻","█" + }; + + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + + if (pixels_set == 6) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], rgba[0], rgba[1]); + } + } +} + +static void ctx_term_output_buf_ascii (uint8_t *pixels, + int width, + int height, + CtxTerm *term, + int mono) +{ + /* this is a crude ascii-mode built on a quick mapping of sexels to ascii */ + int stride = width * 4; + const char *sextants[]={ + " ","`","'","^","🬃","`","~","\"","-","\"","'","\"","-","\"","~","^",",",";", + "=","/","i","[","p","P","z",")","/","7","f",">","/","F",",","\\",":",":", + "\\","\\","(","T","j","T","]","?","s","\\","<","q","_","=","=","=","c","L", + "Q","C","a","b","J","]","m","b","d","@" + }; + uint8_t black[4] = {0,0,0,255}; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + + if (_ctx_rgba8_manhattan_diff (black, rgba[1]) > + _ctx_rgba8_manhattan_diff (black, rgba[0])) + { + for (int c = 0; c < 4; c ++) + { + int tmp = rgba[0][c]; + rgba[0][c] = rgba[1][c]; + rgba[1][c] = tmp; + } + } + if (mono) + { + rgba[1][0] = 0; + rgba[1][1] = 0; + rgba[1][2] = 0; + } + + + int brightest_dark_diff = _ctx_rgba8_manhattan_diff (black, rgba[0]); + + int pixels_set = 0; + for (int y = 0; y < ctx_term_ch; y++) + for (int x = 0; x < ctx_term_cw; x++) + { + int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; +#undef CHECK_IS_SET + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + + + if (pixels_set == 6 && brightest_dark_diff < 40) + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + else + ctx_term_set (term, col +1, row + 1, sextants[unicode], + rgba[0], rgba[1]); + } + } +} + +static void ctx_term_output_buf_braille (uint8_t *pixels, + int width, + int height, + CtxTerm *term, + int mono) +{ + int reverse = 0; + int stride = width * 4; + uint8_t black[4] = {0,0,0,255}; + for (int row = 0; row < height/ctx_term_ch; row++) + { + for (int col = 0; col < width /ctx_term_cw; col++) + { + int unicode = 0; + int bitno = 0; + uint8_t rgba[2][4] = { + {255,255,255,0}, + {0,0,0,0}}; + + ctx_term_find_color_pair (term, col * ctx_term_cw, + row * ctx_term_ch, + ctx_term_cw, + ctx_term_ch, rgba); + + + /* make darkest consistently be background */ + if (_ctx_rgba8_manhattan_diff (black, rgba[1]) > + _ctx_rgba8_manhattan_diff (black, rgba[0])) + { + for (int c = 0; c < 4; c ++) + { + int tmp = rgba[0][c]; + rgba[0][c] = rgba[1][c]; + rgba[1][c] = tmp; + } + } + if (mono) + { + rgba[1][0] = 0; + rgba[1][1] = 0; + rgba[1][2] = 0; + } + + int pixels_set = 0; + for (int x = 0; x < 2; x++) + for (int y = 0; y < 3; y++) + { + int no = (row * 4 + y) * stride + (col*2+x) * 4; +#define CHECK_IS_SET \ + (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \ + _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1])) + + int set = CHECK_IS_SET; + if (reverse) { set = !set; } + if (set) + { unicode |= (1<< (bitno) ); + pixels_set ++; + } + bitno++; + } + { + int x = 0; + int y = 3; + int no = (row * 4 + y) * stride + (col*2+x) * 4; + int setA = CHECK_IS_SET; + no = (row * 4 + y) * stride + (col*2+x+1) * 4; + int setB = CHECK_IS_SET; + + pixels_set += setA; + pixels_set += setB; +#undef CHECK_IS_SET + if (reverse) { setA = !setA; } + if (reverse) { setB = !setB; } + if (setA != 0 && setB==0) + { unicode += 0x2840; } + else if (setA == 0 && setB) + { unicode += 0x2880; } + else if ( (setA != 0) && (setB != 0) ) + { unicode += 0x28C0; } + else + { unicode += 0x2800; } + char utf8[5]; + utf8[ctx_unichar_to_utf8 (unicode, (uint8_t*)utf8)]=0; + +#if 0 + if (pixels_set == 8) + { + if (rgba[0][0] < 32 && rgba[0][1] < 32 && rgba[0][2] < 32) + { + ctx_term_set (term, col +1, row + 1, " ", + rgba[1], rgba[0]); + continue; + } + } +#endif + { + ctx_term_set (term, col +1, row + 1, utf8, + rgba[0], rgba[1]); + } + } + } + } +} + + +inline static void ctx_term_render (void *ctx, + CtxCommand *command) +{ + CtxTerm *term = (void*)ctx; + /* directly forward */ + ctx_process (term->host, &command->entry); +} + +inline static void ctx_term_flush (CtxTerm *term) +{ + int width = term->width; + int height = term->height; + switch (term->mode) + { + case CTX_TERM_QUARTER: + ctx_term_output_buf_quarter (term->pixels, + width, height, term); + break; + case CTX_TERM_ASCII: + ctx_term_output_buf_ascii (term->pixels, + width, height, term, 0); + break; + case CTX_TERM_ASCII_MONO: + ctx_term_output_buf_ascii (term->pixels, + width, height, term, 1); + break; + case CTX_TERM_SEXTANT: + ctx_term_output_buf_sextant (term->pixels, + width, height, term); + break; + case CTX_TERM_BRAILLE: + ctx_term_output_buf_braille (term->pixels, + width, height, term, 0); + break; + case CTX_TERM_BRAILLE_MONO: + ctx_term_output_buf_braille (term->pixels, + width, height, term, 1); + break; + } +#if CTX_BRAILLE_TEXT + CtxRasterizer *rasterizer = (CtxRasterizer*)(term->host->renderer); + // XXX instead sort and inject along with braille + // + + //uint8_t rgba_bg[4]={0,0,0,0}; + //uint8_t rgba_fg[4]={255,0,255,255}; + + for (CtxList *l = rasterizer->glyphs; l; l = l->next) + { + CtxTermGlyph *glyph = l->data; + + uint8_t *pixels = term->pixels; + long rgb_sum[4]={0,0,0}; + for (int v = 0; v < ctx_term_ch; v ++) + for (int u = 0; u < ctx_term_cw; u ++) + { + int i = ((glyph->row-1) * ctx_term_ch + v) * rasterizer->blit_width + + ((glyph->col-1) * ctx_term_cw + u); + for (int c = 0; c < 3; c ++) + rgb_sum[c] += pixels[i*4+c]; + } + for (int c = 0; c < 3; c ++) + glyph->rgba_bg[c] = rgb_sum[c] / (ctx_term_ch * ctx_term_cw); + char utf8[8]; + utf8[ctx_unichar_to_utf8(glyph->unichar, (uint8_t*)utf8)]=0; + ctx_term_set (term, glyph->col, glyph->row, + utf8, glyph->rgba_fg, glyph->rgba_bg); + free (glyph); + } + + printf ("\e[H"); + printf ("\e[0m"); + ctx_term_scanout (term); + printf ("\e[0m"); + fflush(NULL); + while (rasterizer->glyphs) + ctx_list_remove (&rasterizer->glyphs, rasterizer->glyphs->data); +#endif +} + +void ctx_term_free (CtxTerm *term) +{ + while (term->lines) + { + free (term->lines->data); + ctx_list_remove (&term->lines, term->lines->data); + } + printf ("\e[?25h"); // cursor on + nc_at_exit (); + free (term->pixels); + ctx_free (term->host); + free (term); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +int ctx_renderer_is_term (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_term_free) + return 1; + return 0; +} + +float ctx_term_get_cell_width (Ctx *ctx) +{ + return ctx_term_cw; +} + +float ctx_term_get_cell_height (Ctx *ctx) +{ + return ctx_term_ch; +} + +Ctx *ctx_new_term (int width, int height) +{ + Ctx *ctx = ctx_new (); +#if CTX_RASTERIZER + CtxTerm *term = (CtxTerm*)calloc (sizeof (CtxTerm), 1); + + const char *mode = getenv ("CTX_TERM_MODE"); + ctx_term_cw = 2; + ctx_term_ch = 3; + + if (!mode) term->mode = CTX_TERM_SEXTANT; + else if (!strcmp (mode, "sextant")) term->mode = CTX_TERM_SEXTANT; + else if (!strcmp (mode, "ascii")) term->mode = CTX_TERM_ASCII_MONO; + //else if (!strcmp (mode, "ascii-mono")) term->mode = CTX_TERM_ASCII_MONO; + else if (!strcmp (mode, "quarter")) term->mode = CTX_TERM_QUARTER; + //else if (!strcmp (mode, "braille")){ + // term->mode = CTX_TERM_BRAILLE; + // ctx_term_ch = 4; + //} + else if (!strcmp (mode, "braille")){ + term->mode = CTX_TERM_BRAILLE_MONO; + ctx_term_ch = 4; + } + else { + fprintf (stderr, "recognized values for CTX_TERM_MODE:\n" + " sextant ascii quarter braille\n"); + exit (1); + } + + mode = getenv ("CTX_TERM_FORCE_FULL"); + if (mode && strcmp (mode, "0") && strcmp (mode, "no")) + _ctx_term_force_full = 1; + + fprintf (stdout, "\e[?1049h"); + fprintf (stdout, "\e[?25l"); // cursor off + + int maxwidth = ctx_terminal_cols () * ctx_term_cw; + int maxheight = (ctx_terminal_rows ()) * ctx_term_ch; + if (width <= 0 || height <= 0) + { + width = maxwidth; + height = maxheight; + } + if (width > maxwidth) width = maxwidth; + if (height > maxheight) height = maxheight; + term->ctx = ctx; + term->width = width; + term->height = height; + + term->cols = (width + 1) / ctx_term_cw; + term->rows = (height + 2) / ctx_term_ch; + term->lines = 0; + term->pixels = (uint8_t*)malloc (width * height * 4); + term->host = ctx_new_for_framebuffer (term->pixels, + width, height, + width * 4, CTX_FORMAT_RGBA8); +#if CTX_BRAILLE_TEXT + ((CtxRasterizer*)term->host->renderer)->term_glyphs=1; +#endif + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, term); + ctx_set_size (ctx, width, height); + ctx_font_size (ctx, ctx_term_ch); + term->render = ctx_term_render; + term->flush = (void(*)(void*))ctx_term_flush; + term->free = (void(*)(void*))ctx_term_free; +#endif + + + return ctx; +} + +#endif + +#if CTX_EVENTS + +#if !__COSMOPOLITAN__ +#include <fcntl.h> +#include <sys/ioctl.h> +#endif + +typedef struct _CtxTermImg CtxTermImg; +struct _CtxTermImg +{ + void (*render) (void *termimg, CtxCommand *command); + void (*reset) (void *termimg); + void (*flush) (void *termimg); + char *(*get_clipboard) (void *ctxctx); + void (*set_clipboard) (void *ctxctx, const char *text); + void (*free) (void *termimg); + Ctx *ctx; + int width; + int height; + int cols; + int rows; + int was_down; + // we need to have the above members in that order up to here + uint8_t *pixels; + Ctx *host; + CtxList *lines; +}; + +inline static void ctx_termimg_render (void *ctx, + CtxCommand *command) +{ + CtxTermImg *termimg = (void*)ctx; + /* directly forward */ + ctx_process (termimg->host, &command->entry); +} + +inline static void ctx_termimg_flush (CtxTermImg *termimg) +{ + int width = termimg->width; + int height = termimg->height; + if (!termimg->pixels) return; + char *encoded = malloc (width * height * 3 * 3); + ctx_bin2base64 (termimg->pixels, width * height * 3, + encoded); + int encoded_len = strlen (encoded); + + int i = 0; + + printf ("\e[H"); + printf ("\e_Gf=24,s=%i,v=%i,t=d,a=T,m=1;\e\\", width, height); + while (i < encoded_len) + { + if (i + 4096 < encoded_len) + { + printf ("\e_Gm=1;"); + } + else + { + printf ("\e_Gm=0;"); + } + for (int n = 0; n < 4000 && i < encoded_len; n++) + { + printf ("%c", encoded[i]); + i++; + } + printf ("\e\\"); + } + free (encoded); + + fflush (NULL); +} + +void ctx_termimg_free (CtxTermImg *termimg) +{ + while (termimg->lines) + { + free (termimg->lines->data); + ctx_list_remove (&termimg->lines, termimg->lines->data); + } + printf ("\e[?25h"); // cursor on + nc_at_exit (); + free (termimg->pixels); + ctx_free (termimg->host); + free (termimg); + /* we're not destoring the ctx member, this is function is called in ctx' teardown */ +} + +int ctx_renderer_is_termimg (Ctx *ctx) +{ + if (ctx->renderer && + ctx->renderer->free == (void*)ctx_termimg_free) + return 1; + return 0; +} + +Ctx *ctx_new_termimg (int width, int height) +{ + Ctx *ctx = ctx_new (); +#if CTX_RASTERIZER + fprintf (stdout, "\e[?1049h"); + fprintf (stdout, "\e[?25l"); // cursor off + CtxTermImg *termimg = (CtxTermImg*)calloc (sizeof (CtxTermImg), 1); + + + int maxwidth = ctx_terminal_width (); + + int colwidth = maxwidth/ctx_terminal_cols (); + maxwidth-=colwidth; + + int maxheight = ctx_terminal_height (); + if (width <= 0 || height <= 0) + { + width = maxwidth; + height = maxheight; + } + if (width > maxwidth) width = maxwidth; + if (height > maxheight) height = maxheight; + termimg->ctx = ctx; + termimg->width = width; + termimg->height = height; + termimg->lines = 0; + termimg->pixels = (uint8_t*)malloc (width * height * 3); + termimg->host = ctx_new_for_framebuffer (termimg->pixels, + width, height, + width * 3, CTX_FORMAT_RGB8); + _ctx_mouse (ctx, NC_MOUSE_DRAG); + ctx_set_renderer (ctx, termimg); + ctx_set_size (ctx, width, height); + ctx_font_size (ctx, 14.0f); + termimg->render = ctx_termimg_render; + termimg->flush = (void(*)(void*))ctx_termimg_flush; + termimg->free = (void(*)(void*))ctx_termimg_free; +#endif + + return ctx; +} + +#endif + +#if CTX_FORMATTER + +typedef struct _CtxFormatter CtxFormatter; +struct _CtxFormatter +{ + void *target; // FILE + int longform; + int indent; + + void (*add_str)(CtxFormatter *formatter, const char *str, int len); +}; + +static inline void ctx_formatter_addstr (CtxFormatter *formatter, const char *str, int len) +{ + formatter->add_str (formatter, str, len); +} + +static inline void ctx_formatter_addstrf (CtxFormatter *formatter, const char *format, ...) +{ + va_list ap; + size_t needed; + char *buffer; + va_start (ap, format); + needed = vsnprintf (NULL, 0, format, ap) + 1; + buffer = (char*) malloc (needed); + va_end (ap); + va_start (ap, format); + vsnprintf (buffer, needed, format, ap); + va_end (ap); + ctx_formatter_addstr (formatter, buffer, -1); + free (buffer); +} + +static void _ctx_stream_addstr (CtxFormatter *formatter, const char *str, int len) +{ + if (!str || len == 0) + { + return; + } + if (len < 0) len = strlen (str); + fwrite (str, len, 1, (FILE*)formatter->target); +} + +void _ctx_string_addstr (CtxFormatter *formatter, const char *str, int len) +{ + if (!str || len == 0) + { + return; + } + if (len < 0) len = strlen (str); + ctx_string_append_data ((CtxString*)(formatter->target), str, len); +} + + +static void _ctx_print_endcmd (CtxFormatter *formatter) +{ + if (formatter->longform) + { + ctx_formatter_addstr (formatter, ");\n", 3); + } +} + +static void _ctx_indent (CtxFormatter *formatter) +{ + for (int i = 0; i < formatter->indent; i++) + { ctx_formatter_addstr (formatter, " ", 2); + } +} + +const char *_ctx_code_to_name (int code) +{ + switch (code) + { + case CTX_REL_LINE_TO_X4: return "relLinetoX4"; break; + case CTX_REL_LINE_TO_REL_CURVE_TO: return "relLineToRelCurveTo"; break; + case CTX_REL_CURVE_TO_REL_LINE_TO: return "relCurveToRelLineTo"; break; + case CTX_REL_CURVE_TO_REL_MOVE_TO: return "relCurveToRelMoveTo"; break; + case CTX_REL_LINE_TO_X2: return "relLineToX2"; break; + case CTX_MOVE_TO_REL_LINE_TO: return "moveToRelLineTo"; break; + case CTX_REL_LINE_TO_REL_MOVE_TO: return "relLineToRelMoveTo"; break; + case CTX_FILL_MOVE_TO: return "fillMoveTo"; break; + case CTX_REL_QUAD_TO_REL_QUAD_TO: return "relQuadToRelQuadTo"; break; + case CTX_REL_QUAD_TO_S16: return "relQuadToS16"; break; + + case CTX_SET_KEY: return "setParam"; break; + case CTX_COLOR: return "setColor"; break; + case CTX_DEFINE_GLYPH: return "defineGlyph"; break; + case CTX_KERNING_PAIR: return "kerningPair"; break; + case CTX_SET_PIXEL: return "setPixel"; break; + case CTX_GLOBAL_ALPHA: return "globalAlpha"; break; + case CTX_TEXT: return "text"; break; + case CTX_STROKE_TEXT: return "strokeText"; break; + case CTX_SAVE: return "save"; break; + case CTX_RESTORE: return "restore"; break; + case CTX_STROKE_SOURCE: return "strokeSource"; break; + case CTX_NEW_PAGE: return "newPage"; break; + case CTX_START_GROUP: return "startGroup"; break; + case CTX_END_GROUP: return "endGroup"; break; + case CTX_RECTANGLE: return "rectangle"; break; + case CTX_ROUND_RECTANGLE: return "roundRectangle"; break; + case CTX_LINEAR_GRADIENT: return "linearGradient"; break; + case CTX_RADIAL_GRADIENT: return "radialGradient"; break; + case CTX_GRADIENT_STOP: return "gradientAddStop"; break; + case CTX_VIEW_BOX: return "viewBox"; break; + case CTX_MOVE_TO: return "moveTo"; break; + case CTX_LINE_TO: return "lineTo"; break; + case CTX_BEGIN_PATH: return "beginPath"; break; + case CTX_REL_MOVE_TO: return "relMoveTo"; break; + case CTX_REL_LINE_TO: return "relLineTo"; break; + case CTX_FILL: return "fill"; break; + case CTX_EXIT: return "exit"; break; + case CTX_APPLY_TRANSFORM: return "transform"; break; + case CTX_SOURCE_TRANSFORM: return "sourceTransform"; break; + case CTX_REL_ARC_TO: return "relArcTo"; break; + case CTX_GLYPH: return "glyph"; break; + case CTX_TEXTURE: return "texture"; break; + case CTX_DEFINE_TEXTURE: return "defineTexture"; break; + case CTX_IDENTITY: return "identity"; break; + case CTX_CLOSE_PATH: return "closePath"; break; + case CTX_PRESERVE: return "preserve"; break; + case CTX_FLUSH: return "flush"; break; + case CTX_RESET: return "reset"; break; + case CTX_FONT: return "font"; break; + case CTX_STROKE: return "stroke"; break; + case CTX_CLIP: return "clip"; break; + case CTX_ARC: return "arc"; break; + case CTX_SCALE: return "scale"; break; + case CTX_TRANSLATE: return "translate"; break; + case CTX_ROTATE: return "rotate"; break; + case CTX_ARC_TO: return "arcTo"; break; + case CTX_CURVE_TO: return "curveTo"; break; + case CTX_REL_CURVE_TO: return "relCurveTo"; break; + case CTX_REL_QUAD_TO: return "relQuadTo"; break; + case CTX_QUAD_TO: return "quadTo"; break; + case CTX_SMOOTH_TO: return "smoothTo"; break; + case CTX_REL_SMOOTH_TO: return "relSmoothTo"; break; + case CTX_SMOOTHQ_TO: return "smoothqTo"; break; + case CTX_REL_SMOOTHQ_TO: return "relSmoothqTo"; break; + case CTX_HOR_LINE_TO: return "horLineTo"; break; + case CTX_VER_LINE_TO: return "verLineTo"; break; + case CTX_REL_HOR_LINE_TO: return "relHorLineTo"; break; + case CTX_REL_VER_LINE_TO: return "relVerLineTo"; break; + case CTX_COMPOSITING_MODE: return "compositingMode"; break; + case CTX_BLEND_MODE: return "blendMode"; break; + case CTX_TEXT_ALIGN: return "textAlign"; break; + case CTX_TEXT_BASELINE: return "textBaseline"; break; + case CTX_TEXT_DIRECTION: return "textDirection"; break; + case CTX_FONT_SIZE: return "fontSize"; break; + case CTX_MITER_LIMIT: return "miterLimit"; break; + case CTX_LINE_JOIN: return "lineJoin"; break; + case CTX_LINE_CAP: return "lineCap"; break; + case CTX_LINE_WIDTH: return "lineWidth"; break; + case CTX_LINE_DASH_OFFSET: return "lineDashOffset"; break; + case CTX_IMAGE_SMOOTHING: return "imageSmoothing"; break; + case CTX_SHADOW_BLUR: return "shadowBlur"; break; + case CTX_FILL_RULE: return "fillRule"; break; + } + return NULL; +} + +static void _ctx_print_name (CtxFormatter *formatter, int code) +{ +#define CTX_VERBOSE_NAMES 1 +#if CTX_VERBOSE_NAMES + if (formatter->longform) + { + const char *name = NULL; + _ctx_indent (formatter); + //switch ((CtxCode)code) + name = _ctx_code_to_name (code); + if (name) + { + ctx_formatter_addstr (formatter, name, -1); + ctx_formatter_addstr (formatter, " (", 2); + if (code == CTX_SAVE) + { formatter->indent ++; } + else if (code == CTX_RESTORE) + { formatter->indent --; } + return; + } + } +#endif + { + char name[3]; + name[0]=CTX_SET_KEY; + name[2]='\0'; + switch (code) + { + case CTX_GLOBAL_ALPHA: name[1]='a'; break; + case CTX_COMPOSITING_MODE: name[1]='m'; break; + case CTX_BLEND_MODE: name[1]='B'; break; + case CTX_TEXT_ALIGN: name[1]='t'; break; + case CTX_TEXT_BASELINE: name[1]='b'; break; + case CTX_TEXT_DIRECTION: name[1]='d'; break; + case CTX_FONT_SIZE: name[1]='f'; break; + case CTX_MITER_LIMIT: name[1]='l'; break; + case CTX_LINE_JOIN: name[1]='j'; break; + case CTX_LINE_CAP: name[1]='c'; break; + case CTX_LINE_WIDTH: name[1]='w'; break; + case CTX_LINE_DASH_OFFSET: name[1]='D'; break; + case CTX_IMAGE_SMOOTHING: name[1]='S'; break; + case CTX_SHADOW_BLUR: name[1]='s'; break; + case CTX_SHADOW_COLOR: name[1]='C'; break; + case CTX_SHADOW_OFFSET_X: name[1]='x'; break; + case CTX_SHADOW_OFFSET_Y: name[1]='y'; break; + case CTX_FILL_RULE: name[1]='r'; break; + default: + name[0] = code; + name[1] = 0; + break; + } + ctx_formatter_addstr (formatter, name, -1); + if (formatter->longform) + ctx_formatter_addstr (formatter, " (", 2); + else + ctx_formatter_addstr (formatter, " ", 1); + } +} + +static void +ctx_print_entry_enum (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + for (int i = 0; i < args; i ++) + { + int val = ctx_arg_u8 (i); + if (i>0) + { + ctx_formatter_addstr (formatter, " ", 1); + } +#if CTX_VERBOSE_NAMES + if (formatter->longform) + { + const char *str = NULL; + switch (entry->code) + { + case CTX_TEXT_BASELINE: + switch (val) + { + case CTX_TEXT_BASELINE_ALPHABETIC: str = "alphabetic"; break; + case CTX_TEXT_BASELINE_TOP: str = "top"; break; + case CTX_TEXT_BASELINE_BOTTOM: str = "bottom"; break; + case CTX_TEXT_BASELINE_HANGING: str = "hanging"; break; + case CTX_TEXT_BASELINE_MIDDLE: str = "middle"; break; + case CTX_TEXT_BASELINE_IDEOGRAPHIC:str = "ideographic";break; + } + break; + case CTX_TEXT_ALIGN: + switch (val) + { + case CTX_TEXT_ALIGN_LEFT: str = "left"; break; + case CTX_TEXT_ALIGN_RIGHT: str = "right"; break; + case CTX_TEXT_ALIGN_START: str = "start"; break; + case CTX_TEXT_ALIGN_END: str = "end"; break; + case CTX_TEXT_ALIGN_CENTER: str = "center"; break; + } + break; + case CTX_LINE_CAP: + switch (val) + { + case CTX_CAP_NONE: str = "none"; break; + case CTX_CAP_ROUND: str = "round"; break; + case CTX_CAP_SQUARE: str = "square"; break; + } + break; + case CTX_LINE_JOIN: + switch (val) + { + case CTX_JOIN_MITER: str = "miter"; break; + case CTX_JOIN_ROUND: str = "round"; break; + case CTX_JOIN_BEVEL: str = "bevel"; break; + } + break; + case CTX_FILL_RULE: + switch (val) + { + case CTX_FILL_RULE_WINDING: str = "winding"; break; + case CTX_FILL_RULE_EVEN_ODD: str = "evenodd"; break; + } + break; + case CTX_BLEND_MODE: + val = ctx_arg_u32 (i); + switch (val) + { + case CTX_BLEND_NORMAL: str = "normal"; break; + case CTX_BLEND_MULTIPLY: str = "multiply"; break; + case CTX_BLEND_SCREEN: str = "screen"; break; + case CTX_BLEND_OVERLAY: str = "overlay"; break; + case CTX_BLEND_DARKEN: str = "darken"; break; + case CTX_BLEND_LIGHTEN: str = "lighten"; break; + case CTX_BLEND_COLOR_DODGE: str = "colorDodge"; break; + case CTX_BLEND_COLOR_BURN: str = "colorBurn"; break; + case CTX_BLEND_HARD_LIGHT: str = "hardLight"; break; + case CTX_BLEND_SOFT_LIGHT: str = "softLight"; break; + case CTX_BLEND_DIFFERENCE: str = "difference"; break; + case CTX_BLEND_EXCLUSION: str = "exclusion"; break; + case CTX_BLEND_HUE: str = "hue"; break; + case CTX_BLEND_SATURATION: str = "saturation"; break; + case CTX_BLEND_COLOR: str = "color"; break; + case CTX_BLEND_LUMINOSITY: str = "luminosity"; break; + } + break; + case CTX_COMPOSITING_MODE: + val = ctx_arg_u32 (i); + switch (val) + { + case CTX_COMPOSITE_SOURCE_OVER: str = "sourceOver"; break; + case CTX_COMPOSITE_COPY: str = "copy"; break; + case CTX_COMPOSITE_CLEAR: str = "clear"; break; + case CTX_COMPOSITE_SOURCE_IN: str = "sourceIn"; break; + case CTX_COMPOSITE_SOURCE_OUT: str = "sourceOut"; break; + case CTX_COMPOSITE_SOURCE_ATOP: str = "sourceAtop"; break; + case CTX_COMPOSITE_DESTINATION: str = "destination"; break; + case CTX_COMPOSITE_DESTINATION_OVER: str = "destinationOver"; break; + case CTX_COMPOSITE_DESTINATION_IN: str = "destinationIn"; break; + case CTX_COMPOSITE_DESTINATION_OUT: str = "destinationOut"; break; + case CTX_COMPOSITE_DESTINATION_ATOP: str = "destinationAtop"; break; + case CTX_COMPOSITE_XOR: str = "xor"; break; + } + + break; + } + if (str) + { + ctx_formatter_addstr (formatter, str, -1); + } + else + { + ctx_formatter_addstrf (formatter, "%i", val); + } + } + else +#endif + { + ctx_formatter_addstrf (formatter, "%i", val); + } + } + _ctx_print_endcmd (formatter); +} + + +static void +ctx_print_a85 (CtxFormatter *formatter, uint8_t *data, int length) +{ + char *tmp = (char*)malloc (ctx_a85enc_len (length)); + ctx_a85enc (data, tmp, length); + ctx_formatter_addstr (formatter, " ~", 2); + ctx_formatter_addstr (formatter, tmp, -1); + ctx_formatter_addstr (formatter, "~ ", 2); + free (tmp); +} + +static void +ctx_print_yenc (CtxFormatter *formatter, uint8_t *data, int length) +{ + char *tmp = (char*)malloc (length * 2 + 2);// worst case scenario + int enclength = ctx_yenc ((char*)data, tmp, length); + data[enclength]=0; + ctx_formatter_addstr (formatter, " =", 2); + ctx_formatter_addstr (formatter, tmp, enclength); + ctx_formatter_addstr (formatter, "=y ", 2); + free (tmp); +} + +static void +ctx_print_escaped_string (CtxFormatter *formatter, const char *string) +{ + if (!string) { return; } + for (int i = 0; string[i]; i++) + { + switch (string[i]) + { + case '"': + ctx_formatter_addstr (formatter, "\\\"", 2); + break; + case '\\': + ctx_formatter_addstr (formatter, "\\\\", 2); + break; + case '\n': + ctx_formatter_addstr (formatter, "\\n", 2); + break; + default: + ctx_formatter_addstr (formatter, &string[i], 1); + } + } +} + +static void +ctx_print_float (CtxFormatter *formatter, float val) +{ + char temp[128]; + sprintf (temp, "%0.3f", val); + int j; + for (j = 0; temp[j]; j++) + if (j == ',') { temp[j] = '.'; } + j--; + if (j>0) + while (temp[j] == '0') + { + temp[j]=0; + j--; + } + if (temp[j]=='.') + { temp[j]='\0'; } + ctx_formatter_addstr (formatter, temp, -1); +} + +static void +ctx_print_int (CtxFormatter *formatter, int val) +{ + ctx_formatter_addstrf (formatter, "%i", val); +} + +static void +ctx_print_entry (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + for (int i = 0; i < args; i ++) + { + float val = ctx_arg_float (i); + if (i>0 && val >= 0.0f) + { + if (formatter->longform) + { + ctx_formatter_addstr (formatter, ", ", 2); + } + else + { + if (val >= 0.0f) + ctx_formatter_addstr (formatter, " ", 1); + } + } + ctx_print_float (formatter, val); + } + _ctx_print_endcmd (formatter); +} + +static void +ctx_print_glyph (CtxFormatter *formatter, CtxEntry *entry, int args) +{ + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "%i", entry->data.u32[0]); + _ctx_print_endcmd (formatter); +} + +static void +ctx_formatter_process (void *user_data, CtxCommand *c); + + +static void +ctx_formatter_process (void *user_data, CtxCommand *c) +{ + CtxEntry *entry = &c->entry; + CtxFormatter *formatter = (CtxFormatter*)user_data; + + switch (entry->code) + //switch ((CtxCode)(entry->code)) + { + case CTX_GLYPH: + ctx_print_glyph (formatter, entry, 1); + break; + case CTX_LINE_TO: + case CTX_REL_LINE_TO: + case CTX_SCALE: + case CTX_TRANSLATE: + case CTX_MOVE_TO: + case CTX_REL_MOVE_TO: + case CTX_SMOOTHQ_TO: + case CTX_REL_SMOOTHQ_TO: + ctx_print_entry (formatter, entry, 2); + break; + case CTX_TEXTURE: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, c->texture.eid); + ctx_formatter_addstrf (formatter, "\", "); + ctx_print_float (formatter, c->texture.x); + ctx_formatter_addstrf (formatter, ", "); + ctx_print_float (formatter, c->texture.y); + ctx_formatter_addstrf (formatter, " "); + _ctx_print_endcmd (formatter); + break; + + case CTX_DEFINE_TEXTURE: + { + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, c->define_texture.eid); + ctx_formatter_addstrf (formatter, "\", "); + ctx_print_int (formatter, c->define_texture.width); + ctx_formatter_addstrf (formatter, ", "); + ctx_print_int (formatter, c->define_texture.height); + ctx_formatter_addstrf (formatter, ",%i, ", c->define_texture.format); + + uint8_t *pixel_data = ctx_define_texture_pixel_data (entry); +#if 1 + + int stride = ctx_pixel_format_get_stride ((CtxPixelFormat)c->define_texture.format, c->define_texture.width); + int data_len = stride * c->define_texture.height; + if (c->define_texture.format == CTX_FORMAT_YUV420) + data_len = c->define_texture.height * c->define_texture.width + + 2*(c->define_texture.height/2) * (c->define_texture.width/2); + //fprintf (stderr, "encoding %i bytes\n", c->define_texture.height *stride); + //ctx_print_a85 (formatter, pixel_data, c->define_texture.height * stride); + ctx_print_yenc (formatter, pixel_data, data_len); +#else + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, pixel_data); + ctx_formatter_addstrf (formatter, "\" "); + +#endif + + _ctx_print_endcmd (formatter); + } + break; + + case CTX_REL_ARC_TO: + case CTX_ARC_TO: + case CTX_ROUND_RECTANGLE: + ctx_print_entry (formatter, entry, 5); + break; + case CTX_CURVE_TO: + case CTX_REL_CURVE_TO: + case CTX_ARC: + case CTX_RADIAL_GRADIENT: + case CTX_APPLY_TRANSFORM: + ctx_print_entry (formatter, entry, 6); + break; + case CTX_SOURCE_TRANSFORM: + ctx_print_entry (formatter, entry, 6); + break; + case CTX_QUAD_TO: + case CTX_RECTANGLE: + case CTX_REL_QUAD_TO: + case CTX_LINEAR_GRADIENT: + case CTX_VIEW_BOX: + case CTX_SMOOTH_TO: + case CTX_REL_SMOOTH_TO: + ctx_print_entry (formatter, entry, 4); + break; + case CTX_FONT_SIZE: + case CTX_MITER_LIMIT: + case CTX_ROTATE: + case CTX_LINE_WIDTH: + case CTX_LINE_DASH_OFFSET: + case CTX_GLOBAL_ALPHA: + case CTX_SHADOW_BLUR: + case CTX_SHADOW_OFFSET_X: + case CTX_SHADOW_OFFSET_Y: + case CTX_VER_LINE_TO: + case CTX_HOR_LINE_TO: + case CTX_REL_VER_LINE_TO: + case CTX_REL_HOR_LINE_TO: + ctx_print_entry (formatter, entry, 1); + break; +#if 0 + case CTX_SET: + _ctx_print_name (formatter, entry->code); + switch (c->set.key_hash) + { + case CTX_x: ctx_formatter_addstrf (formatter, " 'x' "); break; + case CTX_y: ctx_formatter_addstrf (formatter, " 'y' "); break; + case CTX_width: ctx_formatter_addstrf (formatter, " width "); break; + case CTX_height: ctx_formatter_addstrf (formatter, " height "); break; + default: + ctx_formatter_addstrf (formatter, " %d ", c->set.key_hash); + } + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, (char*)c->set.utf8); + ctx_formatter_addstrf (formatter, "\""); + _ctx_print_endcmd (formatter); + break; +#endif + case CTX_COLOR: + if (formatter->longform || 1) + { + _ctx_indent (formatter); + int model = (int) c->set_color.model; + const char *suffix=""; + if (model & 512) + { + model = model & 511; + suffix = "S"; + } + switch (model) + { + case CTX_GRAY: + ctx_formatter_addstrf (formatter, "gray%s ", suffix); + ctx_print_float (formatter, c->graya.g); + break; + case CTX_GRAYA: + ctx_formatter_addstrf (formatter, "graya%s ", suffix); + ctx_print_float (formatter, c->graya.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->graya.a); + break; + case CTX_RGBA: + if (c->rgba.a != 1.0) + { + ctx_formatter_addstrf (formatter, "rgba%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.a); + break; + } + /* FALLTHROUGH */ + case CTX_RGB: + if (c->rgba.r == c->rgba.g && c->rgba.g == c->rgba.b) + { + ctx_formatter_addstrf (formatter, "gray%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + break; + } + ctx_formatter_addstrf (formatter, "rgb%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + break; + case CTX_DRGB: + ctx_formatter_addstrf (formatter, "drgb%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + break; + case CTX_DRGBA: + ctx_formatter_addstrf (formatter, "drgba%s ", suffix); + ctx_print_float (formatter, c->rgba.r); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.g); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.b); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->rgba.a); + break; + case CTX_CMYK: + ctx_formatter_addstrf (formatter, "cmyk%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + break; + case CTX_CMYKA: + ctx_formatter_addstrf (formatter, "cmyka%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_formatter_addstrf (formatter, "dcmyk%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + break; + case CTX_DCMYKA: + ctx_formatter_addstrf (formatter, "dcmyka%s ", suffix); + ctx_print_float (formatter, c->cmyka.c); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.m); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.y); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.k); + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, c->cmyka.a); + break; + } + } + else + { + ctx_print_entry (formatter, entry, 1); + } + break; + case CTX_SET_RGBA_U8: + if (formatter->longform) + { + _ctx_indent (formatter); + ctx_formatter_addstrf (formatter, "rgba ("); + } + else + { + ctx_formatter_addstrf (formatter, "rgba ("); + } + for (int c = 0; c < 4; c++) + { + if (c) + { + if (formatter->longform) + ctx_formatter_addstrf (formatter, ", "); + else + ctx_formatter_addstrf (formatter, " "); + } + ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (c) ) ); + } + _ctx_print_endcmd (formatter); + break; + case CTX_SET_PIXEL: +#if 0 + ctx_set_pixel_u8 (d_ctx, + ctx_arg_u16 (2), ctx_arg_u16 (3), + ctx_arg_u8 (0), + ctx_arg_u8 (1), + ctx_arg_u8 (2), + ctx_arg_u8 (3) ); +#endif + break; + case CTX_FILL: + case CTX_RESET: + case CTX_STROKE: + case CTX_IDENTITY: + case CTX_CLIP: + case CTX_BEGIN_PATH: + case CTX_CLOSE_PATH: + case CTX_SAVE: + case CTX_PRESERVE: + case CTX_START_GROUP: + case CTX_NEW_PAGE: + case CTX_END_GROUP: + case CTX_RESTORE: + case CTX_STROKE_SOURCE: + ctx_print_entry (formatter, entry, 0); + break; + case CTX_TEXT_ALIGN: + case CTX_TEXT_BASELINE: + case CTX_TEXT_DIRECTION: + case CTX_FILL_RULE: + case CTX_LINE_CAP: + case CTX_LINE_JOIN: + case CTX_COMPOSITING_MODE: + case CTX_BLEND_MODE: + case CTX_IMAGE_SMOOTHING: + ctx_print_entry_enum (formatter, entry, 1); + break; + case CTX_GRADIENT_STOP: + _ctx_print_name (formatter, entry->code); + for (int c = 0; c < 4; c++) + { + if (c) + ctx_formatter_addstrf (formatter, " "); + ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (4+c) ) ); + } + _ctx_print_endcmd (formatter); + break; + case CTX_TEXT: + case CTX_STROKE_TEXT: + case CTX_FONT: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + ctx_print_escaped_string (formatter, ctx_arg_string() ); + ctx_formatter_addstrf (formatter, "\""); + _ctx_print_endcmd (formatter); + break; + case CTX_CONT: + case CTX_EDGE: + case CTX_DATA: + case CTX_DATA_REV: + case CTX_FLUSH: + break; + case CTX_KERNING_PAIR: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + { + uint8_t utf8[16]; + utf8[ctx_unichar_to_utf8 (c->kern.glyph_before, utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\", \""); + utf8[ctx_unichar_to_utf8 (c->kern.glyph_after, utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\""); + sprintf ((char*)utf8, ", %f", c->kern.amount / 256.0); + ctx_print_escaped_string (formatter, (char*)utf8); + } + _ctx_print_endcmd (formatter); + break; + + case CTX_DEFINE_GLYPH: + _ctx_print_name (formatter, entry->code); + ctx_formatter_addstrf (formatter, "\""); + { + uint8_t utf8[16]; + utf8[ctx_unichar_to_utf8 (entry->data.u32[0], utf8)]=0; + ctx_print_escaped_string (formatter, (char*)utf8); + ctx_formatter_addstrf (formatter, "\""); + sprintf ((char*)utf8, ", %f", entry->data.u32[1]/256.0); + ctx_print_escaped_string (formatter, (char*)utf8); + } + _ctx_print_endcmd (formatter); + break; + } +} + +void +ctx_render_stream (Ctx *ctx, FILE *stream, int longform) +{ + CtxIterator iterator; + CtxFormatter formatter; + formatter.target= stream; + formatter.longform = longform; + formatter.indent = 0; + formatter.add_str = _ctx_stream_addstr; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_formatter_process (&formatter, command); } + fprintf (stream, "\n"); +} + +char * +ctx_render_string (Ctx *ctx, int longform, int *retlen) +{ + CtxString *string = ctx_string_new (""); + CtxIterator iterator; + CtxFormatter formatter; + formatter.target= string; + formatter.longform = longform; + formatter.indent = 0; + formatter.add_str = _ctx_string_addstr; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { ctx_formatter_process (&formatter, command); } + char *ret = string->str; + if (retlen) + *retlen = string->length; + ctx_string_free (string, 0); + return ret; +} + + +#endif + +#if CTX_EVENTS +int ctx_width (Ctx *ctx) +{ + return ctx->events.width; +} +int ctx_height (Ctx *ctx) +{ + return ctx->events.height; +} +#else +int ctx_width (Ctx *ctx) +{ + return 512; +} +int ctx_height (Ctx *ctx) +{ + return 384; +} +#endif + +int ctx_rev (Ctx *ctx) +{ + return ctx->rev; +} + +CtxState *ctx_get_state (Ctx *ctx) +{ + return &ctx->state; +} + +void ctx_dirty_rect (Ctx *ctx, int *x, int *y, int *width, int *height) +{ + if ( (ctx->state.min_x > ctx->state.max_x) || + (ctx->state.min_y > ctx->state.max_y) ) + { + if (x) { *x = 0; } + if (y) { *y = 0; } + if (width) { *width = 0; } + if (height) { *height = 0; } + return; + } + if (ctx->state.min_x < 0) + { ctx->state.min_x = 0; } + if (ctx->state.min_y < 0) + { ctx->state.min_y = 0; } + if (x) { *x = ctx->state.min_x; } + if (y) { *y = ctx->state.min_y; } + if (width) { *width = ctx->state.max_x - ctx->state.min_x; } + if (height) { *height = ctx->state.max_y - ctx->state.min_y; } +} + +#if CTX_CURRENT_PATH +CtxIterator * +ctx_current_path (Ctx *ctx) +{ + CtxIterator *iterator = &ctx->current_path_iterator; + ctx_iterator_init (iterator, &ctx->current_path, 0, CTX_ITERATOR_EXPAND_BITPACK); + return iterator; +} + +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2) +{ + float minx = 50000.0; + float miny = 50000.0; + float maxx = -50000.0; + float maxy = -50000.0; + float x = 0; + float y = 0; + + CtxIterator *iterator = ctx_current_path (ctx); + CtxCommand *command; + + while ((command = ctx_iterator_next (iterator))) + { + int got_coord = 0; + switch (command->code) + { + // XXX missing many curve types + case CTX_LINE_TO: + case CTX_MOVE_TO: + x = command->move_to.x; + y = command->move_to.y; + got_coord++; + break; + case CTX_REL_LINE_TO: + case CTX_REL_MOVE_TO: + x += command->move_to.x; + y += command->move_to.y; + got_coord++; + break; + case CTX_CURVE_TO: + x = command->curve_to.x; + y = command->curve_to.y; + got_coord++; + break; + case CTX_REL_CURVE_TO: + x += command->curve_to.x; + y += command->curve_to.y; + got_coord++; + break; + case CTX_ARC: + minx = ctx_minf (minx, command->arc.x - command->arc.radius); + miny = ctx_minf (miny, command->arc.y - command->arc.radius); + maxx = ctx_maxf (maxx, command->arc.x + command->arc.radius); + maxy = ctx_maxf (maxy, command->arc.y + command->arc.radius); + + break; + case CTX_RECTANGLE: + case CTX_ROUND_RECTANGLE: + x = command->rectangle.x; + y = command->rectangle.y; + minx = ctx_minf (minx, x); + miny = ctx_minf (miny, y); + maxx = ctx_maxf (maxx, x); + maxy = ctx_maxf (maxy, y); + + x += command->rectangle.width; + y += command->rectangle.height; + got_coord++; + break; + } + if (got_coord) + { + minx = ctx_minf (minx, x); + miny = ctx_minf (miny, y); + maxx = ctx_maxf (maxx, x); + maxy = ctx_maxf (maxy, y); + } + } + + if (ex1) *ex1 = minx; + if (ey1) *ey1 = miny; + if (ex2) *ex2 = maxx; + if (ey2) *ey2 = maxy; +} + +#else +void +ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2) +{ +} +#endif + + +static inline void +ctx_gstate_push (CtxState *state) +{ + if (state->gstate_no + 1 >= CTX_MAX_STATES) + { return; } + state->gstate_stack[state->gstate_no] = state->gstate; + state->gstate_no++; + ctx_state_set (state, CTX_new_state, 0.0); + state->has_clipped=0; +} + +static inline void +ctx_gstate_pop (CtxState *state) +{ + if (state->gstate_no <= 0) + { return; } + state->gstate = state->gstate_stack[state->gstate_no-1]; + state->gstate_no--; +} + +void +ctx_close_path (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_CLOSE_PATH); +} + +int _ctx_is_rasterizer (Ctx *ctx); + +void +ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh, + CtxPixelFormat format, int dst_stride, + uint8_t *dst_data) +{ + if (0) + { + } +#if CTX_RASTERIZER + else if (_ctx_is_rasterizer (ctx)) + { + CtxRasterizer *rasterizer = (CtxRasterizer*)ctx->renderer; + if (rasterizer->format->pixel_format == format) + { + if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw); + int bytes_per_pix = rasterizer->format->bpp/8; + int y = 0; + for (int v = sy; v < sy + sh; v++, y++) + { + int x = 0; + for (int u = sx; u < sx + sw; u++, x++) + { + uint8_t* src_buf = (uint8_t*)rasterizer->buf; + memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * rasterizer->blit_stride + u * bytes_per_pix], bytes_per_pix); + } + } + return; + } + } +#endif +#if CTX_FB + else if (format == CTX_FORMAT_RGBA8 && + ( + ctx_renderer_is_fb (ctx) +#if CTX_SDL + || ctx_renderer_is_sdl (ctx) +#endif + )) + { + CtxTiled *tiled = (CtxTiled*)ctx->renderer; + { + if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw); + int bytes_per_pix = 4; + int y = 0; + for (int v = sy; v < sy + sh; v++, y++) + { + int x = 0; + for (int u = sx; u < sx + sw; u++, x++) + { + uint8_t* src_buf = (uint8_t*)tiled->pixels; + memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * tiled->width * bytes_per_pix + u * bytes_per_pix], bytes_per_pix); + } + } + return; + } + } +#endif +} + +void +ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format, + uint8_t *data, + int ox, int oy, + int dirtyX, int dirtyY, + int dirtyWidth, int dirtyHeight) +{ + char eid[65]=""; + ctx_save (ctx); + ctx_identity (ctx); + ctx_define_texture (ctx, NULL, w, h, stride, format, data, eid); + if (eid[0]) + { + // XXX set compositor to source + ctx_compositing_mode (ctx, CTX_COMPOSITE_COPY); + ctx_draw_texture_clipped (ctx, eid, ox, oy, w, h, dirtyX, dirtyY, dirtyWidth, dirtyHeight); + } + ctx_restore (ctx); +} + +/* checking if an eid is valid also sets the frame for it + */ +static int ctx_eid_valid (Ctx *ctx, const char *eid, int *w, int *h) +{ + ctx = ctx->texture_cache; + CtxList *to_remove = NULL; + int ret = 0; + //fprintf (stderr, "{%i}\n", ctx->frame); + for (CtxList *l = ctx->eid_db; l; l = l->next) + { + CtxEidInfo *eid_info = (CtxEidInfo*)l->data; + if (ctx->frame - eid_info->frame >= 2) + /* XXX XXX XXX this breaks texture caching since + * it is wrong in some cases where more frames + * have passed? + */ + { + ctx_list_prepend (&to_remove, eid_info); + } + else if (!strcmp (eid_info->eid, eid) && + ctx->frame - eid_info->frame < 2) + { + //fclose (f); + eid_info->frame = ctx->frame; + if (w) *w = eid_info->width; + if (h) *h = eid_info->height; + ret = 1; + } + } + while (to_remove) + { + CtxEidInfo *eid_info = (CtxEidInfo*)to_remove->data; + //FILE *f = fopen ("/tmp/l", "a"); + //fprintf (f, "%i client removing %s\n", getpid(), eid_info->eid); + //fclose (f); + free (eid_info->eid); + free (eid_info); + ctx_list_remove (&ctx->eid_db, eid_info); + ctx_list_remove (&to_remove, eid_info); + } + return ret; +} + +void ctx_texture (Ctx *ctx, const char *eid, float x, float y) +{ + int eid_len = strlen (eid); + char ascii[41]=""; + //fprintf (stderr, "tx %s\n", eid); + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid=ascii; + } + + //FILE *f = fopen ("/tmp/l", "a"); + if (ctx_eid_valid (ctx, eid, 0, 0)) + { + ctx_process_cmd_str_float (ctx, CTX_TEXTURE, eid, x, y); + //fprintf (stderr, "setting texture eid %s\n", eid); + } + else + { + //fprintf (stderr, "tried setting invalid texture eid %s\n", eid); + } + //fclose (f); +} +int +_ctx_frame (Ctx *ctx) +{ + return ctx->frame; +} +int +_ctx_set_frame (Ctx *ctx, int frame) +{ + return ctx->frame = frame; +} + +void ctx_define_texture (Ctx *ctx, + const char *eid, + int width, int height, int stride, int format, void *data, + char *ret_eid) +{ + uint8_t hash[20]=""; + char ascii[41]=""; + int dst_stride = width; + //fprintf (stderr, "df %s\n", eid); + + dst_stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width); + if (stride <= 0) + stride = dst_stride; + + int data_len; + + if (format == CTX_FORMAT_YUV420) + data_len = width * height + ((width/2) * (height/2)) * 2; + else + data_len = height * dst_stride; + + if (eid == NULL) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t *src = (uint8_t*)data; + for (int y = 0; y < height; y++) + { + ctx_sha1_process (sha1, src, dst_stride); + src += stride; + } + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2] =hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + } + + int eid_len = strlen (eid); + + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2] =hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + eid_len = 40; + } + + // we now have eid + + if (ctx_eid_valid (ctx, eid, 0, 0)) + { + ctx_texture (ctx, eid, 0.0, 0.0); + } + else + + { + CtxEntry *commands; + int command_size = 1 + (data_len+1+1)/9 + 1 + (eid_len+1+1)/9 + 1 + 8; + if (ctx->renderer && ctx->renderer->process) + { + commands = (CtxEntry*)calloc (sizeof (CtxEntry), command_size); + } + else + { + commands = NULL; + ctx_drawlist_resize (&ctx->drawlist, ctx->drawlist.count + command_size); + commands = &(ctx->drawlist.entries[ctx->drawlist.count]); + memset (commands, 0, sizeof (CtxEntry) * command_size); + } + /* bottleneck, we can avoid copying sometimes - and even when copying + * we should cut this down to one copy, direct to the drawlist. + * + */ + commands[0] = ctx_u32 (CTX_DEFINE_TEXTURE, width, height); + commands[1].data.u16[0] = format; + + int pos = 2; + + commands[pos].code = CTX_DATA; + commands[pos].data.u32[0] = eid_len; + commands[pos].data.u32[1] = (eid_len+1+1)/9 + 1; + memcpy ((char *) &commands[pos+1].data.u8[0], eid, eid_len); + ((char *) &commands[pos+1].data.u8[0])[eid_len]=0; + + pos = 2 + 1 + ctx_conts_for_entry (&commands[2]); + commands[pos].code = CTX_DATA; + commands[pos].data.u32[0] = data_len; + commands[pos].data.u32[1] = (data_len+1+1)/9 + 1; + { + uint8_t *src = (uint8_t*)data; + uint8_t *dst = &commands[pos+1].data.u8[0]; +#if 1 + memcpy (dst, src, data_len); +#else + for (int y = 0; y < height; y++) + { + memcpy (dst, src, dst_stride); + src += stride; + dst += dst_stride; + } +#endif + } + ((char *) &commands[pos+1].data.u8[0])[data_len]=0; + + if (ctx->renderer && ctx->renderer->process) + { + ctx_process (ctx, commands); + free (commands); + } + else + { + ctx->drawlist.count += ctx_conts_for_entry (commands) + 1; + } + + CtxEidInfo *eid_info = (CtxEidInfo*)calloc (sizeof (CtxEidInfo), 1); + eid_info->width = width; + eid_info->height = height; + eid_info->frame = ctx->texture_cache->frame; + //fprintf (stderr, "%i\n", eid_info->frame); + eid_info->eid = strdup (eid); + ctx_list_prepend (&ctx->texture_cache->eid_db, eid_info); + } + + if (ret_eid) + { + strcpy (ret_eid, eid); + ret_eid[64]=0; + } +} + +void +ctx_texture_load (Ctx *ctx, const char *path, int *tw, int *th, char *reid) +{ + const char *eid = path; + char ascii[41]=""; + int eid_len = strlen (eid); + if (eid_len > 50) + { + CtxSHA1 *sha1 = ctx_sha1_new (); + uint8_t hash[20]=""; + ctx_sha1_process (sha1, (uint8_t*)eid, eid_len); + ctx_sha1_done (sha1, hash); + ctx_sha1_free (sha1); + const char *hex="0123456789abcdef"; + for (int i = 0; i < 20; i ++) + { + ascii[i*2]=hex[hash[i]/16]; + ascii[i*2+1]=hex[hash[i]%16]; + } + ascii[40]=0; + eid = ascii; + } + + if (ctx_eid_valid (ctx, eid , tw, th)) + { + if (reid) + { + strcpy (reid, eid); + } + return; + } + +#ifdef STBI_INCLUDE_STB_IMAGE_H + CtxPixelFormat pixel_format = CTX_FORMAT_RGBA8; + int w, h, components; + unsigned char *pixels = NULL; + + if (!strncmp (path, "file://", 7)) + { + pixels = stbi_load (path + 7, &w, &h, &components, 0); + } + else + { + unsigned char *data = NULL; + long length = 0; + ctx_get_contents (path, &data, &length); + if (data) + { + pixels = stbi_load_from_memory (data, length, &w, &h, &components, 0); + free (data); + } + } + + if (pixels) + { + switch (components) + { + case 1: pixel_format = CTX_FORMAT_GRAY8; break; + case 2: pixel_format = CTX_FORMAT_GRAYA8; break; + case 3: pixel_format = CTX_FORMAT_RGB8; break; + case 4: pixel_format = CTX_FORMAT_RGBA8; break; + } + if (tw) *tw = w; + if (th) *th = h; + ctx_define_texture (ctx, eid, w, h, w * components, pixel_format, pixels, reid); + free (pixels); + } + else + { + fprintf (stderr, "texture loading problem for %s\n", path); + } +#endif +} + +void +ctx_draw_texture_clipped (Ctx *ctx, const char *eid, + float x, float y, + float width, float height, + float clip_x, float clip_y, + float clip_width, float clip_height) +{ + int tex_width = 0; + int tex_height = 0; + if (ctx_eid_valid (ctx, eid , &tex_width, &tex_height)) + { + if (width > 0.0 && height > 0.0) + { + ctx_save (ctx); +#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); + if (clip_width > 0.0f) + { + ctx_translate (ctx, -clip_x, -clip_y); + ctx_scale (ctx, width/clip_width, height/clip_height); + } + else + { + ctx_scale (ctx, width/tex_width, height/tex_height); + } + //ctx_texture (ctx, eid, x / (width/tex_width), y / (height/tex_height)); + ctx_texture (ctx, eid, x, y);// / (width/tex_width), y / (height/tex_height)); + ctx_fill (ctx); + ctx_restore (ctx); + } + } +} + +void ctx_draw_texture (Ctx *ctx, const char *eid, float x, float y, float w, float h) +{ + ctx_draw_texture_clipped (ctx, eid, x, y, w, h, 0,0,0,0); +} + +void ctx_draw_image_clipped (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight) +{ + char reteid[65]; + int width, height; + ctx_texture_load (ctx, path, &width, &height, reteid); + if (reteid[0]) + { + ctx_draw_texture_clipped (ctx, reteid, x, y, w, h, sx, sy, swidth, sheight); + } +} + +void +ctx_draw_image (Ctx *ctx, const char *path, float x, float y, float w, float h) +{ + ctx_draw_image_clipped (ctx, path, x, y, w, h, 0,0,0,0); +} + +void +ctx_set_pixel_u8 (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + CtxEntry command = ctx_u8 (CTX_SET_PIXEL, r, g, b, a, 0, 0, 0, 0); + command.data.u16[2]=x; + command.data.u16[3]=y; + ctx_process (ctx, &command); +} + +void +ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1) +{ + CtxEntry command[2]= + { + ctx_f (CTX_LINEAR_GRADIENT, x0, y0), + ctx_f (CTX_CONT, x1, y1) + }; + ctx_process (ctx, command); +} + +void +ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0, float x1, float y1, float r1) +{ + CtxEntry command[3]= + { + ctx_f (CTX_RADIAL_GRADIENT, x0, y0), + ctx_f (CTX_CONT, r0, x1), + ctx_f (CTX_CONT, y1, r1) + }; + ctx_process (ctx, command); +} + +void ctx_preserve (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_PRESERVE); +} + +void ctx_fill (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_FILL); +} + +void ctx_stroke (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_STROKE); +} + + +static void ctx_empty (Ctx *ctx) +{ +#if CTX_RASTERIZER + if (ctx->renderer == NULL) +#endif + { + ctx->drawlist.count = 0; + ctx->drawlist.bitpack_pos = 0; + } +} + +void _ctx_set_store_clear (Ctx *ctx) +{ + ctx->transformation |= CTX_TRANSFORMATION_STORE_CLEAR; +} + +#if CTX_EVENTS +static void +ctx_event_free (void *event, void *user_data) +{ + free (event); +} + +static void +ctx_collect_events (CtxEvent *event, void *data, void *data2) +{ + Ctx *ctx = (Ctx*)data; + CtxEvent *copy; + if (event->type == CTX_KEY_PRESS && !strcmp (event->string, "idle")) + return; + copy = (CtxEvent*)malloc (sizeof (CtxEvent)); + *copy = *event; + if (copy->string) + copy->string = strdup (event->string); + ctx_list_append_full (&ctx->events.events, copy, ctx_event_free, NULL); +} +#endif + +#if CTX_EVENTS +static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2); +#endif + +void ctx_reset (Ctx *ctx) +{ + /* we do the callback reset first - maybe we need two cbs, + * one for before and one after default impl? + * + * tiled fb and sdl needs to sync + */ + if (ctx->renderer && ctx->renderer->reset) + ctx->renderer->reset (ctx->renderer); + + //CTX_PROCESS_VOID (CTX_RESET); + //if (ctx->transformation & CTX_TRANSFORMATION_STORE_CLEAR) + // { return; } + ctx_empty (ctx); + ctx_state_init (&ctx->state); +#if CTX_EVENTS + ctx_list_free (&ctx->events.items); + ctx->events.last_item = NULL; + + if (ctx->events.ctx_get_event_enabled) + { + ctx_clear_bindings (ctx); + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_PRESS, _ctx_bindings_key_press, ctx, ctx, + NULL, NULL); + + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_UP, ctx_collect_events, ctx, ctx, + NULL, NULL); + ctx_listen_full (ctx, 0,0,0,0, + CTX_KEY_DOWN, ctx_collect_events, ctx, ctx, + NULL, NULL); + + ctx_listen_full (ctx, 0, 0, ctx->events.width, ctx->events.height, + (CtxEventType)(CTX_PRESS|CTX_RELEASE|CTX_MOTION), + ctx_collect_events, ctx, ctx, + NULL, NULL); + } +#endif +} + +void ctx_begin_path (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_BEGIN_PATH); +} + +void ctx_clip (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_CLIP); +} + +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len); + +void ctx_save (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_SAVE); +} +void ctx_restore (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_RESTORE); +} + +void ctx_start_group (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_START_GROUP); +} + +void ctx_end_group (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_END_GROUP); +} + +void ctx_line_width (Ctx *ctx, float x) +{ + if (ctx->state.gstate.line_width != x) + CTX_PROCESS_F1 (CTX_LINE_WIDTH, x); +} + +float ctx_get_miter_limit (Ctx *ctx) +{ + return ctx->state.gstate.miter_limit; +} + +float ctx_get_line_dash_offset (Ctx *ctx) +{ + return ctx->state.gstate.line_dash_offset; +} + +void ctx_line_dash_offset (Ctx *ctx, float x) +{ + if (ctx->state.gstate.line_dash_offset != x) + CTX_PROCESS_F1 (CTX_LINE_DASH_OFFSET, x); +} + +int ctx_get_image_smoothing (Ctx *ctx) +{ + return ctx->state.gstate.image_smoothing; +} + +void ctx_image_smoothing (Ctx *ctx, int enabled) +{ + if (ctx_get_image_smoothing (ctx) != enabled) + CTX_PROCESS_U8 (CTX_IMAGE_SMOOTHING, enabled); +} + + +void ctx_line_dash (Ctx *ctx, float *dashes, int count) +{ + ctx_process_cmd_str_with_len (ctx, CTX_LINE_DASH, (char*)(dashes), count, 0, count * 4); +} + +void ctx_shadow_blur (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_blur != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_BLUR, x); +} + +void ctx_shadow_rgba (Ctx *ctx, float r, float g, float b, float a) +{ + CtxEntry command[3]= + { + ctx_f (CTX_SHADOW_COLOR, CTX_RGBA, r), + ctx_f (CTX_CONT, g, b), + ctx_f (CTX_CONT, a, 0) + }; + ctx_process (ctx, command); +} + +void ctx_shadow_offset_x (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_offset_x != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_X, x); +} + +void ctx_shadow_offset_y (Ctx *ctx, float x) +{ +#if CTX_ENABLE_SHADOW_BLUR + if (ctx->state.gstate.shadow_offset_y != x) +#endif + CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_Y, x); +} + +void +ctx_global_alpha (Ctx *ctx, float global_alpha) +{ + if (ctx->state.gstate.global_alpha_f != global_alpha) + CTX_PROCESS_F1 (CTX_GLOBAL_ALPHA, global_alpha); +} + +float +ctx_get_global_alpha (Ctx *ctx) +{ + return ctx->state.gstate.global_alpha_f; +} + +void +ctx_font_size (Ctx *ctx, float x) +{ + CTX_PROCESS_F1 (CTX_FONT_SIZE, x); +} + +float ctx_get_font_size (Ctx *ctx) +{ + return ctx->state.gstate.font_size; +} + +void +ctx_miter_limit (Ctx *ctx, float limit) +{ + CTX_PROCESS_F1 (CTX_MITER_LIMIT, limit); +} + +float ctx_get_line_width (Ctx *ctx) +{ + return ctx->state.gstate.line_width; +} + +void +_ctx_font (Ctx *ctx, const char *name) +{ + ctx->state.gstate.font = ctx_resolve_font (name); +} + +#if 0 +void +ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len) +{ + if (len <= 0) len = strlen (string); + ctx_process_cmd_str (ctx, CTX_SET, string, key_hash, len); +} + +const char * +ctx_get (Ctx *ctx, const char *key) +{ + static char retbuf[32]; + int len = 0; + CTX_PROCESS_U32(CTX_GET, ctx_strhash (key, 0), 0); + while (read (STDIN_FILENO, &retbuf[len], 1) != -1) + { + if(retbuf[len]=='\n') + break; + retbuf[++len]=0; + } + return retbuf; +} +#endif + +void +ctx_font_family (Ctx *ctx, const char *name) +{ +#if CTX_BACKEND_TEXT + ctx_process_cmd_str (ctx, CTX_FONT, name, 0, 0); + _ctx_font (ctx, name); +#else + _ctx_font (ctx, name); +#endif +} + +void +ctx_font (Ctx *ctx, const char *family_name) +{ + // should also parse size + ctx_font_family (ctx, family_name); +} + +const char * +ctx_get_font (Ctx *ctx) +{ + return ctx_fonts[ctx->state.gstate.font].name; +} + +void ctx_line_to (Ctx *ctx, float x, float y) +{ + if (CTX_UNLIKELY(!ctx->state.has_moved)) + { CTX_PROCESS_F (CTX_MOVE_TO, x, y); } + else + { CTX_PROCESS_F (CTX_LINE_TO, x, y); } +} + +void ctx_move_to (Ctx *ctx, float x, float y) +{ + CTX_PROCESS_F (CTX_MOVE_TO,x,y); +} + +void ctx_curve_to (Ctx *ctx, float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + CtxEntry command[3]= + { + ctx_f (CTX_CURVE_TO, x0, y0), + ctx_f (CTX_CONT, x1, y1), + ctx_f (CTX_CONT, x2, y2) + }; + ctx_process (ctx, command); +} + +void ctx_round_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h, + float radius) +{ + CtxEntry command[3]= + { + ctx_f (CTX_ROUND_RECTANGLE, x0, y0), + ctx_f (CTX_CONT, w, h), + ctx_f (CTX_CONT, radius, 0) + }; + ctx_process (ctx, command); +} + +void ctx_view_box (Ctx *ctx, + float x0, float y0, + float w, float h) +{ + CtxEntry command[3]= + { + ctx_f (CTX_VIEW_BOX, x0, y0), + ctx_f (CTX_CONT, w, h) + }; + ctx_process (ctx, command); +} + +void ctx_rectangle (Ctx *ctx, + float x0, float y0, + float w, float h) +{ + CtxEntry command[3]= + { + ctx_f (CTX_RECTANGLE, x0, y0), + ctx_f (CTX_CONT, w, h) + }; + ctx_process (ctx, command); +} + +void ctx_rel_line_to (Ctx *ctx, float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CTX_PROCESS_F (CTX_REL_LINE_TO,x,y); +} + +void ctx_rel_move_to (Ctx *ctx, float x, float y) +{ + if (!ctx->state.has_moved) + { + CTX_PROCESS_F (CTX_MOVE_TO,x,y); + return; + } + CTX_PROCESS_F (CTX_REL_MOVE_TO,x,y); +} + +CtxLineJoin ctx_get_line_join (Ctx *ctx) +{ + return ctx->state.gstate.line_join; +} + +CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx) +{ + return ctx->state.gstate.compositing_mode; +} + +CtxBlend ctx_get_blend_mode (Ctx *ctx) +{ + return ctx->state.gstate.blend_mode; +} + +CtxTextAlign ctx_get_text_align (Ctx *ctx) +{ + return (CtxTextAlign)ctx_state_get (&ctx->state, CTX_text_align); +} + +CtxTextBaseline ctx_get_text_baseline (Ctx *ctx) +{ + return (CtxTextBaseline)ctx_state_get (&ctx->state, CTX_text_baseline); +} + +CtxLineCap ctx_get_line_cap (Ctx *ctx) +{ + return ctx->state.gstate.line_cap; +} + +CtxFillRule ctx_get_fill_rule (Ctx *ctx) +{ + return ctx->state.gstate.fill_rule; +} + +void ctx_line_cap (Ctx *ctx, CtxLineCap cap) +{ + if (ctx->state.gstate.line_cap != cap) + CTX_PROCESS_U8 (CTX_LINE_CAP, cap); +} + +void ctx_fill_rule (Ctx *ctx, CtxFillRule fill_rule) +{ + if (ctx->state.gstate.fill_rule != fill_rule) + CTX_PROCESS_U8 (CTX_FILL_RULE, fill_rule); +} +void ctx_line_join (Ctx *ctx, CtxLineJoin join) +{ + if (ctx->state.gstate.line_join != join) + CTX_PROCESS_U8 (CTX_LINE_JOIN, join); +} +void ctx_blend_mode (Ctx *ctx, CtxBlend mode) +{ + if (ctx->state.gstate.blend_mode != mode) + CTX_PROCESS_U32 (CTX_BLEND_MODE, mode, 0); +} +void ctx_compositing_mode (Ctx *ctx, CtxCompositingMode mode) +{ + if (ctx->state.gstate.compositing_mode != mode) + CTX_PROCESS_U32 (CTX_COMPOSITING_MODE, mode, 0); +} +void ctx_text_align (Ctx *ctx, CtxTextAlign text_align) +{ + CTX_PROCESS_U8 (CTX_TEXT_ALIGN, text_align); +} +void ctx_text_baseline (Ctx *ctx, CtxTextBaseline text_baseline) +{ + CTX_PROCESS_U8 (CTX_TEXT_BASELINE, text_baseline); +} +void ctx_text_direction (Ctx *ctx, CtxTextDirection text_direction) +{ + CTX_PROCESS_U8 (CTX_TEXT_DIRECTION, text_direction); +} + +void +ctx_rel_curve_to (Ctx *ctx, + float x0, float y0, + float x1, float y1, + float x2, float y2) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[3]= + { + ctx_f (CTX_REL_CURVE_TO, x0, y0), + ctx_f (CTX_CONT, x1, y1), + ctx_f (CTX_CONT, x2, y2) + }; + ctx_process (ctx, command); +} + +void +ctx_rel_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[2]= + { + ctx_f (CTX_REL_QUAD_TO, cx, cy), + ctx_f (CTX_CONT, x, y) + }; + ctx_process (ctx, command); +} + +void +ctx_quad_to (Ctx *ctx, + float cx, float cy, + float x, float y) +{ + if (!ctx->state.has_moved) + { return; } + CtxEntry command[2]= + { + ctx_f (CTX_QUAD_TO, cx, cy), + ctx_f (CTX_CONT, x, y) + }; + ctx_process (ctx, command); +} + +void ctx_arc (Ctx *ctx, + float x0, float y0, + float radius, + float angle1, float angle2, + int direction) +{ + CtxEntry command[3]= + { + ctx_f (CTX_ARC, x0, y0), + ctx_f (CTX_CONT, radius, angle1), + ctx_f (CTX_CONT, angle2, direction) + }; + ctx_process (ctx, command); +} + +static int ctx_coords_equal (float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static float +ctx_point_seg_dist_sq (float x, float y, + float vx, float vy, float wx, float wy) +{ + float l2 = ctx_pow2 (vx-wx) + ctx_pow2 (vy-wy); + if (l2 < 0.0001) + { return ctx_pow2 (x-vx) + ctx_pow2 (y-vy); } + float t = ( (x - vx) * (wx - vx) + (y - vy) * (wy - vy) ) / l2; + t = ctx_maxf (0, ctx_minf (1, t) ); + float ix = vx + t * (wx - vx); + float iy = vy + t * (wy - vy); + return ctx_pow2 (x-ix) + ctx_pow2 (y-iy); +} + +static void +ctx_normalize (float *x, float *y) +{ + float length = ctx_hypotf ( (*x), (*y) ); + if (length > 1e-6f) + { + float r = 1.0f / length; + *x *= r; + *y *= r; + } +} + +void +ctx_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius) +{ + // XXX : should partially move into rasterizer to preserve comand + // even if an arc preserves all geometry, just to ensure roundtripping + // of data + /* from nanovg - but not quite working ; uncertain if arc or wrong + * transfusion is the cause. + */ + float x0 = ctx->state.x; + float y0 = ctx->state.y; + float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1; + int dir; + if (!ctx->state.has_moved) + { return; } + if (1) + { + // Handle degenerate cases. + if (ctx_coords_equal (x0,y0, x1,y1, 0.5f) || + ctx_coords_equal (x1,y1, x2,y2, 0.5f) || + ctx_point_seg_dist_sq (x1,y1, x0,y0, x2,y2) < 0.5 || + radius < 0.5) + { + ctx_line_to (ctx, x1,y1); + return; + } + } + // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). + dx0 = x0-x1; + dy0 = y0-y1; + dx1 = x2-x1; + dy1 = y2-y1; + ctx_normalize (&dx0,&dy0); + ctx_normalize (&dx1,&dy1); + a = ctx_acosf (dx0*dx1 + dy0*dy1); + d = radius / ctx_tanf (a/2.0f); +#if 0 + if (d > 10000.0f) + { + ctx_line_to (ctx, x1, y1); + return; + } +#endif + if ( (dx1*dy0 - dx0*dy1) > 0.0f) + { + cx = x1 + dx0*d + dy0*radius; + cy = y1 + dy0*d + -dx0*radius; + a0 = ctx_atan2f (dx0, -dy0); + a1 = ctx_atan2f (-dx1, dy1); + dir = 0; + } + else + { + cx = x1 + dx0*d + -dy0*radius; + cy = y1 + dy0*d + dx0*radius; + a0 = ctx_atan2f (-dx0, dy0); + a1 = ctx_atan2f (dx1, -dy1); + dir = 1; + } + ctx_arc (ctx, cx, cy, radius, a0, a1, dir); +} + +void +ctx_rel_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius) +{ + x1 += ctx->state.x; + y1 += ctx->state.y; + x2 += ctx->state.x; + y2 += ctx->state.y; + ctx_arc_to (ctx, x1, y1, x2, y2, radius); +} + +void +ctx_exit (Ctx *ctx) +{ + CTX_PROCESS_VOID (CTX_EXIT); +} + +void +ctx_flush (Ctx *ctx) +{ + /* XXX: should be fully moved into the renderers + * to permit different behavior and get rid + * of the extranous flush() vfunc. + */ + ctx->rev++; +// CTX_PROCESS_VOID (CTX_FLUSH); +#if 0 + //printf (" \e[?2222h"); + ctx_drawlist_compact (&ctx->drawlist); + for (int i = 0; i < ctx->drawlist.count - 1; i++) + { + CtxEntry *entry = &ctx->drawlist.entries[i]; + fwrite (entry, 9, 1, stdout); +#if 0 + uint8_t *buf = (void *) entry; + for (int j = 0; j < 9; j++) + { printf ("%c", buf[j]); } +#endif + } + printf ("Xx.Xx.Xx."); + fflush (NULL); +#endif + if (ctx->renderer && ctx->renderer->flush) + ctx->renderer->flush (ctx->renderer); + ctx->frame++; + if (ctx->texture_cache != ctx) + ctx->texture_cache->frame++; + ctx->drawlist.count = 0; + ctx_state_init (&ctx->state); +} + +//////////////////////////////////////// + +static inline void +ctx_interpret_style (CtxState *state, CtxEntry *entry, void *data) +{ + CtxCommand *c = (CtxCommand *) entry; + switch (entry->code) + { + case CTX_LINE_DASH_OFFSET: + state->gstate.line_dash_offset = ctx_arg_float (0); + break; + case CTX_LINE_WIDTH: + state->gstate.line_width = ctx_arg_float (0); + break; +#if CTX_ENABLE_SHADOW_BLUR + case CTX_SHADOW_BLUR: + state->gstate.shadow_blur = ctx_arg_float (0); + break; + case CTX_SHADOW_OFFSET_X: + state->gstate.shadow_offset_x = ctx_arg_float (0); + break; + case CTX_SHADOW_OFFSET_Y: + state->gstate.shadow_offset_y = ctx_arg_float (0); + break; +#endif + case CTX_LINE_CAP: + state->gstate.line_cap = (CtxLineCap) ctx_arg_u8 (0); + break; + case CTX_FILL_RULE: + state->gstate.fill_rule = (CtxFillRule) ctx_arg_u8 (0); + break; + case CTX_LINE_JOIN: + state->gstate.line_join = (CtxLineJoin) ctx_arg_u8 (0); + break; + case CTX_COMPOSITING_MODE: + state->gstate.compositing_mode = (CtxCompositingMode) ctx_arg_u32 (0); + break; + case CTX_BLEND_MODE: + state->gstate.blend_mode = (CtxBlend) ctx_arg_u32 (0); + break; + case CTX_TEXT_ALIGN: + ctx_state_set (state, CTX_text_align, ctx_arg_u8 (0) ); + break; + case CTX_TEXT_BASELINE: + ctx_state_set (state, CTX_text_baseline, ctx_arg_u8 (0) ); + break; + case CTX_TEXT_DIRECTION: + ctx_state_set (state, CTX_text_direction, ctx_arg_u8 (0) ); + break; + case CTX_GLOBAL_ALPHA: + state->gstate.global_alpha_u8 = ctx_float_to_u8 (ctx_arg_float (0) ); + state->gstate.global_alpha_f = ctx_arg_float (0); + break; + case CTX_FONT_SIZE: + state->gstate.font_size = ctx_arg_float (0); + break; + case CTX_MITER_LIMIT: + state->gstate.miter_limit = ctx_arg_float (0); + break; + case CTX_COLOR_SPACE: + /* move this out of this function and only do it in rasterizer? XXX */ + ctx_rasterizer_colorspace_icc (state, (CtxColorSpace)c->colorspace.space_slot, + (char*)c->colorspace.data, + c->colorspace.data_len); + break; + case CTX_IMAGE_SMOOTHING: + state->gstate.image_smoothing = c->entry.data.u8[0]; + break; + case CTX_STROKE_SOURCE: + state->source = 1; + break; + + case CTX_COLOR: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = 0; + + source->type = CTX_SOURCE_COLOR; + + //float components[5]={c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a}; + switch ( ((int) ctx_arg_float (0)) & 511) // XXX remove 511 after stroke source is complete + { + case CTX_RGB: + ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f); + break; + case CTX_RGBA: + ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; + case CTX_DRGBA: + ctx_color_set_drgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a); + break; +#if CTX_ENABLE_CMYK + case CTX_CMYKA: + ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_CMYK: + ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; + case CTX_DCMYKA: + ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a); + break; + case CTX_DCMYK: + ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f); + break; +#endif + case CTX_GRAYA: + ctx_color_set_graya (state, &source->color, c->graya.g, c->graya.a); + break; + case CTX_GRAY: + ctx_color_set_graya (state, &source->color, c->graya.g, 1.0f); + break; + } + } + break; + case CTX_SET_RGBA_U8: + //ctx_source_deinit (&state->gstate.source); + //state->gstate.source_fill.type = CTX_SOURCE_COLOR; + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = 0; + + source->type = CTX_SOURCE_COLOR; + + ctx_color_set_RGBA8 (state, &source->color, + ctx_arg_u8 (0), + ctx_arg_u8 (1), + ctx_arg_u8 (2), + ctx_arg_u8 (3) ); + } + //for (int i = 0; i < 4; i ++) + // state->gstate.source.color.rgba[i] = ctx_arg_u8(i); + break; + //case CTX_TEXTURE: + // state->gstate.source.type = CTX_SOURCE_ + // break; + case CTX_LINEAR_GRADIENT: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = is_stroke ? 2 : 0; + + float x0 = ctx_arg_float (0); + float y0 = ctx_arg_float (1); + float x1 = ctx_arg_float (2); + float y1 = ctx_arg_float (3); + float dx, dy, length, start, end; + + length = ctx_hypotf (x1-x0,y1-y0); + dx = (x1-x0) / length; + dy = (y1-y0) / length; + start = (x0 * dx + y0 * dy) / length; + end = (x1 * dx + y1 * dy) / length; + source->linear_gradient.length = length; + source->linear_gradient.dx = dx; + source->linear_gradient.dy = dy; + source->linear_gradient.start = start; + source->linear_gradient.end = end; + source->linear_gradient.rdelta = (end-start)!=0.0?1.0f/(end - start):1.0; + source->type = CTX_SOURCE_LINEAR_GRADIENT; + source->transform = state->gstate.transform; + ctx_matrix_invert (&source->transform); + } + break; + case CTX_RADIAL_GRADIENT: + { + int is_stroke = (state->source != 0); + CtxSource *source = is_stroke ? + &state->gstate.source_stroke: + &state->gstate.source_fill; + state->source = is_stroke ? 2 : 0; + + float x0 = ctx_arg_float (0); + float y0 = ctx_arg_float (1); + float r0 = ctx_arg_float (2); + float x1 = ctx_arg_float (3); + float y1 = ctx_arg_float (4); + float r1 = ctx_arg_float (5); + source->radial_gradient.x0 = x0; + source->radial_gradient.y0 = y0; + source->radial_gradient.r0 = r0; + source->radial_gradient.x1 = x1; + source->radial_gradient.y1 = y1; + source->radial_gradient.r1 = r1; + source->radial_gradient.rdelta = (r1 - r0) != 0.0 ? 1.0f/(r1-r0):0.0; + source->type = CTX_SOURCE_RADIAL_GRADIENT; + source->transform = state->gstate.transform; + ctx_matrix_invert (&source->transform); + } + break; + } +} + +static inline void +ctx_interpret_transforms (CtxState *state, CtxEntry *entry, void *data) +{ + switch (entry->code) + { + case CTX_SAVE: + ctx_gstate_push (state); + break; + case CTX_RESTORE: + ctx_gstate_pop (state); + break; + case CTX_IDENTITY: + _ctx_matrix_identity (&state->gstate.transform); + break; + case CTX_TRANSLATE: + ctx_matrix_translate (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_SCALE: + ctx_matrix_scale (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1) ); + break; + case CTX_ROTATE: + ctx_matrix_rotate (&state->gstate.transform, ctx_arg_float (0) ); + break; + case CTX_APPLY_TRANSFORM: + { + CtxMatrix m; + ctx_matrix_set (&m, + ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); + _ctx_matrix_multiply (&state->gstate.transform, + &state->gstate.transform, &m); // XXX verify order + } +#if 0 + ctx_matrix_set (&state->gstate.transform, + ctx_arg_float (0), ctx_arg_float (1), + ctx_arg_float (2), ctx_arg_float (3), + ctx_arg_float (4), ctx_arg_float (5) ); +#endif + break; + } +} + +/* + * this transforms the contents of entry according to ctx->transformation + */ +static inline void +ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data) +{ + CtxCommand *c = (CtxCommand *) entry; + float start_x = state->x; + float start_y = state->y; + int had_moved = state->has_moved; + switch (entry->code) + { + case CTX_MOVE_TO: + case CTX_LINE_TO: + { + float x = c->c.x0; + float y = c->c.y0; + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + _ctx_user_to_device (state, &x, &y); + ctx_arg_float (0) = x; + ctx_arg_float (1) = y; + } + } + break; + case CTX_ARC: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + float temp; + _ctx_user_to_device (state, &c->arc.x, &c->arc.y); + temp = 0; + _ctx_user_to_device_distance (state, &c->arc.radius, &temp); + } + break; + case CTX_LINEAR_GRADIENT: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + _ctx_user_to_device (state, &c->linear_gradient.x1, &c->linear_gradient.y1); + _ctx_user_to_device (state, &c->linear_gradient.x2, &c->linear_gradient.y2); + } + break; + case CTX_RADIAL_GRADIENT: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + float temp; + _ctx_user_to_device (state, &c->radial_gradient.x1, &c->radial_gradient.y1); + temp = 0; + _ctx_user_to_device_distance (state, &c->radial_gradient.r1, &temp); + _ctx_user_to_device (state, &c->radial_gradient.x2, &c->radial_gradient.y2); + temp = 0; + _ctx_user_to_device_distance (state, &c->radial_gradient.r2, &temp); + } + break; + case CTX_CURVE_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 3; c ++) + { + float x = entry[c].data.f[0]; + float y = entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + } + break; + case CTX_QUAD_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 2; c ++) + { + float x = entry[c].data.f[0]; + float y = entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + } + break; + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 1; c ++) + { + float x = state->x; + float y = state->y; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + if (entry->code == CTX_REL_MOVE_TO) + { entry->code = CTX_MOVE_TO; } + else + { entry->code = CTX_LINE_TO; } + } + break; + case CTX_REL_CURVE_TO: + { + float nx = state->x + ctx_arg_float (4); + float ny = state->y + ctx_arg_float (5); + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 3; c ++) + { + float x = nx + entry[c].data.f[0]; + float y = ny + entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + entry->code = CTX_CURVE_TO; + } + } + break; + case CTX_REL_QUAD_TO: + { + float nx = state->x + ctx_arg_float (2); + float ny = state->y + ctx_arg_float (3); + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ) + { + for (int c = 0; c < 2; c ++) + { + float x = nx + entry[c].data.f[0]; + float y = ny + entry[c].data.f[1]; + _ctx_user_to_device (state, &x, &y); + entry[c].data.f[0] = x; + entry[c].data.f[1] = y; + } + entry->code = CTX_QUAD_TO; + } + } + break; + } + if ((((Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE)) + { + int components = 0; + _ctx_user_to_device (state, &start_x, &start_y); + switch (entry->code) + { + case CTX_MOVE_TO: + if (had_moved) { components = 1; } + break; + case CTX_LINE_TO: + components = 1; + break; + case CTX_CURVE_TO: + components = 3; + break; + case CTX_QUAD_TO: + components = 2; + break; + } + if (components) + { + for (int c = 0; c < components; c++) + { + entry[c].data.f[0] -= start_x; + entry[c].data.f[1] -= start_y; + } + switch (entry->code) + { + case CTX_MOVE_TO: + entry[0].code = CTX_REL_MOVE_TO; + break; + case CTX_LINE_TO: + entry[0].code = CTX_REL_LINE_TO; + break; + break; + case CTX_CURVE_TO: + entry[0].code = CTX_REL_CURVE_TO; + break; + case CTX_QUAD_TO: + entry[0].code = CTX_REL_QUAD_TO; + break; + } + } + } +} + +static inline void +ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data) +{ + switch (entry->code) + { + case CTX_RESET: + ctx_state_init (state); + break; + case CTX_CLIP: + case CTX_BEGIN_PATH: + case CTX_FILL: + case CTX_STROKE: + state->has_moved = 0; + break; + case CTX_MOVE_TO: + case CTX_LINE_TO: + state->x = ctx_arg_float (0); + state->y = ctx_arg_float (1); + state->has_moved = 1; + break; + case CTX_CURVE_TO: + state->x = ctx_arg_float (4); + state->y = ctx_arg_float (5); + state->has_moved = 1; + break; + case CTX_QUAD_TO: + state->x = ctx_arg_float (2); + state->y = ctx_arg_float (3); + state->has_moved = 1; + break; + case CTX_ARC: + state->x = ctx_arg_float (0) + ctx_cosf (ctx_arg_float (4) ) * ctx_arg_float (2); + state->y = ctx_arg_float (1) + ctx_sinf (ctx_arg_float (4) ) * ctx_arg_float (2); + break; + case CTX_REL_MOVE_TO: + case CTX_REL_LINE_TO: + state->x += ctx_arg_float (0); + state->y += ctx_arg_float (1); + break; + case CTX_REL_CURVE_TO: + state->x += ctx_arg_float (4); + state->y += ctx_arg_float (5); + break; + case CTX_REL_QUAD_TO: + state->x += ctx_arg_float (2); + state->y += ctx_arg_float (3); + break; + // XXX missing some smooths + } +} + +static inline void +ctx_interpret_pos (CtxState *state, CtxEntry *entry, void *data) +{ + if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) || + ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE) ) + { + ctx_interpret_pos_transform (state, entry, data); + } + ctx_interpret_pos_bare (state, entry, data); +} + +#if CTX_BABL +void ctx_colorspace_babl (CtxState *state, + CtxColorSpace icc_slot, + const Babl *space); +#endif + +static void +ctx_state_init (CtxState *state) +{ + ctx_memset (state, 0, sizeof (CtxState) ); + state->gstate.global_alpha_u8 = 255; + state->gstate.global_alpha_f = 1.0; + state->gstate.font_size = 12; + state->gstate.line_width = 2.0; + state->gstate.image_smoothing = 1; + state->gstate.source_stroke.type = CTX_SOURCE_INHERIT_FILL; + ctx_state_set (state, CTX_line_spacing, 1.0f); + state->min_x = 8192; + state->min_y = 8192; + state->max_x = -8192; + state->max_y = -8192; + _ctx_matrix_identity (&state->gstate.transform); +#if CTX_CM +#if CTX_BABL + //ctx_colorspace_babl (state, CTX_COLOR_SPACE_USER_RGB, babl_space ("sRGB")); + //ctx_colorspace_babl (state, CTX_COLOR_SPACE_DEVICE_RGB, babl_space ("ACEScg")); +#endif +#endif +} + +void _ctx_set_transformation (Ctx *ctx, int transformation) +{ + ctx->transformation = transformation; +} + +static void +_ctx_init (Ctx *ctx) +{ + for (int i = 0; i <256;i++) + ctx_u8_float[i] = i/255.0f; + + ctx_state_init (&ctx->state); + + ctx->renderer = NULL; +#if CTX_CURRENT_PATH + ctx->current_path.flags |= CTX_DRAWLIST_CURRENT_PATH; +#endif + //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_SCREEN_SPACE; + //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_RELATIVE; +#if CTX_BITPACK + ctx->drawlist.flags |= CTX_TRANSFORMATION_BITPACK; +#endif + ctx->texture_cache = ctx; +} + +static void ctx_setup (); + +#if CTX_DRAWLIST_STATIC +static Ctx ctx_state; +#endif + +void ctx_set_renderer (Ctx *ctx, + void *renderer) +{ + if (ctx->renderer && ctx->renderer->free) + ctx->renderer->free (ctx->renderer); + ctx->renderer = (CtxImplementation*)renderer; +} + +void *ctx_get_renderer (Ctx *ctx) +{ + return ctx->renderer; +} + +Ctx * +ctx_new (void) +{ + ctx_setup (); +#if CTX_DRAWLIST_STATIC + Ctx *ctx = &ctx_state; +#else + Ctx *ctx = (Ctx *) malloc (sizeof (Ctx) ); +#endif + ctx_memset (ctx, 0, sizeof (Ctx) ); + _ctx_init (ctx); + return ctx; +} + +static inline void +ctx_drawlist_deinit (CtxDrawlist *drawlist) +{ +#if !CTX_DRAWLIST_STATIC + if (drawlist->entries && ! (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) ) + { + free (drawlist->entries); + } +#endif + drawlist->entries = NULL; + drawlist->size = 0; +} + +static void ctx_deinit (Ctx *ctx) +{ + if (ctx->renderer) + { + if (ctx->renderer->free) + ctx->renderer->free (ctx->renderer); + ctx->renderer = NULL; + } + ctx_drawlist_deinit (&ctx->drawlist); +#if CTX_CURRENT_PATH + ctx_drawlist_deinit (&ctx->current_path); +#endif +} + +void ctx_free (Ctx *ctx) +{ + if (!ctx) + { return; } +#if CTX_EVENTS + ctx_clear_bindings (ctx); +#endif + ctx_deinit (ctx); +#if !CTX_DRAWLIST_STATIC + free (ctx); +#endif +} + +Ctx *ctx_new_for_drawlist (void *data, size_t length) +{ + Ctx *ctx = ctx_new (); + ctx->drawlist.flags |= CTX_DRAWLIST_DOESNT_OWN_ENTRIES; + ctx->drawlist.entries = (CtxEntry *) data; + ctx->drawlist.count = length / sizeof (CtxEntry); + return ctx; +} + +static void ctx_setup () +{ + ctx_font_setup (); +} + +void +ctx_render_ctx (Ctx *ctx, Ctx *d_ctx) +{ + CtxIterator iterator; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { + ctx_process (d_ctx, &command->entry); + } +} + +void +ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx) +{ + CtxIterator iterator; + CtxCommand *command; + ctx_iterator_init (&iterator, &ctx->drawlist, 0, + CTX_ITERATOR_EXPAND_BITPACK); + while ( (command = ctx_iterator_next (&iterator) ) ) + { + switch (command->code) + { + default: + //fprintf (stderr, "[%c]", command->code); + break; + case CTX_TEXTURE: + //fprintf (stderr, "t:%s\n", command->texture.eid); + ctx_process (d_ctx, &command->entry); + break; + case CTX_DEFINE_TEXTURE: + //fprintf (stderr, "d:%s\n", command->define_texture.eid); + ctx_process (d_ctx, &command->entry); + break; + } + } +} + +void ctx_quit (Ctx *ctx) +{ +#if CTX_EVENTS + ctx->quit ++; +#endif +} + +int ctx_has_quit (Ctx *ctx) +{ +#if CTX_EVENTS + return (ctx->quit); +#else + return 1; +#endif +} + +int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->bpp; + return -1; +} + +int ctx_pixel_format_get_stride (CtxPixelFormat format, int width) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + { + switch (info->bpp) + { + case 0: + case 1: + return (width + 7)/8; + case 2: + return (width + 3)/4; + case 4: + return (width + 1)/2; + default: + return width * (info->bpp / 8); + } + } + return width; +} + +int ctx_pixel_format_ebpp (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->ebpp; + return -1; +} + +int ctx_pixel_format_components (CtxPixelFormat format) +{ + CtxPixelFormatInfo *info = ctx_pixel_format_info (format); + if (info) + return info->components; + return -1; +} + +#if CTX_EVENTS +void ctx_set_cursor (Ctx *ctx, CtxCursor cursor) +{ + if (ctx->cursor != cursor) + { + ctx_set_dirty (ctx, 1); + ctx->cursor = cursor; + } +} +CtxCursor ctx_get_cursor (Ctx *ctx) +{ + return ctx->cursor; +} + +void ctx_set_clipboard (Ctx *ctx, const char *text) +{ + if (ctx->renderer && ctx->renderer->set_clipboard) + { + ctx->renderer->set_clipboard (ctx->renderer, text); + return; + } +} + +char *ctx_get_clipboard (Ctx *ctx) +{ + if (ctx->renderer && ctx->renderer->get_clipboard) + { + return ctx->renderer->get_clipboard (ctx->renderer); + } + return strdup (""); +} + +void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source) +{ + ((CtxRasterizer*)ctx->renderer)->texture_source = texture_source; +} + +void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache) +{ + ctx->texture_cache = texture_cache; +} + +void ctx_set_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f) +{ + ctx_identity (ctx); + ctx_apply_transform (ctx, a, b, c, d, e, f); +} +#ifndef NO_LIBCURL +#include <curl/curl.h> +static size_t +ctx_string_append_callback (void *contents, size_t size, size_t nmemb, void *userp) +{ + CtxString *string = (CtxString*)userp; + ctx_string_append_data ((CtxString*)string, contents, size * nmemb); + return size * nmemb; +} + +#endif + +int +ctx_get_contents (const char *uri, + unsigned char **contents, + long *length) +{ + char *temp_uri = NULL; // XXX XXX breaks with data uri's + int success = -1; + + if (uri[0] == '/') + { + temp_uri = (char*) malloc (strlen (uri) + 8); + sprintf (temp_uri, "file://%s", uri); + uri = temp_uri; + } + + if (strchr (uri, '#')) + strchr (uri, '#')[0]=0; + + for (CtxList *l = registered_contents; l; l = l->next) + { + CtxFileContent *c = (CtxFileContent*)l->data; + if (!strcmp (c->path, uri)) + { + contents = malloc (c->length+1); + contents[c->length]=0; + if (length) *length = c->length; + free (temp_uri); + return 0; + } + } + + if (!strncmp (uri, "file://", 5)) + { + if (strchr (uri, '?')) + strchr (uri, '?')[0]=0; + } + + if (!strncmp (uri, "file://", 7)) + success = __ctx_file_get_contents (uri + 7, contents, length); + else + { +#ifndef NO_LIBCURL + CURL *curl = curl_easy_init (); + CURLcode res; + + curl_easy_setopt(curl, CURLOPT_URL, uri); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + CtxString *string = ctx_string_new (""); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctx_string_append_callback); + /* we pass our 'chunk' struct to the callback function */ + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)string); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ctx/0.0"); + + res = curl_easy_perform(curl); + /* check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + curl_easy_cleanup (curl); + } + else + { + *contents = (unsigned char*)string->str; + *length = string->length; + ctx_string_free (string, 0); + curl_easy_cleanup (curl); + success = 0; + } +#else + success = __ctx_file_get_contents (uri, contents, length); +#endif + } + free (temp_uri); + return success; +} + + +#endif + +#endif // CTX_IMPLEMENTATION +#ifndef __CTX_CLIENTS_H +#define __CTX_CLIENTS_H + +typedef enum CtxClientFlags { + ITK_CLIENT_UI_RESIZABLE = 1<<0, + ITK_CLIENT_CAN_LAUNCH = 1<<1, + ITK_CLIENT_MAXIMIZED = 1<<2, + ITK_CLIENT_ICONIFIED = 1<<3, + ITK_CLIENT_SHADED = 1<<4, + ITK_CLIENT_TITLEBAR = 1<<5 +} CtxClientFlags; + +struct _CtxClient { + VT *vt; + Ctx *ctx; + char *title; + int x; + int y; + int width; + int height; + CtxClientFlags flags; +#if 0 + int shaded; + int iconified; + int maximized; + int resizable; +#endif + int unmaximized_x; + int unmaximized_y; + int unmaximized_width; + int unmaximized_height; + int do_quit; + long drawn_rev; + int id; + int internal; // render a settings window rather than a vt +#if CTX_THREADS + mtx_t mtx; +#endif +#if VT_RECORD + Ctx *recording; +#endif +}; + +typedef struct _CtxClient CtxClient; + + +extern CtxList *clients; +extern CtxClient *active; +extern CtxClient *active_tab; + + +int ctx_client_resize (int id, int width, int height); +void ctx_client_maximize (int id); + +CtxClient *vt_get_client (VT *vt); +CtxClient *ctx_client_new (Ctx *ctx, const char *commandline, + int x, int y, int width, int height, + CtxClientFlags flags); +CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags); +int ctx_clients_need_redraw (Ctx *ctx); + +extern float ctx_shape_cache_rate; +extern int _ctx_max_threads; + +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int w, int h); +void ctx_client_shade_toggle (int id); +float ctx_client_min_y_pos (Ctx *ctx); +float ctx_client_max_y_pos (Ctx *ctx); + +CtxClient *client_by_id (int id); + +void ctx_client_remove (Ctx *ctx, CtxClient *client); + +int ctx_client_height (int id); + +int ctx_client_x (int id); +int ctx_client_y (int id); +void ctx_client_raise_top (int id); +void ctx_client_lower_bottom (int id); +void ctx_client_iconify (int id); +int ctx_client_is_iconified (int id); +void ctx_client_uniconify (int id); +void ctx_client_maximize (int id); +int ctx_client_is_maximized (int id); +void ctx_client_unmaximize (int id); +void ctx_client_maximized_toggle (int id); +void ctx_client_shade (int id); +int ctx_client_is_shaded (int id); +void ctx_client_unshade (int id); +void ctx_client_toggle_maximized (int id); +void ctx_client_shade_toggle (int id); +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int width, int height); + + +#endif +#ifndef MRG_UTF8_H +#define MRG_UTF8_H + +#if !__COSMOPOLITAN__ +#include <string.h> +#include <stdint.h> +#endif + +static inline int mrg_utf8_len (const unsigned char first_byte) +{ + if ((first_byte & 0x80) == 0) + return 1; /* ASCII */ + else if ((first_byte & 0xE0) == 0xC0) + return 2; + else if ((first_byte & 0xF0) == 0xE0) + return 3; + else if ((first_byte & 0xF8) == 0xF0) + return 4; + return 1; +} + +static inline const char *mrg_utf8_skip (const char *s, int utf8_length) +{ + int count; + if (!s) + return NULL; + for (count = 0; *s; s++) + { + if ((*s & 0xC0) != 0x80) + count++; + if (count == utf8_length+1) + return s; + } + return s; +} + +int mrg_unichar_to_utf8 (unsigned int ch, + unsigned char *dest); +unsigned int mrg_utf8_to_unichar (unsigned char *utf8); + +////////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 1 + +static const uint8_t utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 +}; + +static inline uint32_t +utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state*16 + type]; + return *state; +} + +#endif +#if CTX_VT + +/* mrg - MicroRaptor Gui + * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef VT_LINE_H +#define VT_LINE_H + +#include "ctx.h" + +#ifndef CTX_UNLIKELY +#define CTX_UNLIKELY(x) __builtin_expect(!!(x), 0) +#define CTX_LIKELY(x) __builtin_expect(!!(x), 1) +#endif +#ifndef CTX_MAX +#define CTX_MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +typedef struct _VtLine VtLine; + +struct _VtLine +{ + CtxString string; + /* line extends string, permitting string ops to operate on it */ + + uint64_t *style; + int style_size; + + void *ctx; // each line can have an attached ctx context; + char *prev; + int prev_length; + CtxString *frame; + + int wrapped; + + void *ctx_copy; // each line can have an attached ctx context; + // clearing should be brutal enough to unset the context of the current + // at least in alt-screen mode + int double_width; + int double_height_top; + int double_height_bottom; + int contains_proportional; + float xscale; + float yscale; + float y_offset; + int in_scrolling_region; + + /* XXX: needs refactoring to a CtxList of links/images */ + void *images[4]; + int image_col[4]; + float image_X[4]; // 0.0 - 1.0 offset in cell + float image_Y[4]; + int image_rows[4]; + int image_cols[4]; + int image_subx[4]; + int image_suby[4]; + int image_subw[4]; + int image_subh[4]; +}; + + +static inline uint64_t vt_line_get_style (VtLine *string, int pos) +{ + if (string->string.is_line==0) + return 0; + if (pos < 0 || pos >= string->style_size) + return 0; + return string->style[pos]; +} + +#if !__COSMOPOLITAN__ +#include <stdlib.h> +#endif + +static inline void vt_line_set_style (VtLine *string, int pos, uint64_t style) +{ + if (string->string.is_line==0) + return; + if (pos < 0 || pos >= 512) + return; + if (pos >= string->style_size) + { + int new_size = pos + 16; + string->style = realloc (string->style, new_size * sizeof (uint64_t) ); + memset (&string->style[string->style_size], 0, (new_size - string->style_size) * sizeof (uint64_t) ); + string->style_size = new_size; + } + string->style[pos] = style; +} + +VtLine *vt_line_new_with_size (const char *initial, int initial_size); +VtLine *vt_line_new (const char *initial); + +static inline void vt_line_free (VtLine *line, int freealloc) +{ + CtxString *string = (CtxString*)line; + +#if 1 + //if (string->is_line) + { + VtLine *line = (VtLine*)string; + if (line->frame) + ctx_string_free (line->frame, 1); + if (line->style) + { free (line->style); } + if (line->ctx) + { ctx_free (line->ctx); } + if (line->ctx_copy) + { ctx_free (line->ctx_copy); } + } +#endif + + ctx_string_free (string, freealloc); +} +static inline const char *vt_line_get (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get (string); +} +static inline uint32_t vt_line_get_unichar (VtLine *line, int pos) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_unichar (string, pos); +} +static inline int vt_line_get_length (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_length (string); +} +static inline int vt_line_get_utf8length (VtLine *line) +{ + CtxString *string = (CtxString*)line; + return ctx_string_get_utf8length (string); +} +static inline void vt_line_set (VtLine *line, const char *new_string) +{ + CtxString *string = (CtxString*)line; + ctx_string_set (string, new_string); +} +static inline void vt_line_clear (VtLine *line) +{ + CtxString *string = (CtxString*)line; + ctx_string_clear (string); +} +static inline void vt_line_append_str (VtLine *line, const char *str) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_str (string, str); +} + +#if 0 +static inline void _ctx_string_append_byte (CtxString *string, char val) +{ + if (CTX_LIKELY((val & 0xC0) != 0x80)) + { string->utf8_length++; } + if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length)) + { + char *old = string->str; + string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2); + string->str = (char*)realloc (old, string->allocated_length); + } + string->str[string->length++] = val; + string->str[string->length] = '\0'; +} +#endif + +static inline void vt_line_append_byte (VtLine *line, char val) +{ + CtxString *string = (CtxString*)line; + _ctx_string_append_byte (string, val); +} +static inline void vt_line_append_string (VtLine *line, CtxString *string2) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_string (string, string2); +} +static inline void vt_line_append_unichar (VtLine *line, unsigned int unichar) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_unichar (string, unichar); +} + + +static inline void vt_line_append_data (VtLine *line, const char *data, int len) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_data (string, data, len); +} +static inline void vt_line_append_utf8char (VtLine *line, const char *str) +{ + CtxString *string = (CtxString*)line; + ctx_string_append_utf8char (string, str); +} +static inline void vt_line_replace_utf8 (VtLine *line, int pos, const char *new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_replace_utf8 (string, pos, new_glyph); +} +static inline void vt_line_insert_utf8 (VtLine *line, int pos, const char *new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_insert_utf8 (string, pos, new_glyph); + int len = vt_line_get_length (line); + for (int i = pos; i < len; i++) + vt_line_set_style (line, i, vt_line_get_style (line, i-1)); +} + +static inline void vt_line_insert_unichar (VtLine *line, int pos, uint32_t new_glyph) +{ + CtxString *string = (CtxString*)line; + ctx_string_insert_unichar (string, pos, new_glyph); + int len = vt_line_get_length (line); + for (int i = 1; i < len; i++) + vt_line_set_style (line, i, vt_line_get_style (line, i-1)); +} +static inline void vt_line_replace_unichar (VtLine *line, int pos, uint32_t unichar) +{ + CtxString *string = (CtxString*)line; + ctx_string_replace_unichar (string, pos, unichar); +} + +static inline void vt_line_remove (VtLine *line, int pos) +{ + CtxString *string = (CtxString*)line; + ctx_string_remove (string, pos); + + for (int i = pos; i < line->style_size-1; i++) + { + line->style[i] = line->style[i+1]; + } +} + + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#endif +/* mrg - MicroRaptor Gui + * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#if !__COSMOPOLITAN__ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#endif + +int ctx_unichar_to_utf8 (uint32_t ch, uint8_t *dest); +#define mrg_unichar_to_utf8 ctx_unichar_to_utf8 +void ctx_string_init (CtxString *string, int initial_size); + +VtLine *vt_line_new_with_size (const char *initial, int initial_size) +{ + VtLine *line = calloc (sizeof (VtLine), 1); + CtxString *string = (CtxString*)line; + ctx_string_init (string, initial_size); + if (initial) + { ctx_string_append_str (string, initial); } + line->style = calloc (sizeof (uint64_t), initial_size); + line->style_size = initial_size; + string->is_line = 1; + return line; +} + +VtLine *vt_line_new (const char *initial) +{ + return vt_line_new_with_size (initial, 8); +} + +typedef struct VtPty +{ + int pty; + pid_t pid; + int done; +} VtPty; +ssize_t vtpty_read (void *vtpty, void *buf, size_t count); +ssize_t vtpty_write (void *vtpty, const void *buf, size_t count); +void vtpty_resize (void *vtpty, int cols, int rows, + int px_width, int px_height); +int vtpty_waitdata (void *vtpty, int timeout); +extern CtxList *vts; +#define MAX_COLS 2048 // used for tabstops + + +typedef struct AudioState +{ + int action; + int samplerate; // 8000 + int channels; // 1 + int bits; // 8 + int type; // 'u' u-law f-loat s-igned u-nsigned + int buffer_size; // desired size of audiofragment in frames + // (both for feeding SDL and as desired chunking + // size) + + + int mic; // <- should + // request permisson, + // and if gotten, start streaming + // audio packets in the incoming direction + // + int encoding; // 'a' ascci85 'b' base64 + int compression; // '0': none , 'z': zlib 'o': opus(reserved) + + int frames; + + uint8_t *data; + int data_size; +} AudioState; + +typedef struct GfxState +{ + int action; + int id; + int buf_width; + int buf_height; + int format; + int compression; + int transmission; + int multichunk; + int buf_size; + int x; + int y; + int w; + int h; + int x_cell_offset; + int y_cell_offset; + int columns; + int rows; + int z_index; + int delete; + + uint8_t *data; + int data_size; +} GfxState; + +struct _VT +{ + VtPty vtpty; + int id; + unsigned char buf[BUFSIZ]; // need one per vt + int keyrepeat; + int lastx; + int lasty; + int result; + long rev; + //SDL_Rect dirty; + float dirtpad; + float dirtpad1; + float dirtpad2; + float dirtpad3; + + void *client; + + ssize_t (*write) (void *serial_obj, const void *buf, size_t count); + ssize_t (*read) (void *serial_obj, void *buf, size_t count); + int (*waitdata)(void *serial_obj, int timeout); + void (*resize) (void *serial_obj, int cols, int rows, int px_width, int px_height); + + + char *title; + void (*state) (VT *vt, int byte); + + AudioState audio; // < want to move this one level up and share impl + GfxState gfx; + + CtxList *saved_lines; + int in_alt_screen; + int saved_line_count; + CtxList *lines; + int line_count; + CtxList *scrollback; + int scrollback_count; + int leds[4]; + uint64_t cstyle; + + uint8_t fg_color[3]; + uint8_t bg_color[3]; + + int in_smooth_scroll; + int smooth_scroll; + float scroll_offset; + int debug; + int bell; + int origin; + int at_line_home; + int charset[4]; + int saved_charset[4]; + int shifted_in; + int reverse_video; + int echo; + int bracket_paste; + int ctx_events; + int font_is_mono; + int palette_no; + int has_blink; // if any of the set characters are blinking + // updated on each draw of the screen + + int can_launch; + + int unit_pixels; + int mouse; + int mouse_drag; + int mouse_all; + int mouse_decimal; + + + uint8_t utf8_holding[64]; /* only 4 needed for utf8 - but it's purpose + is also overloaded for ctx journal command + buffering , and the bigger sizes for the svg-like + ctx parsing mode */ + int utf8_expected_bytes; + int utf8_pos; + + + int ref_len; + char reference[16]; + int in_prev_match; + CtxParser *ctxp; + // text related data + float letter_spacing; + + float word_spacing; + float font_stretch; // horizontal expansion + float font_size_adjust; + // font-variant + // font-weight + // text-decoration + + int encoding; // 0 = utf8 1=pc vga 2=ascii + + int local_editing; /* terminal operates without pty */ + + int insert_mode; + int autowrap; + int justify; + float cursor_x; + int cursor_y; + int cols; + int rows; + VtLine *current_line; + + + int cr_on_lf; + int cursor_visible; + int saved_x; + int saved_y; + uint32_t saved_style; + int saved_origin; + int cursor_key_application; + int margin_top; + int margin_bottom; + int margin_left; + int margin_right; + + int left_right_margin_mode; + + int scrollback_limit; + float scroll; + int scroll_on_input; + int scroll_on_output; + + char *argument_buf; + int argument_buf_len; + int argument_buf_cap; + uint8_t tabs[MAX_COLS]; + int inert; + + int width; + int height; + + int cw; // cell width + int ch; // cell height + float font_to_cell_scale; + float font_size; // when set with set_font_size, cw and ch are recomputed + float line_spacing; // using line_spacing + float scale_x; + float scale_y; + + int ctx_pos; // 1 is graphics above text, 0 or -1 is below text + Ctx *root_ctx; /* only used for knowledge of top-level dimensions */ + + int blink_state; + + FILE *log; + + int cursor_down; + + int select_begin_col; + int select_begin_row; + int select_start_col; + int select_start_row; + int select_end_col; + int select_end_row; + int select_begin_x; + int select_begin_y; + int select_active; + + int popped; + + /* used to make runs of background on one line be drawn + * as a single filled rectangle + */ + int bg_active; + float bg_x0; + float bg_y0; + float bg_width; + float bg_height; + uint8_t bg_rgba[4]; +}; + + +VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch); + +void vt_open_log (VT *vt, const char *path); + +void vt_set_px_size (VT *vt, int width, int height); +void vt_set_term_size (VT *vt, int cols, int rows); + +int vt_cw (VT *vt); +int vt_ch (VT *vt); +void vt_set_font_size (VT *vt, float font_size); +float vt_get_font_size (VT *vt); +void vt_set_line_spacing (VT *vt, float line_spacing); + +const char *vt_find_shell_command (void); + +int vt_keyrepeat (VT *vt); + +int vt_get_result (VT *vt); +int vt_is_done (VT *vt); +int vt_poll (VT *vt, int timeout); +long vt_rev (VT *vt); +void vt_destroy (VT *vt); +int vt_has_blink (VT *vt); + +/* this is how mrg/mmm based key-events are fed into the vt engine + */ +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str); + +void vt_paste (VT *vt, const char *str); + +/* not needed when passing a commandline for command to + * run, but could be used for injecting commands, or + * output from stored shell commands/sessions to display + */ +//void vt_feed_byte (VT *vt, int byte); + +//)#define DEFAULT_SCROLLBACK (1<<16) +#define DEFAULT_SCROLLBACK (1<<13) +#define DEFAULT_ROWS 24 +#define DEFAULT_COLS 80 + +int vt_get_line_count (VT *vt); + +pid_t vt_get_pid (VT *vt); + +const char *vt_get_line (VT *vt, int no); + +void vt_set_scrollback_lines (VT *vt, int scrollback_lines); +int vt_get_scrollback_lines (VT *vt); + +void vt_set_scroll (VT *vt, int scroll); +int vt_get_scroll (VT *vt); + +int vt_get_cols (VT *vt); +int vt_get_rows (VT *vt); + +char *vt_get_selection (VT *vt); +int vt_get_cursor_x (VT *vt); +int vt_get_cursor_y (VT *vt); + +void vt_draw (VT *vt, Ctx *ctx, double x, double y); +void vt_register_events (VT *vt, Ctx *ctx, double x0, double y0); + +void vt_rev_inc (VT *vt); + +int vt_mic (VT *vt); +void vt_set_ctx (VT *vt, Ctx *ctx); /* XXX: rename, this sets the parent/global ctx */ + + +int vt_get_local (VT *vt); // this is a hack for the settings tab +void vt_set_local (VT *vt, int local); + + +typedef enum VtMouseEvent +{ + VT_MOUSE_MOTION = 0, + VT_MOUSE_PRESS, + VT_MOUSE_DRAG, + VT_MOUSE_RELEASE, +} VtMouseEvent; + +void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y); + +static ssize_t vt_write (VT *vt, const void *buf, size_t count) +{ + if (!vt->write) { return 0; } + return vt->write (&vt->vtpty, buf, count); +} +static ssize_t vt_read (VT *vt, void *buf, size_t count) +{ + if (!vt->read) { return 0; } + return vt->read (&vt->vtpty, buf, count); +} +static int vt_waitdata (VT *vt, int timeout) +{ + if (!vt->waitdata) { return 0; } + return vt->waitdata (&vt->vtpty, timeout); +} +static void vt_resize (VT *vt, int cols, int rows, int px_width, int px_height) +{ + if (vt && vt->resize) + { vt->resize (&vt->vtpty, cols, rows, px_width, px_height); } +} + +/* atty - audio interface and driver for terminals + * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef NO_SDL +#include <SDL.h> +#include <zlib.h> + +static int ydec (const void *srcp, void *dstp, int count) +{ + const char *src = srcp; + char *dst = dstp; + int out_len = 0; + for (int i = 0; i < count; i ++) + { + int o = src[i]; + switch (o) + { + case '=': + i++; + o = src[i]; + o = (o-42-64) % 256; + break; + case '\n': + case '\033': + case '\r': + case '\0': + break; + default: + o = (o-42) % 256; + break; + } + dst[out_len++] = o; + } + dst[out_len]=0; + return out_len; +} + +#ifndef NO_SDL +static SDL_AudioDeviceID speaker_device = 0; +#endif + +//#define AUDIO_CHUNK_SIZE 512 + +// our pcm queue is currently always 16 bit +// signed stereo + +static int16_t pcm_queue[1<<18]; +static int pcm_write_pos = 0; +static int pcm_read_pos = 0; + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right) +{ + if (pcm_write_pos >= (1<<18)-1) + { + /* TODO : fix cyclic buffer */ + pcm_write_pos = 0; + pcm_read_pos = 0; + } + pcm_queue[pcm_write_pos++]=sample_left; + pcm_queue[pcm_write_pos++]=sample_right; +} + +float click_volume = 0.05; + +void vt_feed_audio (VT *vt, void *samples, int bytes); +int mic_device = 0; // when non 0 we have an active mic device + + +/* https://jonathanhays.me/2018/11/14/mu-law-and-a-law-compression-tutorial/ + */ + +#if 0 +static char MuLawCompressTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +unsigned char LinearToMuLawSample(int16_t sample) +{ + const int cBias = 0x84; + const int cClip = 32635; + int sign = (sample >> 8) & 0x80; + + if (sign) + sample = (int16_t)-sample; + + if (sample > cClip) + sample = cClip; + + sample = (int16_t)(sample + cBias); + + int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF]; + int mantissa = (sample >> (exponent+3)) & 0x0F; + + int compressedByte = ~ (sign | (exponent << 4) | mantissa); + + return (unsigned char)compressedByte; +} +#endif + +void vt_feed_audio (VT *vt, void *samples, int bytes) +{ + char buf[256]; + AudioState *audio = &vt->audio; + uint8_t *data = samples; + int frames = bytes / (audio->bits/8) / audio->channels; + + if (audio->compression == 'z') + { + uLongf len = compressBound(bytes); + data = malloc (len); + int z_result = compress (data, &len, samples, len); + if (z_result != Z_OK) + { + char buf[256]= "\033_Ao=z;zlib error2\033\\"; + vt_write (vt, buf, strlen(buf)); + data = samples; + } + else + { + bytes = len; + } + } + + char *encoded = malloc (bytes * 2); + encoded[0]=0; + if (audio->encoding == 'a') + { + ctx_a85enc (data, encoded, bytes); + } + else /* if (audio->encoding == 'b') */ + { + ctx_bin2base64 (data, bytes, encoded); + } + + sprintf (buf, "\033[_Af=%i;", frames); + vt_write (vt, buf, strlen (buf)); + vt_write (vt, encoded, strlen(encoded)); + free (encoded); + + if (data != samples) + free (data); + + //vt_write (vt, samples, bytes); + buf[0]='\033'; + buf[1]='\\'; + buf[2]=0; + vt_write (vt, buf, 2); +} + +#define MIC_BUF_LEN 40960 + +uint8_t mic_buf[MIC_BUF_LEN]; +int mic_buf_pos = 0; + +static void mic_callback(void* userdata, + uint8_t * stream, + int len) +{ + AudioState *audio = userdata; + int16_t *sstream = (void*)stream; + int frames; + int channels = audio->channels; + + frames = len / 2; + + if (audio->bits == 8) + { + if (audio->type == 'u') + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < channels; c++) + { + mic_buf[mic_buf_pos++] = LinearToMuLawSample (sstream[i]); + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } + else + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < audio->channels; c++) + { + mic_buf[mic_buf_pos++] = (sstream[i]) / 256; + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } + } + else + { + for (int i = 0; i < frames; i++) + { + for (int c = 0; c < audio->channels; c++) + { + *((int16_t*)(&mic_buf[mic_buf_pos])) = (sstream[i]); + mic_buf_pos+=2; + if (mic_buf_pos >= MIC_BUF_LEN - 4) + mic_buf_pos = 0; + } + } + } +} + +static long int ticks (void) +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return tp.tv_sec * 1000 + tp.tv_usec / 1000; +} + +static long int silence_start = 0; + +static void sdl_audio_init () +{ + static int done = 0; + if (!done) + { +#ifndef NO_SDL + if (SDL_Init(SDL_INIT_AUDIO) < 0) + { + fprintf (stderr, "sdl audio init fail\n"); + } +#endif + done = 1; + } +} + +void vt_audio_task (VT *vt, int click) +{ + if (!vt) return; + AudioState *audio = &vt->audio; +#ifndef NO_SDL + + if (audio->mic) + { + if (mic_device == 0) + { + SDL_AudioSpec spec_want, spec_got; + sdl_audio_init (); + + spec_want.freq = audio->samplerate; + spec_want.channels = 1; + spec_want.format = AUDIO_S16; + spec_want.samples = audio->buffer_size; + spec_want.callback = mic_callback; + spec_want.userdata = audio; + mic_device = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, SDL_TRUE), 1, &spec_want, &spec_got, 0); + + SDL_PauseAudioDevice(mic_device, 0); + } + + if (mic_buf_pos) + { + SDL_LockAudioDevice (mic_device); + vt_feed_audio (vt, mic_buf, mic_buf_pos); + mic_buf_pos = 0; + SDL_UnlockAudioDevice (mic_device); + } + } + else + { + if (mic_device) + { + SDL_PauseAudioDevice(mic_device, 1); + SDL_CloseAudioDevice(mic_device); + mic_device = 0; + } + } + + int free_frames = audio->buffer_size - SDL_GetQueuedAudioSize(speaker_device); + int queued = (pcm_write_pos - pcm_read_pos)/2; // 2 for stereo + //if (free_frames > 6) free_frames -= 4; + int frames = queued; + + if (frames > free_frames) frames = free_frames; + if (frames > 0) + { + if (speaker_device == 0) + { + SDL_AudioSpec spec_want, spec_got; + sdl_audio_init (); + + spec_want.freq = audio->samplerate; + if (audio->bits == 8 && audio->type == 'u') + { + spec_want.format = AUDIO_S16; + spec_want.channels = 2; + } + else if (audio->bits == 8 && audio->type == 's') + { + spec_want.format = AUDIO_S8; + spec_want.channels = audio->channels; + } + else if (audio->bits == 16 && audio->type == 's') + { + spec_want.format = AUDIO_S16; + spec_want.channels = audio->channels; + } + else + { + spec_want.format = AUDIO_S16; // XXX : error + spec_want.channels = audio->channels; + } + + /* In SDL we always set 16bit stereo, but with the + * requested sample rate. + */ + spec_want.format = AUDIO_S16; + spec_want.channels = 2; + + spec_want.samples = audio->buffer_size; + spec_want.callback = NULL; + + speaker_device = SDL_OpenAudioDevice (NULL, 0, &spec_want, &spec_got, 0); + if (!speaker_device){ + fprintf (stderr, "sdl openaudiodevice fail\n"); + } + SDL_PauseAudioDevice (speaker_device, 0); + } + +#if 0 + { + int i; + unsigned char *b = (void*)(&pcm_queue[pcm_read_pos]); + for (i = 0; i < frames * 4; i++) + { + if ((b[i] > ' ') && (b[i] <= '~')) + fprintf (stderr, "[%c]", b[i]); + else + fprintf (stderr, "[%i]", b[i]); + } + } +#endif + SDL_QueueAudio (speaker_device, (void*)&pcm_queue[pcm_read_pos], frames * 4); + pcm_read_pos += frames*2; + silence_start = ticks(); + } + else + { + if (speaker_device && (ticks() - silence_start > 2000)) + { + SDL_PauseAudioDevice(speaker_device, 1); + SDL_CloseAudioDevice(speaker_device); + speaker_device = 0; + } + } +#endif +} + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right); + +static unsigned char vt_bell_audio[] = { +#if 1 + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +#else + 0x7e, 0xfe, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0xff, + 0xff, 0xfe, 0xfe, 0x7e, 0xff, 0xfe, 0xfd, 0xfd, 0xfe, 0xfe, 0xfd, 0xfd, + 0xfe, 0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, + 0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0xfd, 0xfd, 0x7e, 0x7e, 0xfd, 0xfe, 0xfe, + 0xfe, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0xfe, 0xfe, 0xff, 0xfe, + 0xfe, 0xfe, 0x7d, 0x7c, 0xfb, 0xfa, 0xfc, 0xfd, 0xfc, 0x76, 0x75, 0xfa, + 0xfb, 0x7b, 0xfc, 0xef, 0xf6, 0x77, 0x6d, 0x7b, 0xf8, 0x78, 0x78, 0xfa, + 0xf7, 0xfd, 0xfd, 0xfc, 0xfc, 0xfa, 0xf5, 0xf7, 0x7d, 0x7b, 0x78, 0x77, + 0x7c, 0x6f, 0x7b, 0xf5, 0xfb, 0x7b, 0x7c, 0x78, 0x76, 0xea, 0xf2, 0x6d, + 0xfd, 0xed, 0x7a, 0x6d, 0x6e, 0x71, 0xfe, 0x76, 0x6d, 0xfb, 0xef, 0x7e, + 0xfa, 0xef, 0xec, 0xed, 0xf8, 0xf0, 0xea, 0xf9, 0x70, 0x7c, 0x7c, 0x6b, + 0x6d, 0x75, 0xfb, 0xf1, 0xf9, 0xfe, 0xec, 0xea, 0x7c, 0x75, 0xff, 0xfb, + 0x7d, 0x77, 0x7a, 0x71, 0x6e, 0x6c, 0x6e, 0x7b, 0x7e, 0x7a, 0x7c, 0xf4, + 0xf9, 0x7b, 0x7b, 0xfa, 0xfe, 0x73, 0x79, 0xfe, 0x7b, 0x76, 0xfe, 0xf3, + 0xf9, 0x76, 0x77, 0x7e, 0x7e, 0x7d, 0x7c, 0xf9, 0xee, 0xf2, 0x7d, 0xf8, + 0xec, 0xee, 0xf7, 0xfa, 0xf7, 0xf6, 0xfd, 0x77, 0x75, 0x7b, 0xfa, 0xfe, + 0x78, 0x79, 0x7c, 0x76, 0x7e, 0xf7, 0xfb, 0xf5, 0xf6, 0x75, 0x6f, 0x74, + 0x6e, 0x6e, 0x6d, 0x6c, 0x7a, 0xf9, 0x75, 0x77, 0xf4, 0xf0, 0xf0, 0xf1, + 0xef, 0xf3, 0xf6, 0xfd, 0xfc, 0xfb, 0xfd, 0xfc, 0xf6, 0xf8, 0xfb, 0xf9, + 0xfa, 0xfd, 0xfb, 0xfc, 0x7a, 0x7c, 0x77, 0x75, 0x78, 0x7a, 0x7a, 0x78, + 0x7a, 0xfa, 0xf9, 0x7c, 0xff, 0xfb, 0x7d, 0x77, 0x73, 0x6c, 0x6e, 0x7b, + 0xfc, 0xfe, 0x7e, 0xfb, 0xf1, 0xeb, 0xee, 0xf6, 0xf6, 0xef, 0xf7, 0x7c, + 0x76, 0x76, 0x7b, 0x7a, 0x7b, 0x73, 0x73, 0x7c, 0x79, 0x70, 0x79, 0xfb, + 0xfd, 0xf8, 0xf9, 0xfc, 0xfc, 0xf8, 0xfb, 0xff, 0xfc, 0xf9, 0x75, 0x6f, + 0x74, 0xfe, 0xff, 0xfd, 0x7d, 0xf5, 0xef, 0xee, 0xf8, 0xfd, 0xfd, 0xf3, + 0xfa, 0xfe, 0xfe, 0x7c, 0x77, 0x7a, 0xfb, 0x79, 0x7e, 0x7b, 0xfd, 0x6d, + 0xfc, 0x7a, 0xf0, 0x74, 0xee, 0x79, 0xea, 0x79, 0xf9, 0x6d, 0xf7, 0x71, + 0x79, 0x76, 0x7c, 0x77, 0x6f, 0xf3, 0x6c, 0xe8, 0x67, 0xe3, 0x5e, 0xdc, + 0x58, 0xd8, 0x4e, 0xce, 0x46, 0xc5, 0x40, 0x67, 0xba, 0x49, 0xac, 0x26, + 0xba, 0x3e, 0xc5, 0xc8, 0x2b, 0xa8, 0x32, 0xbd, 0xe4, 0x3e, 0xb7, 0x3b, + 0xb7, 0x3a, 0x33, 0xab, 0x3f, 0xc8, 0x46, 0x5f, 0xb7, 0x69, 0xd4, 0x3d, + 0xc0, 0x4c, 0xf2, 0xdb, 0x3b, 0xdd, 0x69, 0xc5, 0x5f, 0xd8, 0xd8, 0xda, + 0xc6, 0x39, 0xba, 0x3f, 0x35, 0xb3, 0x3e, 0xbb, 0x4a, 0x4a, 0xe7, 0x60, + 0xae, 0x2c, 0xcb, 0x53, 0x45, 0xaf, 0x2a, 0xae, 0x3e, 0x4a, 0xae, 0x2a, + 0xad, 0x38, 0xcc, 0xbb, 0x36, 0xae, 0x2c, 0xc6, 0xce, 0x38, 0xb1, 0x2f, + 0xb9, 0x54, 0x7c, 0xb3, 0x28, 0xae, 0x3d, 0xcf, 0xbb, 0x2e, 0xb4, 0x41, + 0xc6, 0x78, 0x39, 0xbc, 0x41, 0xc8, 0x59, 0x5b, 0xc7, 0x43, 0xbc, 0x45, + 0xf3, 0xdc, 0x69, 0xd6, 0x48, 0xc9, 0x4e, 0xd9, 0x59, 0x61, 0xde, 0x4b, + 0xc9, 0x44, 0xc8, 0xf5, 0x43, 0xc5, 0x37, 0xba, 0x65, 0x4d, 0xc8, 0x31, + 0xaf, 0x47, 0xdb, 0xd6, 0x36, 0xad, 0x37, 0xbb, 0x61, 0x3a, 0xae, 0x2d, + 0xb4, 0x47, 0x49, 0xb2, 0x30, 0xac, 0x3a, 0xcd, 0xbc, 0x2e, 0xaf, 0x32, + 0xbd, 0xd7, 0x34, 0xaf, 0x32, 0xbb, 0x55, 0x4a, 0xb4, 0x30, 0xbb, 0x40, + 0xeb, 0xbf, 0x39, 0xba, 0x3a, 0xd6, 0xd3, 0x48, 0xc0, 0x3b, 0xce, 0x5e, + 0xe7, 0xd3, 0x46, 0xcb, 0x4c, 0xce, 0x74, 0x7e, 0x7e, 0x55, 0xcf, 0x44, + 0xc4, 0x5b, 0x7c, 0xd3, 0x3f, 0xbc, 0x44, 0xcb, 0xfa, 0x46, 0xb9, 0x37, + 0xb8, 0x51, 0x54, 0xbe, 0x33, 0xb1, 0x3d, 0xce, 0xc4, 0x34, 0xaf, 0x2f, + 0xbd, 0xf8, 0x37, 0xb0, 0x2d, 0xb1, 0x4c, 0x4a, 0xb3, 0x2c, 0xb0, 0x3c, + 0xe4, 0xbf, 0x2f, 0xaf, 0x35, 0xc0, 0xdb, 0x39, 0xb3, 0x31, 0xbb, 0x5d, + 0x4c, 0xb8, 0x37, 0xb9, 0x48, 0xe8, 0xc7, 0x3d, 0xba, 0x43, 0xce, 0xdd, + 0x52, 0xc6, 0x46, 0xce, 0x55, 0xdf, 0xe8, 0x52, 0xd5, 0x48, 0xca, 0x4d, + 0xef, 0x68, 0x4c, 0xc7, 0x42, 0xc2, 0x49, 0x78, 0xce, 0x3e, 0xb9, 0x3c, + 0xc8, 0xef, 0x43, 0xb7, 0x35, 0xb8, 0x4a, 0x53, 0xb8, 0x32, 0xaf, 0x3b, + 0xde, 0xc1, 0x34, 0xaf, 0x32, 0xc3, 0xde, 0x3b, 0xaf, 0x2e, 0xb6, 0x4e, + 0x48, 0xb4, 0x2e, 0xb2, 0x3d, 0xf0, 0xbf, 0x33, 0xb2, 0x37, 0xc8, 0xd9, + 0x3d, 0xb5, 0x36, 0xbc, 0x56, 0x4f, 0xbc, 0x39, 0xbc, 0x47, 0xf6, 0xcf, + 0x44, 0xbf, 0x46, 0xce, 0x68, 0x5b, 0xd0, 0x4a, 0xcc, 0x4d, 0xd3, 0x60, + 0x6a, 0xcf, 0x49, 0xc8, 0x45, 0xd0, 0x7b, 0x58, 0xc3, 0x3c, 0xbf, 0x48, + 0xe2, 0xc9, 0x3b, 0xb7, 0x39, 0xc5, 0xdb, 0x40, 0xb6, 0x31, 0xb9, 0x50, + 0x50, 0xb9, 0x2f, 0xb3, 0x3b, 0xdc, 0xbf, 0x33, 0xaf, 0x32, 0xc1, 0xd6, + 0x3b, 0xb0, 0x2f, 0xb8, 0x54, 0x4a, 0xb6, 0x30, 0xb4, 0x3f, 0xfd, 0xc0, + 0x36, 0xb5, 0x39, 0xcc, 0xd9, 0x41, 0xb9, 0x39, 0xc2, 0x59, 0x57, 0xc1, + 0x3e, 0xc2, 0x49, 0xe2, 0xd7, 0x4c, 0xcb, 0x47, 0xcf, 0x5b, 0xec, 0xe0, + 0x53, 0xcb, 0x4b, 0xca, 0x55, 0xf6, 0xdb, 0x48, 0xc0, 0x43, 0xc9, 0x5f, + 0x54, 0xc0, 0x3c, 0xbb, 0x43, 0xe8, 0xc8, 0x39, 0xb5, 0x39, 0xc6, 0xde, + 0x3d, 0xb4, 0x32, 0xba, 0x4f, 0x4c, 0xb9, 0x30, 0xb2, 0x3c, 0xec, 0xc1, + 0x33, 0xaf, 0x35, 0xc4, 0xd7, 0x3a, 0xb2, 0x31, 0xba, 0x56, 0x48, 0xb9, + 0x33, 0xb7, 0x44, 0x7e, 0xc3, 0x39, 0xb7, 0x3d, 0xcd, 0xe3, 0x42, 0xbd, + 0x3d, 0xc2, 0x58, 0x5d, 0xcb, 0x43, 0xc4, 0x4c, 0xd8, 0xf8, 0x58, 0xcd, + 0x4c, 0xcb, 0x4e, 0xda, 0x71, 0x5c, 0xcc, 0x46, 0xc4, 0x49, 0xdc, 0xdc, + 0x46, 0xbe, 0x3d, 0xc4, 0x59, 0x53, 0xbe, 0x38, 0xb8, 0x41, 0xe1, 0xc5, + 0x39, 0xb3, 0x38, 0xc4, 0xde, 0x3d, 0xb2, 0x32, 0xb9, 0x4e, 0x4b, 0xb7, + 0x30, 0xb3, 0x3d, 0xf2, 0xbf, 0x33, 0xb1, 0x36, 0xc9, 0xd9, 0x3a, 0xb4, + 0x33, 0xbc, 0x58, 0x49, 0xba, 0x36, 0xb9, 0x46, 0x7e, 0xc8, 0x3c, 0xba, + 0x3f, 0xcd, 0xe8, 0x4b, 0xc1, 0x41, 0xc7, 0x57, 0xfe, 0xd3, 0x4e, 0xc9, + 0x4d, 0xd0, 0x5e, 0x7c, 0xda, 0x4e, 0xca, 0x47, 0xcd, 0x5b, 0x68, 0xcc, + 0x40, 0xbf, 0x42, 0xd2, 0xe4, 0x42, 0xbd, 0x3a, 0xbf, 0x56, 0x50, 0xbd, + 0x36, 0xb6, 0x40, 0xe2, 0xc5, 0x36, 0xb2, 0x37, 0xc5, 0xde, 0x3c, 0xb3, + 0x32, 0xba, 0x52, 0x4a, 0xb7, 0x31, 0xb4, 0x3f, 0xef, 0xbf, 0x34, 0xb2, + 0x39, 0xc8, 0xd3, 0x3c, 0xb6, 0x37, 0xbe, 0x5c, 0x4c, 0xbd, 0x39, 0xbc, + 0x49, 0xf2, 0xcc, 0x3f, 0xbf, 0x44, 0xcf, 0xfd, 0x51, 0xca, 0x48, 0xcb, + 0x54, 0xe4, 0xeb, 0x57, 0xcf, 0x4d, 0xcc, 0x4f, 0xe0, 0xee, 0x51, 0xc7, + 0x44, 0xc6, 0x4f, 0x78, 0xcc, 0x3f, 0xbd, 0x3e, 0xce, 0xe5, 0x42, 0xba, + 0x38, 0xbe, 0x50, 0x4f, 0xbb, 0x35, 0xb6, 0x3e, 0xe8, 0xc2, 0x36, 0xb2, + 0x37, 0xc6, 0xda, 0x3c, 0xb3, 0x32, 0xba, 0x55, 0x4a, 0xb7, 0x33, 0xb5, + 0x41, 0x7e, 0xbf, 0x37, 0xb4, 0x3b, 0xcd, 0xd8, 0x3e, 0xb8, 0x39, 0xc2, + 0x5b, 0x4f, 0xc0, 0x3c, 0xbf, 0x4a, 0xee, 0xd1, 0x47, 0xc5, 0x47, 0xd0, + 0x68, 0x63, 0xd0, 0x4d, 0xcd, 0x4e, 0xd6, 0x67, 0x68, 0xd6, 0x4b, 0xc9, + 0x4a, 0xd1, 0x6e, 0x52, 0xc5, 0x3f, 0xc0, 0x4b, 0xfd, 0xcb, 0x3d, 0xba, + 0x3d, 0xcc, 0xe2, 0x41, 0xb8, 0x37, 0xbc, 0x53, 0x4e, 0xba, 0x34, 0xb6, + 0x3f, 0xee, 0xc1, 0x36, 0xb2, 0x38, 0xc8, 0xd6, 0x3c, 0xb3, 0x34, 0xbc, + 0x58, 0x49, 0xb9, 0x34, 0xb7, 0x44, 0x73, 0xc3, 0x38, 0xb7, 0x3d, 0xce, + 0xd9, 0x40, 0xbc, 0x3c, 0xc5, 0x5e, 0x55, 0xc6, 0x40, 0xc3, 0x4d, 0xe5, + 0xde, 0x4d, 0xca, 0x4b, 0xce, 0x5c, 0xfa, 0xe1, 0x54, 0xcd, 0x4d, 0xcd, + 0x56, 0xf3, 0xd9, 0x4a, 0xc4, 0x44, 0xcb, 0x67, 0x53, 0xc3, 0x3d, 0xbe, + 0x48, 0xf0, 0xca, 0x3c, 0xb8, 0x3b, 0xca, 0xdf, 0x3f, 0xb7, 0x36, 0xbc, + 0x54, 0x4c, 0xb9, 0x34, 0xb6, 0x40, 0xf7, 0xc1, 0x36, 0xb3, 0x39, 0xca, + 0xd6, 0x3c, 0xb4, 0x35, 0xbe, 0x5b, 0x49, 0xba, 0x36, 0xba, 0x47, 0x6f, + 0xc5, 0x3b, 0xba, 0x3f, 0xd2, 0xdd, 0x46, 0xbe, 0x3f, 0xc9, 0x5c, 0x5d, + 0xcc, 0x47, 0xc8, 0x4e, 0xdd, 0xf5, 0x5a, 0xd1, 0x4e, 0xcf, 0x52, 0xde, + 0x7d, 0x5c, 0xcf, 0x49, 0xc9, 0x4d, 0xdd, 0xde, 0x49, 0xc1, 0x3f, 0xc6, + 0x5d, 0x53, 0xc0, 0x3b, 0xbc, 0x46, 0xeb, 0xc8, 0x3b, 0xb7, 0x3b, 0xc8, + 0xde, 0x3e, 0xb6, 0x35, 0xbb, 0x57, 0x4c, 0xb9, 0x34, 0xb6, 0x42, 0xff, + 0xc1, 0x36, 0xb4, 0x3a, 0xcc, 0xd7, 0x3d, 0xb7, 0x37, 0xbf, 0x5e, 0x4a, + 0xbc, 0x38, 0xbc, 0x4a, 0x6e, 0xc9, 0x3e, 0xbe, 0x43, 0xd5, 0xe2, 0x4b, + 0xc5, 0x45, 0xcb, 0x5e, 0x6e, 0xd6, 0x4e, 0xcd, 0x51, 0xd7, 0x65, 0x74, + 0xdc, 0x54, 0xcd, 0x4d, 0xd1, 0x5e, 0x6b, 0xcf, 0x46, 0xc4, 0x47, 0xd7, + 0xe3, 0x48, 0xbe, 0x3d, 0xc3, 0x58, 0x54, 0xbe, 0x39, 0xba, 0x43, 0xee, + 0xc7, 0x3a, 0xb6, 0x3a, 0xc9, 0xdc, 0x3e, 0xb5, 0x35, 0xbd, 0x57, 0x4b, + 0xb9, 0x34, 0xb7, 0x43, 0x6f, 0xc1, 0x38, 0xb6, 0x3c, 0xcf, 0xd3, 0x3e, + 0xb8, 0x3a, 0xc3, 0x64, 0x4c, 0xbe, 0x3c, 0xbf, 0x4d, 0x72, 0xcc, 0x42, + 0xc1, 0x48, 0xd9, 0xed, 0x51, 0xcc, 0x4b, 0xcf, 0x5a, 0xef, 0xe8, 0x59, + 0xd3, 0x50, 0xd1, 0x58, 0xe1, 0xec, 0x56, 0xcc, 0x49, 0xca, 0x55, 0x7c, + 0xcf, 0x44, 0xbf, 0x43, 0xcf, 0xe5, 0x47, 0xbc, 0x3b, 0xbf, 0x56, 0x52, + 0xbe, 0x38, 0xb9, 0x42, 0xee, 0xc6, 0x39, 0xb6, 0x3a, 0xca, 0xdc, 0x3d, + 0xb6, 0x36, 0xbd, 0x59, 0x4a, 0xba, 0x35, 0xb9, 0x45, 0x6b, 0xc2, 0x39, + 0xb8, 0x3d, 0xd2, 0xd5, 0x3f, 0xbb, 0x3c, 0xc6, 0x69, 0x4f, 0xc1, 0x3f, + 0xc3, 0x50, 0x7d, 0xd0, 0x49, 0xc8, 0x4c, 0xd8, 0x7d, 0x5d, 0xd4, 0x4f, + 0xd2, 0x57, 0xde, 0x6e, 0x69, 0xda, 0x50, 0xcd, 0x4e, 0xd6, 0x71, 0x59, + 0xca, 0x44, 0xc5, 0x4d, 0xf3, 0xce, 0x41, 0xbd, 0x3f, 0xcd, 0xe5, 0x45, + 0xbb, 0x3a, 0xbf, 0x55, 0x51, 0xbd, 0x37, 0xb9, 0x43, 0xf1, 0xc5, 0x39, + 0xb6, 0x3b, 0xcc, 0xd9, 0x3d, 0xb7, 0x37, 0xbf, 0x5d, 0x49, 0xbb, 0x37, + 0xba, 0x48, 0x69, 0xc4, 0x3b, 0xba, 0x40, 0xd5, 0xd7, 0x42, 0xbd, 0x3f, + 0xc9, 0x69, 0x52, 0xc7, 0x44, 0xc7, 0x52, 0xfa, 0xda, 0x4f, 0xcd, 0x4f, + 0xd8, 0x66, 0x72, 0xdf, 0x59, 0xd4, 0x52, 0xd6, 0x5d, 0xef, 0xde, 0x4f, + 0xca, 0x49, 0xce, 0x64, 0x5a, 0xc8, 0x40, 0xc1, 0x4a, 0xec, 0xcd, 0x3f, + 0xbc, 0x3e, 0xcc, 0xe5, 0x43, 0xba, 0x39, 0xbe, 0x55, 0x4f, 0xbc, 0x37, + 0xb9, 0x43, 0xf8, 0xc4, 0x39, 0xb6, 0x3b, 0xcd, 0xd7, 0x3e, 0xb7, 0x38, + 0xc0, 0x60, 0x4b, 0xbc, 0x39, 0xbc, 0x4b, 0x6b, 0xc6, 0x3d, 0xbd, 0x43, + 0xd9, 0xda, 0x47, 0xc1, 0x42, 0xcd, 0x66, 0x5a, 0xcd, 0x48, 0xcc, 0x54, + 0xe9, 0xe9, 0x59, 0xd5, 0x54, 0xd5, 0x5c, 0xe4, 0xfb, 0x61, 0xd4, 0x4f, + 0xcd, 0x51, 0xdf, 0xe3, 0x4e, 0xc5, 0x45, 0xca, 0x5e, 0x5b, 0xc6, 0x3e, + 0xbf, 0x48, 0xea, 0xcc, 0x3e, 0xbb, 0x3d, 0xcb, 0xe4, 0x42, 0xba, 0x38, + 0xbe, 0x56, 0x4e, 0xbc, 0x37, 0xb9, 0x44, 0x7b, 0xc4, 0x39, 0xb7, 0x3c, + 0xcf, 0xd7, 0x3e, 0xb9, 0x3a, 0xc2, 0x62, 0x4c, 0xbd, 0x3b, 0xbe, 0x4d, + 0x6b, 0xc9, 0x3f, 0xbf, 0x46, 0xd9, 0xde, 0x4b, 0xc5, 0x47, 0xce, 0x63, + 0x65, 0xd3, 0x4e, 0xcf, 0x55, 0xdf, 0x74, 0x67, 0xdc, 0x55, 0xd3, 0x54, + 0xda, 0x68, 0x67, 0xd5, 0x4c, 0xca, 0x4d, 0xdc, 0xe7, 0x4d, 0xc4, 0x42, + 0xc8, 0x5b, 0x59, 0xc4, 0x3d, 0xbe, 0x47, 0xec, 0xcc, 0x3d, 0xba, 0x3d, + 0xcc, 0xe1, 0x42, 0xba, 0x39, 0xbf, 0x5a, 0x4f, 0xbc, 0x38, 0xba, 0x46, + 0x7d, 0xc5, 0x3b, 0xb9, 0x3e, 0xd0, 0xd8, 0x40, 0xbb, 0x3c, 0xc5, 0x63, + 0x4d, 0xc0, 0x3d, 0xc1, 0x4e, 0x6e, 0xcd, 0x44, 0xc4, 0x49, 0xdb, 0xec, + 0x50, 0xcc, 0x4a, 0xd1, 0x5c, 0x7b, 0xde, 0x56, 0xd2, 0x54, 0xd8, 0x62, + 0xf2, 0xe2, 0x58, 0xcf, 0x4e, 0xd0, 0x5d, 0x72, 0xd3, 0x4a, 0xc5, 0x49, + 0xd6, 0xe8, 0x4b, 0xc0, 0x3f, 0xc5, 0x5b, 0x58, 0xc2, 0x3c, 0xbd, 0x47, + 0xee, 0xca, 0x3d, 0xba, 0x3d, 0xcd, 0xdf, 0x41, 0xba, 0x3a, 0xc0, 0x5b, + 0x4d, 0xbd, 0x39, 0xbc, 0x48, 0x73, 0xc6, 0x3c, 0xbb, 0x3f, 0xd4, 0xd9, + 0x43, 0xbd, 0x3e, 0xc8, 0x67, 0x50, 0xc4, 0x40, 0xc4, 0x51, 0x7b, 0xd1, + 0x4a, 0xc8, 0x4d, 0xd9, 0xf6, 0x5c, 0xd0, 0x51, 0xd2, 0x5c, 0xe8, 0xf2, + 0x62, 0xd8, 0x55, 0xd4, 0x56, 0xe1, 0x7c, 0x59, 0xcf, 0x49, 0xcc, 0x53, + 0x7a, 0xd4, 0x46, 0xc4, 0x45, 0xd5, 0xef, 0x49, 0xc0, 0x3d, 0xc5, 0x57, + 0x55, 0xc2, 0x3b, 0xbd, 0x47, 0xed, 0xc9, 0x3d, 0xb9, 0x3e, 0xcc, 0xdb, + 0x43, 0xb9, 0x3b, 0xc0, 0x61, 0x4f, 0xbd, 0x3b, 0xbc, 0x4b, 0x75, 0xc6, + 0x3d, 0xbc, 0x43, 0xd7, 0xd9, 0x45, 0xbf, 0x40, 0xcc, 0x68, 0x54, 0xc9, + 0x44, 0xca, 0x53, 0x7d, 0xda, 0x4d, 0xce, 0x4f, 0xdb, 0x6d, 0x68, 0xdd, + 0x55, 0xd6, 0x56, 0xdc, 0x67, 0x71, 0xde, 0x53, 0xce, 0x4f, 0xd6, 0x6e, + 0x5c, 0xcc, 0x47, 0xc7, 0x4f, 0xef, 0xd0, 0x45, 0xbf, 0x44, 0xcf, 0xe6, + 0x48, 0xbe, 0x3d, 0xc3, 0x5a, 0x54, 0xc0, 0x3b, 0xbc, 0x47, 0xfa, 0xc9, + 0x3c, 0xba, 0x3e, 0xd0, 0xdc, 0x41, 0xbb, 0x3b, 0xc4, 0x5f, 0x4d, 0xbf, + 0x3c, 0xbf, 0x4c, 0x6d, 0xc9, 0x3f, 0xbe, 0x46, 0xda, 0xdc, 0x49, 0xc3, + 0x45, 0xce, 0x6b, 0x5b, 0xcd, 0x4a, 0xcd, 0x57, 0xee, 0xe4, 0x58, 0xd4, + 0x54, 0xd9, 0x60, 0xf1, 0xed, 0x5f, 0xd7, 0x53, 0xd3, 0x5a, 0xeb, 0xe1, + 0x52, 0xca, 0x4b, 0xce, 0x68, 0x5c, 0xc9, 0x44, 0xc4, 0x4e, 0xec, 0xce, + 0x43, 0xbe, 0x42, 0xcf, 0xe4, 0x47, 0xbd, 0x3c, 0xc3, 0x5b, 0x50, 0xbf, + 0x3b, 0xbd, 0x48, 0x73, 0xc8, 0x3c, 0xbb, 0x3f, 0xd4, 0xda, 0x41, 0xbc, + 0x3d, 0xc7, 0x67, 0x4d, 0xc0, 0x3d, 0xc2, 0x4f, 0x68, 0xcb, 0x42, 0xc2, + 0x4a, 0xdd, 0xdd, 0x4d, 0xc7, 0x4a, 0xd1, 0x6d, 0x65, 0xd3, 0x52, 0xd1, + 0x5b, 0xe3, 0xf8, 0x68, 0xdd, 0x5a, 0xd8, 0x59, 0xde, 0x6d, 0x68, 0xd9, + 0x4e, 0xce, 0x4f, 0xe1, 0xeb, 0x4e, 0xc9, 0x45, 0xcd, 0x5d, 0x58, 0xc9, + 0x3f, 0xc2, 0x4b, 0xf6, 0xce, 0x3f, 0xbd, 0x40, 0xcf, 0xe0, 0x45, 0xbc, + 0x3d, 0xc3, 0x5e, 0x51, 0xbf, 0x3c, 0xbd, 0x4b, 0x7b, 0xc7, 0x3e, 0xbb, + 0x42, 0xd4, 0xd6, 0x45, 0xbd, 0x3f, 0xc9, 0x6f, 0x50, 0xc3, 0x40, 0xc5, + 0x53, 0x6c, 0xce, 0x46, 0xc7, 0x4c, 0xe0, 0xeb, 0x51, 0xce, 0x4d, 0xd7, + 0x5f, 0x6c, 0xe3, 0x57, 0xd9, 0x57, 0xde, 0x64, 0xfd, 0xe9, 0x5b, 0xd5, + 0x52, 0xd5, 0x61, 0x78, 0xd6, 0x4d, 0xc9, 0x4e, 0xd8, 0xe5, 0x4e, 0xc4, + 0x44, 0xc9, 0x5f, 0x5a, 0xc6, 0x3f, 0xc0, 0x4b, 0xf5, 0xcd, 0x3f, 0xbd, + 0x40, 0xd2, 0xdf, 0x43, 0xbd, 0x3c, 0xc6, 0x5e, 0x4e, 0xbf, 0x3b, 0xbf, + 0x4c, 0x6b, 0xc9, 0x3e, 0xbd, 0x44, 0xda, 0xd8, 0x45, 0xbf, 0x42, 0xcc, + 0x75, 0x52, 0xc6, 0x45, 0xc8, 0x58, 0x76, 0xd2, 0x4c, 0xca, 0x51, 0xdd, + 0xed, 0x5d, 0xd3, 0x55, 0xd7, 0x61, 0xec, 0xef, 0x65, 0xdc, 0x58, 0xd7, + 0x5a, 0xe4, 0xfd, 0x5c, 0xd2, 0x4c, 0xcf, 0x57, 0x77, 0xd8, 0x49, 0xc7, + 0x48, 0xd8, 0xeb, 0x4b, 0xc3, 0x40, 0xc8, 0x5d, 0x57, 0xc4, 0x3e, 0xbf, + 0x4b, 0xf8, 0xca, 0x3f, 0xbc, 0x42, 0xd1, 0xd9, 0x45, 0xbc, 0x3e, 0xc6, + 0x6a, 0x4f, 0xbf, 0x3d, 0xc0, 0x4f, 0x67, 0xc9, 0x3f, 0xbf, 0x47, 0xdf, + 0xdb, 0x47, 0xc4, 0x44, 0xd1, 0x6c, 0x53, 0xcc, 0x48, 0xce, 0x58, 0x74, + 0xdc, 0x50, 0xd1, 0x54, 0xdf, 0x74, 0x6a, 0xde, 0x5b, 0xd9, 0x5d, 0xdd, + 0x6e, 0xfc, 0xdd, 0x5a, 0xcf, 0x54, 0xd6, 0x7b, 0x60, 0xcd, 0x4b, 0xc9, + 0x55, 0xf2, 0xd2, 0x48, 0xc3, 0x47, 0xd5, 0xe6, 0x4a, 0xc1, 0x3f, 0xc8, + 0x5d, 0x52, 0xc4, 0x3d, 0xc0, 0x4b, 0x71, 0xcb, 0x3e, 0xbd, 0x41, 0xd7, + 0xdc, 0x43, 0xbe, 0x3e, 0xc9, 0x6a, 0x4e, 0xc1, 0x3e, 0xc3, 0x52, 0x6a, + 0xca, 0x43, 0xc1, 0x4b, 0xdd, 0xda, 0x4c, 0xc6, 0x4a, 0xd1, 0x77, 0x5d, + 0xce, 0x4e, 0xcf, 0x5c, 0xf3, 0xe5, 0x5a, 0xd8, 0x58, 0xdd, 0x62, 0xfb, + 0xf4, 0x5e, 0xdc, 0x54, 0xd9, 0x5a, 0xf6, 0xea, 0x52, 0xce, 0x4c, 0xd4, + 0x67, 0x5c, 0xcc, 0x46, 0xc8, 0x50, 0xf8, 0xd0, 0x45, 0xc0, 0x46, 0xd3, + 0xe1, 0x49, 0xbf, 0x3f, 0xc6, 0x63, 0x54, 0xc1, 0x3e, 0xbf, 0x4d, 0x76, + 0xc9, 0x3f, 0xbd, 0x44, 0xd8, 0xda, 0x44, 0xbe, 0x3f, 0xcb, 0x6b, 0x4e, + 0xc4, 0x3f, 0xc7, 0x53, 0x66, 0xcd, 0x45, 0xc6, 0x4d, 0xe3, 0xdf, 0x4e, + 0xcb, 0x4d, 0xd5, 0x6f, 0x65, 0xd6, 0x55, 0xd4, 0x5e, 0xe5, 0xf1, 0x6b, + 0xdc, 0x5d, 0xd8, 0x5e, 0xdf, 0x79, 0x6d, 0xd9, 0x53, 0xcf, 0x56, 0xe3, + 0xe8, 0x52, 0xcb, 0x49, 0xcf, 0x61, 0x5a, 0xcb, 0x43, 0xc6, 0x4d, 0x7c, + 0xd1, 0x42, 0xc0, 0x43, 0xd6, 0xe3, 0x47, 0xbf, 0x3e, 0xc8, 0x63, 0x51, + 0xc1, 0x3e, 0xc0, 0x4e, 0x6f, 0xc9, 0x3f, 0xbe, 0x47, 0xd9, 0xd7, 0x47, + 0xbf, 0x43, 0xcc, 0x79, 0x51, 0xc6, 0x44, 0xc9, 0x58, 0x6a, 0xd0, 0x49, + 0xca, 0x4f, 0xe6, 0xea, 0x54, 0xd1, 0x50, 0xdb, 0x65, 0x6e, 0xe4, 0x5a, + 0xdc, 0x5a, 0xe1, 0x67, 0xfe, 0xea, 0x5d, 0xd7, 0x55, 0xd8, 0x63, 0x74, + 0xd8, 0x4f, 0xcb, 0x4f, 0xdc, 0xe5, 0x50, 0xc7, 0x47, 0xcc, 0x65, 0x5b, + 0xc8, 0x43, 0xc4, 0x4e, 0xfa, 0xcd, 0x42, 0xbf, 0x44, 0xd6, 0xdf, 0x46, + 0xbf, 0x3f, 0xc9, 0x64, 0x4f, 0xc3, 0x3e, 0xc3, 0x4f, 0x68, 0xcb, 0x40, + 0xc0, 0x48, 0xde, 0xd9, 0x48, 0xc2, 0x45, 0xcf, 0x7b, 0x55, 0xc9, 0x48, + 0xcb, 0x5c, 0x75, 0xd3, 0x4f, 0xcd, 0x56, 0xe0, 0xed, 0x5e, 0xd7, 0x58, + 0xdb, 0x63, 0xef, 0xf5, 0x65, 0xdf, 0x5a, 0xdb, 0x5b, 0xea, 0x7d, 0x5d, + 0xd6, 0x4e, 0xd3, 0x59, 0x73, 0xd9, 0x4b, 0xca, 0x4b, 0xdc, 0xeb, 0x4d, + 0xc6, 0x44, 0xcb, 0x61, 0x59, 0xc7, 0x41, 0xc2, 0x4e, 0xfb, 0xcc, 0x42, + 0xbe, 0x46, 0xd5, 0xdb, 0x47, 0xbe, 0x40, 0xc9, 0x6e, 0x50, 0xc2, 0x3f, + 0xc4, 0x52, 0x69, 0xcb, 0x42, 0xc3, 0x4a, 0xe2, 0xdc, 0x49, 0xc6, 0x48, + 0xd4, 0x71, 0x56, 0xcd, 0x4a, 0xcf, 0x5b, 0x73, 0xdd, 0x53, 0xd3, 0x57, + 0xe2, 0x78, 0x6a, 0xdf, 0x5d, 0xdb, 0x5e, 0xe1, 0x6f, 0x7a, 0xe0, 0x5b, + 0xd4, 0x57, 0xdb, 0x79, 0x5f, 0xd0, 0x4d, 0xcd, 0x58, 0xfd, 0xd6, 0x4a, + 0xc7, 0x4a, 0xd9, 0xe8, 0x4c, 0xc5, 0x42, 0xcb, 0x5f, 0x55, 0xc7, 0x3f, + 0xc4, 0x4e, 0x71, 0xcd, 0x41, 0xbf, 0x46, 0xd9, 0xdb, 0x47, 0xbf, 0x41, + 0xcb, 0x70, 0x51, 0xc4, 0x42, 0xc5, 0x57, 0x6b, 0xcc, 0x46, 0xc4, 0x4d, + 0xe0, 0xdb, 0x4d, 0xc8, 0x4c, 0xd5, 0x78, 0x5d, 0xd1, 0x4f, 0xd3, 0x5d, + 0xfb, 0xe6, 0x5a, 0xda, 0x59, 0xe1, 0x67, 0x7c, 0xf1, 0x5f, 0xde, 0x58, + 0xdc, 0x5e, 0xf9, 0xe8, 0x56, 0xd1, 0x4f, 0xd7, 0x6d, 0x5e, 0xce, 0x4a, + 0xcb, 0x55, 0xf7, 0xd3, 0x49, 0xc4, 0x4a, 0xd7, 0xe3, 0x4c, 0xc2, 0x43, + 0xca, 0x66, 0x56, 0xc5, 0x40, 0xc3, 0x4f, 0x74, 0xcc, 0x42, 0xc0, 0x47, + 0xdb, 0xdc, 0x47, 0xc1, 0x42, 0xce, 0x6e, 0x50, 0xc7, 0x43, 0xc9, 0x57, + 0x67, 0xcf, 0x48, 0xc9, 0x4e, 0xe6, 0xe0, 0x50, 0xcd, 0x4e, 0xd8, 0x6f, + 0x65, 0xd7, 0x55, 0xd7, 0x5f, 0xeb, 0xf3, 0x68, 0xde, 0x5e, 0xdc, 0x60, + 0xe5, 0x7b, 0x6b, 0xdc, 0x56, 0xd4, 0x59, 0xe8, 0xe8, 0x55, 0xcd, 0x4c, + 0xd3, 0x68, 0x5c, 0xcd, 0x47, 0xc9, 0x52, 0xfe, 0xd2, 0x47, 0xc4, 0x48, + 0xd8, 0xe2, 0x4a, 0xc2, 0x42, 0xcb, 0x68, 0x54, 0xc5, 0x40, 0xc4, 0x51, + 0x6e, 0xcc, 0x42, 0xc1, 0x49, 0xdd, 0xda, 0x49, 0xc3, 0x46, 0xcf, 0x7a, + 0x53, 0xc8, 0x46, 0xcb, 0x5b, 0x6a, 0xd2, 0x4b, 0xcc, 0x52, 0xe7, 0xe7, + 0x56, 0xd2, 0x53, 0xdc, 0x6a, 0x6d, 0xe2, 0x5b, 0xdc, 0x5e, 0xe5, 0x6d, + 0x7a, 0xea, 0x5f, 0xda, 0x59, 0xdc, 0x68, 0x70, 0xdb, 0x52, 0xcf, 0x53, + 0xe0, 0xe9, 0x53, 0xcb, 0x4a, 0xcf, 0x66, 0x5c, 0xcb, 0x46, 0xc7, 0x51, + 0xfb, 0xcf, 0x46, 0xc2, 0x48, 0xd8, 0xdf, 0x4a, 0xc2, 0x42, 0xcb, 0x69, + 0x53, 0xc5, 0x41, 0xc5, 0x53, 0x6b, 0xcc, 0x44, 0xc3, 0x4a, 0xe0, 0xdb, + 0x4a, 0xc5, 0x48, 0xd2, 0x78, 0x55, 0xcb, 0x49, 0xce, 0x5c, 0x6e, 0xd7, + 0x4f, 0xcf, 0x56, 0xe6, 0xef, 0x5d, 0xd8, 0x59, 0xdc, 0x67, 0xf6, 0xee, + 0x65, 0xdf, 0x5d, 0xdd, 0x61, 0xec, 0xf4, 0x5f, 0xd8, 0x54, 0xd6, 0x5f, + 0x78, 0xda, 0x4f, 0xcc, 0x4f, 0xde, 0xea, 0x50, 0xc9, 0x48, 0xce, 0x64, + 0x5a, 0xca, 0x45, 0xc7, 0x50, 0x7b, 0xcf, 0x45, 0xc3, 0x48, 0xda, 0xde, + 0x4a, 0xc2, 0x43, 0xcc, 0x6d, 0x53, 0xc6, 0x43, 0xc7, 0x56, 0x6a, 0xcd, + 0x46, 0xc5, 0x4d, 0xe2, 0xdc, 0x4c, 0xc8, 0x4b, 0xd5, 0x7a, 0x59, 0xce, + 0x4d, 0xd0, 0x5e, 0x76, 0xdc, 0x55, 0xd4, 0x5a, 0xe5, 0x7d, 0x68, 0xdf, + 0x5d, 0xde, 0x60, 0xe9, 0x73, 0x70, 0xe4, 0x5c, 0xd9, 0x59, 0xe1, 0x7a, + 0x60, 0xd5, 0x4f, 0xd1, 0x5b, 0x7e, 0xd9, 0x4d, 0xca, 0x4d, 0xdb, 0xe8, + 0x4f, 0xc8, 0x47, 0xcd, 0x66, 0x5a, 0xc9, 0x44, 0xc6, 0x51, 0x78, 0xce, + 0x45, 0xc3, 0x49, 0xdb, 0xdd, 0x49, 0xc3, 0x44, 0xce, 0x6f, 0x53, 0xc7, + 0x44, 0xc9, 0x57, 0x69, 0xce, 0x48, 0xc8, 0x4e, 0xe6, 0xde, 0x4e, 0xcb, + 0x4d, 0xd8, 0x78, 0x5d, 0xd2, 0x50, 0xd5, 0x5f, 0xfc, 0xe4, 0x5c, 0xda, + 0x5c, 0xe2, 0x6e, 0x7d, 0xeb, 0x63, 0xde, 0x5d, 0xde, 0x65, 0xf9, 0xe7, + 0x5a, 0xd5, 0x54, 0xdb, 0x6f, 0x5f, 0xd2, 0x4d, 0xce, 0x58, 0x7d, 0xd7, + 0x4b, 0xc9, 0x4c, 0xdb, 0xe7, 0x4e, 0xc6, 0x46, 0xcd, 0x67, 0x58, 0xc8, + 0x44, 0xc7, 0x53, 0x72, 0xce, 0x45, 0xc3, 0x4a, 0xdd, 0xdc, 0x4a, 0xc4, + 0x46, 0xcf, 0x76, 0x54, 0xc9, 0x46, 0xcb, 0x5a, 0x69, 0xd0, 0x4a, 0xcb, + 0x51, 0xe8, 0xe1, 0x52, 0xce, 0x50, 0xdb, 0x72, 0x63, 0xda, 0x56, 0xda, + 0x5f, 0xf1, 0xf3, 0x65, 0xe0, 0x5e, 0xe0, 0x63, 0xec, 0x7c, 0x6a, 0xde, + 0x59, 0xd8, 0x5c, 0xec, 0xe9, 0x58, 0xd0, 0x4f, 0xd7, 0x6c, 0x5f, 0xcf, + 0x4b, 0xcc, 0x56, 0xfc, 0xd5, 0x4a, 0xc7, 0x4b, 0xdb, 0xe4, 0x4d, 0xc6, + 0x46, 0xcd, 0x69, 0x57, 0xc8, 0x44, 0xc7, 0x54, 0x6e, 0xce, 0x46, 0xc5, + 0x4b, 0xdf, 0xdc, 0x4b, 0xc6, 0x48, 0xd2, 0x79, 0x55, 0xcb, 0x49, 0xcd, + 0x5d, 0x6c, 0xd4, 0x4d, 0xcd, 0x55, 0xe8, 0xe6, 0x58, 0xd3, 0x55, 0xdd, + 0x6e, 0x6d, 0xe0, 0x5d, 0xdd, 0x5f, 0xe9, 0x73, 0x75, 0xe9, 0x60, 0xdd, + 0x5c, 0xe0, 0x6c, 0x6e, 0xde, 0x55, 0xd4, 0x57, 0xe6, 0xeb, 0x56, 0xce, + 0x4d, 0xd4, 0x6a, 0x5e, 0xce, 0x49, 0xcb, 0x55, 0xfe, 0xd3, 0x49, 0xc6, + 0x4b, 0xdb, 0xe2, 0x4c, 0xc5, 0x46, 0xce, 0x6c, 0x56, 0xc8, 0x45, 0xc8, + 0x56, 0x6c, 0xce, 0x47, 0xc6, 0x4d, 0xe3, 0xdd, 0x4c, 0xc8, 0x4a, 0xd5, + 0x7a, 0x57, 0xcd, 0x4b, 0xcf, 0x5e, 0x6d, 0xd8, 0x50, 0xd1, 0x58, 0xe9, + 0xee, 0x5d, 0xd9, 0x5a, 0xde, 0x6a, 0xfe, 0xec, 0x65, 0xe0, 0x5f, 0xe0, + 0x67, 0xf0, 0xf1, 0x63, 0xda, 0x58, 0xda, 0x65, 0x78, 0xdc, 0x53, 0xcf, + 0x54, 0xe1, 0xeb, 0x54, 0xcc, 0x4b, 0xd1, 0x68, 0x5d, 0xcd, 0x48, 0xca, + 0x54, 0x7b, 0xd2, 0x48, 0xc6, 0x4b, 0xdc, 0xe0, 0x4c, 0xc6, 0x47, 0xcf, + 0x6e, 0x55, 0xc9, 0x45, 0xca, 0x58, 0x6b, 0xcf, 0x48, 0xc8, 0x4e, 0xe5, + 0xdd, 0x4e, 0xca, 0x4c, 0xd8, 0x7b, 0x5a, 0xcf, 0x4e, 0xd3, 0x60, 0x73, + 0xdd, 0x56, 0xd6, 0x5b, 0xe8, 0xfc, 0x67, 0xdf, 0x5e, 0xdf, 0x65, 0xed, + 0x7b, 0x6e, 0xe5, 0x5e, 0xdc, 0x5d, 0xe6, 0xff, 0x63, 0xd8, 0x53, 0xd5, + 0x5e, 0x7c, 0xdb, 0x4f, 0xcd, 0x50, 0xde, 0xea, 0x52, 0xcb, 0x4a, 0xcf, + 0x68, 0x5c, 0xcc, 0x47, 0xc9, 0x54, 0x79, 0xd1, 0x48, 0xc6, 0x4b, 0xdd, + 0xdf, 0x4c, 0xc6, 0x47, 0xd0, 0x6f, 0x56, 0xca, 0x47, 0xcb, 0x5a, 0x6b, + 0xd1, 0x4a, 0xca, 0x50, 0xe7, 0xdf, 0x50, 0xcd, 0x4f, 0xda, 0x79, 0x5e, + 0xd5, 0x52, 0xd7, 0x62, 0xff, 0xe4, 0x5c, 0xdb, 0x5d, 0xe5, 0x73, 0x77, + 0xea, 0x64, 0xe0, 0x5f, 0xe2, 0x6b, 0xfe, 0xe8, 0x5c, 0xd8, 0x58, 0xde, + 0x76, 0x63, 0xd5, 0x50, 0xd1, 0x5b, 0xff, 0xda, 0x4e, 0xcb, 0x4f, 0xdd, + 0xe9, 0x50, 0xca, 0x49, 0xcf, 0x69, 0x5b, 0xcb, 0x47, 0xc9, 0x55, 0x75, + 0xd1, 0x48, 0xc7, 0x4c, 0xde, 0xde, 0x4c, 0xc7, 0x49, 0xd2, 0x76, 0x57, + 0xcb, 0x49, 0xcd, 0x5c, 0x6b, 0xd3, 0x4c, 0xcd, 0x54, 0xe9, 0xe3, 0x54, + 0xcf, 0x52, 0xdc, 0x75, 0x64, 0xda, 0x58, 0xdb, 0x63, 0xf4, 0xee, 0x65, + 0xe0, 0x5f, 0xe3, 0x68, 0xf1, 0xf9, 0x6a, 0xe0, 0x5d, 0xdc, 0x60, 0xef, + 0xeb, 0x5b, 0xd5, 0x54, 0xda, 0x6d, 0x62, 0xd3, 0x4e, 0xcf, 0x59, 0xfd, + 0xd9, 0x4d, 0xca, 0x4e, 0xdd, 0xe8, 0x4f, 0xc9, 0x49, 0xcf, 0x69, 0x5a, + 0xcb, 0x47, 0xca, 0x56, 0x73, 0xd1, 0x49, 0xc7, 0x4d, 0xdf, 0xdf, 0x4d, + 0xc8, 0x4a, 0xd4, 0x77, 0x58, 0xcd, 0x4b, 0xcf, 0x5d, 0x6d, 0xd6, 0x4e, + 0xcf, 0x56, 0xea, 0xe9, 0x59, 0xd5, 0x56, 0xde, 0x6f, 0x6d, 0xdf, 0x5d, + 0xdd, 0x62, 0xeb, 0x7c, 0x71, 0xe8, 0x62, 0xdf, 0x60, 0xe5, 0x73, 0x6f, + 0xdf, 0x5a, 0xd7, 0x5c, 0xe9, 0xec, 0x5a, 0xd1, 0x50, 0xd7, 0x6c, 0x61, + 0xd1, 0x4c, 0xcd, 0x58, 0xfd, 0xd7, 0x4c, 0xc9, 0x4d, 0xdd, 0xe7, 0x4f, + 0xc9, 0x49, 0xcf, 0x6b, 0x59, 0xcb, 0x47, 0xcb, 0x57, 0x6f, 0xd1, 0x49, + 0xc9, 0x4e, 0xe2, 0xdf, 0x4e, 0xca, 0x4c, 0xd6, 0x78, 0x5a, 0xcf, 0x4d, + 0xd1, 0x5f, 0x70, 0xda, 0x52, 0xd3, 0x59, 0xe9, 0xef, 0x5e, 0xda, 0x5a, + 0xdf, 0x6b, 0x7b, 0xeb, 0x63, 0xe1, 0x61, 0xe5, 0x6b, 0xfa, 0xf0, 0x64, + 0xdd, 0x5b, 0xde, 0x68, 0x75, 0xdf, 0x56, 0xd4, 0x58, 0xe4, 0xed, 0x58, + 0xcf, 0x4e, 0xd5, 0x6a, 0x60, 0xcf, 0x4b, 0xcc, 0x57, 0xfd, 0xd6, 0x4b, + 0xc9, 0x4d, 0xdd, 0xe5, 0x4f, 0xc9, 0x49, 0xd0, 0x6d, 0x5a, 0xcc, 0x48, + 0xcc, 0x59, 0x6f, 0xd3, 0x4b, 0xca, 0x4f, 0xe5, 0xe1, 0x50, 0xcc, 0x4e, + 0xd9, 0x77, 0x5d, 0xd2, 0x4f, 0xd5, 0x60, 0x77, 0xde, 0x57, 0xd7, 0x5c, + 0xe8, 0xfa, 0x67, 0xdf, 0x5e, 0xe0, 0x67, 0xf0, 0xfc, 0x6d, 0xe5, 0x5f, + 0xdf, 0x61, 0xeb, 0xfb, 0x65, 0xdb, 0x57, 0xd9, 0x61, 0x7c, 0xde, 0x54, + 0xd0, 0x54, 0xe1, 0xed, 0x56, 0xcd, 0x4d, 0xd3, 0x69, 0x5f, 0xce, 0x4b, + 0xcc, 0x57, 0x7d, 0xd5, 0x4b, 0xc9, 0x4e, 0xde, 0xe4, 0x4f, 0xc9, 0x4a, + 0xd2, 0x6f, 0x5a, 0xcc, 0x4a, 0xcd, 0x5b, 0x6f, 0xd4, 0x4c, 0xcc, 0x52, + 0xe7, 0xe4, 0x53, 0xce, 0x50, 0xdb, 0x76, 0x60, 0xd6, 0x54, 0xd8, 0x62, + 0xff, 0xe5, 0x5d, 0xdc, 0x5e, 0xe7, 0x75, 0x73, 0xe9, 0x64, 0xe2, 0x63, + 0xe7, 0x6f, 0x7b, 0xe9, 0x5f, 0xdb, 0x5c, 0xe2, 0x78, 0x65, 0xd9, 0x54, + 0xd6, 0x5e, 0xfe, 0xdd, 0x51, 0xce, 0x52, 0xdf, 0xec, 0x54, 0xcd, 0x4c, + 0xd2, 0x69, 0x5d, 0xce, 0x4a, 0xcc, 0x57, 0x7a, 0xd5, 0x4b, 0xc9, 0x4e, + 0xdf, 0xe3, 0x4f, 0xca, 0x4b, 0xd4, 0x70, 0x5a, 0xcd, 0x4b, 0xce, 0x5c, + 0x6f, 0xd6, 0x4e, 0xce, 0x55, 0xe8, 0xe7, 0x57, 0xd2, 0x54, 0xdd, 0x73, + 0x66, 0xdb, 0x59, 0xdb, 0x63, 0xf5, 0xee, 0x65, 0xe0, 0x60, 0xe5, 0x6b, + 0xf7, 0xf6, 0x69, 0xe2, 0x5f, 0xdf, 0x65, 0xf5, 0xeb, 0x5d, 0xd8, 0x58, + 0xdd, 0x71, 0x65, 0xd7, 0x51, 0xd2, 0x5c, 0xfd, 0xdb, 0x4f, 0xcd, 0x50, + 0xde, 0xea, 0x53, 0xcc, 0x4c, 0xd2, 0x6a, 0x5d, 0xce, 0x4a, 0xcc, 0x58, + 0x78, 0xd4, 0x4b, 0xca, 0x4f, 0xe1, 0xe3, 0x4f, 0xcb, 0x4c, 0xd6, 0x74, + 0x5b, 0xcf, 0x4d, 0xd0, 0x5e, 0x70, 0xd9, 0x50, 0xd0, 0x58, 0xea, 0xeb, + 0x5b, 0xd6, 0x58, 0xdf, 0x6f, 0x6d, 0xe1, 0x5d, 0xde, 0x64, 0xed, 0xff, + 0x6e, 0xe8, 0x63, 0xe2, 0x64, 0xe9, 0x77, 0x6e, 0xe2, 0x5c, 0xdb, 0x5e, + 0xec, 0xed, 0x5c, 0xd5, 0x54, 0xda, 0x6d, 0x64, 0xd5, 0x4f, 0xd0, 0x5a, + 0xfd, 0xda, 0x4e, 0xcc, 0x4f, 0xde, 0xe9, 0x52, 0xcb, 0x4b, 0xd3, 0x6c, + 0x5c, 0xce, 0x4a, 0xcd, 0x5a, 0x74, 0xd5, 0x4c, 0xcb, 0x50, 0xe4, 0xe3, + 0x51, 0xcc, 0x4e, 0xd8, 0x77, 0x5c, 0xd1, 0x4f, 0xd4, 0x60, 0x72, 0xdc, + 0x55, 0xd5, 0x5b, 0xeb, 0xef, 0x5f, 0xdb, 0x5c, 0xe1, 0x6d, 0x7a, 0xeb, + 0x65, 0xe3, 0x64, 0xe8, 0x6e, 0xfc, 0xef, 0x66, 0xdf, 0x5e, 0xe0, 0x6b, + 0x76, 0xe0, 0x59, 0xd7, 0x5a, 0xe8, 0xee, 0x5a, 0xd2, 0x51, 0xd8, 0x6c, + 0x62, 0xd3, 0x4e, 0xcf, 0x5a, 0xff, 0xd9, 0x4e, 0xcc, 0x4f, 0xdf, 0xe7, + 0x51, 0xcb, 0x4c, 0xd4, 0x6e, 0x5c, 0xce, 0x4b, 0xce, 0x5b, 0x71, 0xd5, + 0x4d, 0xcd, 0x53, 0xe7, 0xe3, 0x53, 0xce, 0x50, 0xdb, 0x78, 0x5e, 0xd5, + 0x52, 0xd7, 0x63, 0x78, 0xdf, 0x5a, 0xd9, 0x5e, 0xea, 0xf9, 0x69, 0xe1, + 0x60, 0xe3, 0x6a, 0xf2, 0xfa, 0x6e, 0xe7, 0x63, 0xe1, 0x65, 0xed, 0xfa, + 0x67, 0xdd, 0x5a, 0xdc, 0x65, 0x7b, 0xdf, 0x57, 0xd4, 0x57, 0xe4, 0xee, + 0x59, 0xd0, 0x4f, 0xd6, 0x6b, 0x60, 0xd2, 0x4d, 0xce, 0x59, 0x7d, 0xd8, + 0x4d, 0xcc, 0x4f, 0xe0, 0xe7, 0x51, 0xcc, 0x4c, 0xd5, 0x6f, 0x5b, 0xce, + 0x4c, 0xcf, 0x5c, 0x70, 0xd7, 0x4e, 0xce, 0x55, 0xe9, 0xe5, 0x56, 0xd1, + 0x53, 0xdd, 0x78, 0x62, 0xd9, 0x57, 0xda, 0x66, 0x7e, 0xe6, 0x5e, 0xde, + 0x60, 0xe9, 0x78, 0x73, 0xea, 0x66, 0xe4, 0x66, 0xea, 0x71, 0x7a, 0xea, + 0x61, 0xde, 0x5e, 0xe5, 0x7a, 0x67, 0xdb, 0x57, 0xd8, 0x60, 0x7e, 0xde, + 0x54, 0xd1, 0x55, 0xe2, 0xed, 0x57, 0xcf, 0x4e, 0xd6, 0x6b, 0x5f, 0xd0, + 0x4c, 0xce, 0x5a, 0x7a, 0xd8, 0x4d, 0xcc, 0x50, 0xe3, 0xe5, 0x51, 0xcc, + 0x4d, 0xd7, 0x74, 0x5c, 0xcf, 0x4d, 0xd1, 0x5e, 0x6f, 0xd9, 0x50, 0xd0, + 0x58, 0xea, 0xe7, 0x59, 0xd4, 0x57, 0xde, 0x77, 0x68, 0xdd, 0x5b, 0xdd, + 0x66, 0xf7, 0xee, 0x67, 0xe3, 0x64, 0xe7, 0x6d, 0xf9, 0xf5, 0x6b, 0xe5, + 0x62, 0xe2, 0x68, 0xf6, 0xed, 0x5f, 0xdb, 0x5a, 0xdf, 0x72, 0x67, 0xd9, + 0x54, 0xd5, 0x5e, 0xfd, 0xdd, 0x52, 0xcf, 0x53, 0xe1, 0xed, 0x56, 0xce, + 0x4e, 0xd5, 0x6b, 0x5e, 0xd0, 0x4c, 0xce, 0x5a, 0x77, 0xd7, 0x4d, 0xcc, + 0x52, 0xe4, 0xe5, 0x53, 0xcd, 0x4e, 0xd8, 0x76, 0x5d, 0xd1, 0x4f, 0xd3, + 0x5f, 0x71, 0xdb, 0x53, 0xd4, 0x5a, 0xec, 0xec, 0x5d, 0xd9, 0x5a, 0xe1, + 0x72, 0x6e, 0xe3, 0x5f, 0xe0, 0x66, 0xef, 0xfe, 0x6f, 0xe9, 0x66, 0xe5, + 0x67, 0xec, 0x79, 0x70, 0xe5, 0x5e, 0xdd, 0x60, 0xee, 0xee, 0x5e, 0xd8, + 0x57, 0xdc, 0x6f, 0x67, 0xd8, 0x52, 0xd3, 0x5d, 0xfd, 0xdc, 0x51, 0xce, + 0x53, 0xe0, 0xeb, 0x55, 0xce, 0x4e, 0xd6, 0x6d, 0x5e, 0xd0, 0x4c, 0xcf, + 0x5b, 0x75, 0xd8, 0x4e, 0xcd, 0x53, 0xe6, 0xe5, 0x54, 0xce, 0x50, 0xda, + 0x78, 0x5e, 0xd4, 0x51, 0xd6, 0x63, 0x74, 0xdd, 0x57, 0xd7, 0x5d, 0xec, + 0xf0, 0x61, 0xdd, 0x5e, 0xe4, 0x6f, 0x7a, 0xec, 0x67, 0xe5, 0x67, 0xea, + 0x70, 0xfe, 0xf0, 0x68, 0xe2, 0x61, 0xe3, 0x6d, 0x77, 0xe4, 0x5c, 0xda, + 0x5d, 0xea, 0xef, 0x5d, 0xd6, 0x54, 0xdb, 0x6c, 0x65, 0xd6, 0x50, 0xd2, + 0x5c, 0xfe, 0xdc, 0x50, 0xce, 0x52, 0xe1, 0xea, 0x55, 0xcd, 0x4e, 0xd6, + 0x6e, 0x5e, 0xd0, 0x4d, 0xcf, 0x5d, 0x74, 0xd8, 0x4f, 0xce, 0x55, 0xe8, + 0xe6, 0x56, 0xd0, 0x53, 0xdc, 0x78, 0x60, 0xd7, 0x55, 0xd9, 0x65, 0x78, + 0xe2, 0x5b, 0xdb, 0x5f, 0xec, 0xfa, 0x69, 0xe3, 0x62, 0xe6, 0x6b, 0xf6, + 0xfa, 0x6e, 0xe9, 0x66, 0xe5, 0x68, 0xee, 0xfb, 0x69, 0xdf, 0x5d, 0xde, + 0x68, 0x7c, 0xe3, 0x5a, 0xd7, 0x5a, 0xe6, 0xef, 0x5c, 0xd3, 0x52, 0xd9, + 0x6c, 0x64, 0xd5, 0x4f, 0xd1, 0x5b, 0xff, 0xdb, 0x4f, 0xce, 0x53, 0xe2, + 0xe9, 0x55, 0xce, 0x4e, 0xd7, 0x70, 0x5e, 0xd1, 0x4e, 0xd1, 0x5e, 0x73, + 0xd9, 0x50, 0xd0, 0x58, 0xea, 0xe7, 0x58, 0xd3, 0x56, 0xde, 0x78, 0x64, + 0xdb, 0x59, 0xdc, 0x67, 0x7d, 0xe8, 0x5f, 0xdf, 0x62, 0xeb, 0x79, 0x72, + 0xeb, 0x67, 0xe7, 0x68, 0xec, 0x74, 0x79, 0xec, 0x64, 0xe0, 0x60, 0xe8, + 0x7a, 0x6a, 0xde, 0x5a, 0xdb, 0x63, 0xfe, 0xe1, 0x58, 0xd4, 0x58, 0xe4, + 0xef, 0x5a, 0xd2, 0x51, 0xd8, 0x6c, 0x63, 0xd4, 0x4f, 0xd0, 0x5c, 0x7d, + 0xda, 0x4f, 0xce, 0x53, 0xe3, 0xe8, 0x55, 0xce, 0x4f, 0xd9, 0x73, 0x5e, + 0xd2, 0x4f, 0xd4, 0x5f, 0x72, 0xdb, 0x53, 0xd3, 0x5a, 0xeb, 0xea, 0x5b, + 0xd7, 0x59, 0xe0, 0x75, 0x6a, 0xde, 0x5d, 0xde, 0x68, 0xf9, 0xef, 0x67, + 0xe5, 0x65, 0xe9, 0x6f, 0xfb, 0xf5, 0x6c, 0xe7, 0x64, 0xe5, 0x6a, 0xf8, + 0xee, 0x62, 0xdd, 0x5d, 0xe2, 0x73, 0x6a, 0xdc, 0x57, 0xd8, 0x5f, 0xfb, + 0xe0, 0x56, 0xd2, 0x57, 0xe2, 0xee, 0x59, 0xd1, 0x50, 0xd8, 0x6d, 0x62, + 0xd3, 0x4e, 0xd0, 0x5c, 0x7a, 0xda, 0x4f, 0xce, 0x54, 0xe5, 0xe8, 0x56, + 0xcf, 0x50, 0xda, 0x75, 0x5f, 0xd4, 0x51, 0xd6, 0x62, 0x74, 0xdd, 0x56, + 0xd6, 0x5c, 0xec, 0xed, 0x5e, 0xdb, 0x5c, 0xe3, 0x73, 0x6e, 0xe5, 0x60, + 0xe3, 0x68, 0xf1, 0xfd, 0x6f, 0xeb, 0x67, 0xe7, 0x69, 0xee, 0x7a, 0x71, + 0xe7, 0x61, 0xdf, 0x64, 0xef, 0xef, 0x60, 0xda, 0x5a, 0xde, 0x70, 0x69, + 0xda, 0x55, 0xd6, 0x5e, 0xfb, 0xde, 0x55, 0xd1, 0x56, 0xe2, 0xed, 0x59, + 0xd0, 0x50, 0xd8, 0x6d, 0x60, 0xd3, 0x4f, 0xd1, 0x5d, 0x79, 0xda, 0x50, + 0xcf, 0x56, 0xe7, 0xe8, 0x57, 0xd1, 0x53, 0xdc, 0x77, 0x60, 0xd7, 0x54, + 0xd8, 0x64, 0x76, 0xdf, 0x59, 0xd9, 0x5e, 0xed, 0xf2, 0x64, 0xde, 0x5f, + 0xe5, 0x6f, 0x79, 0xec, 0x68, 0xe7, 0x68, 0xec, 0x73, 0x7e, 0xf1, 0x6a, + 0xe5, 0x63, 0xe7, 0x6f, 0x77, 0xe7, 0x5e, 0xdc, 0x5e, 0xeb, 0xf2, 0x5f, + 0xd9, 0x57, 0xdc, 0x6d, 0x68, 0xd9, 0x53, 0xd5, 0x5d, 0xfc, 0xde, 0x53, + 0xd0, 0x55, 0xe3, 0xed, 0x58, 0xd0, 0x50, 0xd8, 0x6e, 0x60, 0xd3, 0x4f, + 0xd2, 0x5e, 0x77, 0xdb, 0x52, 0xd1, 0x57, 0xe8, 0xe9, 0x59, 0xd3, 0x55, + 0xdd, 0x78, 0x64, 0xd9, 0x57, 0xdb, 0x66, 0x7b, 0xe4, 0x5d, 0xdc, 0x61, + 0xec, 0xfa, 0x6b, 0xe4, 0x64, 0xe7, 0x6d, 0xf7, 0xf8, 0x6f, 0xea, 0x68, + 0xe8, 0x6a, 0xf2, 0xfa, 0x6b, 0xe3, 0x5f, 0xe1, 0x69, 0x7c, 0xe6, 0x5c, + 0xda, 0x5c, 0xe9, 0xf3, 0x5e, 0xd7, 0x55, 0xdb, 0x6c, 0x67, 0xd8, 0x52, + 0xd4, 0x5d, 0xfd, 0xdd, 0x53, 0xd0, 0x55, 0xe3, 0xec, 0x58, 0xd0, 0x50, + 0xd9, 0x6f, 0x61, 0xd4, 0x50, 0xd4, 0x5f, 0x77, 0xdc, 0x54, 0xd3, 0x59, + 0xea, 0xea, 0x5b, 0xd6, 0x58, 0xdf, 0x77, 0x67, 0xdc, 0x5a, 0xdd, 0x68, + 0xfe, 0xea, 0x62, 0xdf, 0x64, 0xeb, 0x7b, 0x73, 0xeb, 0x68, 0xe8, 0x6a, + 0xee, 0x77, 0x79, 0xed, 0x67, 0xe3, 0x64, 0xeb, 0x7c, 0x6b, 0xe0, 0x5c, + 0xdd, 0x65, 0xfe, 0xe5, 0x5a, 0xd8, 0x5a, 0xe6, 0xf3, 0x5d, 0xd5, 0x54, + 0xda, 0x6d, 0x67, 0xd7, 0x51, 0xd3, 0x5d, 0xfe, 0xdd, 0x53, 0xd0, 0x56, + 0xe5, 0xeb, 0x58, 0xd1, 0x52, 0xda, 0x71, 0x61, 0xd6, 0x52, 0xd6, 0x61, + 0x77, 0xdd, 0x56, 0xd5, 0x5b, 0xeb, 0xec, 0x5d, 0xd9, 0x5a, 0xe2, 0x75, + 0x6b, 0xe0, 0x5e, 0xe0, 0x68, 0xf9, 0xf0, 0x68, 0xe5, 0x66, 0xeb, 0x70, + 0xfe, 0xf4, 0x6c, 0xe9, 0x67, 0xe8, 0x6d, 0xfa, 0xef, 0x65, 0xdf, 0x5f, + 0xe6, 0x75, 0x6b, 0xde, 0x5a, 0xdb, 0x62, 0xfc, 0xe3, 0x59, 0xd6, 0x59, + 0xe5, 0xf2, 0x5c, 0xd4, 0x53, 0xda, 0x6d, 0x65, 0xd7, 0x51, 0xd4, 0x5e, + 0x7e, 0xdd, 0x53, 0xd1, 0x57, 0xe6, 0xeb, 0x59, 0xd2, 0x53, 0xdc, 0x75, + 0x62, 0xd7, 0x54, 0xd8, 0x64, 0x78, 0xdf, 0x59, 0xd8, 0x5d, 0xec, 0xee, + 0x60, 0xdc, 0x5d, 0xe4, 0x74, 0x70, 0xe6, 0x63, 0xe3, 0x6a, 0xf2, 0xfc, + 0x70, 0xeb, 0x69, 0xe9, 0x6b, 0xf0, 0x7d, 0x72, 0xe9, 0x64, 0xe3, 0x67, + 0xf2, 0xf2, 0x63, 0xdd, 0x5c, 0xe1, 0x70, 0x6b, 0xdd, 0x58, 0xd9, 0x5f, + 0xfb, 0xe2, 0x58, 0xd4, 0x58, 0xe5, 0xf1, 0x5b, 0xd4, 0x53, 0xda, 0x6d, + 0x64, 0xd6, 0x51, 0xd4, 0x5e, 0x7b, 0xdd, 0x54, 0xd2, 0x58, 0xe8, 0xeb, + 0x5a, 0xd4, 0x55, 0xdd, 0x76, 0x64, 0xd9, 0x57, 0xda, 0x65, 0x79, 0xe2, + 0x5b, 0xdb, 0x5f, 0xed, 0xf3, 0x66, 0xdf, 0x61, 0xe7, 0x71, 0x7a, 0xed, + 0x69, 0xe8, 0x6a, 0xed, 0x76, 0x7e, 0xf1, 0x6b, 0xe7, 0x66, 0xea, 0x71, + 0x77, 0xe9, 0x60, 0xde, 0x61, 0xed, 0xf5, 0x61, 0xdb, 0x5a, 0xde, 0x6e, + 0x6b, 0xdc, 0x57, 0xd8, 0x5f, 0xfb, 0xe1, 0x57, 0xd4, 0x57, 0xe5, 0xef, + 0x5b, 0xd3, 0x53, 0xda, 0x6e, 0x64, 0xd7, 0x52, 0xd5, 0x5f, 0x7b, 0xdd, + 0x55, 0xd3, 0x59, 0xe9, 0xeb, 0x5b, 0xd6, 0x58, 0xde, 0x77, 0x67, 0xdb, + 0x59, 0xdc, 0x68, 0x7d, 0xe6, 0x5f, 0xde, 0x63, 0xed, 0xfb, 0x6b, 0xe6, + 0x65, 0xe8, 0x6e, 0xfa, 0xf8, 0x6e, 0xeb, 0x69, 0xea, 0x6c, 0xf5, 0xfa, + 0x6c, 0xe5, 0x61, 0xe4, 0x6c, 0x7c, 0xe8, 0x5e, 0xdc, 0x5e, 0xea, 0xf4, + 0x60, 0xd9, 0x58, 0xdd, 0x6e, 0x6a, 0xdb, 0x55, 0xd7, 0x5e, 0xfc, 0xdf, + 0x56, 0xd3, 0x58, 0xe5, 0xee, 0x5b, 0xd3, 0x54, 0xdb, 0x6f, 0x64, 0xd7, + 0x53, 0xd7, 0x60, 0x79, 0xde, 0x56, 0xd5, 0x5b, 0xeb, 0xed, 0x5d, 0xd8, + 0x5a, 0xe1, 0x76, 0x69, 0xde, 0x5c, 0xde, 0x69, 0xfd, 0xeb, 0x64, 0xe2, + 0x66, 0xed, 0x7c, 0x74, 0xec, 0x6a, 0xe9, 0x6c, 0xef, 0x7a, 0x78, 0xee, + 0x68, 0xe6, 0x67, 0xed, 0x7c, 0x6d, 0xe3, 0x5e, 0xdf, 0x68, 0xfe, 0xe7, + 0x5d, 0xda, 0x5d, 0xe8, 0xf4, 0x5f, 0xd8, 0x57, 0xdc, 0x6e, 0x69, 0xda, + 0x55, 0xd6, 0x5e, 0xfd, 0xdf, 0x56, 0xd3, 0x58, 0xe6, 0xed, 0x5a, 0xd4, + 0x54, 0xdc, 0x72, 0x64, 0xd8, 0x55, 0xd8, 0x63, 0x78, 0xdf, 0x58, 0xd8, + 0x5d, 0xed, 0xee, 0x5f, 0xdb, 0x5c, 0xe4, 0x76, 0x6d, 0xe2, 0x5f, 0xe2, + 0x6a, 0xfa, 0xf1, 0x6a, 0xe7, 0x68, 0xec, 0x72, 0x7e, 0xf5, 0x6e, 0xea, + 0x69, 0xea, 0x6e, 0xfc, 0xf0, 0x68, 0xe2, 0x62, 0xe7, 0x77, 0x6d, 0xe0, + 0x5d, 0xdd, 0x65, 0xfb, 0xe6, 0x5b, 0xd9, 0x5b, 0xe7, 0xf4, 0x5e, 0xd7, + 0x56, 0xdc, 0x6e, 0x68, 0xd9, 0x54, 0xd6, 0x5f, 0xfe, 0xdf, 0x56, 0xd4, + 0x59, 0xe7, 0xed, 0x5b, 0xd5, 0x56, 0xdd, 0x73, 0x65, 0xda, 0x57, 0xda, + 0x65, 0x79, 0xe2, 0x5b, 0xda, 0x5f, 0xed, 0xf1, 0x63, 0xde, 0x5f, 0xe6, + 0x75, 0x72, 0xe8, 0x64, 0xe6, 0x6b, 0xf4, 0xfb, 0x70, 0xec, 0x6a, 0xeb, + 0x6d, 0xf3, 0x7e, 0x72, 0xeb, 0x66, 0xe5, 0x69, 0xf5, 0xf3, 0x66, 0xdf, + 0x5f, 0xe4, 0x73, 0x6d, 0xdf, 0x5b, 0xdc, 0x63, 0xfb, 0xe5, 0x5a, 0xd7, + 0x5a, 0xe6, 0xf3, 0x5d, 0xd7, 0x56, 0xdc, 0x6e, 0x67, 0xd9, 0x55, 0xd7, + 0x5f, 0x7e, 0xdf, 0x57, 0xd5, 0x5a, 0xe9, 0xed, 0x5c, 0xd6, 0x58, 0xde, + 0x76, 0x67, 0xdb, 0x59, 0xdc, 0x66, 0x7b, 0xe5, 0x5d, 0xdc, 0x61, 0xee, + 0xf6, 0x67, 0xe2, 0x62, 0xe8, 0x71, 0x7a, 0xee, 0x69, 0xe9, 0x6b, 0xef, + 0x78, 0x7c, 0xf0, 0x6c, 0xe9, 0x69, 0xec, 0x76, 0x77, 0xea, 0x63, 0xe1, + 0x65, 0xee, 0xf4, 0x65, 0xdd, 0x5d, 0xe1, 0x70, 0x6d, 0xde, 0x59, 0xda, + 0x61, 0xfb, 0xe4, 0x59, 0xd7, 0x5a, 0xe6, 0xf2, 0x5d, 0xd6, 0x56, 0xdc, + 0x6e, 0x67, 0xd9, 0x55, 0xd8, 0x61, 0x7c, 0xdf, 0x58, 0xd6, 0x5b, 0xea, + 0xee, 0x5d, 0xd8, 0x59, 0xe0, 0x76, 0x69, 0xdd, 0x5b, 0xde, 0x68, 0x7d, + 0xe9, 0x5f, 0xdf, 0x63, 0xee, 0xfc, 0x6c, 0xe7, 0x66, 0xe9, 0x6f, 0xfb, + 0xf7, 0x6e, 0xec, 0x6a, 0xec, 0x6e, 0xf8, 0xf9, 0x6d, 0xe7, 0x65, 0xe7, + 0x6e, 0x7c, 0xea, 0x61, 0xde, 0x61, 0xec, 0xf6, 0x64, 0xdc, 0x5b, 0xdf, + 0x6f, 0x6c, 0xdd, 0x58, 0xd9, 0x61, 0xfb, 0xe3, 0x59, 0xd6, 0x5a, 0xe7, + 0xf1, 0x5d, 0xd6, 0x56, 0xdd, 0x6f, 0x67, 0xda, 0x56, 0xd9, 0x62, 0x7c, + 0xe0, 0x59, 0xd8, 0x5d, 0xeb, 0xee, 0x5f, 0xda, 0x5b, 0xe2, 0x76, 0x6b, + 0xe0, 0x5d, 0xe0, 0x6a, 0xfe, 0xed, 0x65, 0xe4, 0x67, 0xee, 0x7b, 0x73, + 0xed, 0x6a, 0xeb, 0x6d, 0xf2, 0x7b, 0x77, 0xee, 0x6a, 0xe9, 0x6a, 0xef, + 0x7e, 0x6e, 0xe6, 0x61, 0xe2, 0x6a, 0xfe, 0xe9, 0x5f, 0xdc, 0x5f, 0xea, + 0xf7, 0x62, 0xdb, 0x5a, 0xde, 0x6e, 0x6c, 0xdc, 0x58, 0xd9, 0x60, 0xfc, + 0xe2, 0x59, 0xd6, 0x5a, 0xe7, 0xef, 0x5d, 0xd7, 0x57, 0xde, 0x72, 0x67, + 0xdb, 0x57, 0xda, 0x64, 0x7b, 0xe2, 0x5b, 0xda, 0x5e, 0xed, 0xf0, 0x61, + 0xdd, 0x5e, 0xe5, 0x77, 0x6e, 0xe4, 0x61, 0xe3, 0x6c, 0xf9, 0xf1, 0x6b, + 0xe8, 0x6a, 0xed, 0x75, 0x7e, 0xf5, 0x6e, 0xec, 0x6b, 0xec, 0x71, 0xfe, + 0xf1, 0x69, 0xe5, 0x65, 0xea, 0x78, 0x6e, 0xe4, 0x5e, 0xdf, 0x67, 0xfc, + 0xe9, 0x5e, 0xdb, 0x5d, 0xe9, 0xf6, 0x61, 0xda, 0x59, 0xde, 0x6e, 0x6b, + 0xdc, 0x57, 0xd9, 0x61, 0xfd, 0xe2, 0x59, 0xd7, 0x5b, 0xe9, 0xef, 0x5d, + 0xd8, 0x58, 0xdf, 0x73, 0x68, 0xdc, 0x59, 0xdc, 0x66, 0x7b, 0xe4, 0x5c, + 0xdc, 0x60, 0xee, 0xf4, 0x65, 0xdf, 0x60, 0xe7, 0x75, 0x73, 0xe9, 0x66, + 0xe7, 0x6c, 0xf5, 0xfa, 0x71, 0xed, 0x6b, 0xec, 0x6e, 0xf6, 0xfe, 0x73, + 0xec, 0x68, 0xe9, 0x6b, 0xf7, 0xf5, 0x68, 0xe2, 0x61, 0xe7, 0x73, 0x6e, + 0xe2, 0x5d, 0xde, 0x65, 0xfa, 0xe8, 0x5d, 0xda, 0x5d, 0xe8, 0xf6, 0x5f, + 0xd9, 0x59, 0xdd, 0x6f, 0x6a, 0xdc, 0x58, 0xd9, 0x62, 0xfe, 0xe2, 0x59, + 0xd7, 0x5c, 0xe9, 0xef, 0x5e, 0xd9, 0x5a, 0xdf, 0x75, 0x69, 0xdd, 0x5b, + 0xdd, 0x68, 0x7d, 0xe7, 0x5f, 0xde, 0x63, 0xee, 0xf7, 0x69, 0xe4, 0x64, + 0xe9, 0x72, 0x7b, 0xee, 0x6b, 0xea, 0x6c, 0xf0, 0x79, 0x7b, 0xf2, 0x6d, + 0xeb, 0x6b, 0xee, 0x76, 0x78, 0xec, 0x66, 0xe4, 0x67, 0xf0, 0xf7, 0x67, + 0xdf, 0x5e, 0xe4, 0x70, 0x6e, 0xe0, 0x5c, 0xdc, 0x63, 0xfa, 0xe7, 0x5c, + 0xd9, 0x5c, 0xe8, 0xf5, 0x5f, 0xd9, 0x58, 0xde, 0x6f, 0x6a, 0xdc, 0x58, + 0xda, 0x62, 0x7e, 0xe3, 0x5a, 0xd9, 0x5d, 0xeb, 0xf0, 0x5f, 0xdb, 0x5b, + 0xe2, 0x74, 0x6b, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xea, 0x63, 0xe1, 0x65, + 0xee, 0xfd, 0x6e, 0xe8, 0x68, 0xea, 0x6f, 0xfc, 0xf8, 0x6f, 0xed, 0x6c, + 0xed, 0x70, 0xf9, 0xf9, 0x6e, 0xe9, 0x68, 0xe9, 0x6f, 0x7c, 0xec, 0x64, + 0xe1, 0x64, 0xed, 0xf8, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6e, 0xdf, 0x5b, + 0xdc, 0x63, 0xfb, 0xe6, 0x5b, 0xd9, 0x5c, 0xe8, 0xf4, 0x5f, 0xd9, 0x59, + 0xde, 0x70, 0x6a, 0xdc, 0x59, 0xdb, 0x64, 0x7e, 0xe3, 0x5b, 0xda, 0x5e, + 0xec, 0xf0, 0x61, 0xdc, 0x5d, 0xe4, 0x75, 0x6d, 0xe2, 0x5f, 0xe1, 0x6b, + 0xfb, 0xee, 0x67, 0xe5, 0x68, 0xee, 0x7c, 0x75, 0xed, 0x6b, 0xec, 0x6e, + 0xf4, 0x7d, 0x77, 0xef, 0x6c, 0xea, 0x6b, 0xf1, 0xff, 0x6f, 0xe8, 0x64, + 0xe6, 0x6c, 0xff, 0xeb, 0x62, 0xdf, 0x61, 0xec, 0xf8, 0x65, 0xdd, 0x5c, + 0xe0, 0x6f, 0x6d, 0xdf, 0x5a, 0xdb, 0x63, 0xfa, 0xe6, 0x5b, 0xd9, 0x5c, + 0xe8, 0xf3, 0x5f, 0xda, 0x59, 0xdf, 0x71, 0x6a, 0xdd, 0x59, 0xdc, 0x65, + 0x7d, 0xe5, 0x5d, 0xdc, 0x5f, 0xed, 0xf3, 0x64, 0xde, 0x5f, 0xe6, 0x75, + 0x70, 0xe6, 0x63, 0xe5, 0x6c, 0xf8, 0xf3, 0x6c, 0xe9, 0x6a, 0xed, 0x76, + 0x7e, 0xf5, 0x6e, 0xec, 0x6c, 0xee, 0x74, 0xfe, 0xf2, 0x6b, 0xe8, 0x68, + 0xec, 0x7a, 0x6f, 0xe6, 0x61, 0xe2, 0x6a, 0xfc, 0xea, 0x60, 0xdd, 0x5f, + 0xea, 0xf8, 0x64, 0xdc, 0x5b, 0xdf, 0x6e, 0x6d, 0xde, 0x5a, 0xdb, 0x63, + 0xfb, 0xe5, 0x5b, 0xd9, 0x5d, 0xea, 0xf3, 0x5f, 0xda, 0x5a, 0xe0, 0x72, + 0x6b, 0xde, 0x5b, 0xdd, 0x67, 0x7e, 0xe7, 0x5e, 0xdd, 0x62, 0xee, 0xf6, + 0x68, 0xe1, 0x62, 0xe8, 0x74, 0x75, 0xeb, 0x68, 0xe8, 0x6c, 0xf6, 0xfb, + 0x72, 0xed, 0x6d, 0xed, 0x70, 0xf7, 0xfe, 0x74, 0xed, 0x6a, 0xea, 0x6d, + 0xf7, 0xf6, 0x6a, 0xe5, 0x64, 0xe9, 0x75, 0x70, 0xe5, 0x5f, 0xdf, 0x67, + 0xfa, 0xea, 0x5f, 0xdc, 0x5e, 0xe9, 0xf8, 0x63, 0xdc, 0x5b, 0xdf, 0x6f, + 0x6c, 0xde, 0x5a, 0xdb, 0x63, 0xfc, 0xe6, 0x5c, 0xda, 0x5e, 0xea, 0xf3, + 0x61, 0xdb, 0x5c, 0xe2, 0x74, 0x6c, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xe9, + 0x61, 0xdf, 0x64, 0xef, 0xfa, 0x6b, 0xe6, 0x65, 0xea, 0x72, 0x7b, 0xef, + 0x6c, 0xeb, 0x6d, 0xf1, 0x7a, 0x7a, 0xf3, 0x6e, 0xec, 0x6c, 0xef, 0x78, + 0x78, 0xed, 0x68, 0xe7, 0x6a, 0xf2, 0xf7, 0x6a, 0xe2, 0x61, 0xe6, 0x72, + 0x70, 0xe3, 0x5e, 0xde, 0x66, 0xfa, 0xe9, 0x5e, 0xdc, 0x5e, 0xe9, 0xf8, + 0x62, 0xdb, 0x5b, 0xdf, 0x6f, 0x6c, 0xde, 0x5a, 0xdc, 0x64, 0xfd, 0xe6, + 0x5c, 0xdb, 0x5e, 0xeb, 0xf4, 0x62, 0xdd, 0x5d, 0xe3, 0x74, 0x6d, 0xe2, + 0x5e, 0xe1, 0x69, 0xfd, 0xed, 0x65, 0xe3, 0x67, 0xee, 0xfe, 0x6f, 0xea, + 0x69, 0xeb, 0x70, 0xfb, 0xf7, 0x70, 0xed, 0x6d, 0xee, 0x72, 0xfa, 0xf9, + 0x6f, 0xeb, 0x6a, 0xeb, 0x71, 0x7c, 0xed, 0x67, 0xe3, 0x66, 0xee, 0xf9, + 0x69, 0xe0, 0x5f, 0xe4, 0x71, 0x71, 0xe2, 0x5d, 0xdd, 0x65, 0xf8, 0xe9, + 0x5e, 0xdb, 0x5e, 0xe8, 0xf7, 0x63, 0xdb, 0x5b, 0xdf, 0x70, 0x6d, 0xde, + 0x5b, 0xdc, 0x65, 0xfc, 0xe7, 0x5d, 0xdc, 0x5f, 0xec, 0xf5, 0x64, 0xde, + 0x5e, 0xe5, 0x73, 0x6f, 0xe5, 0x60, 0xe3, 0x6a, 0xfc, 0xf0, 0x68, 0xe7, + 0x69, 0xef, 0x7b, 0x75, 0xee, 0x6c, 0xec, 0x6f, 0xf5, 0x7d, 0x77, 0xef, + 0x6d, 0xec, 0x6d, 0xf3, 0xff, 0x70, 0xea, 0x66, 0xe8, 0x6e, 0xfe, 0xed, + 0x65, 0xe1, 0x64, 0xed, 0xfa, 0x68, 0xdf, 0x5e, 0xe2, 0x6f, 0x6f, 0xe1, + 0x5d, 0xdd, 0x65, 0xf9, 0xe9, 0x5e, 0xdb, 0x5e, 0xe9, 0xf6, 0x63, 0xdc, + 0x5b, 0xe0, 0x71, 0x6d, 0xdf, 0x5c, 0xdd, 0x66, 0xfe, 0xe9, 0x5f, 0xdd, + 0x61, 0xed, 0xf7, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x73, 0xe8, 0x64, 0xe6, + 0x6c, 0xf8, 0xf5, 0x6c, 0xeb, 0x6b, 0xee, 0x76, 0x7d, 0xf4, 0x6f, 0xed, + 0x6d, 0xef, 0x75, 0x7e, 0xf4, 0x6c, 0xe9, 0x6a, 0xee, 0x7a, 0x71, 0xe9, + 0x64, 0xe5, 0x6b, 0xfc, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfa, 0x67, 0xde, + 0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd, 0x64, 0xf9, 0xe8, 0x5e, 0xdb, + 0x5e, 0xea, 0xf6, 0x64, 0xdc, 0x5c, 0xe1, 0x72, 0x6d, 0xe0, 0x5d, 0xde, + 0x68, 0xfd, 0xea, 0x61, 0xdf, 0x63, 0xed, 0xf9, 0x6a, 0xe4, 0x63, 0xe9, + 0x74, 0x77, 0xec, 0x69, 0xe9, 0x6d, 0xf6, 0xfd, 0x73, 0xee, 0x6d, 0xee, + 0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6c, 0xec, 0x6f, 0xf9, 0xf6, 0x6c, 0xe8, + 0x66, 0xeb, 0x76, 0x72, 0xe8, 0x62, 0xe2, 0x69, 0xfa, 0xec, 0x62, 0xde, + 0x60, 0xea, 0xfb, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd, + 0x65, 0xfa, 0xe9, 0x5e, 0xdc, 0x5f, 0xeb, 0xf7, 0x64, 0xdd, 0x5d, 0xe3, + 0x72, 0x6e, 0xe2, 0x5e, 0xe0, 0x69, 0xfc, 0xec, 0x64, 0xe1, 0x65, 0xef, + 0xfc, 0x6c, 0xe7, 0x66, 0xeb, 0x72, 0x7c, 0xf1, 0x6c, 0xec, 0x6d, 0xf2, + 0x7b, 0x7a, 0xf2, 0x6e, 0xed, 0x6e, 0xf1, 0x7a, 0x79, 0xee, 0x6a, 0xe9, + 0x6c, 0xf4, 0xf9, 0x6b, 0xe5, 0x64, 0xe8, 0x73, 0x73, 0xe6, 0x60, 0xe0, + 0x68, 0xf9, 0xec, 0x61, 0xde, 0x5f, 0xea, 0xfa, 0x66, 0xde, 0x5d, 0xe1, + 0x6f, 0x6f, 0xe1, 0x5c, 0xdd, 0x66, 0xfb, 0xe9, 0x5e, 0xdd, 0x5f, 0xec, + 0xf7, 0x65, 0xde, 0x5e, 0xe5, 0x74, 0x6f, 0xe5, 0x60, 0xe2, 0x6a, 0xfc, + 0xee, 0x67, 0xe5, 0x68, 0xef, 0xfe, 0x71, 0xeb, 0x6a, 0xec, 0x72, 0xfc, + 0xf8, 0x71, 0xee, 0x6e, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xed, 0x6c, 0xed, + 0x73, 0x7d, 0xef, 0x69, 0xe6, 0x68, 0xef, 0xfa, 0x6b, 0xe3, 0x62, 0xe6, + 0x72, 0x73, 0xe5, 0x5f, 0xdf, 0x67, 0xf8, 0xeb, 0x60, 0xdd, 0x5f, 0xea, + 0xfb, 0x65, 0xde, 0x5d, 0xe2, 0x6f, 0x6e, 0xe1, 0x5d, 0xde, 0x66, 0xfd, + 0xea, 0x5f, 0xde, 0x61, 0xed, 0xf8, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x71, + 0xe8, 0x63, 0xe5, 0x6c, 0xfa, 0xf1, 0x6b, 0xe8, 0x6a, 0xef, 0x7c, 0x77, + 0xef, 0x6d, 0xed, 0x71, 0xf5, 0x7e, 0x79, 0xf0, 0x6e, 0xed, 0x6f, 0xf4, + 0xfe, 0x73, 0xeb, 0x69, 0xe9, 0x6f, 0xfd, 0xee, 0x67, 0xe4, 0x66, 0xed, + 0xfb, 0x6a, 0xe2, 0x60, 0xe5, 0x70, 0x72, 0xe5, 0x5e, 0xdf, 0x66, 0xf8, + 0xeb, 0x5f, 0xdd, 0x5f, 0xea, 0xfa, 0x65, 0xde, 0x5d, 0xe3, 0x6f, 0x6e, + 0xe2, 0x5d, 0xdf, 0x67, 0xfc, 0xeb, 0x60, 0xdf, 0x63, 0xed, 0xf9, 0x69, + 0xe3, 0x63, 0xe9, 0x74, 0x75, 0xea, 0x67, 0xe8, 0x6d, 0xf9, 0xf7, 0x6e, + 0xec, 0x6c, 0xef, 0x78, 0x7e, 0xf5, 0x71, 0xee, 0x6e, 0xf0, 0x77, 0xfe, + 0xf4, 0x6e, 0xeb, 0x6c, 0xef, 0x7b, 0x74, 0xea, 0x67, 0xe7, 0x6d, 0xfb, + 0xee, 0x66, 0xe1, 0x64, 0xec, 0xfc, 0x69, 0xe1, 0x5f, 0xe4, 0x6f, 0x72, + 0xe4, 0x5e, 0xdf, 0x66, 0xf9, 0xeb, 0x5f, 0xdd, 0x5f, 0xeb, 0xf9, 0x66, + 0xde, 0x5e, 0xe3, 0x71, 0x6f, 0xe4, 0x5f, 0xe0, 0x69, 0xfc, 0xec, 0x63, + 0xe1, 0x65, 0xef, 0xfb, 0x6b, 0xe6, 0x65, 0xea, 0x74, 0x79, 0xed, 0x6a, + 0xea, 0x6e, 0xf6, 0xfc, 0x73, 0xef, 0x6e, 0xef, 0x73, 0xf9, 0xfd, 0x75, + 0xef, 0x6d, 0xed, 0x70, 0xfa, 0xf7, 0x6e, 0xe9, 0x69, 0xec, 0x77, 0x74, + 0xe9, 0x65, 0xe5, 0x6b, 0xfa, 0xee, 0x64, 0xe0, 0x63, 0xec, 0xfc, 0x69, + 0xe0, 0x5f, 0xe3, 0x6f, 0x72, 0xe4, 0x5e, 0xdf, 0x66, 0xfa, 0xeb, 0x60, + 0xde, 0x60, 0xeb, 0xf9, 0x67, 0xdf, 0x5f, 0xe5, 0x72, 0x70, 0xe5, 0x60, + 0xe2, 0x6a, 0xfc, 0xee, 0x66, 0xe4, 0x67, 0xef, 0xfd, 0x6e, 0xe9, 0x68, + 0xec, 0x73, 0x7d, 0xf3, 0x6e, 0xed, 0x6e, 0xf3, 0x7b, 0x7b, 0xf5, 0x70, + 0xee, 0x6f, 0xf3, 0x7a, 0x7a, 0xef, 0x6c, 0xeb, 0x6d, 0xf5, 0xf9, 0x6d, + 0xe7, 0x67, 0xea, 0x75, 0x75, 0xe8, 0x63, 0xe3, 0x69, 0xf8, 0xed, 0x64, + 0xdf, 0x62, 0xeb, 0xfc, 0x69, 0xdf, 0x5f, 0xe3, 0x70, 0x71, 0xe3, 0x5e, + 0xdf, 0x67, 0xfa, 0xeb, 0x61, 0xdf, 0x61, 0xed, 0xfa, 0x68, 0xe1, 0x60, + 0xe7, 0x73, 0x71, 0xe7, 0x62, 0xe5, 0x6b, 0xfb, 0xf0, 0x69, 0xe7, 0x69, + 0xef, 0x7e, 0x72, 0xed, 0x6b, 0xed, 0x72, 0xfc, 0xf9, 0x72, 0xef, 0x6e, + 0xf1, 0x74, 0xfc, 0xf9, 0x71, 0xee, 0x6d, 0xee, 0x75, 0x7d, 0xf0, 0x6b, + 0xe8, 0x6b, 0xf0, 0xfa, 0x6d, 0xe6, 0x64, 0xe8, 0x72, 0x75, 0xe8, 0x62, + 0xe2, 0x69, 0xf8, 0xed, 0x63, 0xdf, 0x61, 0xeb, 0xfc, 0x68, 0xdf, 0x5f, + 0xe4, 0x70, 0x71, 0xe4, 0x5f, 0xe0, 0x68, 0xfb, 0xec, 0x62, 0xdf, 0x63, + 0xed, 0xfa, 0x69, 0xe3, 0x62, 0xe8, 0x74, 0x74, 0xea, 0x65, 0xe7, 0x6c, + 0xfa, 0xf4, 0x6c, 0xea, 0x6b, 0xef, 0x7a, 0x78, 0xf0, 0x6e, 0xee, 0x71, + 0xf6, 0xff, 0x79, 0xf2, 0x6f, 0xee, 0x70, 0xf5, 0xfd, 0x74, 0xed, 0x6b, + 0xec, 0x70, 0xfe, 0xf0, 0x69, 0xe7, 0x68, 0xef, 0xfd, 0x6c, 0xe5, 0x62, + 0xe7, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x62, 0xdf, 0x61, + 0xeb, 0xfb, 0x68, 0xe0, 0x5f, 0xe4, 0x70, 0x71, 0xe5, 0x5f, 0xe1, 0x69, + 0xfb, 0xed, 0x64, 0xe1, 0x65, 0xee, 0xfb, 0x6b, 0xe5, 0x65, 0xe9, 0x74, + 0x78, 0xec, 0x69, 0xe9, 0x6e, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xef, 0x77, + 0x7e, 0xf6, 0x72, 0xef, 0x70, 0xf2, 0x78, 0x7e, 0xf6, 0x6f, 0xed, 0x6d, + 0xf0, 0x7b, 0x75, 0xed, 0x69, 0xe9, 0x6e, 0xfb, 0xf0, 0x68, 0xe5, 0x66, + 0xee, 0xfd, 0x6b, 0xe4, 0x61, 0xe6, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67, + 0xf8, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfb, 0x69, 0xe0, 0x5f, 0xe5, 0x71, + 0x72, 0xe6, 0x61, 0xe2, 0x6a, 0xfa, 0xee, 0x66, 0xe3, 0x67, 0xee, 0xfc, + 0x6e, 0xe8, 0x67, 0xeb, 0x74, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfd, + 0x75, 0xf0, 0x6f, 0xef, 0x74, 0xfa, 0xfd, 0x75, 0xf0, 0x6e, 0xef, 0x73, + 0xfb, 0xf8, 0x6e, 0xeb, 0x6a, 0xee, 0x78, 0x76, 0xeb, 0x67, 0xe7, 0x6c, + 0xfa, 0xf0, 0x67, 0xe3, 0x65, 0xed, 0xfe, 0x6b, 0xe3, 0x61, 0xe5, 0x6f, + 0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x63, 0xdf, 0x62, 0xec, 0xfc, + 0x69, 0xe2, 0x60, 0xe6, 0x72, 0x73, 0xe8, 0x63, 0xe4, 0x6b, 0xfa, 0xef, + 0x68, 0xe5, 0x69, 0xef, 0xfe, 0x70, 0xea, 0x6a, 0xec, 0x74, 0x7e, 0xf3, + 0x6f, 0xed, 0x6f, 0xf3, 0x7c, 0x7b, 0xf4, 0x71, 0xef, 0x71, 0xf4, 0x7c, + 0x7a, 0xf0, 0x6e, 0xec, 0x6f, 0xf6, 0xfa, 0x6f, 0xea, 0x69, 0xeb, 0x76, + 0x77, 0xeb, 0x66, 0xe5, 0x6b, 0xf8, 0xef, 0x67, 0xe2, 0x64, 0xed, 0xfe, + 0x6b, 0xe3, 0x60, 0xe5, 0x6f, 0x74, 0xe7, 0x60, 0xe1, 0x68, 0xf9, 0xee, + 0x63, 0xe1, 0x63, 0xed, 0xfd, 0x6a, 0xe4, 0x62, 0xe8, 0x72, 0x75, 0xe9, + 0x65, 0xe6, 0x6c, 0xfa, 0xf2, 0x6b, 0xe8, 0x6a, 0xef, 0x7d, 0x74, 0xed, + 0x6d, 0xed, 0x73, 0xfb, 0xf9, 0x74, 0xef, 0x70, 0xf1, 0x77, 0xfd, 0xfa, + 0x73, 0xef, 0x6e, 0xef, 0x76, 0x7d, 0xf2, 0x6c, 0xeb, 0x6c, 0xf2, 0xfc, + 0x6e, 0xe9, 0x67, 0xea, 0x72, 0x77, 0xea, 0x65, 0xe4, 0x6a, 0xf8, 0xef, + 0x66, 0xe2, 0x63, 0xec, 0xff, 0x6b, 0xe3, 0x60, 0xe5, 0x70, 0x75, 0xe7, + 0x61, 0xe2, 0x69, 0xf9, 0xee, 0x65, 0xe2, 0x64, 0xed, 0xfd, 0x6c, 0xe5, + 0x64, 0xe9, 0x73, 0x77, 0xeb, 0x67, 0xe8, 0x6d, 0xf9, 0xf6, 0x6d, 0xeb, + 0x6c, 0xef, 0x7a, 0x7a, 0xf1, 0x6f, 0xee, 0x72, 0xf7, 0xff, 0x78, 0xf3, + 0x70, 0xef, 0x72, 0xf7, 0xfe, 0x75, 0xee, 0x6c, 0xed, 0x72, 0xfd, 0xf2, + 0x6b, 0xe8, 0x6a, 0xef, 0xfd, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x78, 0xea, + 0x63, 0xe4, 0x69, 0xf7, 0xef, 0x66, 0xe1, 0x64, 0xec, 0xff, 0x6b, 0xe3, + 0x61, 0xe6, 0x70, 0x75, 0xe8, 0x62, 0xe3, 0x6a, 0xf9, 0xef, 0x67, 0xe3, + 0x66, 0xee, 0xfe, 0x6d, 0xe7, 0x66, 0xea, 0x74, 0x7a, 0xee, 0x6a, 0xea, + 0x6e, 0xf7, 0xfa, 0x70, 0xee, 0x6e, 0xf1, 0x77, 0x7e, 0xf7, 0x72, 0xf0, + 0x71, 0xf4, 0x79, 0x7d, 0xf6, 0x71, 0xee, 0x6f, 0xf2, 0x7c, 0x77, 0xee, + 0x6b, 0xeb, 0x6f, 0xfb, 0xf2, 0x6b, 0xe7, 0x68, 0xee, 0xff, 0x6e, 0xe6, + 0x64, 0xe7, 0x70, 0x78, 0xea, 0x63, 0xe3, 0x69, 0xf7, 0xef, 0x66, 0xe2, + 0x64, 0xec, 0xfe, 0x6b, 0xe4, 0x62, 0xe6, 0x71, 0x76, 0xe9, 0x63, 0xe4, + 0x6a, 0xf9, 0xf1, 0x68, 0xe5, 0x67, 0xef, 0x7e, 0x6f, 0xea, 0x68, 0xec, + 0x74, 0x7c, 0xf1, 0x6c, 0xec, 0x6f, 0xf6, 0xfe, 0x76, 0xf1, 0x6f, 0xf0, + 0x75, 0xfb, 0xfd, 0x76, 0xf1, 0x6f, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xec, + 0x6d, 0xee, 0x79, 0x78, 0xed, 0x6a, 0xe9, 0x6d, 0xf9, 0xf2, 0x6a, 0xe6, + 0x67, 0xed, 0x7e, 0x6d, 0xe6, 0x63, 0xe7, 0x70, 0x78, 0xe9, 0x63, 0xe3, + 0x69, 0xf7, 0xf0, 0x66, 0xe2, 0x64, 0xec, 0xfe, 0x6c, 0xe5, 0x63, 0xe7, + 0x71, 0x77, 0xea, 0x65, 0xe6, 0x6b, 0xf8, 0xf3, 0x6a, 0xe8, 0x69, 0xef, + 0x7e, 0x72, 0xec, 0x6b, 0xed, 0x74, 0xfe, 0xf5, 0x6f, 0xee, 0x6f, 0xf4, + 0x7c, 0x7b, 0xf5, 0x71, 0xf0, 0x72, 0xf6, 0x7b, 0x79, 0xf3, 0x6e, 0xee, + 0x6f, 0xf7, 0xfb, 0x6f, 0xec, 0x6b, 0xed, 0x76, 0x79, 0xed, 0x68, 0xe7, + 0x6c, 0xf7, 0xf2, 0x69, 0xe5, 0x66, 0xec, 0x7e, 0x6e, 0xe5, 0x63, 0xe7, + 0x6f, 0x78, 0xe9, 0x63, 0xe4, 0x69, 0xf7, 0xf0, 0x67, 0xe3, 0x65, 0xed, + 0xff, 0x6d, 0xe6, 0x65, 0xe9, 0x73, 0x78, 0xeb, 0x67, 0xe7, 0x6c, 0xf8, + 0xf5, 0x6d, 0xea, 0x6b, 0xf0, 0x7c, 0x76, 0xef, 0x6d, 0xee, 0x72, 0xfc, + 0xfb, 0x74, 0xf2, 0x70, 0xf3, 0x76, 0xfe, 0xfb, 0x75, 0xf0, 0x6f, 0xf1, + 0x77, 0x7d, 0xf4, 0x6e, 0xec, 0x6d, 0xf3, 0xfe, 0x70, 0xeb, 0x69, 0xeb, + 0x73, 0x7a, 0xed, 0x67, 0xe7, 0x6b, 0xf7, 0xf4, 0x69, 0xe5, 0x66, 0xec, + 0x7d, 0x6d, 0xe5, 0x63, 0xe6, 0x6f, 0x79, 0xea, 0x64, 0xe3, 0x6a, 0xf6, + 0xf0, 0x68, 0xe4, 0x66, 0xed, 0xff, 0x6e, 0xe7, 0x66, 0xea, 0x73, 0x7a, + 0xed, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a, + 0xf3, 0x6f, 0xf0, 0x72, 0xf8, 0x7e, 0x78, 0xf5, 0x70, 0xf1, 0x72, 0xf9, + 0xff, 0x75, 0xf0, 0x6d, 0xef, 0x72, 0xfd, 0xf4, 0x6d, 0xeb, 0x6b, 0xf1, + 0xff, 0x70, 0xea, 0x67, 0xea, 0x72, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6, + 0xf3, 0x68, 0xe4, 0x66, 0xec, 0x7d, 0x6e, 0xe6, 0x64, 0xe7, 0x70, 0x79, + 0xea, 0x65, 0xe4, 0x6b, 0xf6, 0xf2, 0x69, 0xe5, 0x68, 0xed, 0xff, 0x6f, + 0xe9, 0x68, 0xeb, 0x73, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfc, 0x72, + 0xee, 0x6e, 0xf1, 0x77, 0xfe, 0xf8, 0x73, 0xf2, 0x71, 0xf5, 0x79, 0x7d, + 0xf7, 0x72, 0xef, 0x6f, 0xf4, 0x7b, 0x77, 0xf0, 0x6c, 0xec, 0x6f, 0xfb, + 0xf5, 0x6c, 0xe9, 0x6a, 0xef, 0x7d, 0x6f, 0xe9, 0x66, 0xe9, 0x70, 0x7a, + 0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf3, 0x69, 0xe4, 0x65, 0xec, 0x7d, 0x6e, + 0xe7, 0x64, 0xe8, 0x70, 0x79, 0xeb, 0x66, 0xe6, 0x6b, 0xf7, 0xf4, 0x6b, + 0xe7, 0x69, 0xee, 0x7d, 0x72, 0xeb, 0x6a, 0xec, 0x73, 0xff, 0xf2, 0x6e, + 0xed, 0x6f, 0xf5, 0xfe, 0x78, 0xf2, 0x70, 0xf1, 0x75, 0xfb, 0xfd, 0x77, + 0xf3, 0x70, 0xf1, 0x75, 0xfb, 0xfa, 0x73, 0xee, 0x6e, 0xf0, 0x78, 0x79, + 0xef, 0x6b, 0xeb, 0x6e, 0xf8, 0xf5, 0x6c, 0xe8, 0x69, 0xee, 0x7c, 0x6f, + 0xe9, 0x66, 0xe8, 0x6f, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf4, 0x69, + 0xe5, 0x66, 0xed, 0x7c, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x7a, 0xec, 0x68, + 0xe7, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6b, 0xef, 0x7c, 0x75, 0xed, 0x6c, + 0xed, 0x74, 0xfd, 0xf7, 0x71, 0xef, 0x70, 0xf4, 0x7c, 0x7c, 0xf6, 0x74, + 0xf1, 0x73, 0xf6, 0x7d, 0x7b, 0xf3, 0x70, 0xef, 0x71, 0xf7, 0xfd, 0x72, + 0xee, 0x6c, 0xee, 0x75, 0x7a, 0xef, 0x6a, 0xea, 0x6d, 0xf7, 0xf6, 0x6b, + 0xe7, 0x68, 0xed, 0x7b, 0x6f, 0xe8, 0x66, 0xe9, 0x6f, 0x7b, 0xec, 0x66, + 0xe5, 0x6a, 0xf6, 0xf4, 0x6a, 0xe6, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x66, + 0xe9, 0x72, 0x7a, 0xee, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6e, 0xeb, 0x6c, + 0xf0, 0x7b, 0x78, 0xf0, 0x6e, 0xef, 0x74, 0xfa, 0xfb, 0x76, 0xf1, 0x71, + 0xf3, 0x78, 0xfd, 0xfa, 0x76, 0xf1, 0x71, 0xf3, 0x78, 0x7e, 0xf5, 0x6f, + 0xed, 0x6f, 0xf4, 0xfe, 0x72, 0xec, 0x6b, 0xec, 0x74, 0x7c, 0xee, 0x6a, + 0xe8, 0x6c, 0xf6, 0xf5, 0x6b, 0xe7, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x65, + 0xe9, 0x6f, 0x7a, 0xed, 0x66, 0xe6, 0x6a, 0xf6, 0xf5, 0x6a, 0xe7, 0x68, + 0xee, 0x7c, 0x70, 0xea, 0x68, 0xeb, 0x72, 0x7c, 0xef, 0x6b, 0xeb, 0x6e, + 0xf7, 0xfa, 0x70, 0xee, 0x6d, 0xf1, 0x79, 0x7b, 0xf5, 0x70, 0xf0, 0x74, + 0xf7, 0x7e, 0x7a, 0xf4, 0x73, 0xf1, 0x75, 0xf9, 0xfe, 0x77, 0xf1, 0x6f, + 0xef, 0x74, 0xfc, 0xf6, 0x6f, 0xec, 0x6d, 0xf1, 0x7e, 0x73, 0xeb, 0x6a, + 0xeb, 0x73, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6, 0x6b, 0xe7, 0x67, + 0xed, 0x7b, 0x6f, 0xe9, 0x66, 0xe9, 0x6f, 0x7b, 0xed, 0x67, 0xe7, 0x6b, + 0xf7, 0xf6, 0x6b, 0xe8, 0x68, 0xee, 0x7c, 0x72, 0xeb, 0x69, 0xec, 0x73, + 0x7d, 0xf2, 0x6d, 0xec, 0x6f, 0xf7, 0xfc, 0x75, 0xef, 0x6f, 0xf1, 0x78, + 0xfe, 0xf9, 0x75, 0xf2, 0x73, 0xf5, 0x7a, 0x7e, 0xf8, 0x74, 0xf1, 0x71, + 0xf4, 0x7c, 0x79, 0xf1, 0x6e, 0xee, 0x71, 0xfb, 0xf7, 0x6e, 0xeb, 0x6b, + 0xf0, 0x7c, 0x72, 0xeb, 0x69, 0xea, 0x71, 0x7d, 0xee, 0x68, 0xe7, 0x6b, + 0xf5, 0xf6, 0x6b, 0xe7, 0x68, 0xed, 0x7c, 0x70, 0xe9, 0x67, 0xe9, 0x70, + 0x7b, 0xed, 0x68, 0xe8, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6a, 0xef, 0x7b, + 0x74, 0xed, 0x6b, 0xed, 0x73, 0xff, 0xf5, 0x6f, 0xee, 0x6f, 0xf6, 0x7e, + 0x79, 0xf3, 0x71, 0xf2, 0x76, 0xfb, 0xfd, 0x78, 0xf5, 0x72, 0xf3, 0x76, + 0xfc, 0xfa, 0x74, 0xef, 0x6f, 0xf2, 0x79, 0x7a, 0xf1, 0x6d, 0xec, 0x6f, + 0xf9, 0xf7, 0x6d, 0xea, 0x6b, 0xee, 0x7c, 0x72, 0xeb, 0x68, 0xea, 0x70, + 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf4, 0xf6, 0x6c, 0xe7, 0x68, 0xed, 0x7b, + 0x71, 0xea, 0x67, 0xea, 0x71, 0x7c, 0xee, 0x69, 0xe9, 0x6c, 0xf6, 0xf8, + 0x6e, 0xeb, 0x6b, 0xf0, 0x7a, 0x76, 0xef, 0x6d, 0xee, 0x73, 0xfd, 0xf9, + 0x72, 0xf0, 0x70, 0xf5, 0x7b, 0x7c, 0xf7, 0x73, 0xf3, 0x74, 0xf8, 0x7c, + 0x7b, 0xf5, 0x71, 0xf1, 0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6d, 0xef, 0x77, + 0x7c, 0xf1, 0x6c, 0xeb, 0x6e, 0xf7, 0xf7, 0x6d, 0xe9, 0x6a, 0xee, 0x7c, + 0x72, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6, + 0x6c, 0xe7, 0x69, 0xed, 0x7c, 0x72, 0xea, 0x68, 0xeb, 0x71, 0x7d, 0xef, + 0x6b, 0xea, 0x6d, 0xf7, 0xfa, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a, 0xf2, + 0x6f, 0xef, 0x74, 0xfa, 0xfc, 0x76, 0xf3, 0x72, 0xf4, 0x78, 0xfe, 0xfb, + 0x75, 0xf3, 0x72, 0xf4, 0x79, 0x7e, 0xf7, 0x70, 0xef, 0x70, 0xf5, 0xfe, + 0x75, 0xee, 0x6c, 0xee, 0x75, 0x7d, 0xf0, 0x6c, 0xea, 0x6d, 0xf7, 0xf8, + 0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x73, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee, + 0x69, 0xe8, 0x6b, 0xf6, 0xf7, 0x6c, 0xe8, 0x69, 0xee, 0x7b, 0x73, 0xec, + 0x6a, 0xec, 0x71, 0x7d, 0xf2, 0x6c, 0xec, 0x6e, 0xf7, 0xfc, 0x73, 0xee, + 0x6e, 0xf1, 0x79, 0x7c, 0xf6, 0x72, 0xf1, 0x74, 0xf9, 0x7e, 0x7a, 0xf6, + 0x73, 0xf3, 0x75, 0xfa, 0xfe, 0x78, 0xf3, 0x70, 0xf1, 0x76, 0xfd, 0xf7, + 0x70, 0xed, 0x6e, 0xf3, 0x7e, 0x75, 0xed, 0x6c, 0xed, 0x73, 0x7e, 0xf0, + 0x6b, 0xea, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x72, 0xeb, + 0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe9, 0x6c, 0xf5, 0xf8, 0x6d, 0xea, + 0x6a, 0xef, 0x7b, 0x75, 0xed, 0x6b, 0xed, 0x72, 0xff, 0xf4, 0x6e, 0xed, + 0x6f, 0xf6, 0xfe, 0x75, 0xf1, 0x70, 0xf3, 0x78, 0xfe, 0xf9, 0x75, 0xf3, + 0x73, 0xf6, 0x7b, 0x7d, 0xf9, 0x74, 0xf2, 0x72, 0xf7, 0x7c, 0x7a, 0xf3, + 0x6f, 0xef, 0x73, 0xfb, 0xf8, 0x6f, 0xed, 0x6d, 0xf1, 0x7c, 0x75, 0xed, + 0x6b, 0xec, 0x73, 0x7d, 0xef, 0x6b, 0xe9, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9, + 0x69, 0xee, 0x7a, 0x73, 0xeb, 0x69, 0xeb, 0x71, 0x7e, 0xef, 0x6b, 0xe9, + 0x6d, 0xf6, 0xf8, 0x6e, 0xea, 0x6c, 0xef, 0x7c, 0x77, 0xee, 0x6d, 0xee, + 0x74, 0xfe, 0xf7, 0x70, 0xef, 0x70, 0xf6, 0x7d, 0x78, 0xf5, 0x71, 0xf4, + 0x76, 0xfb, 0xfe, 0x78, 0xf6, 0x73, 0xf5, 0x76, 0xfd, 0xfb, 0x75, 0xf2, + 0x70, 0xf3, 0x7a, 0x7b, 0xf3, 0x6e, 0xed, 0x71, 0xf9, 0xf8, 0x6f, 0xec, + 0x6c, 0xef, 0x7c, 0x75, 0xec, 0x6a, 0xec, 0x71, 0x7e, 0xf0, 0x6a, 0xe9, + 0x6c, 0xf6, 0xf9, 0x6d, 0xe9, 0x69, 0xee, 0x7a, 0x73, 0xec, 0x69, 0xeb, + 0x71, 0x7e, 0xf0, 0x6b, 0xea, 0x6d, 0xf6, 0xfa, 0x6f, 0xec, 0x6d, 0xf0, + 0x7b, 0x79, 0xf0, 0x6e, 0xef, 0x75, 0xfc, 0xf9, 0x74, 0xf1, 0x72, 0xf6, + 0x7b, 0x7c, 0xf8, 0x75, 0xf4, 0x74, 0xf8, 0x7d, 0x7c, 0xf7, 0x73, 0xf2, + 0x74, 0xf9, 0xfd, 0x76, 0xf0, 0x6f, 0xf0, 0x77, 0x7d, 0xf3, 0x6d, 0xed, + 0x6f, 0xf8, 0xfa, 0x6f, 0xeb, 0x6b, 0xef, 0x7b, 0x75, 0xec, 0x6a, 0xeb, + 0x71, 0x7e, 0xf0, 0x6a, 0xe9, 0x6c, 0xf5, 0xf9, 0x6d, 0xea, 0x6a, 0xee, + 0x7a, 0x74, 0xed, 0x6a, 0xec, 0x71, 0x7e, 0xf2, 0x6c, 0xec, 0x6e, 0xf5, + 0xfc, 0x72, 0xed, 0x6e, 0xf1, 0x79, 0x7b, 0xf3, 0x70, 0xef, 0x74, 0xfa, + 0xfc, 0x78, 0xf3, 0x73, 0xf4, 0x79, 0xfc, 0xfa, 0x77, 0xf3, 0x74, 0xf4, + 0x7a, 0xfe, 0xf7, 0x73, 0xef, 0x71, 0xf7, 0x7e, 0x76, 0xef, 0x6e, 0xef, + 0x75, 0x7d, 0xf3, 0x6d, 0xec, 0x6e, 0xf7, 0xfa, 0x6e, 0xeb, 0x6b, 0xef, + 0x79, 0x74, 0xed, 0x69, 0xeb, 0x6f, 0x7e, 0xf1, 0x6a, 0xea, 0x6c, 0xf6, + 0xfa, 0x6e, 0xeb, 0x6a, 0xef, 0x7a, 0x76, 0xee, 0x6b, 0xed, 0x72, 0xfe, + 0xf4, 0x6e, 0xed, 0x6f, 0xf6, 0xfd, 0x75, 0xef, 0x6f, 0xf1, 0x79, 0x7e, + 0xf6, 0x75, 0xf1, 0x76, 0xf8, 0xfd, 0x7a, 0xf5, 0x74, 0xf2, 0x76, 0xf8, + 0x7e, 0x7a, 0xf6, 0x71, 0xec, 0x76, 0xfa, 0x7e, 0x7b, 0xf9, 0x72, 0xf5, + 0x7d, 0xfb, 0xfb, 0x76, 0xf9, 0x75, 0x7b, 0xf1, 0x77, 0xfe, 0x7a, 0xfb, + 0x7e, 0x73, 0xfa, 0x7e, 0xfb, 0x7d, 0x77, 0xf7, 0x7e, 0x7c, 0xfe, 0x78, + 0xfa, 0x7e, 0xff, 0x7e, 0x76, 0xf9, 0xfd, 0xf9, 0xfe, 0x75, 0xf1, 0x76, + 0x7c, 0xfc, 0x7a, 0xf3, 0x70, 0xfd, 0x7e, 0x7d, 0xf3, 0x70, 0xf7, 0x78, + 0x7e, 0xf9, 0x72, 0xef, 0x77, 0xfc, 0xfd, 0x75, 0xee, 0x72, 0xf7, 0xfb +#endif +}; + +static short MuLawDecompressTable[256] = +{ + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, -1, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + + +void vt_bell (VT *vt) +{ + if (vt->bell < 2) + return; + for (int i = 0; i < (int)sizeof (vt_bell_audio); i++) + { + int16_t val = MuLawDecompressTable[vt_bell_audio[i]] * vt->bell / 8; + terminal_queue_pcm (val, val); + } +} + + +void terminal_queue_pcm (int16_t sample_left, int16_t sample_right); + +void vt_audio (VT *vt, const char *command) +{ + AudioState *audio = &vt->audio; + // the simplest form of audio is raw audio + // _As=8000,c=2,b=8,e=u + // + // multiple voices: + // ids to queue - store samples as images... + // + // reusing samples + // .. pitch bend and be able to do a mod player? + const char *payload = NULL; + char key = 0; + int value; + int pos = 1; + + audio->frames=0; + audio->action='t'; + + int configure = 0; + while (command[pos] != ';') + { + pos ++; // G or , + if (command[pos] == ';') break; + key = command[pos]; pos++; + if (command[pos] == ';') break; + pos ++; // = + if (command[pos] == ';') break; + + if (command[pos] >= '0' && command[pos] <= '9') + value = atoi(&command[pos]); + else + value = command[pos]; + while (command[pos] && + command[pos] != ',' && + command[pos] != ';') pos++; + + if (value=='?') + { + char buf[256]; + const char *range=""; + switch (key) + { + case 's':range="8000,16000,24000,48000";break; + case 'b':range="8,16";break; + case 'B':range="512-65536";break; + case 'c':range="1";break; + case 'T':range="u,s,f";break; + case 'e':range="b,a";break; + case 'o':range="z,0";break; + case 'a':range="t,q";break; + default:range="unknown";break; + } + sprintf (buf, "\033_A%c=?;%s\033\\", key, range); + vt_write (vt, buf, strlen(buf)); + return; + } + + switch (key) + { + case 's': audio->samplerate = value; configure = 1; break; + case 'b': audio->bits = value; configure = 1; break; + case 'B': audio->buffer_size = value; configure = 1; break; + case 'c': audio->channels = value; configure = 1; break; + case 'a': audio->action = value; configure = 1; break; + case 'T': audio->type = value; configure = 1; break; + case 'f': audio->frames = value; configure = 1; break; + case 'e': audio->encoding = value; configure = 1; break; + case 'o': audio->compression = value; configure = 1; break; + case 'm': + audio->mic = value?1:0; + break; + } + + if (configure) + { + /* these are the specific sample rates supported by opus, + * instead of enabling anything SDL supports, the initial + * implementation limits itself to the opus sample rates + */ + if (audio->samplerate <= 8000) + { + audio->samplerate = 8000; + } + else if (audio->samplerate <= 16000) + { + audio->samplerate = 16000; + } + else if (audio->samplerate <= 24000) + { + audio->samplerate = 24000; + } + else + { + audio->samplerate = 48000; + } + + if (audio->bits != 8 && audio->bits != 16) + audio->bits = 8; + + if (audio->buffer_size > 2048) + audio->buffer_size = 2048; + else if (audio->buffer_size < 512) + audio->buffer_size = 512; + + switch (audio->type) + { + case 'u': + case 's': + case 'f': + break; + default: + audio->type = 's'; + } + + /* only 1 and 2 channels supported */ + if (audio->channels <= 0 || audio->channels > 2) + { + audio->channels = 1; + } + } + } + + if (audio->frames || audio->action != 'd') + { + payload = &command[pos+1]; + + // accumulate incoming data + { + int chunk_size = strlen (payload); + int old_size = audio->data_size; + if (audio->data == NULL) + { + audio->data_size = chunk_size; + audio->data = malloc (audio->data_size + 1); + } + else + { + audio->data_size += chunk_size; + audio->data = realloc (audio->data, audio->data_size + 1); + } + memcpy (audio->data + old_size, payload, chunk_size); + audio->data[audio->data_size]=0; + } + + if (audio->frames) + switch (audio->encoding) + { + case 'y': + audio->data_size = ydec (audio->data, audio->data, audio->data_size); + break; + case 'a': + { + int bin_length = audio->data_size; + if (bin_length) + { + uint8_t *data2 = malloc ((unsigned int)ctx_a85len ((char*)audio->data, audio->data_size) + 1); + // a85len is inaccurate but gives an upper bound, + // should be fixed. + bin_length = ctx_a85dec ((char*)audio->data, + (void*)data2, + bin_length); + free (audio->data); + audio->data = data2; + audio->data_size = bin_length; + } + } + break; + + case 'b': + { + int bin_length = audio->data_size; + uint8_t *data2 = malloc (audio->data_size); + bin_length = ctx_base642bin ((char*)audio->data, + &bin_length, + data2); + memcpy (audio->data, data2, bin_length + 1); + audio->data_size = bin_length; + free (data2); + } + break; + } + + if (audio->frames) + switch (audio->compression) + { + case 'z': + { + unsigned long actual_uncompressed_size = audio->frames * audio->bits/8 * audio->channels + 512; + unsigned char *data2 = malloc (actual_uncompressed_size); + /* if a buf size is set (rather compression, but + * this works first..) then */ + int z_result = uncompress (data2, &actual_uncompressed_size, + audio->data, + audio->data_size); + if (z_result != Z_OK) + { + // fprintf (stderr, "[z error %i %i]", __LINE__, z_result); + } + +#if 0 + // XXX : we seem to get buf-error (-5) here, which indicates not enough + // space in output buffer, which is odd + // + // it is non fatal though so we ignore it and use the validly + // decompressed bits. + { + char buf[256]; + sprintf (buf, "\e_Ao=z;zlib error1 %i\e\\", z_result); + vt_write (vt, buf, strlen(buf)); + //goto cleanup; + } +#endif + free (audio->data); + audio->data = data2; + audio->data_size = actual_uncompressed_size; + } + + break; + case 'o': + break; + default: + break; + } + + if (audio->frames == 0) + { + /* implicit frame count */ + audio->frames = audio->data_size / + (audio->bits/8) / + audio->channels; + } + + +#if 0 + if (audio->format == 100/* opus */) + { + int channels; + uint8_t *new_data = NULL;//stbi_load_from_memory (audio->data, audio->data_size, &audio->buf_width, &audio->buf_height, &channels, 4); + + if (!new_data) + { + char buf[256]= "\e_Gf=100;audio decode error\e\\"; + vt_write (vt, buf, strlen(buf)); + goto cleanup; + } + audio->format = 32; + free (audio->data); + audio->data = new_data; + audio->data_size = audio->buf_width * audio->buf_height * 4; + } +#endif + + switch (audio->action) + { + case 't': // transfer + if (audio->type == 'u') // implied 8bit + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = MuLawDecompressTable[audio->data[i*2]]; + int val_right = MuLawDecompressTable[audio->data[i*2+1]]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = MuLawDecompressTable[audio->data[i]]; + terminal_queue_pcm (val, val); + } + } + } + else if (audio->type == 's') + { + if (audio->bits == 8) + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = 256*((int8_t*)(audio->data))[i*2]; + int val_right = 256*((int8_t*)(audio->data))[i*2+1]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = 256*((int8_t*)(audio->data))[i]; + terminal_queue_pcm (val, val); + } + } + } + else + { + if (audio->channels == 2) + { + for (int i = 0; i < audio->frames; i++) + { + int val_left = ((int16_t*)(audio->data))[i*2]; + int val_right = ((int16_t*)(audio->data))[i*2+1]; + terminal_queue_pcm (val_left, val_right); + } + } + else + { + for (int i = 0; i < audio->frames; i++) + { + int val = ((int16_t*)(audio->data))[i]; + terminal_queue_pcm (val, val); + } + } + } + } + free (audio->data); + audio->data = NULL; + audio->data_size=0; + break; + case 'q': // query + { + char buf[512]; + sprintf (buf, "\033_As=%i,b=%i,c=%i,T=%c,B=%i,e=%c,o=%c;OK\033\\", + audio->samplerate, audio->bits, audio->channels, audio->type, + audio->buffer_size, + audio->encoding?audio->encoding:'0', + audio->compression?audio->compression:'0' + /*audio->transmission*/); + + vt_write (vt, buf, strlen(buf)); + } + break; + } + } + +//cleanup: + if (audio->data) + free (audio->data); + audio->data = NULL; + audio->data_size=0; +} +#endif +/* DEC terminals/xterm family terminal with ANSI, utf8, vector graphics and + * audio. + * + * Copyright (c) 2014, 2016, 2018, 2020 Øyvind Kolås <pippin@gimp.org> + * + * Adhering to the standards with modern extensions. + * + * Features: + * vt100 - 101 points on scoresheet + * UTF8, cp437 + * dim, bold, strikethrough, underline, italic, reverse + * ANSI colors, 256 colors (non-redefineable), 24bit color + * realtime audio transmission + * raster sprites (kitty spec) + * vector graphics + * vt320 - horizontal margins + * proportional fonts + * + * BBS/ANSI-art mode + * + * 8bit clean + * + * + * Todo: + * DECCIR - cursor state report https://vt100.net/docs/vt510-rm/DECCIR.html + * make absolute positioning take proportional into account + * HTML / PNG / SVG / PDF export of scrollback / screen + * sixels + * + */ + +#define _GNU_SOURCE +#define _BSD_SOURCE +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 // for posix_openpt +#endif + +#if !__COSMOPOLITAN__ +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <assert.h> + +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <zlib.h> +#endif + +#include "ctx.h" + + +#define CTX_VT_USE_FRAMEDIFF 0 // is a larger drain than neccesary when everything is per-byte? + // is anyways currently disabled also in ctx + +//#define STB_IMAGE_IMPLEMENTATION +//#include "stb_image.h" + +//#include "vt-line.h" +//#include "vt.h" +//#include "ctx-clients.h" + + + +#define VT_LOG_INFO (1<<0) +#define VT_LOG_CURSOR (1<<1) +#define VT_LOG_COMMAND (1<<2) +#define VT_LOG_WARNING (1<<3) +#define VT_LOG_ERROR (1<<4) +#define VT_LOG_INPUT (1<<5) +#define VT_LOG_ALL 0xff + +static int vt_log_mask = VT_LOG_INPUT; +//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR;// | VT_LOG_COMMAND;// | VT_LOG_INFO | VT_LOG_COMMAND; +//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR | VT_LOG_INFO | VT_LOG_COMMAND | VT_LOG_INPUT; +//static int vt_log_mask = VT_LOG_ALL; + +#if 0 +#define vt_log(domain, fmt, ...) + +#define VT_input(str, ...) +#define VT_info(str, ...) +#define VT_command(str, ...) +#define VT_cursor(str, ...) +#define VT_warning(str, ...) +#define VT_error(str, ...) +#else +#define vt_log(domain, line, a...) \ + do {fprintf (stderr, "%i %s ", line, domain);fprintf(stderr, ##a);fprintf(stderr, "\n");}while(0) +#define VT_info(a...) if (vt_log_mask & VT_LOG_INFO) vt_log ("INFO ", __LINE__, ##a) +#define VT_input(a...) if (vt_log_mask & VT_LOG_INPUT) vt_log ("INPUT ", __LINE__, ##a) +#define VT_command(a...) if (vt_log_mask & VT_LOG_COMMAND) vt_log ("CMD ", __LINE__, ##a) +#define VT_cursor(a...) if (vt_log_mask & VT_LOG_CURSOR) vt_log ("CURSOR",__LINE__, ##a) +#define VT_warning(a...) if (vt_log_mask & VT_LOG_WARNING) vt_log ("WARN ",__LINE__, ##a) +#define VT_error(a...) if (vt_log_mask & VT_LOG_ERROR) vt_log ("ERROR",__LINE__, ##a) + +#endif + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +static void vt_state_neutral (VT *vt, int byte); +static void vt_state_esc (VT *vt, int byte); +static void vt_state_osc (VT *vt, int byte); +static void vt_state_apc (VT *vt, int byte); +static void vt_state_apc_generic (VT *vt, int byte); +static void vt_state_sixel (VT *vt, int byte); +static void vt_state_esc_sequence (VT *vt, int byte); +static void vt_state_esc_foo (VT *vt, int byte); +static void vt_state_swallow (VT *vt, int byte); +static void vt_state_ctx (VT *vt, int byte); +static void vt_state_vt52 (VT *vt, int byte); + +#if 0 +/* barebones linked list */ + +typedef struct _CtxList CtxList; +struct _CtxList +{ + void *data; + CtxList *next; +}; + +static inline int ctx_list_length (CtxList *list) +{ + int length = 0; + for (CtxList *l = list; l; l = l->next, length++); + return length; +} + +static inline void ctx_list_prepend (CtxList **list, void *data) +{ + CtxList *new_=calloc (sizeof (CtxList), 1); + new_->next = *list; + new_->data = data; + *list = new_; +} + +static inline void *ctx_list_last (CtxList *list) +{ + if (list) + { + CtxList *last; + for (last = list; last->next; last=last->next); + return last->data; + } + return NULL; +} + +static inline void ctx_list_append (CtxList **list, void *data) +{ + CtxList *new_= calloc (sizeof (CtxList), 1); + new_->data=data; + if (*list) + { + CtxList *last; + for (last = *list; last->next; last=last->next); + last->next = new_; + return; + } + *list = new_; + return; +} + +static inline void ctx_list_remove (CtxList **list, void *data) +{ + CtxList *iter, *prev = NULL; + if ( (*list)->data == data) + { + prev = (void *) (*list)->next; + free (*list); + *list = prev; + return; + } + for (iter = *list; iter; iter = iter->next) + if (iter->data == data) + { + prev->next = iter->next; + free (iter); + break; + } + else + { prev = iter; } +} + +static inline void +ctx_list_insert_before (CtxList **list, CtxList *sibling, + void *data) +{ + if (*list == NULL || *list == sibling) + { + ctx_list_prepend (list, data); + } + else + { + CtxList *prev = NULL; + for (CtxList *l = *list; l; l=l->next) + { + if (l == sibling) + { break; } + prev = l; + } + if (prev) + { + CtxList *new_=calloc (sizeof (CtxList), 1); + new_->next = sibling; + new_->data = data; + prev->next=new_; + } + } +} +#endif + + +typedef enum +{ + STYLE_REVERSE = 1 << 0, + STYLE_BOLD = 1 << 1, + STYLE_BLINK = 1 << 2, + STYLE_UNDERLINE = 1 << 3, + STYLE_DIM = 1 << 4, + STYLE_HIDDEN = 1 << 5, + STYLE_ITALIC = 1 << 6, + STYLE_UNDERLINE_VAR = 1 << 7, + STYLE_STRIKETHROUGH = 1 << 8, + STYLE_OVERLINE = 1 << 9, + STYLE_BLINK_FAST = 1 << 10, + STYLE_PROPORTIONAL = 1 << 11, + STYLE_FG_COLOR_SET = 1 << 12, + STYLE_BG_COLOR_SET = 1 << 13, + STYLE_FG24_COLOR_SET = 1 << 14, + STYLE_BG24_COLOR_SET = 1 << 15, + //STYLE_NONERASABLE = 1 << 16 // needed for selective erase +} TerminalStyle; + +typedef struct Image +{ + int kitty_format; + int width; + int height; + int id; + int eid_no; + int size; + uint8_t *data; +} Image; + +#define MAX_IMAGES 128 + +static Image image_db[MAX_IMAGES]= {{0,},}; + +static Image *image_query (int id) +{ + for (int i = 0; i < MAX_IMAGES; i++) + { + Image *image = &image_db[i]; + if (image->id == id) + { return image; } + } + return NULL; +} + +static int image_eid_no = 0; + +static Image *image_add (int width, + int height, + int id, + int format, + int size, + uint8_t *data) +{ + // look for id if id is not 0 + Image *image; + for (int i = 0; i < MAX_IMAGES; i++) + { + image = &image_db[i]; + if (image->data == NULL) + { break; } + } + if (image->data) + { + // not a good eviction strategy + image = &image_db[random() %MAX_IMAGES]; + } + if (image->data) + { free (image->data); } + image->kitty_format = format; + image->width = width; + image->height = height; + image->id = id; + image->size = size; + image->data = data; + image->eid_no = image_eid_no++; + return image; +} + + + +void vtpty_resize (void *data, int cols, int rows, int px_width, int px_height) +{ + VtPty *vtpty = data; + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = cols; + ws.ws_xpixel = px_width; + ws.ws_ypixel = px_height; + ioctl (vtpty->pty, TIOCSWINSZ, &ws); +} + +ssize_t vtpty_write (void *data, const void *buf, size_t count) +{ + VtPty *vtpty = data; + return write (vtpty->pty, buf, count); +} + +ssize_t vtpty_read (void *data, void *buf, size_t count) +{ + VtPty *vtpty = data; + return read (vtpty->pty, buf, count); +} + +int vtpty_waitdata (void *data, int timeout) +{ + VtPty *vtpty = data; + struct timeval tv; + fd_set fdset; + FD_ZERO (&fdset); + FD_SET (vtpty->pty, &fdset); + tv.tv_sec = 0; + tv.tv_usec = timeout; + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + if (select (vtpty->pty+1, &fdset, NULL, NULL, &tv) == -1) + { + perror ("select"); + return 0; + } + if (FD_ISSET (vtpty->pty, &fdset) ) + { + return 1; + } + return 0; +} + + +/* on current line */ +static int vt_col_to_pos (VT *vt, int col) +{ + int pos = col; + if (vt->current_line->contains_proportional) + { + Ctx *ctx = ctx_new (); + ctx_font (ctx, "regular"); + ctx_font_size (ctx, vt->font_size); + int x = 0; + pos = 0; + int prev_prop = 0; + while (x <= col * vt->cw) + { + if (vt_line_get_style (vt->current_line, pos) & STYLE_PROPORTIONAL) + { + x += ctx_glyph_width (ctx, vt_line_get_unichar (vt->current_line, pos) ); + prev_prop = 1; + } + else + { + if (prev_prop) + { + int new_cw = vt->cw - ( (x % vt->cw) ); + if (new_cw < vt->cw*3/2) + { new_cw += vt->cw; } + x += new_cw; + } + else + { + x += vt->cw; + } + prev_prop = 0; + } + pos ++; + } + pos --; + ctx_free (ctx); + } + return pos; +} + +static int vt_margin_left (VT *vt) +{ + int left = vt->left_right_margin_mode?vt->margin_left:1; + return vt_col_to_pos (vt, left); +} + +#define VT_MARGIN_LEFT vt_margin_left(vt) + +static int vt_margin_right (VT *vt) +{ + int right = vt->left_right_margin_mode?vt->margin_right:vt->cols; + return vt_col_to_pos (vt, right); +} + +#define VT_MARGIN_RIGHT vt_margin_right(vt) + + +void vt_rev_inc (VT *vt) +{ + if (vt) + vt->rev++; +} + +long vt_rev (VT *vt) +{ + return vt?vt->rev:0; +} + +static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence); +int vt_set_prop (VT *vt, uint32_t key_hash, const char *val); +uint64_t ctx_strhash (const char *utf8, int ignored); + +static void vt_set_title (VT *vt, const char *new_title) +{ + if (vt->inert) return; + + if (vt->title) + { free (vt->title); } + vt->title = strdup (new_title); + vt_set_prop (vt, ctx_strhash ("title", 0), (char*)new_title); +} + +const char *vt_get_title (VT *vt) +{ + return vt->title; +} + +static void vt_run_command (VT *vt, const char *command, const char *term); +static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence); +static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence); +static void _vt_move_to (VT *vt, int y, int x); + +static void vtcmd_clear (VT *vt, const char *sequence) +{ + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + } + vt->lines = NULL; + vt->line_count = 0; + + if (1) + { /* TODO: detect if this is neccesary.. due to images present + in lines in scrollback */ + for (int i=0; i<vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->scrollback, vt->current_line); + vt->scrollback_count++; + } + } + + /* populate lines */ + for (int i=0; i<vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } +} + +#define set_fg_rgb(r, g, b) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\ + vt->cstyle |= ((uint64_t)(r)<<16);\ + vt->cstyle |= ((uint64_t)(g)<<(16+8));\ + vt->cstyle |= ((uint64_t)(b)<<(16+8+8));\ + vt->cstyle |= STYLE_FG_COLOR_SET;\ + vt->cstyle |= STYLE_FG24_COLOR_SET;\ + +#define set_bg_rgb(r, g, b) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\ + vt->cstyle |= ((uint64_t)(r)<<40);\ + vt->cstyle |= ((uint64_t)(g)<<(40+8));\ + vt->cstyle |= ((uint64_t)(b)<<(40+8+8));\ + vt->cstyle |= STYLE_BG_COLOR_SET;\ + vt->cstyle |= STYLE_BG24_COLOR_SET;\ + +#define set_fg_idx(idx) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\ + vt->cstyle ^= (vt->cstyle & STYLE_FG24_COLOR_SET);\ + vt->cstyle |= ((idx)<<16);\ + vt->cstyle |= STYLE_FG_COLOR_SET; + +#define set_bg_idx(idx) \ + vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\ + vt->cstyle ^= (vt->cstyle & STYLE_BG24_COLOR_SET);\ + vt->cstyle |= ((int64_t)(idx)<<40) ;\ + vt->cstyle |= STYLE_BG_COLOR_SET; + + +static void _vt_compute_cw_ch (VT *vt) +{ + vt->cw = (vt->font_size / vt->line_spacing * vt->scale_x) + 0.99; + vt->ch = vt->font_size; +} + +static void vtcmd_set_132_col (VT *vt, int set) +{ + // this should probably force the window as well + if (set == 0 && vt->scale_x == 1.0f) return; + if (set == 1 && vt->scale_x != 1.0f) return; + if (set) // 132 col + { + vt->scale_x = 74.0/132.0; // show all - po + //vt->scale_x = 80.0/132.0; + vt->scale_y = 1.0; + _vt_compute_cw_ch (vt); + vt_set_term_size (vt, vt->cols * 132/80.0, vt->rows); + } + else // 80 col + { + vt->scale_x = 1.0; + vt->scale_y = 1.0; + _vt_compute_cw_ch (vt); + vt_set_term_size (vt, vt->cols * 80/132.0, vt->rows); + } +} + +static void vt_line_feed (VT *vt); +static void vt_carriage_return (VT *vt); + +static int vt_trimlines (VT *vt, int max); +static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence) +{ + VT_info ("reset %s", sequence); + if (getenv ("VT_DEBUG") ) + { vt->debug = 1; } + vtcmd_clear (vt, sequence); + vt->encoding = 0; + vt->bracket_paste = 0; + vt->ctx_events = 0; + vt->cr_on_lf = 0; + vtcmd_set_top_and_bottom_margins (vt, "[r"); + vtcmd_set_left_and_right_margins (vt, "[s"); + vt->autowrap = 1; + vt->justify = 0; + vt->cursor_visible = 1; + vt->charset[0] = 0; + vt->charset[1] = 0; + vt->charset[2] = 0; + vt->charset[3] = 0; + vt->bell = 3; + vt->scale_x = 1.0; + vt->scale_y = 1.0; + vt->saved_x = 1; + vt->saved_y = 1; + vt->saved_style = 1; + vt->reverse_video = 0; + vt->cstyle = 0; + vt->keyrepeat = 1; + vt->cursor_key_application = 0; + vt->argument_buf_len = 0; + vt->argument_buf[0] = 0; + vt->vtpty.done = 0; + vt->result = -1; + vt->state = vt_state_neutral; + vt->scroll_on_output = 0; + vt->scroll_on_input = 1; + vt->unit_pixels = 0; + vt->mouse = 0; + vt->mouse_drag = 0; + vt->mouse_all = 0; + vt->mouse_decimal = 0; + _vt_compute_cw_ch (vt); + for (int i = 0; i < MAX_COLS; i++) + { vt->tabs[i] = i % 8 == 0? 1 : 0; } + _vt_move_to (vt, vt->margin_top, vt->cursor_x); + vt_carriage_return (vt); + //if (vt->ctx) + // { ctx_reset (vt->ctx); } + vt->audio.bits = 8; + vt->audio.channels = 1; + vt->audio.type = 'u'; + vt->audio.samplerate = 8000; + vt->audio.buffer_size = 1024; + vt->audio.encoding = 'a'; + vt->audio.compression = '0'; + vt->audio.mic = 0; + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + vt->scrollback_count = 0; +} + +void vt_set_font_size (VT *vt, float font_size) +{ + vt->font_size = font_size; + _vt_compute_cw_ch (vt); +} + +float vt_get_font_size (VT *vt) +{ + return vt->font_size; +} + +void vt_set_line_spacing (VT *vt, float line_spacing) +{ + vt->line_spacing = line_spacing; + _vt_compute_cw_ch (vt); +} + +VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch) +{ + VT *vt = calloc (sizeof (VT), 1); + vt->id = id; + vt->lastx = -1; + vt->lasty = -1; + vt->state = vt_state_neutral; + vt->smooth_scroll = 0; + vt->can_launch = can_launch; + vt->scroll_offset = 0.0; + vt->waitdata = vtpty_waitdata; + vt->read = vtpty_read; + vt->write = vtpty_write; + vt->resize = vtpty_resize; + vt->font_to_cell_scale = 0.98; + vt->cursor_visible = 1; + vt->lines = NULL; + vt->line_count = 0; + vt->current_line = NULL; + vt->cols = 0; + vt->rows = 0; + + vt->scrollback_limit = DEFAULT_SCROLLBACK; + vt->argument_buf_len = 0; + vt->argument_buf_cap = 64; + vt->argument_buf = malloc (vt->argument_buf_cap); + vt->argument_buf[0] = 0; + vt->vtpty.done = 0; + vt->result = -1; + vt->line_spacing = 1.0; + vt->scale_x = 1.0; + vt->scale_y = 1.0; + vt_set_font_size (vt, font_size); + vt_set_line_spacing (vt, line_spacing); + if (command) + { + vt_run_command (vt, command, NULL); + } + if (width <= 0) width = 640; + if (height <= 0) width = 480; + vt_set_px_size (vt, width, height); + + vt->fg_color[0] = 216; + vt->fg_color[1] = 216; + vt->fg_color[2] = 216; + vt->bg_color[0] = 0; + vt->bg_color[1] = 0; + vt->bg_color[2] = 0; + vtcmd_reset_to_initial_state (vt, NULL); + //vt->ctx = ctx_new (); + ctx_list_prepend (&vts, vt); + return vt; +} + +int vt_cw (VT *vt) +{ + return vt->cw; +} + +int vt_ch (VT *vt) +{ + return vt->ch; +} + +static int vt_trimlines (VT *vt, int max) +{ + CtxList *chop_point = NULL; + CtxList *l; + int i; + if (vt->line_count < max) + { + return 0; + } + for (l = vt->lines, i = 0; l && i < max-1; l = l->next, i++); + if (l) + { + chop_point = l->next; + l->next = NULL; + } + while (chop_point) + { + if (vt->in_alt_screen) + { + vt_line_free (chop_point->data, 1); + } + else + { + ctx_list_prepend (&vt->scrollback, chop_point->data); + vt->scrollback_count ++; + } + ctx_list_remove (&chop_point, chop_point->data); + vt->line_count--; + } + if (vt->scrollback_count > vt->scrollback_limit + 1024) + { + CtxList *l = vt->scrollback; + int no = 0; + while (l && no < vt->scrollback_limit) + { + l = l->next; + no++; + } + chop_point = NULL; + if (l) + { + chop_point = l->next; + l->next = NULL; + } + while (chop_point) + { + vt_line_free (chop_point->data, 1); + ctx_list_remove (&chop_point, chop_point->data); + vt->scrollback_count --; + } + } + return 0; +} + +static void vt_rewrap_pair (VT *vt, VtLine *topline, VtLine *bottomline, int max_col) +{ + int toplen = 0; + + while ((toplen = vt_line_get_utf8length (topline)) > max_col) + { + uint32_t unichar = vt_line_get_unichar (topline, toplen-1); + uint32_t style = vt_line_get_style (topline, toplen-1); + vt_line_insert_unichar (bottomline, 0, unichar); + vt_line_remove (topline, toplen-1); + vt_line_set_style (bottomline, 0, style); + } + + while (vt_line_get_length (bottomline) && + (toplen = vt_line_get_utf8length (topline)) < max_col) + { + uint32_t unichar = vt_line_get_unichar (bottomline, 0); + uint32_t style = vt_line_get_style (bottomline, 0); + vt_line_append_unichar (topline, unichar); + vt_line_set_style (topline, toplen, style); + vt_line_remove (bottomline, 0); + } +} + +static void vt_rewrap (VT *vt, int max_col) +{ + if (max_col < 8) max_col = 8; + CtxList *list = NULL; + + for (CtxList *l = vt->lines; l;) + { + CtxList *next = l->next; + ctx_list_prepend (&list, l->data); + ctx_list_remove (&vt->lines, l->data); + l = next; + } + for (CtxList *l = vt->scrollback; l;) + { + CtxList *next = l->next; + ctx_list_prepend (&list, l->data); + ctx_list_remove (&vt->scrollback, l->data); + l = next; + } + + for (CtxList *l = list; l; l = l->next) + { + VtLine *line = l->data; + VtLine *next = l->next ?l->next->data:NULL; + + if (vt_line_get_utf8length (line) >= max_col || (next && next->wrapped)) + { + if (!next) + { + ctx_list_append (&list, vt_line_new ("")); + next = l->next->data; + next->wrapped = 1; + } + else if (!next->wrapped) + { + ctx_list_insert_before (&list, l->next, vt_line_new ("")); + next = l->next->data; + next->wrapped = 1; + } + vt_rewrap_pair (vt, line, next, max_col); + if (vt_line_get_utf8length (next) == 0) + ctx_list_remove (&list, l->next->data); + } + } + + int rows = vt->rows; + int total_rows = ctx_list_length (list); + + int scrollback_rows = total_rows - rows; + + int c = 0; + CtxList *l; + for (l = list; l && c < scrollback_rows;) + { + CtxList *next = l->next; + ctx_list_prepend (&vt->scrollback, l->data); + ctx_list_remove (&list, l->data); + l = next; + c++; + } + for (; l ;) + { + CtxList *next = l->next; + ctx_list_prepend (&vt->lines, l->data); + ctx_list_remove (&list, l->data); + l = next; + c++; + } +} + +void vt_set_term_size (VT *vt, int icols, int irows) +{ + if (vt->rows == irows && vt->cols == icols) + return; + + + if (vt->state == vt_state_ctx) + { + // we should queue a pending resize instead, + // .. or set a flag indicating that the last + // rendered frame is discarded? + return; + } + + if(1)vt_rewrap (vt, icols); + + while (irows > vt->rows) + { + if (vt->scrollback_count) + { + vt->scrollback_count--; + ctx_list_append (&vt->lines, vt->scrollback->data); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + vt->cursor_y++; + } + else + { + ctx_list_prepend (&vt->lines, vt_line_new_with_size ("", vt->cols) ); + } + vt->line_count++; + vt->rows++; + } + while (irows < vt->rows) + { + vt->cursor_y--; + vt->rows--; + } + vt->rows = irows; + vt->cols = icols; + vt_resize (vt, vt->cols, vt->rows, vt->width, vt->height); + vt_trimlines (vt, vt->rows); + vt->margin_top = 1; + vt->margin_left = 1; + vt->margin_bottom = vt->rows; + vt->margin_right = vt->cols; + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->rev++; + VT_info ("resize %i %i", irows, icols); + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + vt->ctxp = NULL; +} + +void vt_set_px_size (VT *vt, int width, int height) +{ + int cols = width / vt->cw; + int rows = height / vt->ch; + vt->width = width; + vt->height = height; + vt_set_term_size (vt, cols, rows); +} + + + +static void vt_argument_buf_reset (VT *vt, const char *start) +{ + if (start) + { + strcpy (vt->argument_buf, start); + vt->argument_buf_len = strlen (start); + } + else + { vt->argument_buf[vt->argument_buf_len=0]=0; } +} + +static inline void vt_argument_buf_add (VT *vt, int ch) +{ + if (vt->argument_buf_len + 1 >= 1024 * 1024 * 16) + return; + // + if (vt->argument_buf_len + 1 >= + vt->argument_buf_cap) + { + vt->argument_buf_cap = vt->argument_buf_cap * 2; + vt->argument_buf = realloc (vt->argument_buf, vt->argument_buf_cap); + } + vt->argument_buf[vt->argument_buf_len] = ch; + vt->argument_buf[++vt->argument_buf_len] = 0; +} + +static void +_vt_move_to (VT *vt, int y, int x) +{ + int i; + x = x < 1 ? 1 : (x > vt->cols ? vt->cols : x); + y = y < 1 ? 1 : (y > vt->rows ? vt->rows : y); + vt->at_line_home = 0; + vt->cursor_x = x; + vt->cursor_y = y; + i = vt->rows - y; + CtxList *l; + for (l = vt->lines; l && i >= 1; l = l->next, i--); + if (l) + { + vt->current_line = l->data; + } + else + { + for (; i > 0; i--) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_append (&vt->lines, vt->current_line); + vt->line_count++; + } + } + VT_cursor ("%i,%i (_vt_move_to)", y, x); + vt->rev++; +} + +static void vt_scroll (VT *vt, int amount); + +static void _vt_add_str (VT *vt, const char *str) +{ + int logical_margin_right = VT_MARGIN_RIGHT; + if (vt->cstyle & STYLE_PROPORTIONAL) + { vt->current_line->contains_proportional = 1; } + if (vt->cursor_x > logical_margin_right) + { + if (vt->autowrap) + { + int chars = 0; + int old_x = vt->cursor_x; + VtLine *old_line = vt->current_line; + if (vt->justify && str[0] != ' ') + { + while (old_x-1-chars >1 && vt_line_get_unichar (vt->current_line, + old_x-1-chars) !=' ') + { + chars++; + } + chars--; + if (chars > (vt->margin_right - vt->margin_left) * 3 / 2) + { chars = 0; } + } + if (vt->cursor_y == vt->margin_bottom) + { + vt_scroll (vt, -1); + } + else + { + _vt_move_to (vt, vt->cursor_y+1, 1); + } + vt->current_line->wrapped=1; + vt_carriage_return (vt); + for (int i = 0; i < chars; i++) + { + vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle); + vt_line_replace_unichar (vt->current_line, vt->cursor_x - 1, + vt_line_get_unichar (old_line, old_x-1-chars+i) ); + vt->cursor_x++; + } + for (int i = 0; i < chars; i++) + { + vt_line_replace_unichar (old_line, old_x-1-chars+i, ' '); + } + if (str[0] == ' ') + return; + } + else + { + vt->cursor_x = logical_margin_right; + } + } + if (vt->insert_mode) + { + vt_line_insert_utf8 (vt->current_line, vt->cursor_x - 1, str); + while (vt->current_line->string.utf8_length > logical_margin_right) + { vt_line_remove (vt->current_line, logical_margin_right); } + } + else + { + vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1, str); + } + vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle); + vt->cursor_x += 1; + vt->at_line_home = 0; + vt->rev++; +} + +static void _vt_backspace (VT *vt) +{ + if (vt->current_line) + { + vt->cursor_x --; + if (vt->cursor_x == VT_MARGIN_RIGHT) { vt->cursor_x--; } + if (vt->cursor_x < VT_MARGIN_LEFT) + { + vt->cursor_x = VT_MARGIN_LEFT; + vt->at_line_home = 1; + } + VT_cursor ("backspace"); + } + vt->rev++; +} + +static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence) +{ + int top = 1, bottom = vt->rows; + /* w3m issues this; causing reset of cursor position, why it is issued + * is unknown + */ + if (!strcmp (sequence, "[?1001r")) + return; + if (strlen (sequence) > 2) + { + sscanf (sequence, "[%i;%ir", &top, &bottom); + } + VT_info ("margins: %i %i", top, bottom); + if (top <1) { top = 1; } + if (top > vt->rows) { top = vt->rows; } + if (bottom > vt->rows) { bottom = vt->rows; } + if (bottom < top) { bottom = top; } + vt->margin_top = top; + vt->margin_bottom = bottom; +#if 0 + _vt_move_to (vt, top, 1); +#endif + vt_carriage_return (vt); + VT_cursor ("%i, %i (home)", top, 1); +} +static void vtcmd_save_cursor_position (VT *vt, const char *sequence); + +static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence) +{ + int left = 1, right = vt->cols; + if (!vt->left_right_margin_mode) + { + vtcmd_save_cursor_position (vt, sequence); + return; + } + if (strlen (sequence) > 2) + { + sscanf (sequence, "[%i;%is", &left, &right); + } + VT_info ("hor margins: %i %i", left, right); + if (left <1) { left = 1; } + if (left > vt->cols) { left = vt->cols; } + if (right > vt->cols) { right = vt->cols; } + if (right < left) { right = left; } + vt->margin_left = left; + vt->margin_right = right; + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt_carriage_return (vt); + //VT_cursor ("%i, %i (home)", left, 1); +} + +static inline int parse_int (const char *arg, int def_val) +{ + if (!isdigit (arg[1]) || strlen (arg) == 2) + { return def_val; } + return atoi (arg+1); +} + + +static void vtcmd_set_line_home (VT *vt, const char *sequence) +{ + int val = parse_int (sequence, 1); + char buf[256]; + vt->left_right_margin_mode = 1; + sprintf (buf, "[%i;%it", val, vt->margin_right); + vtcmd_set_left_and_right_margins (vt, buf); +} + +static void vtcmd_set_line_limit (VT *vt, const char *sequence) +{ + int val = parse_int (sequence, 0); + char buf[256]; + vt->left_right_margin_mode = 1; + if (val < vt->margin_left) { val = vt->margin_left; } + sprintf (buf, "[%i;%it", vt->margin_left, val); + vtcmd_set_left_and_right_margins (vt, buf); +} + +static void vt_scroll (VT *vt, int amount) +{ + int remove_no, insert_before; + VtLine *string = NULL; + if (amount == 0) { amount = 1; } + if (amount < 0) + { + remove_no = vt->margin_top; + insert_before = vt->margin_bottom; + } + else + { + remove_no = vt->margin_bottom; + insert_before = vt->margin_top; + } + CtxList *l; + int i; + for (i=vt->rows, l = vt->lines; i > 0 && l; l=l->next, i--) + { + if (i == remove_no) + { + string = l->data; + ctx_list_remove (&vt->lines, string); + break; + } + } + if (string) + { + if (!vt->in_alt_screen && + (vt->margin_top == 1 && vt->margin_bottom == vt->rows) ) + { + ctx_list_prepend (&vt->scrollback, string); + vt->scrollback_count ++; + } + else + { + vt_line_free (string, 1); + } + } + string = vt_line_new_with_size ("", vt->cols/4); + if (amount > 0 && vt->margin_top == 1) + { + ctx_list_append (&vt->lines, string); + } + else + { + for (i=vt->rows, l = vt->lines; l; l=l->next, i--) + { + if (i == insert_before) + { + ctx_list_insert_before (&vt->lines, l, string); + break; + } + } + if (i != insert_before) + { + ctx_list_append (&vt->lines, string); + } + } + vt->current_line = string; + /* not updating line count since we should always remove one and add one */ + if (vt->smooth_scroll) + { + if (amount < 0) + { + vt->scroll_offset = -1.0; + vt->in_smooth_scroll = -1; + } + else + { + vt->scroll_offset = 1.0; + vt->in_smooth_scroll = 1; + } + } + + { + vt->select_begin_row += amount; + vt->select_end_row += amount; + vt->select_start_row += amount; + } +} + +typedef struct Sequence +{ + const char *prefix; + char suffix; + void (*vtcmd) (VT *vt, const char *sequence); + uint32_t compat; +} Sequence; + +static void vtcmd_cursor_position (VT *vt, const char *sequence) +{ + int y = 1, x = 1; + const char *semi; + if (sequence[0] != 'H' && sequence[0] != 'f') + { + y = parse_int (sequence, 1); + if ( (semi = strchr (sequence, ';') ) ) + { + x = parse_int (semi, 1); + } + } + if (x == 0) { x = 1; } + if (y == 0) { y = 1; } + if (vt->origin) + { + y += vt->margin_top - 1; + _vt_move_to (vt, y, vt->cursor_x); + x += VT_MARGIN_LEFT - 1; + } + VT_cursor ("%i %i CUP", y, x); + _vt_move_to (vt, y, x); +} + + +static void vtcmd_horizontal_position_absolute (VT *vt, const char *sequence) +{ + int x = parse_int (sequence, 1); + if (x<=0) { x = 1; } + _vt_move_to (vt, vt->cursor_y, x); +} + +static void vtcmd_goto_row (VT *vt, const char *sequence) +{ + int y = parse_int (sequence, 1); + _vt_move_to (vt, y, vt->cursor_x); +} + +static void vtcmd_cursor_forward (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + vt->cursor_x++; + } + if (vt->cursor_x > VT_MARGIN_RIGHT) + { vt->cursor_x = VT_MARGIN_RIGHT; } +} + +static void vtcmd_cursor_backward (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + vt->cursor_x--; + } + if (vt->cursor_x < VT_MARGIN_LEFT) + { + vt->cursor_x = VT_MARGIN_LEFT; // should this wrap?? + vt->at_line_home = 1; + } +} + +static void vtcmd_reverse_index (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_top) + { + vt_scroll (vt, 1); + _vt_move_to (vt, vt->margin_top, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x); + } + } +} + +static void vtcmd_cursor_up (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_top) + { + //_vt_move_to (vt, 1, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x); + } + } +} + +static void vtcmd_back_index (VT *vt, const char *sequence) +{ + // XXX implement +} + +static void vtcmd_forward_index (VT *vt, const char *sequence) +{ + // XXX implement +} + +static void vtcmd_index (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y == vt->margin_bottom) + { + vt_scroll (vt, -1); + _vt_move_to (vt, vt->margin_bottom, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x); + } + } +} + +static void vtcmd_cursor_down (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n==0) { n = 1; } + for (int i = 0; i < n; i++) + { + if (vt->cursor_y >= vt->margin_bottom) + { + _vt_move_to (vt, vt->margin_bottom, vt->cursor_x); + } + else + { + _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x); + } + } +} + +static void vtcmd_next_line (VT *vt, const char *sequence) +{ + vtcmd_index (vt, sequence); + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt_carriage_return (vt); + vt->cursor_x = VT_MARGIN_LEFT; +} + +static void vtcmd_cursor_preceding_line (VT *vt, const char *sequence) +{ + vtcmd_cursor_up (vt, sequence); + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->cursor_x = VT_MARGIN_LEFT; +} + +static void vtcmd_erase_in_line (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 0); + switch (n) + { + case 0: // clear to end of line + { + char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1); + if (p) { *p = 0; } + // XXX : this is chopping lines + for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col - 1, vt->cstyle); } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); + } + break; + case 1: // clear from beginning to cursor + { + for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++) + { + vt_line_replace_utf8 (vt->current_line, col-1, " "); + } + for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); // should be a nop + } + break; + case 2: // clear entire line + for (int col = VT_MARGIN_LEFT; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + vt_line_set (vt->current_line, ""); // XXX not all + break; + } +} + +static void vtcmd_erase_in_display (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 0); + switch (n) + { + case 0: // clear to end of screen + { + char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1); + if (p) { *p = 0; } + vt->current_line->string.length = strlen (vt->current_line->string.str); + vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); + } + for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++) + { vt_line_set_style (vt->current_line, col-1, vt->cstyle); } + { + CtxList *l; + int no = vt->rows; + for (l = vt->lines; l->data != vt->current_line; l = l->next, no--) + { + VtLine *buf = l->data; + buf->string.str[0] = 0; + buf->string.length = 0; + buf->string.utf8_length = 0; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (buf, col-1, vt->cstyle); } + } + } + break; + case 1: // clear from beginning to cursor + { + for (int col = 1; col <= vt->cursor_x; col++) + { + vt_line_replace_utf8 (vt->current_line, col-1, " "); + vt_line_set_style (vt->current_line, col-1, vt->cstyle); + } + } + { + CtxList *l; + int there_yet = 0; + int no = vt->rows; + for (l = vt->lines; l; l = l->next, no--) + { + VtLine *buf = l->data; + if (there_yet) + { + buf->string.str[0] = 0; + buf->string.length = 0; + buf->string.utf8_length = 0; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (buf, col-1, vt->cstyle); } + } + if (buf == vt->current_line) + { + there_yet = 1; + } + } + } + break; + case 3: // also clear scrollback + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + vt->scrollback_count = 0; + /* FALLTHROUGH */ + case 2: // clear entire screen but keep cursor; + { + int tx = vt->cursor_x; + int ty = vt->cursor_y; + vtcmd_clear (vt, ""); + _vt_move_to (vt, ty, tx); + for (CtxList *l = vt->lines; l; l = l->next) + { + VtLine *line = l->data; + for (int col = 1; col <= vt->cols; col++) + { vt_line_set_style (line, col-1, vt->cstyle); } + } + } + break; + } +} + +static void vtcmd_screen_alignment_display (VT *vt, const char *sequence) +{ + for (int y = 1; y <= vt->rows; y++) + { + _vt_move_to (vt, y, 1); + for (int x = 1; x <= vt->cols; x++) + { + _vt_add_str (vt, "E"); + } + } +} + +#if 0 +static int find_idx (int r, int g, int b) +{ + r = r / 255.0 * 5; + g = g / 255.0 * 5; + b = b / 255.0 * 5; + return 16 + r * 6 * 6 + g * 6 + b; +} +#endif + +static void vtcmd_set_graphics_rendition (VT *vt, const char *sequence) +{ + const char *s = sequence; + if (s[0]) { s++; } + while (s && *s) + { + int n = parse_int (s - 1, 0); // works until color + // both fg and bg could be set in 256 color mode FIXME + // + /* S_GR@38@Set forground color@foo bar baz@ */ + if (n == 38) // set foreground + { + s = strchr (s, ';'); + if (!s) + { + VT_warning ("incomplete [38m expected ; %s", sequence); + return; + } + n = parse_int (s, 0); + if (n == 5) + { + s++; + if (strchr (s, ';') ) + { s = strchr (s, ';'); } + else + { s = strchr (s, ':'); } + if (s) + { + n = parse_int (s, 0); + set_fg_idx (n); + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + else if (n == 2) + { + int r = 0, g = 0, b = 0; + s++; + if (strchr (s, ';') ) + { + s = strchr (s, ';'); + if (s) + { sscanf (s, ";%i;%i;%i", &r, &g, &b); } + } + else + { + s = strchr (s, ':'); + if (s) + { sscanf (s, ":%i:%i:%i", &r, &g, &b); } + } + if (s) + for (int i = 0; i < 3; i++) + { + if (*s) + { + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + set_fg_rgb (r,g,b); + } + else + { + VT_warning ("unhandled %s %i", sequence, n); + return; + } + //return; // XXX we should continue, and allow further style set after complex color + } + else if (n == 48) // set background + { + s = strchr (s, ';'); + if (!s) + { + VT_warning ("incomplete [38m expected ; %s", sequence); + return; + } + n = parse_int (s, 0); + if (n == 5) + { + s++; + if (strchr (s, ';') ) + { s = strchr (s, ';'); } + else + { s = strchr (s, ':'); } + if (s) + { n = parse_int (s, 0); } + set_bg_idx (n); + if (s) + { + s++; + while (*s && *s >= '0' && *s <='9') { s++; } + } + } + else if (n == 2) + { + int r = 0, g = 0, b = 0; + s++; + if (strchr (s, ';') ) + { + s = strchr (s, ';'); + if (s) + { sscanf (s, ";%i;%i;%i", &r, &g, &b); } + } + else + { + s = strchr (s, ':'); + if (s) + { sscanf (s, ":%i:%i:%i", &r, &g, &b); } + } + if (s) + for (int i = 0; i < 3; i++) + { + s++; + while (*s >= '0' && *s <='9') { s++; } + } + set_bg_rgb (r,g,b); + } + else + { + VT_warning ("unhandled %s %i", sequence, n); + return; + } + //return; // we XXX should continue, and allow further style set after complex color + } + else + switch (n) + { + case 0: /* SGR@0@Style reset@@ */ + if (vt->cstyle & STYLE_PROPORTIONAL) + { vt->cstyle = STYLE_PROPORTIONAL; } + else + { vt->cstyle = 0; } + break; + case 1: /* SGR@@Bold@@ */ + vt->cstyle |= STYLE_BOLD; + break; + case 2: /* SGR@@Dim@@ */ + vt->cstyle |= STYLE_DIM; + break; + case 3: /* SGR@@Italic@@ */ + vt->cstyle |= STYLE_ITALIC; + break; + case 4: /* SGR@@Underscore@@ */ + /* SGR@4:2@Double underscore@@ */ + /* SGR@4:3@Curvy underscore@@ */ + if (s[1] == ':') + { + switch (s[2]) + { + case '0': + break; + case '1': + vt->cstyle |= STYLE_UNDERLINE; + break; + case '2': + vt->cstyle |= STYLE_UNDERLINE| + STYLE_UNDERLINE_VAR; + break; + default: + case '3': + vt->cstyle |= STYLE_UNDERLINE_VAR; + break; + } + } + else + { + vt->cstyle |= STYLE_UNDERLINE; + } + break; + case 5: /* SGR@@Blink@@ */ + vt->cstyle |= STYLE_BLINK; + break; + case 6: /* SGR@@Blink Fast@@ */ + vt->cstyle |= STYLE_BLINK_FAST; + break; + case 7: /* SGR@@Reverse@@ */ + vt->cstyle |= STYLE_REVERSE; + break; + case 8: /* SGR@@Hidden@@ */ + vt->cstyle |= STYLE_HIDDEN; + break; + case 9: /* SGR@@Strikethrough@@ */ + vt->cstyle |= STYLE_STRIKETHROUGH; + break; + case 10: /* SGR@@Font 0@@ */ + break; + case 11: /* SGR@@Font 1@@ */ + break; + case 12: /* SGR@@Font 2(ignored)@@ */ + case 13: /* SGR@@Font 3(ignored)@@ */ + case 14: /* SGR@@Font 4(ignored)@@ */ + break; + case 22: /* SGR@@Bold off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_BOLD); + vt->cstyle ^= (vt->cstyle & STYLE_DIM); + break; + case 23: /* SGR@@Italic off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_ITALIC); + break; + case 24: /* SGR@@Underscore off@@ */ + vt->cstyle ^= (vt->cstyle & (STYLE_UNDERLINE|STYLE_UNDERLINE_VAR) ); + break; + case 25: /* SGR@@Blink off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_BLINK); + vt->cstyle ^= (vt->cstyle & STYLE_BLINK_FAST); + break; + case 26: /* SGR@@Proportional spacing @@ */ + vt->cstyle |= STYLE_PROPORTIONAL; + break; + case 27: /* SGR@@Reverse off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_REVERSE); + break; + case 28: /* SGR@@Hidden off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_HIDDEN); + break; + case 29: /* SGR@@Strikethrough off@@ */ + vt->cstyle ^= (vt->cstyle & STYLE_STRIKETHROUGH); + break; + case 30: /* SGR@@black text color@@ */ + set_fg_idx (0); + break; + case 31: /* SGR@@red text color@@ */ + set_fg_idx (1); + break; + case 32: /* SGR@@green text color@@ */ + set_fg_idx (2); + break; + case 33: /* SGR@@yellow text color@@ */ + set_fg_idx (3); + break; + case 34: /* SGR@@blue text color@@ */ + set_fg_idx (4); + break; + case 35: /* SGR@@magenta text color@@ */ + set_fg_idx (5); + break; + case 36: /* SGR@@cyan text color@@ */ + set_fg_idx (6); + break; + case 37: /* SGR@@light gray text color@@ */ + set_fg_idx (7); + break; + /* SGR@38;5;Pn@256 color index foreground color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6 RGB cube and in the end a grayscale without white and black.@ */ + /* SGR@38;2;Pr;Pg;Pb@24 bit RGB foreground color each of Pr Pg and Pb have 0-255 range@@ */ + case 39: /* SGR@@default text color@@ */ + set_fg_idx (vt->reverse_video?0:15); + vt->cstyle ^= (vt->cstyle & STYLE_FG_COLOR_SET); + break; + case 40: /* SGR@@black background color@@ */ + set_bg_idx (0); + break; + case 41: /* SGR@@red background color@@ */ + set_bg_idx (1); + break; + case 42: /* SGR@@green background color@@ */ + set_bg_idx (2); + break; + case 43: /* SGR@@yellow background color@@ */ + set_bg_idx (3); + break; + case 44: /* SGR@@blue background color@@ */ + set_bg_idx (4); + break; + case 45: /* SGR@@magenta background color@@ */ + set_bg_idx (5); + break; + case 46: /* SGR@@cyan background color@@ */ + set_bg_idx (6); + break; + case 47: /* SGR@@light gray background color@@ */ + set_bg_idx (7); + break; + + /* SGR@48;5;Pn@256 color index background color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6 RGB cube and in the end a grayscale without white and black.@ */ + /* SGR@48;2;Pr;Pg;Pb@24 bit RGB background color@Where Pr Pg and Pb have 0-255 range@ */ + + case 49: /* SGR@@default background color@@ */ + set_bg_idx (vt->reverse_video?15:0); + vt->cstyle ^= (vt->cstyle & STYLE_BG_COLOR_SET); + break; + case 50: /* SGR@@Proportional spacing off @@ */ + vt->cstyle ^= (vt->cstyle & STYLE_PROPORTIONAL); + break; + // 51 : framed + // 52 : encircled + case 53: /* SGR@@Overlined@@ */ + vt->cstyle |= STYLE_OVERLINE; + break; + case 55: /* SGR@@Not Overlined@@ */ + vt->cstyle ^= (vt->cstyle&STYLE_OVERLINE); + break; + case 90: /* SGR@@dark gray text color@@ */ + set_fg_idx (8); + break; + case 91: /* SGR@@light red text color@@ */ + set_fg_idx (9); + break; + case 92: /* SGR@@light green text color@@ */ + set_fg_idx (10); + break; + case 93: /* SGR@@light yellow text color@@ */ + set_fg_idx (11); + break; + case 94: /* SGR@@light blue text color@@ */ + set_fg_idx (12); + break; + case 95: /* SGR@@light magenta text color@@ */ + set_fg_idx (13); + break; + case 96: /* SGR@@light cyan text color@@ */ + set_fg_idx (14); + break; + case 97: /* SGR@@white text color@@ */ + set_fg_idx (15); + break; + case 100: /* SGR@@dark gray background color@@ */ + set_bg_idx (8); + break; + case 101: /* SGR@@light red background color@@ */ + set_bg_idx (9); + break; + case 102: /* SGR@@light green background color@@ */ + set_bg_idx (10); + break; + case 103: /* SGR@@light yellow background color@@ */ + set_bg_idx (11); + break; + case 104: /* SGR@@light blue background color@@ */ + set_bg_idx (12); + break; + case 105: /* SGR@@light magenta background color@@ */ + set_bg_idx (13); + break; + case 106: /* SGR@@light cyan background color@@ */ + set_bg_idx (14); + break; + case 107: /* SGR@@white background color@@ */ + set_bg_idx (15); + break; + default: + VT_warning ("unhandled style code %i in sequence \\033%s\n", n, sequence); + return; + } + while (s && *s && *s != ';') { s++; } + if (s && *s == ';') { s++; } + } +} + +static void vtcmd_ignore (VT *vt, const char *sequence) +{ + VT_info ("ignoring sequence %s", sequence); +} + +static void vtcmd_clear_all_tabs (VT *vt, const char *sequence) +{ + memset (vt->tabs, 0, sizeof (vt->tabs) ); +} + +static void vtcmd_clear_current_tab (VT *vt, const char *sequence) +{ + vt->tabs[ (int) (vt->cursor_x-1)] = 0; +} + +static void vtcmd_horizontal_tab_set (VT *vt, const char *sequence) +{ + vt->tabs[ (int) vt->cursor_x-1] = 1; +} + +static void vtcmd_save_cursor_position (VT *vt, const char *sequence) +{ + vt->saved_x = vt->cursor_x; + vt->saved_y = vt->cursor_y; +} + +static void vtcmd_restore_cursor_position (VT *vt, const char *sequence) +{ + _vt_move_to (vt, vt->saved_y, vt->saved_x); +} + + +static void vtcmd_save_cursor (VT *vt, const char *sequence) +{ + vt->saved_style = vt->cstyle; + vt->saved_origin = vt->origin; + vtcmd_save_cursor_position (vt, sequence); + for (int i = 0; i < 4; i++) + { vt->saved_charset[i] = vt->charset[i]; } +} + +static void vtcmd_restore_cursor (VT *vt, const char *sequence) +{ + vtcmd_restore_cursor_position (vt, sequence); + vt->cstyle = vt->saved_style; + vt->origin = vt->saved_origin; + for (int i = 0; i < 4; i++) + { vt->charset[i] = vt->saved_charset[i]; } +} + +static void vtcmd_erase_n_chars (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1 + n, " "); + vt_line_set_style (vt->current_line, vt->cursor_x + n - 1, vt->cstyle); + } +} + +static void vtcmd_delete_n_chars (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + int count = n; + while (count--) + { + vt_line_remove (vt->current_line, vt->cursor_x - 1); + } +} + +static void vtcmd_delete_n_lines (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + for (int a = 0; a < n; a++) + { + int i; + CtxList *l; + VtLine *string = vt->current_line; + vt_line_set (string, ""); + ctx_list_remove (&vt->lines, vt->current_line); + for (i=vt->rows, l = vt->lines; l; l=l->next, i--) + { + if (i == vt->margin_bottom) + { + vt->current_line = string; + ctx_list_insert_before (&vt->lines, l, string); + break; + } + } + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); // updates current_line + } +} + +static void vtcmd_insert_character (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + vt_line_insert_utf8 (vt->current_line, vt->cursor_x-1, " "); + } + while (vt->current_line->string.utf8_length > vt->cols) + { vt_line_remove (vt->current_line, vt->cols); } + // XXX update style +} + +static void vtcmd_scroll_up (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + while (n--) + { vt_scroll (vt, -1); } +} + +static void vtcmd_scroll_down (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + while (n--) + { vt_scroll (vt, 1); } +} + +static void vtcmd_insert_blank_lines (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + if (n == 0) { n = 1; } + { + int st = vt->margin_top; + int sb = vt->margin_bottom; + vt->margin_top = vt->cursor_y; + while (n--) + { + vt_scroll (vt, 1); + } + vt->margin_top = st; + vt->margin_bottom = sb; + } +} + +static void vtcmd_set_default_font (VT *vt, const char *sequence) +{ + vt->charset[0] = 0; +} + +static void vtcmd_set_alternate_font (VT *vt, const char *sequence) +{ + vt->charset[0] = 1; +} + +int _ctx_set_frame (Ctx *ctx, int frame); +int _ctx_frame (Ctx *ctx); + +static void vt_ctx_exit (void *data) +{ + VT *vt = data; + vt->state = vt_state_neutral; + vt->rev ++; + if (!vt->current_line) + return; +#if 0 + fprintf (stderr, "\n"); + if (vt->current_line->prev) + fprintf (stderr, "---prev(%i)----\n%s", (int)strlen(vt->current_line->prev),vt->current_line->prev); + fprintf (stderr, "---new(%i)----\n%s", (int)strlen(vt->current_line->frame->str),vt->current_line->frame->str); + fprintf (stderr, "--------\n"); +#endif + +#if CTX_VT_USE_FRAME_DIFF + if (vt->current_line->prev) + free (vt->current_line->prev); + vt->current_line->prev = NULL; + if (vt->current_line->frame) + { + vt->current_line->prev = vt->current_line->frame->str; + vt->current_line->prev_length = vt->current_line->frame->length; + + ctx_string_free (vt->current_line->frame, 0); + vt->current_line->frame = NULL; + } +#endif + + void *tmp = vt->current_line->ctx; + vt->current_line->ctx = vt->current_line->ctx_copy; + vt->current_line->ctx_copy = tmp; + + _ctx_set_frame (vt->current_line->ctx, _ctx_frame (vt->current_line->ctx) + 1); + _ctx_set_frame (vt->current_line->ctx_copy, _ctx_frame (vt->current_line->ctx)); +#if 1 + if (vt->ctxp) // XXX: ugly hack to aid double buffering + ((void**)vt->ctxp)[0]= vt->current_line->ctx; +#endif + + //ctx_parser_free (vt->ctxp); + //vt->ctxp = NULL; +} +#if 0 +#define CTX_x CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_y CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0) +#define CTX_lower CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0) +#define CTX_raise CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0) +#define CTX_raise_top CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0) +#define CTX_terminate CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0) +#define CTX_maximize CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0) +#define CTX_unmaximize CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0) +#define CTX_width CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0) +#define CTX_title CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0) +#define CTX_action CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0) +#define CTX_height CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0) +#endif + + +static int vt_get_prop (VT *vt, const char *key, const char **val, int *len) +{ +#if 0 + uint32_t key_hash = ctx_strhash (key, 0); + char str[4096]=""; + fprintf (stderr, "%s: %s %i\n", __FUNCTION__, key, key_hash); + CtxClient *client = ctx_client_by_id (ct->id); + if (!client) + return 0; + switch (key_hash) + { + case CTX_title: + sprintf (str, "setkey %s %s\n", key, client->title); + break; + case CTX_x: + sprintf (str, "setkey %s %i\n", key, client->x); + break; + case CTX_y: + sprintf (str, "setkey %s %i\n", key, client->y); + break; + case CTX_width: + sprintf (str, "setkey %s %i\n", key, client->width); + break; + case CTX_height: + sprintf (str, "setkey %s %i\n", key, client->width); + break; + default: + sprintf (str, "setkey %s undefined\n", key); + break; + } + if (str[0]) + { + vtpty_write ((void*)ct, str, strlen (str)); + fprintf (stderr, "%s", str); + } +#endif + return 0; +} + +static void vtcmd_set_mode (VT *vt, const char *sequence) +{ + int set = 1; + if (sequence[strlen (sequence)-1]=='l') + { set = 0; } + if (sequence[1]=='?') + { + int qval; + sequence++; +qagain: + qval = parse_int (sequence, 1); + switch (qval) + { + case 1: /*MODE;DECCKM;Cursor key mode;Application;Cursor;*/ + vt->cursor_key_application = set; + break; + case 2: /*MODE;DECANM;VT52 emulation;on;off; */ + if (set==0) + { vt->state = vt_state_vt52; } + break; + case 3: /*MODE;DECCOLM;Column mode;132 columns;80 columns;*/ + vtcmd_set_132_col (vt, set); + break; // set 132 col + case 4: /*MODE;DECSCLM;Scrolling mode;smooth;jump;*/ + vt->smooth_scroll = set; + break; // set 132 col + case 5: /*MODE;DECSCNM;Screen mode;Reverse;Normal;*/ + vt->reverse_video = set; + break; + case 6: /*MODE;DECOM;Origin mode;Relative;Absolute;*/ + vt->origin = set; + if (set) + { + _vt_move_to (vt, vt->margin_top, 1); + vt_carriage_return (vt); + } + else + { _vt_move_to (vt, 1, 1); } + break; + case 7: /*MODE;DECAWM;Autowrap;on;off;*/ + vt->autowrap = set; + break; + case 8: /*MODE;DECARM;Auto repeat;on;off;*/ + vt->keyrepeat = set; + break; + // case 9: // send mouse x & y on button press + + // 10 - Block DECEDM + // 18 - Print form feed DECPFF default off + // 19 - Print extent fullscreen DECPEX default on + case 12: + vtcmd_ignore (vt, sequence); + break; // blinking_cursor + case 25:/*MODE;DECTCEM;Cursor visible;on;off; */ + vt->cursor_visible = set; + break; + case 30: // from rxvt - show/hide scrollbar + break; + case 34: // DECRLM - right to left mode + break; + case 38: // DECTEK - enter tektronix mode + break; + case 60: // horizontal cursor coupling + case 61: // vertical cursor coupling + break; + case 69:/*MODE;DECVSSM;Left right margin mode;on;off; */ + vt->left_right_margin_mode = set; + break; + case 80:/* DECSDM Sixel scrolling */ + break; + case 437:/*MODE;;Encoding/cp437mode;cp437;utf8; */ + vt->encoding = set ? 1 : 0; + break; + case 1000:/*MODE;;Mouse reporting;on;off;*/ + vt->mouse = set; + break; + case 1001: + case 1002:/*MODE;;Mouse drag;on;off;*/ + vt->mouse_drag = set; + break; + case 1003:/*MODE;;Mouse all;on;off;*/ + vt->mouse_all = set; + break; + case 1006:/*MODE;;Mouse decimal;on;off;*/ + vt->mouse_decimal = set; + break; + case 47: + case 1047: + //case 1048: + case 1049:/*MODE;;Alt screen;on;off;*/ + if (set) + { + if (vt->in_alt_screen) + { + } + else + { + vtcmd_save_cursor (vt, ""); + vt->saved_lines = vt->lines; + vt->saved_line_count = vt->line_count; + vt->line_count = 0; + vt->lines = NULL; + for (int i = 0; i < vt->rows; i++) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_append (&vt->lines, vt->current_line); + vt->line_count++; + } + vt->in_alt_screen = 1; + vt_line_feed (vt); + _vt_move_to (vt, 1, 1); + vt_carriage_return (vt); + } + } + else + { + if (vt->in_alt_screen) + { + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + } + vt->line_count = vt->saved_line_count; + vt->lines = vt->saved_lines; + vtcmd_restore_cursor (vt, ""); + vt->saved_lines = NULL; + vt->in_alt_screen = 0; + } + else + { + } + } + break; // alt screen + case 1010: /*MODE;;scroll on output;on;off; */ //rxvt + vt->scroll_on_output = set; + break; + case 1011:/*MODE:;scroll on input;on;off; */ //rxvt) + vt->scroll_on_input = set; + break; + case 2004:/*MODE;;bracketed paste;on;off; */ + vt->bracket_paste = set; + break; + case 201:/*MODE;;ctx-events;on;off;*/ + vt->ctx_events = set; + break; + + case 200:/*MODE;;ctx vector graphics mode;on;;*/ + if (set) + { + if (!vt->current_line->ctx) + { + vt->current_line->ctx = ctx_new (); + vt->current_line->ctx_copy = ctx_new (); + ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx); + _ctx_set_transformation (vt->current_line->ctx, 0); + _ctx_set_transformation (vt->current_line->ctx_copy, 0); + + //ctx_set_texture_cache (vt->current_line->ctx, vt->current_line->ctx_copy); + //ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx); +#if CTX_VT_USE_FRAME_DIFF + vt->current_line->frame = ctx_string_new (""); +#endif + } + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + + vt->ctxp = ctx_parser_new (vt->current_line->ctx, + vt->cols * vt->cw, vt->rows * vt->ch, + vt->cw, vt->ch, vt->cursor_x, vt->cursor_y, + (void*)vt_set_prop, (void*)vt_get_prop, vt, vt_ctx_exit, vt); + vt->utf8_holding[vt->utf8_pos=0]=0; // XXX : needed? + vt->state = vt_state_ctx; + } + break; + default: + VT_warning ("unhandled CSI ? %i%s", qval, set?"h":"l"); + return; + } + if (strchr (sequence + 1, ';') ) + { + sequence = strchr (sequence + 1, ';'); + goto qagain; + } + } + else + { + int val; +again: + val = parse_int (sequence, 1); + switch (val) + { + case 1:/*GATM - transfer enhanced data */ + case 2:/*KAM - keyboard action mode */ + break; + case 3:/*CRM - control representation mode */ + /* show control chars? */ + break; + case 4:/*MODE2;IRM;Insert Mode;Insert;Replace; */ + vt->insert_mode = set; + break; + case 9: /* interlace mode */ + break; + case 12:/*MODE2;SRM;Local echo;on;off; */ + vt->echo = set; + break; + case 20:/*MODE2;LNM;Carriage Return on LF/Newline;on;off;*/ + ; + vt->cr_on_lf = set; + break; + case 21: // GRCM - whether SGR accumulates or a reset on each command + break; + case 32: // WYCRTSAVM - screen saver + break; + case 33: // WYSTCURM - steady cursor + break; + case 34: // WYULCURM - underline cursor + break; + default: + VT_warning ("unhandled CSI %ih", val); + return; + } + if (strchr (sequence, ';') && sequence[0] != ';') + { + sequence = strchr (sequence, ';'); + goto again; + } + } +} + +static void vtcmd_request_mode (VT *vt, const char *sequence) +{ + char buf[64]=""; + if (sequence[1]=='?') + { + int qval; + sequence++; + qval = parse_int (sequence, 1); + int is_set = -1; // -1 undefiend 0 reset 1 set 1000 perm_reset 1001 perm_set + switch (qval) + { + case 1: + is_set = vt->cursor_key_application; + break; + case 2: /*VT52 emulation;;enable; */ + //if (set==0) vt->in_vt52 = 1; + is_set = 1001; + break; + case 3: + is_set = 0; + break; + case 4: + is_set = vt->smooth_scroll; + break; + case 5: + is_set = vt->reverse_video; + break; + case 6: + is_set = vt->origin; + break; + case 7: + is_set = vt->autowrap; + break; + case 8: + is_set = vt->keyrepeat; + break; + case 9: // should be dynamic + is_set = 1000; + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 16: + case 18: + case 19: + is_set = 1000; + break; + case 25: + is_set = vt->cursor_visible; + break; + case 45: + is_set = 1000; + break; + case 47: + is_set = vt->in_alt_screen; + break; + case 69: + is_set = vt->left_right_margin_mode; + break; + case 437: + is_set = vt->encoding; + break; + case 1000: + is_set = vt->mouse; + break; + // 1001 hilite tracking + case 1002: + is_set = vt->mouse_drag; + break; + case 1003: + is_set = vt->mouse_all; + break; + case 1006: + is_set = vt->mouse_decimal; + break; + case 201: + is_set = vt->ctx_events; + break; + case 2004: + is_set = vt->bracket_paste; + break; + case 1010: // scroll to bottom on tty output (rxvt) + is_set = vt->scroll_on_output; + break; + case 1011: // scroll to bottom on key press (rxvt) + is_set = vt->scroll_on_input; + break; + case 1049: + is_set = vt->in_alt_screen; + break; + break; + case 200:/*ctx protocol;On;;*/ + is_set = (vt->state == vt_state_ctx); + break; + case 80:/* DECSDM Sixel scrolling */ + case 30: // from rxvt - show/hide scrollbar + case 34: // DECRLM - right to left mode + case 60: // horizontal cursor coupling + case 61: // vertical cursor coupling + default: + break; + } + switch (is_set) + { + case 0: + sprintf (buf, "\033[?%i;%i$y", qval, 2); + break; + case 1: + { sprintf (buf, "\033[?%i;%i$y", qval, 1); } + break; + case 1000: + sprintf (buf, "\033[?%i;%i$y", qval, 4); + break; + case 1001: + sprintf (buf, "\033[?%i;%i$y", qval, 3); + break; + case -1: + { sprintf (buf, "\033[?%i;%i$y", qval, 0); } + } + } + else + { + int val; + val = parse_int (sequence, 1); + switch (val) + { + case 1: + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 2:/* AM - keyboard action mode */ + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 3:/*CRM - control representation mode */ + sprintf (buf, "\033[%i;%i$y", val, 0); + break; + case 4:/*Insert Mode;Insert;Replace; */ + sprintf (buf, "\033[%i;%i$y", val, vt->insert_mode?1:2); + break; + case 9: /* interlace mode */ + sprintf (buf, "\033[%i;%i$y", val, 1); + break; + case 12: + sprintf (buf, "\033[%i;%i$y", val, vt->echo?1:2); + break; + case 20:/*Carriage Return on LF/Newline;on;off;*/ + ; + sprintf (buf, "\033[%i;%i$y", val, vt->cr_on_lf?1:2); + break; + case 21: // GRCM - whether SGR accumulates or a reset on each command + default: + sprintf (buf, "\033[%i;%i$y", val, 0); + } + } + if (buf[0]) + { vt_write (vt, buf, strlen (buf) ); } +} + +static void vtcmd_set_t (VT *vt, const char *sequence) +{ + /* \e[21y is request title - allows inserting keychars */ + if (!strcmp (sequence, "[1t")) { ctx_client_unshade (vt->id); } + else if (!strcmp (sequence, "[2t")) { ctx_client_shade (vt->id); } + else if (!strncmp (sequence, "[3;", 3)) { + int x=0,y=0; + sscanf (sequence, "[3;%i;%ir", &y, &x); + ctx_client_move (vt->id, x, y); + } + else if (!strncmp (sequence, "[4;", 3)) + { + int width = 0, height = 0; + sscanf (sequence, "[4;%i;%ir", &height , &width); + if (width < 0) width = vt->cols * vt->cw; + if (height < 0) height = vt->rows * vt->ch; + if (width == 0) width = ctx_width (vt->root_ctx); + if (height == 0) height = ctx_height (vt->root_ctx); + ctx_client_resize (vt->id, width, height); + } + else if (!strcmp (sequence, "[5t") ) { ctx_client_raise_top (vt->id); } + else if (!strcmp (sequence, "[6t") ) { ctx_client_lower_bottom (vt->id); } + else if (!strcmp (sequence, "[7t") ) { vt->rev++; /* refresh */ } + else if (!strncmp (sequence, "[8;", 3) ) + { + int cols = 0, rows = 0; + sscanf (sequence, "[8;%i;%ir", &rows, &cols); + if (cols < 0) cols = vt->cols; + if (rows < 0) rows = vt->rows; + if (cols == 0) cols = ctx_width (vt->root_ctx) / vt->cw; + if (rows == 0) rows = ctx_height (vt->root_ctx) / vt->ch; + ctx_client_resize (vt->id, cols * vt->cw, rows * vt->ch); + } + else if (!strcmp (sequence, "[9;0t") ) { ctx_client_unmaximize (vt->id); } + else if (!strcmp (sequence, "[9;1t") ) { ctx_client_maximize (vt->id);} + + /* should actually be full-screen */ + else if (!strcmp (sequence, "[10;0t") ) { ctx_client_unmaximize (vt->id); } + else if (!strcmp (sequence, "[10;1t") ) { ctx_client_maximize (vt->id);} + else if (!strcmp (sequence, "[10;2t") ) { ctx_client_toggle_maximized (vt->id);} + + else if (!strcmp (sequence, "[11t") ) /* report window state */ + { + char buf[128]; + if (ctx_client_is_iconified (vt->id)) + sprintf (buf, "\033[2t"); + else + sprintf (buf, "\033[1t"); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[13t") ) /* request terminal position */ + { + char buf[128]; + sprintf (buf, "\033[3;%i;%it", ctx_client_y (vt->id), ctx_client_x (vt->id)); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[14t") ) /* request terminal dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[4;%i;%it", vt->rows * vt->ch, vt->cols * vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[15t") ) /* request root dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[5;%i;%it", ctx_height (vt->root_ctx), ctx_width(vt->root_ctx)); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[16t") ) /* request char dimensions in px */ + { + char buf[128]; + sprintf (buf, "\033[6;%i;%it", vt->ch, vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[18t") ) /* request terminal dimensions */ + { + char buf[128]; + sprintf (buf, "\033[8;%i;%it", vt->rows, vt->cols); + vt_write (vt, buf, strlen (buf) ); + } + else if (!strcmp (sequence, "[19t") ) /* request root window size in char */ + { + char buf[128]; + sprintf (buf, "\033[9;%i;%it", ctx_height(vt->root_ctx)/vt->ch, ctx_width (vt->root_ctx)/vt->cw); + vt_write (vt, buf, strlen (buf) ); + } + +#if 0 + {"[", 's', foo, VT100}, /*args:PnSP id:DECSWBV Set warning bell volume */ +#endif + else if (sequence[strlen (sequence)-2]==' ') /* DECSWBV */ + { + int val = parse_int (sequence, 0); + if (val <= 1) { vt->bell = 0; } + if (val <= 8) { vt->bell = val; } + } + else + { + // XXX: X for ints >=24 resize to that number of lines + VT_info ("unhandled subsequence %s", sequence); + } +} + +static void _vt_htab (VT *vt) +{ + do + { + vt->cursor_x ++; + } + while (vt->cursor_x < VT_MARGIN_RIGHT && ! vt->tabs[ (int) vt->cursor_x-1]); + if (vt->cursor_x > VT_MARGIN_RIGHT) + { vt->cursor_x = VT_MARGIN_RIGHT; } +} + +static void _vt_rev_htab (VT *vt) +{ + do + { + vt->cursor_x--; + } + while ( ! vt->tabs[ (int) vt->cursor_x-1] && vt->cursor_x > 1); + if (vt->cursor_x < VT_MARGIN_LEFT) + { vt_carriage_return (vt); } +} + +static void vtcmd_insert_n_tabs (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + _vt_htab (vt); + } +} + +static void vtcmd_rev_n_tabs (VT *vt, const char *sequence) +{ + int n = parse_int (sequence, 1); + while (n--) + { + _vt_rev_htab (vt); + } +} + +static void vtcmd_set_double_width_double_height_top_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 1; + vt->current_line->double_height_bottom = 0; +} +static void vtcmd_set_double_width_double_height_bottom_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 1; +} +static void vtcmd_set_single_width_single_height_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 0; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 0; +} +static void +vtcmd_set_double_width_single_height_line +(VT *vt, const char *sequence) +{ + vt->current_line->double_width = 1; + vt->current_line->double_height_top = 0; + vt->current_line->double_height_bottom = 0; +} + +static void vtcmd_set_led (VT *vt, const char *sequence) +{ + int val = 0; + //fprintf (stderr, "%s\n", sequence); + for (const char *s = sequence; *s; s++) + { + switch (*s) + { + case '0': val = 0; break; + case '1': val = 1; break; + case '2': val = 2; break; + case '3': val = 3; break; + case '4': val = 4; break; + case ';': + case 'q': + if (val == 0) + { vt->leds[0] = vt->leds[1] = vt->leds[2] = vt->leds[3] = 0; } + else + { vt->leds[val-1] = 1; } + val = 0; + break; + } + } +} + +static void vtcmd_char_at_cursor (VT *vt, const char *sequence) +{ + char *buf=""; + vt_write (vt, buf, strlen (buf) ); +} + +static void vtcmd_DECELR (VT *vt, const char *sequence) +{ + int ps1 = parse_int (sequence, 0); + int ps2 = 0; + const char *s = strchr (sequence, ';'); + if (ps1) {/* unused */}; + if (s) + { ps2 = parse_int (s, 0); } + if (ps2 == 1) + { vt->unit_pixels = 1; } + else + { vt->unit_pixels = 0; } +} + +static void vtcmd_graphics (VT *vt, const char *sequence) +{ + fprintf (stderr, "gfx intro [%s]\n",sequence); // maybe implement such as well? +} + +static void vtcmd_report (VT *vt, const char *sequence) +{ + char buf[64]=""; + if (!strcmp (sequence, "[5n") ) // DSR device status report + { + sprintf (buf, "\033[0n"); // we're always OK :) + } + else if (!strcmp (sequence, "[?15n") ) // printer status + { + sprintf (buf, "\033[?13n"); // no printer + } + else if (!strcmp (sequence, "[?26n") ) // keyboard dialect + { + sprintf (buf, "\033[?27;1n"); // north american/ascii + } + else if (!strcmp (sequence, "[?25n") ) // User Defined Key status + { + sprintf (buf, "\033[?21n"); // locked + } +#if 0 + {"[6n", 0, }, /* id:DSR cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */ +#endif + else if (!strcmp (sequence, "[6n") ) // DSR cursor position report + { + sprintf (buf, "\033[%i;%iR", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) ); + } + else if (!strcmp (sequence, "[?6n") ) // DECXPR extended cursor position report + { +#if 0 + {"[?6n", 0, }, /* id:DEXCPR extended cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */ +#endif + sprintf (buf, "\033[?%i;%i;1R", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) ); + } + else if (!strcmp (sequence, "[>c") ) + { + sprintf (buf, "\033[>23;01;1c"); + } + else if (sequence[strlen (sequence)-1]=='c') // device attributes + { + //buf = "\033[?1;2c"; // what rxvt reports + //buf = "\033[?1;6c"; // VT100 with AVO ang GPO + //buf = "\033[?2c"; // VT102 + sprintf (buf, "\033[?63;14;4;22c"); + } + else if (sequence[strlen (sequence)-1]=='x') // terminal parameters + { + if (!strcmp (sequence, "[1x") ) + { sprintf (buf, "\033[3;1;1;120;120;1;0x"); } + else + { sprintf (buf, "\033[2;1;1;120;120;1;0x"); } + } + if (buf[0]) + { vt_write (vt, buf, strlen (buf) ); } +} + +static char *charmap_cp437[]= +{ + " ","☺","☻","♥","♦","♣","♠","•","◘","○","◙","♂","♀","♪","♫","☼", + "►","◄","↕","‼","¶","§","▬","↨","↑","↓","→","←","∟","↔","▲","▼", + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/", + "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", + "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", + "p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","⌂", + "Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å", + "É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","¢","£","¥","₧","ƒ", + "á","í","ó","ú","ñ","Ñ","ª","º","¿","⌐","¬","½","¼","¡","«","»", + "░","▒","▓","│","┤","╡","╢","╖","╕","╣","║","╗","╝","╜","╛","┐", + "└","┴","┬","├","─","┼","╞","╟","╚","╔","╩","╦","╠","═","╬","╧", + "╨","╤","╥","╙","╘","╒","╓","╫","╪","┘","┌","█","▄","▌","▐","▀", + "α","ß","Γ","π","Σ","σ","µ","τ","Φ","Θ","Ω","δ","∞","φ","ε","∩", + "≡","±","≥","≤","⌠","⌡","÷","≈","°","∙","·","√","ⁿ","²","■"," " +}; + + +static char *charmap_graphics[]= +{ + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "◆","▒","␉","␌","␍","␊","°","±","","␋","┘","┐","┌","└","┼","⎺","⎻", + "─","⎼","⎽","├","┤","┴","┬","│","≤","≥","π","≠","£","·"," " +}; + +static char *charmap_uk[]= +{ + " ","!","\"","£","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", + "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," " +}; + +static char *charmap_ascii[]= +{ + " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0", + "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?", + "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_", + "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", + "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," " +}; + +static void vtcmd_justify (VT *vt, const char *sequence) +{ + int n = parse_int (vt->argument_buf, 0); + switch (n) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + vt->justify = n; + break; + default: + vt->justify = 0; + } +} + +static void vtcmd_sixel_related_req (VT *vt, const char *sequence) +{ + fprintf (stderr, "it happens!\n"); +} + +static void vtcmd_set_charmap (VT *vt, const char *sequence) +{ + int slot = 0; + int set = sequence[1]; + if (sequence[0] == ')') { slot = 1; } + if (set == 'G') { set = 'B'; } + vt->charset[slot] = set; +} +#if 0 + +CSI Pm ' } ' +Insert Ps Column (s) (default = 1) (DECIC), VT420 and up. + +CSI Pm ' ~ ' +Delete Ps Column (s) (default = 1) (DECDC), VT420 and up. + + +in text. When bracketed paste mode is set, the program will receive: +ESC [ 2 0 0 ~, + followed by the pasted text, followed by + ESC [ 2 0 1 ~ . + + + CSI I + when the terminal gains focus, and CSI O when it loses focus. +#endif + +#define COMPAT_FLAG_LEVEL_0 (1<<1) +#define COMPAT_FLAG_LEVEL_1 (1<<2) +#define COMPAT_FLAG_LEVEL_2 (1<<3) +#define COMPAT_FLAG_LEVEL_3 (1<<4) +#define COMPAT_FLAG_LEVEL_4 (1<<5) +#define COMPAT_FLAG_LEVEL_5 (1<<6) + +#define COMPAT_FLAG_LEVEL_102 (1<<7) + +#define COMPAT_FLAG_ANSI (1<<8) +#define COMPAT_FLAG_OBSOLETE (1<<9) + +#define COMPAT_FLAG_ANSI_COLOR (1<<10) +#define COMPAT_FLAG_256_COLOR (1<<11) +#define COMPAT_FLAG_24_COLOR (1<<12) + +#define COMPAT_FLAG_MOUSE_REPORT (1<<13) + +#define COMPAT_FLAG_AUDIO (1<<14) +#define COMPAT_FLAG_GRAPHICS (1<<15) + +#define ANSI COMPAT_FLAG_ANSI +#define OBS COMPAT_FLAG_OBSOLETE + +#define VT100 (COMPAT_FLAG_LEVEL_0|COMPAT_FLAG_LEVEL_1) +#define VT102 (VT100|COMPAT_FLAG_LEVEL_102) +#define VT200 (VT102|COMPAT_FLAG_LEVEL_2) +#define VT300 (VT200|COMPAT_FLAG_LEVEL_3) +#define VT400 (VT300|COMPAT_FLAG_LEVEL_4) +#define VT220 VT200 +#define VT320 VT300 + +#define XTERM (VT400|COMPAT_FLAG_24_COLOR|COMPAT_FLAG_256_COLOR|COMPAT_FLAG_ANSI_COLOR) + +#define VT2020 (XTERM|COMPAT_FLAG_GRAPHICS|COMPAT_FLAG_AUDIO) + + + static Sequence sequences[]= + { + /* + prefix suffix command */ + //{"B", 0, vtcmd_break_permitted}, + //{"C", 0, vtcmd_nobreak_here}, + {"D", 0, vtcmd_index, VT100}, /* args: id:IND Index */ + {"E", 0, vtcmd_next_line}, /* ref:none id: Next line */ + {"_", 'G', vtcmd_graphics}, + {"H", 0, vtcmd_horizontal_tab_set, VT100}, /* id:HTS Horizontal Tab Set */ + + //{"I", 0, vtcmd_char_tabulation_with_justification}, + //{"K", 0, PLD partial line down + //{"L", 0, PLU partial line up + {"M", 0, vtcmd_reverse_index, VT100}, /* ref:none id:RI Reverse Index */ + //{"N", 0, vtcmd_ignore}, /* Set Single Shift 2 - SS2*/ + //{"O", 0, vtcmd_ignore}, /* Set Single Shift 3 - SS3*/ + +#if 0 + {"[0F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY disable justification and wordwrap */ // needs special link to ANSI standard + {"[1F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY enable wordwrap */ +#endif + + /* these need to occur before vtcmd_preceding_line to have precedence */ + {"[0 F", 0, vtcmd_justify, ANSI}, + {"[1 F", 0, vtcmd_justify, ANSI}, + {"[2 F", 0, vtcmd_justify}, + {"[3 F", 0, vtcmd_justify}, + {"[4 F", 0, vtcmd_justify}, + {"[5 F", 0, vtcmd_justify}, + {"[6 F", 0, vtcmd_justify}, + {"[7 F", 0, vtcmd_justify}, + {"[8 F", 0, vtcmd_justify}, +// XXX missing DECIC DECDC insert and delete column + {"[", 'A', vtcmd_cursor_up, VT100}, /* args:Pn id:CUU Cursor Up */ + {"[", 'B', vtcmd_cursor_down, VT100}, /* args:Pn id:CUD Cursor Down */ + {"[", 'C', vtcmd_cursor_forward, VT100}, /* args:Pn id:CUF Cursor Forward */ + {"[", 'D', vtcmd_cursor_backward, VT100}, /* args:Pn id:CUB Cursor Backward */ + {"[", 'j', vtcmd_cursor_backward, ANSI}, /* args:Pn ref:none id:HPB Horizontal Position Backward */ + {"[", 'k', vtcmd_cursor_up, ANSI}, /* args:Pn ref:none id:VPB Vertical Position Backward */ + {"[", 'E', vtcmd_next_line, VT100}, /* args:Pn id:CNL Cursor Next Line */ + {"[", 'F', vtcmd_cursor_preceding_line, VT100}, /* args:Pn id:CPL Cursor Preceding Line */ + {"[", 'G', vtcmd_horizontal_position_absolute}, /* args:Pn id:CHA Cursor Horizontal Absolute */ + {"[", 'H', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:CUP Cursor Position */ + {"[", 'I', vtcmd_insert_n_tabs}, /* args:Pn id:CHT Cursor Horizontal Forward Tabulation */ + {"[", 'J', vtcmd_erase_in_display, VT100}, /* args:Ps id:ED Erase in Display */ + {"[", 'K', vtcmd_erase_in_line, VT100}, /* args:Ps id:EL Erase in Line */ + {"[", 'L', vtcmd_insert_blank_lines, VT102}, /* args:Pn id:IL Insert Line */ + {"[", 'M', vtcmd_delete_n_lines, VT102}, /* args:Pn id:DL Delete Line */ + // [ N is EA - Erase in field + // [ O is EA - Erase in area + {"[", 'P', vtcmd_delete_n_chars, VT102}, /* args:Pn id:DCH Delete Character */ + // [ Q is SEE - Set editing extent + // [ R is CPR - active cursor position report + {"[?", 'S', vtcmd_sixel_related_req}, + {"[", 'S', vtcmd_scroll_up, VT100}, /* args:Pn id:SU Scroll Up */ + {"[", 'T', vtcmd_scroll_down, VT100}, /* args:Pn id:SD Scroll Down */ + {"[",/*SP*/'U', vtcmd_set_line_home, ANSI}, /* args:PnSP id=SLH Set Line Home */ + {"[",/*SP*/'V', vtcmd_set_line_limit, ANSI},/* args:PnSP id=SLL Set Line Limit */ + // [ W is cursor tabulation control + // [ Pn Y - cursor line tabulation + // + {"[", 'X', vtcmd_erase_n_chars}, /* args:Pn id:ECH Erase Character */ + {"[", 'Z', vtcmd_rev_n_tabs}, /* args:Pn id:CBT Cursor Backward Tabulation */ + {"[", '^', vtcmd_scroll_down} , /* muphry alternate from ECMA */ + {"[", '@', vtcmd_insert_character, VT102}, /* args:Pn id:ICH Insert Character */ + + {"[", 'a', vtcmd_cursor_forward, ANSI}, /* args:Pn id:HPR Horizontal Position Relative */ + {"[", 'b', vtcmd_cursor_forward, ANSI}, /* REP previous char XXX incomplete */ + {"[", 'c', vtcmd_report}, /* ref:none id:DA args:... Device Attributes */ + {"[", 'd', vtcmd_goto_row}, /* args:Pn id:VPA Vertical Position Absolute */ + {"[", 'e', vtcmd_cursor_down}, /* args:Pn id:VPR Vertical Position Relative */ + {"[", 'f', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:HVP Cursor Position */ + {"[g", 0, vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */ + {"[0g", 0, vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */ + {"[3g", 0, vtcmd_clear_all_tabs, VT100}, /* id:TBC clear all tabs */ + {"[", 'm', vtcmd_set_graphics_rendition, VT100}, /* args:Ps;Ps;.. id:SGR Select Graphics Rendition */ + {"[", 'n', vtcmd_report, VT200}, /* id:DSR args:... CPR Cursor Position Report */ + {"[", 'r', vtcmd_set_top_and_bottom_margins, VT100}, /* args:Pt;Pb id:DECSTBM Set Top and Bottom Margins */ +#if 0 + // handled by set_left_and_right_margins - in if 0 to be documented + {"[s", 0, vtcmd_save_cursor_position, VT100}, /*ref:none id:SCP Save Cursor Position */ +#endif + {"[u", 0, vtcmd_restore_cursor_position, VT100}, /*ref:none id:RCP Restore Cursor Position */ + {"[", 's', vtcmd_set_left_and_right_margins, VT400}, /* args:Pl;Pr id:DECSLRM Set Left and Right Margins */ + {"[", '`', vtcmd_horizontal_position_absolute, ANSI}, /* args:Pn id:HPA Horizontal Position Absolute */ + + {"[", 'h', vtcmd_set_mode, VT100}, /* args:Pn[;...] id:SM Set Mode */ + {"[", 'l', vtcmd_set_mode, VT100}, /* args:Pn[;...] id:RM Reset Mode */ + {"[", 't', vtcmd_set_t}, + {"[", 'q', vtcmd_set_led, VT100}, /* args:Ps id:DECLL Load LEDs */ + {"[", 'x', vtcmd_report}, /* ref:none id:DECREQTPARM */ + {"[", 'z', vtcmd_DECELR}, /* ref:none id:DECELR set locator res */ + + {"5", 0, vtcmd_char_at_cursor, VT300}, /* ref:none id:DECXMIT */ + {"6", 0, vtcmd_back_index, VT400}, /* id:DECBI Back index (hor. scroll) */ + {"7", 0, vtcmd_save_cursor, VT100}, /* id:DECSC Save Cursor */ + {"8", 0, vtcmd_restore_cursor, VT100}, /* id:DECRC Restore Cursor */ + {"9", 0, vtcmd_forward_index, VT400}, /* id:DECFI Forward index (hor. scroll)*/ + + //{"Z", 0, vtcmd_device_attributes}, + //{"%G",0, vtcmd_set_default_font}, // set_alternate_font + + + {"(0", 0, vtcmd_set_charmap}, + {"(1", 0, vtcmd_set_charmap}, + {"(2", 0, vtcmd_set_charmap}, + {"(A", 0, vtcmd_set_charmap}, + {"(B", 0, vtcmd_set_charmap}, + {")0", 0, vtcmd_set_charmap}, + {")1", 0, vtcmd_set_charmap}, + {")2", 0, vtcmd_set_charmap}, + {")A", 0, vtcmd_set_charmap}, + {")B", 0, vtcmd_set_charmap}, + {"%G", 0, vtcmd_set_charmap}, + + {"#3", 0, vtcmd_set_double_width_double_height_top_line, VT100}, /*id:DECDHL Top half of double-width, double-height line */ + {"#4", 0, vtcmd_set_double_width_double_height_bottom_line, VT100}, /*id:DECDHL Bottom half of double-width, double-height line */ + {"#5", 0, vtcmd_set_single_width_single_height_line, VT100}, /* id:DECSWL Single-width line */ + {"#6", 0, vtcmd_set_double_width_single_height_line, VT100}, /* id:DECDWL Double-width line */ + + {"#8", 0, vtcmd_screen_alignment_display, VT100}, /* id:DECALN Screen Alignment Pattern */ + {"=", 0, vtcmd_ignore}, // keypad mode change + {">", 0, vtcmd_ignore}, // keypad mode change + {"c", 0, vtcmd_reset_to_initial_state, VT100}, /* id:RIS Reset to Initial State */ + {"[!", 'p', vtcmd_ignore}, // soft reset? + {"[", 'p', vtcmd_request_mode}, /* args:Pa$ id:DECRQM Request ANSI Mode */ +#if 0 + {"[?", 'p', vtcmd_request_mode}, /* args:Pd$ id:DECRQM Request DEC Mode */ +#endif + + {NULL, 0, NULL} + }; + + static void handle_sequence (VT *vt, const char *sequence) +{ + int i0 = strlen (sequence)-1; + int i; + vt->rev ++; + for (i = 0; sequences[i].prefix; i++) + { + if (!strncmp (sequence, sequences[i].prefix, strlen (sequences[i].prefix) ) ) + { + if (! (sequences[i].suffix && (sequence[i0] != sequences[i].suffix) ) ) + { + VT_command ("%s", sequence); + sequences[i].vtcmd (vt, sequence); + return; + } + } + } +#ifndef ASANBUILD + VT_warning ("unhandled: %c%c%c%c%c%c%c%c%c\n", sequence[0], sequence[1], sequence[2], sequence[3], sequence[4], sequence[5], sequence[6], sequence[7], sequence[8]); +#endif +} + +static void vt_line_feed (VT *vt) +{ + int was_home = vt->at_line_home; + if (vt->margin_top == 1 && vt->margin_bottom == vt->rows) + { + if (vt->lines == NULL || + (vt->lines->data == vt->current_line && vt->cursor_y != vt->rows) ) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } + if (vt->cursor_y >= vt->margin_bottom) + { + vt->cursor_y = vt->margin_bottom; + vt_scroll (vt, -1); + } + else + { + vt->cursor_y++; + } + } + else + { + if (vt->lines->data == vt->current_line && + (vt->cursor_y != vt->margin_bottom) && 0) + { + vt->current_line = vt_line_new_with_size ("", vt->cols); + ctx_list_prepend (&vt->lines, vt->current_line); + vt->line_count++; + } + vt->cursor_y++; + if (vt->cursor_y > vt->margin_bottom) + { + vt->cursor_y = vt->margin_bottom; + vt_scroll (vt, -1); + } + } + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + if (vt->cr_on_lf) + { vt_carriage_return (vt); } + vt_trimlines (vt, vt->rows); + if (was_home) + { vt_carriage_return (vt); } +} + +//#include "vt-encodings.h" + +#ifndef NO_SDL +static void vt_state_apc_audio (VT *vt, int byte) +{ + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt_audio (vt, vt->argument_buf); + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + } +} + +#else + +void vt_audio_task (VT *vt, int click) +{ +} + +void vt_bell (VT *vt) +{ +} +static void vt_state_apc_audio (VT *vt, int byte) +{ + vt->state = vt_state_apc_generic; +} + +#endif + +static void +vt_carriage_return (VT *vt) +{ + _vt_move_to (vt, vt->cursor_y, vt->cursor_x); + vt->cursor_x = VT_MARGIN_LEFT; + vt->at_line_home = 1; +} + +/* if the byte is a non-print control character, handle it and return 1 + * oterhwise return 0*/ +static int _vt_handle_control (VT *vt, int byte) +{ + /* the big difference between ANSI-BBS mode and VT100+ mode is that + * most C0 characters are printable + */ + if (CTX_UNLIKELY(vt->encoding == 1)) // this codepage is for ansi-bbs use + switch (byte) + { + case '\0': + return 1; + case 1: /* SOH start of heading */ + case 2: /* STX start of text */ + case 3: /* ETX end of text */ + case 4: /* EOT end of transmission */ + case 5: /* ENQuiry */ + case 6: /* ACKnolwedge */ + case '\v': /* VT vertical tab */ + case '\f': /* VF form feed */ + case 14: /* SO shift in - alternate charset */ + case 15: /* SI shift out - (back to normal) */ + case 16: /* DLE data link escape */ + case 17: /* DC1 device control 1 - XON */ + case 18: /* DC2 device control 2 */ + case 19: /* DC3 device control 3 - XOFF */ + case 20: /* DC4 device control 4 */ + case 21: /* NAK negative ack */ + case 22: /* SYNchronous idle */ + case 23: /* ETB end of trans. blk */ + case 24: /* CANcel (vt100 aborts sequence) */ + case 25: /* EM end of medium */ + case 26: /* SUB stitute */ + case 28: /* FS file separator */ + case 29: /* GS group separator */ + case 30: /* RS record separator */ + case 31: /* US unit separator */ + _vt_add_str (vt, charmap_cp437[byte]); + return 1; + } + switch (byte) + { + case '\0': + case 1: /* SOH start of heading */ + case 2: /* STX start of text */ + case 3: /* ETX end of text */ + case 4: /* EOT end of transmission */ + case 6: /* ACKnolwedge */ + return 1; + case 5: /* ENQuiry */ + { + const char *reply = getenv ("TERM_ENQ_REPLY"); + if (reply) + { + char *copy = strdup (reply); + for (uint8_t *c = (uint8_t *) copy; *c; c++) + { + if (*c < ' ' || * c > 127) { *c = 0; } + } + vt_write (vt, reply, strlen (reply) ); + free (copy); + } + } + return 1; + case '\a': /* BELl */ + vt_bell (vt); + return 1; + case '\b': /* BS */ + _vt_backspace (vt); + return 1; + case '\t': /* HT tab */ + _vt_htab (vt); + return 1; + case '\v': /* VT vertical tab */ + case '\f': /* VF form feed */ + case '\n': /* LF line ffed */ + vt_line_feed (vt); + return 1; + case '\r': /* CR carriage return */ + vt_carriage_return (vt); + return 1; + case 14: /* SO shift in - alternate charset */ + vt->shifted_in = 1; // XXX not in vt52 + return 1; + case 15: /* SI shift out - (back to normal) */ + vt->shifted_in = 0; + return 1; + case 16: /* DLE data link escape */ + case 17: /* DC1 device control 1 - XON */ + case 18: /* DC2 device control 2 */ + case 19: /* DC3 device control 3 - XOFF */ + case 20: /* DC4 device control 4 */ + case 21: /* NAK negative ack */ + case 22: /* SYNchronous idle */ + case 23: /* ETB end of trans. blk */ + case 24: /* CANcel (vt100 aborts sequence) */ + case 25: /* EM end of medium */ + case 26: /* SUB stitute */ + _vt_add_str (vt, "¿"); // in vt52? XXX + return 1; + case 27: /* ESCape */ + return 0; + break; + case 28: /* FS file separator */ + case 29: /* GS group separator */ + case 30: /* RS record separator */ + case 31: /* US unit separator */ + case 127: /* DEL */ + return 1; + default: + return 0; + } +} + +void vt_open_log (VT *vt, const char *path) +{ + unlink (path); + vt->log = fopen (path, "w"); +} + +/* the function shared by sixels, kitty mode and iterm2 mode for + * doing inline images. it attaches an image to the current line + */ +static void display_image (VT *vt, Image *image, + int col, + float xoffset, + float yoffset, + int rows, + int cols, + int subx, + int suby, + int subw, + int subh + ) +{ + int i = 0; + for (i = 0; vt->current_line->images[i] && i < 4; i++) + { + if (vt->current_line->image_col[i] == vt->cursor_x) + break; + } + //for (i = 0; vt->current_line->images[i] && i < 4; i++); + if (i >= 4) { i = 3; } + /* this needs a struct and dynamic allocation */ + vt->current_line->images[i] = image; + vt->current_line->image_col[i] = vt->cursor_x; + vt->current_line->image_X[i] = xoffset; + vt->current_line->image_Y[i] = yoffset; + vt->current_line->image_subx[i] = subx; + vt->current_line->image_suby[i] = suby; + vt->current_line->image_subw[i] = subw; + vt->current_line->image_subh[i] = subh; + vt->current_line->image_rows[i] = rows; + vt->current_line->image_cols[i] = cols; +} + +static int vt_gfx_pending=0; + +void vt_gfx (VT *vt, const char *command) +{ + const char *payload = NULL; + char key = 0; + int value; + int pos = 1; + if (vt->gfx.multichunk == 0) + { + memset (&vt->gfx, 0, sizeof (GfxState) ); + vt->gfx.action='t'; + vt->gfx.transmission='d'; + } + while (command[pos] != ';') + { + pos ++; // G or , + if (command[pos] == ';') { break; } + key = command[pos]; + pos++; + if (command[pos] == ';') { break; } + pos ++; // = + if (command[pos] == ';') { break; } + if (command[pos] >= '0' && command[pos] <= '9') + { value = atoi (&command[pos]); } + else + { value = command[pos]; } + while (command[pos] && + command[pos] != ',' && + command[pos] != ';') { pos++; } + switch (key) + { + case 'a': + vt->gfx.action = value; + break; + case 'd': + vt->gfx.delete = value; + break; + case 'i': + vt->gfx.id = value; + break; + case 'S': + vt->gfx.buf_size = value; + break; + case 's': + vt->gfx.buf_width = value; + break; + case 'v': + vt->gfx.buf_height = value; + break; + case 'f': + vt->gfx.format = value; + break; + case 'm': + vt->gfx.multichunk = value; + break; + case 'o': + vt->gfx.compression = value; + break; + case 't': + vt->gfx.transmission = value; + break; + case 'x': + vt->gfx.x = value; + break; + case 'y': + vt->gfx.y = value; + break; + case 'w': + vt->gfx.w = value; + break; + case 'h': + vt->gfx.h = value; + break; + case 'X': + vt->gfx.x_cell_offset = value; + break; + case 'Y': + vt->gfx.y_cell_offset = value; + break; + case 'c': + vt->gfx.columns = value; + break; + case 'r': + vt->gfx.rows = value; + break; + case 'z': + vt->gfx.z_index = value; + break; + } + } + payload = &command[pos+1]; + { + int chunk_size = strlen (payload); + int old_size = vt->gfx.data_size; + // accumulate incoming data + if (vt->gfx.data == NULL) + { + vt->gfx.data_size = chunk_size; + vt->gfx.data = malloc (vt->gfx.data_size + 1); + } + else + { + vt->gfx.data_size += chunk_size; + vt->gfx.data = realloc (vt->gfx.data, vt->gfx.data_size + 1); + } + memcpy (vt->gfx.data + old_size, payload, chunk_size); + vt->gfx.data[vt->gfx.data_size]=0; + } + if (vt->gfx.multichunk == 0) + { + if (vt->gfx.transmission != 'd') /* */ + { + char buf[256]; + sprintf (buf, "\033_Gi=%i;only direct transmission supported\033\\", + vt->gfx.id); + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + { + int bin_length = vt->gfx.data_size; + uint8_t *data2 = malloc (vt->gfx.data_size); + bin_length = ctx_base642bin ( (char *) vt->gfx.data, + &bin_length, + data2); + memcpy (vt->gfx.data, data2, bin_length + 1); + vt->gfx.data_size = bin_length; + free (data2); + } + if (vt->gfx.buf_width) + { + // implicit buf_size + vt->gfx.buf_size = vt->gfx.buf_width * vt->gfx.buf_height * + (vt->gfx.format == 24 ? 3 : 4); + } + if (vt->gfx.compression == 'z') + { + //vt->gfx.buf_size) + unsigned char *data2 = malloc (vt->gfx.buf_size + 1); + /* if a buf size is set (rather compression, but + * this works first..) then */ + unsigned long actual_uncompressed_size = vt->gfx.buf_size; + int z_result = uncompress (data2, &actual_uncompressed_size, + vt->gfx.data, + vt->gfx.data_size); + if (z_result != Z_OK) + { + char buf[256]= "\033_Go=z;zlib error\033\\"; + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + free (vt->gfx.data); + vt->gfx.data = data2; + vt->gfx.data_size = actual_uncompressed_size; + vt->gfx.compression = 0; + } + if (vt->gfx.format == 100) + { + int channels; + uint8_t *new_data = stbi_load_from_memory (vt->gfx.data, vt->gfx.data_size, &vt->gfx.buf_width, &vt->gfx.buf_height, &channels, 4); + if (!new_data) + { + char buf[256]= "\033_Gf=100;image decode error\033\\"; + vt_write (vt, buf, strlen (buf) ); + goto cleanup; + } + vt->gfx.format = 32; + free (vt->gfx.data); + vt->gfx.data = new_data; + vt->gfx.data_size= vt->gfx.buf_width * vt->gfx.buf_height * 4; + } + Image *image = NULL; + switch (vt->gfx.action) + { + case 't': // transfer + case 'T': // transfer and present + switch (vt->gfx.format) + { + case 24: + case 32: + image = image_add (vt->gfx.buf_width, vt->gfx.buf_height, vt->gfx.id, + vt->gfx.format, vt->gfx.data_size, vt->gfx.data); + vt->gfx.data = NULL; + vt->gfx.data_size=0; + break; + } + if (vt->gfx.action == 't') + { break; } + // fallthrough + case 'p': // present + if (!image && vt->gfx.id) + { image = image_query (vt->gfx.id); } + if (image) + { + display_image (vt, image, vt->cursor_x, vt->gfx.rows, vt->gfx.columns, + vt->gfx.x_cell_offset * 1.0 / vt->cw, + vt->gfx.y_cell_offset * 1.0 / vt->ch, + vt->gfx.x, + vt->gfx.y, + vt->gfx.w, + vt->gfx.h); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + } + break; + case 'q': // query + if (image_query (vt->gfx.id) ) + { + char buf[256]; + sprintf (buf, "\033_Gi=%i;OK\033\\", vt->gfx.id); + vt_write (vt, buf, strlen (buf) ); + } + break; + case 'd': // delete + { + int row = vt->rows; // probably not right at start of session XXX + for (CtxList *l = vt->lines; l; l = l->next, row --) + { + VtLine *line = l->data; + for (int i = 0; i < 4; i ++) + { + int free_resource = 0; + int match = 0; + if (line->images[i]) + switch (vt->gfx.delete) + { + case 'A': + free_resource = 1; + /* FALLTHROUGH */ + case 'a': /* all images visible on screen */ + match = 1; + break; + case 'I': + free_resource = 1; + /* FALLTHROUGH */ + case 'i': /* all images with specified id */ + if ( ( (Image *) (line->images[i]) )->id == vt->gfx.id) + { match = 1; } + break; + case 'P': + free_resource = 1; + /* FALLTHROUGH */ + case 'p': /* all images intersecting cell + specified with x and y */ + if (line->image_col[i] == vt->gfx.x && + row == vt->gfx.y) + { match = 1; } + break; + case 'Q': + free_resource = 1; + /* FALLTHROUGH */ + case 'q': /* all images with specified cell (x), row(y) and z */ + if (line->image_col[i] == vt->gfx.x && + row == vt->gfx.y) + { match = 1; } + break; + case 'Y': + free_resource = 1; + /* FALLTHROUGH */ + case 'y': /* all images with specified row (y) */ + if (row == vt->gfx.y) + { match = 1; } + break; + case 'X': + free_resource = 1; + /* FALLTHROUGH */ + case 'x': /* all images with specified column (x) */ + if (line->image_col[i] == vt->gfx.x) + { match = 1; } + break; + case 'Z': + free_resource = 1; + /* FALLTHROUGH */ + case 'z': /* all images with specified z-index (z) */ + break; + } + if (match) + { + line->images[i] = NULL; + if (free_resource) + { + // XXX : NYI + } + } + } + } + } + break; + } +cleanup: + if (vt->gfx.data) + { free (vt->gfx.data); } + vt->gfx.data = NULL; + vt->gfx.data_size=0; + vt->gfx.multichunk=0; + vt_gfx_pending = 0; + } + else + vt_gfx_pending = 1; +} + +static void vt_state_vt52 (VT *vt, int byte) +{ + /* in vt52 mode, utf8_pos being non 0 means we got ESC prior */ + switch (vt->utf8_pos) + { + case 0: + if (_vt_handle_control (vt, byte) == 0) + switch (byte) + { + case 27: /* ESC */ + vt->utf8_pos = 1; + break; + default: + { + char str[2] = {byte & 127, 0}; + /* we're not validating utf8, and our utf8 manipulation + * functions are not robust against malformed utf8, + * hence we strip to ascii + */ + _vt_add_str (vt, str); + } + break; + } + break; + case 1: + vt->utf8_pos = 0; + switch (byte) + { + case 'A': + vtcmd_cursor_up (vt, " "); + break; + case 'B': + vtcmd_cursor_down (vt, " "); + break; + case 'C': + vtcmd_cursor_forward (vt, " "); + break; + case 'D': + vtcmd_cursor_backward (vt, " "); + break; + case 'F': + vtcmd_set_alternate_font (vt, " "); + break; + case 'G': + vtcmd_set_default_font (vt, " "); + break; + case 'H': + _vt_move_to (vt, 1, 1); + break; + case 'I': + vtcmd_reverse_index (vt, " "); + break; + case 'J': + vtcmd_erase_in_display (vt, "[0J"); + break; + case 'K': + vtcmd_erase_in_line (vt, "[0K"); + break; + case 'Y': + vt->utf8_pos = 2; + break; + case 'Z': + vt_write (vt, "\033/Z", 3); + break; + case '<': + vt->state = vt_state_neutral; + break; + default: + break; + } + break; + case 2: + _vt_move_to (vt, byte - 31, vt->cursor_x); + vt->utf8_pos = 3; + break; + case 3: + _vt_move_to (vt, vt->cursor_y, byte - 31); + vt->utf8_pos = 0; + break; + } +} + +static void vt_sixels (VT *vt, const char *sixels) +{ + uint8_t colors[256][3]; + int width = 0; + int height = 0; + int x = 0; + int y = 0; + Image *image; + uint8_t *pixels = NULL; + int repeat = 1; + const char *p = sixels; + int pal_no = 0; +#if 0 + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) ); + // should be 0 + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) ); + // if 1 then transparency is enabled - otherwise use bg color + for (; *p && *p != 'q'; p++); +#endif + //for (; *p && *p != '"'; p++); + while (*p && *p != 'q') { p++; } + if (*p == 'q') { p++; } + if (*p == '"') { p++; } + //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p)); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p)); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + width = atoi (p); + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + height = atoi (p); + if (width * height > 2048 * 2048) + return; + if (width <= 0 || height <=0) + { + width = 0; + height = 0; + // XXX : a copy paste dry-run + for (const char *t=p; *t; t++) + { + if (*t == '#') + { + t++; + while (*t && *t >= '0' && *t <= '9') { t++; } + if (*t == ';') + { + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + for (; *t && *t != ';'; t++); + if (*t == ';') { t ++; } + while (*t && *t >= '0' && *t <= '9') { t++; } + t--; + } + else + { + t--; + } + } + else if (*t == '$') // carriage return + { + if (x > width) { width = x; } + x = 0; + } + else if (*t == '-') // line feed + { + y += 6; + x = 0; + } + else if (*t == '!') // repeat + { + t++; + repeat = atoi (t); + while (*t && *t >= '0' && *t <= '9') { t++; } + t--; + } + else if (*t >= '?' && *t <= '~') /* sixel data */ + { + x += repeat; + repeat = 1; + } + } + height = y; + } + x = 0; + y = 0; + pixels = calloc (width * (height + 6), 4); + image = image_add (width, height, 0, + 32, width*height*4, pixels); + uint8_t *dst = pixels; + for (; *p; p++) + { + if (*p == '#') + { + p++; + pal_no = atoi (p); + if (pal_no < 0 || pal_no > 255) { pal_no = 255; } + while (*p && *p >= '0' && *p <= '9') { p++; } + if (*p == ';') + { + /* define a palette */ + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + // color_model , 2 is rgb + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][0] = atoi (p) * 255 / 100; + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][1] = atoi (p) * 255 / 100; + for (; *p && *p != ';'; p++); + if (*p == ';') { p ++; } + colors[pal_no][2] = atoi (p) * 255 / 100; + while (*p && *p >= '0' && *p <= '9') { p++; } + p--; + } + else + { + p--; + } + } + else if (*p == '$') // carriage return + { + x = 0; + dst = &pixels[ (4 * width * y)]; + } + else if (*p == '-') // line feed + { + y += 6; + x = 0; + dst = &pixels[ (4 * width * y)]; + } + else if (*p == '!') // repeat + { + p++; + repeat = atoi (p); + while (*p && *p >= '0' && *p <= '9') { p++; } + p--; + } + else if (*p >= '?' && *p <= '~') /* sixel data */ + { + int sixel = (*p) - '?'; + if (x + repeat <= width && y < height) + { + for (int bit = 0; bit < 6; bit ++) + { + if (sixel & (1 << bit) ) + { + for (int u = 0; u < repeat; u++) + { + for (int c = 0; c < 3; c++) + { + dst[ (bit * width * 4) + u * 4 + c] = colors[pal_no][c]; + dst[ (bit * width * 4) + u * 4 + 3] = 255; + } + } + } + } + } + x += repeat; + dst += (repeat * 4); + repeat = 1; + } + } + if (image) + { + display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + vt_line_feed (vt); + vt_carriage_return (vt); + } + vt->rev++; +} + +static inline void vt_ctx_unrled (VT *vt, char byte) +{ +#if CTX_VT_USE_FRAMEDIFF + ctx_string_append_byte (vt->current_line->frame, byte); +#endif + ctx_parser_feed_byte (vt->ctxp, byte); +} + +static void vt_state_ctx (VT *vt, int byte) +{ +#if 0 + //fprintf (stderr, "%c", byte); + if (CTX_UNLIKELY(byte == CTX_CODEC_CHAR)) + { + if (CTX_UNLIKELY(vt->in_prev_match)) + { + char *prev = vt->current_line->prev; + int prev_length = vt->current_line->prev_length; + int start = atoi (vt->reference); + int len = 0; + if (strchr (vt->reference, ' ')) + len = atoi (strchr (vt->reference, ' ')+1); + + //fprintf (stderr, "%i-%i:", start, len); + + if (start < 0) start = 0; + if (start >= (prev_length))start = prev_length-1; + if (len + start > prev_length) + len = prev_length - start; + + //fprintf (stderr, "%i-%i\n", start, len); + + if (CTX_UNLIKELY (start == 0 && len == 0)) + { + vt_ctx_unrled (vt, CTX_CODEC_CHAR); + } + else + { + if (prev) + for (int i = 0; i < len && start + i < prev_length; i++) + { + vt_ctx_unrled (vt, prev[start + i]); + } + } + vt->ref_len = 0; + vt->reference[0]=0; + vt->in_prev_match = 0; + } + else + { + vt->reference[0]=0; + vt->ref_len = 0; + vt->in_prev_match = 1; + } + } + else + { + if (CTX_UNLIKELY(vt->in_prev_match)) + { + if (vt->ref_len < 15) + { + vt->reference[vt->ref_len++]=byte; + vt->reference[vt->ref_len]=0; + } + } + else + { + vt_ctx_unrled (vt, byte); + } + } +#else + vt_ctx_unrled (vt, byte); +#endif +} + +static int vt_decoder_feed (VT *vt, int byte) +{ + int encoding = vt->encoding; + switch (encoding) + { + case 0: /* utf8 */ + if (!vt->utf8_expected_bytes) + { + vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1; + vt->utf8_pos = 0; + } + if (vt->utf8_expected_bytes) + { + vt->utf8_holding[vt->utf8_pos++] = byte; + vt->utf8_holding[vt->utf8_pos] = 0; + if (vt->utf8_pos == vt->utf8_expected_bytes + 1) + { + vt->utf8_expected_bytes = 0; + vt->utf8_pos = 0; + } + else + { + return 1; + } + } + else + { + vt->utf8_holding[0] = byte; + vt->utf8_holding[0] &= 127; + vt->utf8_holding[1] = 0; + if (vt->utf8_holding[0] == 0) + { vt->utf8_holding[0] = 32; } + } + break; + case 1: + if ( ! (byte>=0 && byte < 256) ) + { byte = 255; } + strcpy ( (char *) &vt->utf8_holding[0], &charmap_cp437[byte][0]); + vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1; // ? + break; + default: + vt->utf8_holding[0] = byte & 127; + vt->utf8_holding[1] = 0; + break; + } + return 0; +} + +static void vt_state_swallow (VT *vt, int byte) +{ + vt->state = vt_state_neutral; +} + +static int vt_decode_hex_digit (char digit) +{ + if (digit >= '0' && digit <='9') + return digit - '0'; + if (digit >= 'a' && digit <='f') + return digit - 'a' + 10; + if (digit >= 'A' && digit <='F') + return digit - 'A' + 10; + return 0; +} + +static int vt_decode_hex (const char *two_digits) +{ + return vt_decode_hex_digit (two_digits[0]) * 16 + + vt_decode_hex_digit (two_digits[1]); +} + +static uint8_t palettes[][16][3]= +{ + { +{0, 0, 0}, +{160, 41, 41}, +{74, 160, 139}, +{135, 132, 83}, +{36, 36, 237}, +{171, 74, 223}, +{59, 107, 177}, +{195, 195, 195}, +{111, 111, 111}, +{237, 172, 130}, +{153, 237, 186}, +{233, 216, 8}, +{130, 180, 237}, +{214, 111, 237}, +{29, 225, 237}, +{255, 255, 255}, + + }, + + { + {0, 0, 0}, + {127, 0, 0}, + {90, 209, 88}, + {136, 109, 0}, + {3, 9, 235}, + {90, 4, 150}, + {43, 111, 150}, + {178, 178, 178}, + {87, 87, 87}, + {193, 122, 99}, + {110, 254, 174}, + {255, 200, 0}, + {10, 126, 254}, + {146, 155, 249}, + {184, 208, 254}, + {255, 255, 255}, + + },{ + {0, 0, 0}, + {147, 53, 38}, + {30, 171, 82}, + {188, 153, 0}, + {32, 71, 193}, + {236, 49, 188}, + {42, 182, 253}, + {149, 149, 149}, + {73, 73, 73}, + {210, 36, 0}, + {96, 239, 97}, + {247, 240, 2}, + {93, 11, 249}, + {222, 42, 255}, + {11, 227, 255}, + {233, 235, 235}, + }, + + + { {0, 0, 0},{97, 27, 0},{129, 180, 0},{127, 100, 0},{44, 15, 255},{135, 10, 167},{20, 133, 164},{174, 174, 174},{71, 71, 71},{167, 114, 90},{162, 214, 127},{255, 251, 83},{118, 77, 253},{192, 121, 255},{14, 217, 255},{255, 255, 255}, + },{ + + +#if 0 + { + {0, 0, 0}, + {144, 0, 0}, + {9, 154, 9}, + {255, 137, 113}, + {3, 0, 255}, + {56, 0, 132}, + {0, 131, 131}, + {204, 204, 204}, + {127, 127, 127}, + {255, 33, 0}, + {113, 255, 88}, + {255, 236, 8}, + {1, 122, 255}, + {235, 0, 222}, + {0, 217, 255}, + {255, 255, 255}, + },{ +#endif + + + {0, 0, 0}, + {139, 0, 0}, + {9, 154, 9}, + {255, 137, 113}, + {3, 0, 255}, + {56, 0, 132}, + {0, 111, 111}, + {204, 204, 204}, + {127, 127, 127}, + {255, 33, 0}, + {118, 255, 92}, + {255, 230, 15}, + {1, 122, 255}, + {232, 0, 220}, + {1, 217, 255}, + {255, 255, 255}, + }, + { + + {0, 0, 0}, + {191, 0, 0}, + {3, 187, 0}, + {254, 212, 0}, + {0, 0, 255}, + {80, 0, 128}, + {0, 156, 255}, + {166, 166, 166}, + {84, 84, 84}, + {255, 62, 0}, + {85, 255, 143}, + {255, 255, 0}, + {67, 80, 255}, + {243, 70, 255}, + {30, 255, 222}, + {255, 255, 255}, + }, + { + /* */ + { 32, 32, 32}, // 0 - background (black) + {165, 15, 21}, // 1 red + { 95,130, 10}, // 2 green + {205,145, 60}, // 3 yellow + { 49,130,189}, // 4 blue + {120, 40,160}, // 5 magenta + {120,230,230}, // 6 cyan + {196,196,196},// 7 light-gray + { 85, 85, 85},// 8 dark gray + + {251,106, 74},// 9 light red + {130,215,140},// 10 light green + {255,255, 0},// 11 light yellow + {107,174,214},// 12 light blue + {215,130,160},// 13 light magenta + {225,255,245},// 14 light cyan + {255,255,255},// 15 - foreground (white) + },{ + /* */ + { 32, 32, 32}, // 0 - background (black) + {160, 0, 0}, // 1 red + { 9,233, 0}, // 2 green + {220,110, 44}, // 3 yellow + { 0, 0,200}, // 4 blue + { 90, 0,130}, // 5 magenta + { 0,156,180}, // 6 cyan + {196,196,196}, // 7 light-gray + { 85, 85, 85}, // 8 dark gray + + {240, 60, 40}, // 9 light red + {170,240, 80}, // 10 light green + {248,248, 0}, // 11 light yellow + { 0, 40,255}, // 12 light blue + {204, 62,214}, // 13 light magenta + { 10,234,254}, // 14 light cyan + {255,255,255}, // 15 - foreground (white) + }, + /* inspired by DEC */ + { { 0, 0, 0}, // 0 - background black + {150, 10, 10}, // 1 red + { 21,133, 0}, // 2 green + + {103,103, 24}, // 3 yellow + { 44, 44,153}, // 4 blue + {123, 94,183}, // 5 magenta + + { 20,183,193}, // 6 cyan + + {177,177,177},// 7 light-gray + {100,100,100},// 8 dark gray + + {244, 39, 39},// 9 light red + { 61,224, 81},// 10 light green + {255,255, 0},// 11 light yellow + { 61, 61,244},// 12 light blue + {240, 11,240},// 13 light magenta + { 61,234,234},// 14 light cyan + + {255,255,255},// 15 - foreground white + }, +}; + +static void vt_state_osc (VT *vt, int byte) +{ + // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html + // and in "\033\" rather than just "\033", this would cause + // a stray char + //if (byte == '\a' || byte == 27 || byte == 0 || byte < 32) + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + int n = parse_int (vt->argument_buf, 0); + switch (n) + { + case 0: + case 1: + case 2: +#if 0 + {"]0;New_title\e\", 0, , }, /* id: set window title */ " +#endif + vt_set_title (vt, vt->argument_buf + 3); + break; + case 4: // set palette entry + { + int color_no = parse_int (vt->argument_buf + 2, 0); + char *rest = vt->argument_buf + 3; + rest = strchr (rest, ';'); + + if (rest++) + if (strlen(rest)>10 && + rest[0] == 'r' && + rest[1] == 'g' && + rest[2] == 'b' && + rest[3] == ':' && + rest[6] == '/' && + rest[9] == '/') + { + int red = vt_decode_hex (&rest[4]); + int green = vt_decode_hex (&rest[7]); + int blue = vt_decode_hex (&rest[10]); + // fprintf (stderr, "set color:%i %i %i %i\n", color_no, red, green, blue); + if (color_no >= 0 && color_no <= 15) + { + palettes[0][color_no][0]=red; + palettes[0][color_no][1]=green; + palettes[0][color_no][2]=blue; + } + } + } + break; + case 12: // text cursor color + break; + case 17: // highlight color + break; + case 19: // ?? + break; + + case 10: // text fg +#if 0 +#if 0 + {"]11;", 0, , }, /* id: set foreground color */ +#endif + { + /* request current foreground color, xterm does this to + determine if it can use 256 colors, when this test fails, + it still mixes in color 130 together with stock colors + */ + char buf[128]; + sprintf (buf, "\033]10;rgb:%2x/%2x/%2x\033\\", + vt->fg_color[0], vt->fg_color[1], vt->fg_color[2]); + vt_write (vt, buf, strlen (buf) ); + } +#endif + break; + case 11: // text bg +#if 0 + {"]11;", 0, , }, /* id: get background color */ + { + /* get background color */ + char buf[128]; + sprintf (buf, "\033]11;rgb:%2x/%2x/%2x\033\\", + vt->bg_color[0], vt->bg_color[1], vt->bg_color[2]); + vt_write (vt, buf, strlen (buf) ); + } +#endif + break; +#if 0 + {"]1337;key=value:base64data\b\", 0, vtcmd_erase_in_line, VT100}, /* args:keyvalue id: iterm2 graphics */ " +#endif + case 1337: + if (!strncmp (&vt->argument_buf[6], "File=", 5) ) + { + { + /* iTerm2 image protocol */ + int width = 0; + int height = 0; + int file_size = 0; + int show_inline = 0; + int preserve_aspect = 1; + char *name = NULL; + char *p = &vt->argument_buf[11]; + char key[128]=""; + char value[128]=""; + int in_key=1; + if (preserve_aspect) {}; /* XXX : NYI */ + for (; *p && *p!=':'; p++) + { + if (in_key) + { + if (*p == '=') + { in_key = 0; } + else + { + if (strlen (key) < 124) + { + key[strlen (key)+1] = 0; + key[strlen (key)] = *p; + } + } + } + else + { + if (*p == ';') + { + if (!strcmp (key, "name") ) + { + name = strdup (value); + } + else if (!strcmp (key, "width") ) + { + width = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + width = width / 100.0 * (vt->cw * vt->cols); + } + else + { /* chars */ width = width * vt->cw; } + } + else if (!strcmp (key, "height") ) + { + height = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + height = height / 100.0 * (vt->ch * vt->rows); + } + else + { /* chars */ height = height * vt->ch; } + } + else if (!strcmp (key, "preserveAspectRatio") ) + { + preserve_aspect = atoi (value); + } + else if (!strcmp (key, "inline") ) + { + show_inline = atoi (value); + } + key[0]=0; + value[0]=0; + in_key = 1; + } + else + { + if (strlen (value) < 124) + { + value[strlen (value)+1] = 0; + value[strlen (value)] = *p; + } + } + } + } + if (key[0]) + { + // code-dup + if (!strcmp (key, "name") ) + { + name = strdup (value); + } + else if (!strcmp (key, "width") ) + { + width = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + width = width / 100.0 * (vt->cw * vt->cols); + } + else + { /* chars */ width = width * vt->cw; } + } + else if (!strcmp (key, "height") ) + { + height = atoi (value); + if (strchr (value, 'x') ) + { /* pixels */ } + else if (strchr (value, '%') ) + { + /* percent */ + height = height / 100.0 * (vt->ch * vt->rows); + } + else + { /* chars */ height = height * vt->ch; } + } + else if (!strcmp (key, "preserveAspectRatio") ) + { + preserve_aspect = atoi (value); + } + else if (!strcmp (key, "inline") ) + { + show_inline = atoi (value); + } + } + if (*p == ':') + { + p++; + } + if (0) + fprintf (stderr, "%s %i %i %i %i{%s\n", name?name:"", + width, height, file_size, show_inline, + p); + Image *image = NULL; + { + int bin_length = vt->argument_buf_len; + uint8_t *data2 = malloc (bin_length); + bin_length = ctx_base642bin ( (char *) p, + &bin_length, + data2); + int channels = 4; + int buf_width = 0; + int buf_height = 0; + uint8_t *new_data = stbi_load_from_memory (data2, bin_length, &buf_width, &buf_height, &channels, 4); + free (data2); + if (new_data) + { + image = image_add (buf_width, buf_height, 0, + 32, buf_width*buf_height*4, new_data); + } + else + { + fprintf (stderr, "image decoding problem %s\n", stbi_failure_reason()); + fprintf (stderr, "len: %i\n", bin_length); + } + } + if (image) + { + display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0); + int right = (image->width + (vt->cw-1) ) /vt->cw; + int down = (image->height + (vt->ch-1) ) /vt->ch; + for (int i = 0; i<down - 1; i++) + { vtcmd_index (vt, " "); } + for (int i = 0; i<right; i++) + { vtcmd_cursor_forward (vt, " "); } + } + } + } + break; + case 104: + break; + case 8: + fprintf (stderr, "unhandled OSC 8, hyperlink\n"); + break; + default: + fprintf (stderr, "unhandled OSC %i\n", n); + break; + } + if (byte == 27) + { + vt->state = vt_state_swallow; + } + else + { + vt->state = vt_state_neutral; + } + } + else + { + vt_argument_buf_add (vt, byte); + } +} + + +static void vt_state_sixel (VT *vt, int byte) +{ + // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html + // and in "\033\" rather than just "\033", this would cause + // a stray char + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt_sixels (vt, vt->argument_buf); + if (byte == 27) + { + vt->state = vt_state_swallow; + } + else + { + vt->state = vt_state_neutral; + } + } + else + { + vt_argument_buf_add (vt, byte); + //fprintf (stderr, "\r%i ", vt->argument_buf_len); + } +} + +//void add_tab (Ctx *ctx, const char *commandline, int can_launch); +//void vt_screenshot (const char *output_path); + +static void vt_state_apc_generic (VT *vt, int byte) +{ + if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + if (vt->argument_buf[1] == 'G') /* graphics - from kitty */ + { + vt_gfx (vt, vt->argument_buf); + } + else if (vt->argument_buf[1] == 'C') /* launch command */ + { + if (vt->can_launch) + { + int can_launch = 0; + int no_title = 0; + int no_move = 0; + int no_resize = 0; + int layer = 0; + // escape subsequent arguments so that we dont have to pass a string? + float x = -1.0; + float y = -1.0; + int z = 0; + float width = -1.0; + float height = -1.0; + + for (int i=2; vt->argument_buf[i]; i++) + { + if (!strncmp (&vt->argument_buf[i], "can_launch=1", strlen ("can_launch=1"))) + can_launch = 1; + if (!strncmp (&vt->argument_buf[i], "no_title=1", strlen("no_title=1"))) + no_title = 1; + if (!strncmp (&vt->argument_buf[i], "no_move=1", strlen("no_move=1"))) + no_move = 1; + else if (!strncmp (&vt->argument_buf[i], "z=", 2)) + z=atoi(&vt->argument_buf[i]+strlen("z=")); + else if (!strncmp (&vt->argument_buf[i], "x=", 2)) + x=atof(&vt->argument_buf[i]+strlen("x=")); + else if (!strncmp (&vt->argument_buf[i], "y=", 2)) + y=atof(&vt->argument_buf[i]+strlen("y=")); + else if (!strncmp (&vt->argument_buf[i], "width=", 6)) + width=atof(&vt->argument_buf[i]+strlen("width=")); + else if (!strncmp (&vt->argument_buf[i], "height=", 7)) + height=atof(&vt->argument_buf[i]+strlen("height=")); + } + + if (width + no_resize + layer + height + x + y + no_title + no_move + z + can_launch) {}; + + char *sep = strchr(vt->argument_buf, ';'); + if (sep) + { + //fprintf (stderr, "[%s]", sep + 1); + if (!strncmp (sep + 1, "fbsave", 6)) + { + // vt_screenshot (sep + 8); + } + else + { + // add_tab (ctx, sep + 1, can_launch); + } + } + } + + } + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + } +} + +#if 0 + {"_G..\e\", 0, vtcmd_delete_n_chars, VT102}, /* ref:none id: <a href='https://sw.kovidgoyal.net/kitty/graphics-protocol.html'>kitty graphics</a> */ " + {"_A..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id: <a href='https://github.com/hodefoting/atty/'>atty</a> audio input/output */ " + {"_C..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id: run command */ " +#endif +static void vt_state_apc (VT *vt, int byte) +{ + if (byte == 'A') + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_apc_audio; + } + else if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) ) + { + vt->state = ( (byte == 27) ? vt_state_swallow : vt_state_neutral); + } + else + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_apc_generic; + } +} + +static void vt_state_esc_foo (VT *vt, int byte) +{ + vt_argument_buf_add (vt, byte); + vt->state = vt_state_neutral; + handle_sequence (vt, vt->argument_buf); +} + +static void vt_state_esc_sequence (VT *vt, int byte) +{ + if (_vt_handle_control (vt, byte) == 0) + { + if (byte == 27) + { + } + else if (byte >= '@' && byte <= '~') + { + vt_argument_buf_add (vt, byte); + vt->state = vt_state_neutral; + handle_sequence (vt, vt->argument_buf); + } + else + { + vt_argument_buf_add (vt, byte); + } + } +} + +static void vt_state_esc (VT *vt, int byte) +{ + if (_vt_handle_control (vt, byte) == 0) + switch (byte) + { + case 27: /* ESCape */ + break; + case ')': + case '#': + case '(': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_esc_foo; + } + break; + case '[': + case '%': + case '+': + case '*': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_esc_sequence; + } + break; + +#if 0 + {"Psixel_data\e\", 0, , }, /* id: sixels */ " +#endif + + case 'P': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_sixel; + } + break; + case ']': + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_osc; + } + break; + case '^': // privacy message + case '_': // APC + case 'X': // SOS + { + char tmp[]= {byte, '\0'}; + vt_argument_buf_reset (vt, tmp); + vt->state = vt_state_apc; + } + break; + default: + { + char tmp[]= {byte, '\0'}; + tmp[0]=byte; + vt->state = vt_state_neutral; + handle_sequence (vt, tmp); + } + break; + } +} + +static void vt_state_neutral (VT *vt, int byte) +{ + if (CTX_UNLIKELY(_vt_handle_control (vt, byte) != 0)) + return; + if (CTX_LIKELY(byte != 27)) + { + if (vt_decoder_feed (vt, byte) ) + return; + if (vt->charset[vt->shifted_in] != 0 && + vt->charset[vt->shifted_in] != 'B') + { + char **charmap; + switch (vt->charset[vt->shifted_in]) + { + case 'A': + charmap = charmap_uk; + break; + case 'B': + charmap = charmap_ascii; + break; + case '0': + charmap = charmap_graphics; + break; + case '1': + charmap = charmap_cp437; + break; + case '2': + charmap = charmap_graphics; + break; + default: + charmap = charmap_ascii; + break; + } + if ( (vt->utf8_holding[0] >= ' ') && (vt->utf8_holding[0] <= '~') ) + { + _vt_add_str (vt, charmap[vt->utf8_holding[0]-' ']); + } + } + else + { + // ensure vt->utf8_holding contains a valid utf8 + uint32_t codepoint; + uint32_t state = 0; + for (int i = 0; vt->utf8_holding[i]; i++) + { utf8_decode (&state, &codepoint, vt->utf8_holding[i]); } + if (state != UTF8_ACCEPT) + { + /* otherwise mangle it so that it does */ + vt->utf8_holding[0] &= 127; + vt->utf8_holding[1] = 0; + if (vt->utf8_holding[0] == 0) + { vt->utf8_holding[0] = 32; } + } + _vt_add_str (vt, (char *) vt->utf8_holding); + } + } + else // ESCape + { + vt->state = vt_state_esc; + } +} + +int vt_poll (VT *vt, int timeout) +{ + if (!vt) return 0; + int read_size = sizeof (vt->buf); + int got_data = 0; + + // read_size 1m1.142s + // read_size*10 52s + // read_size*5 53.8s + // read_size*4 53.78s + // read_size*3 .....s + // read_size*2 56.99s + int remaining_chars = read_size * 3;// * 100; + int len = 0; + vt_audio_task (vt, 0); +#if 1 + if (vt->cursor_visible && vt->smooth_scroll) + { + remaining_chars = vt->cols / 2; + } +#endif + read_size = MIN (read_size, remaining_chars); + long start_ticks = ctx_ticks (); + long ticks = start_ticks; + while (remaining_chars > 0 && + vt_waitdata (vt, 0) && + ( ticks - start_ticks < timeout || vt->state == vt_state_ctx)) + { + if (vt->in_smooth_scroll) + { + remaining_chars = 1; + // XXX : need a bail condition - + // /// so that we can stop accepting data until autowrap or similar + } + len = vt_read (vt, vt->buf, read_size); + if (len >0) + { + // fwrite (vt->buf, len, 1, vt->log); + // fwrite (vt->buf, len, 1, stdout); + } + for (int i = 0; i < len; i++) + { vt->state (vt, vt->buf[i]); } + // XXX allow state to break out in ctx mode on flush + got_data+=len; + remaining_chars -= len; + if (vt->state == vt_state_ctx) { + if (remaining_chars < read_size) + { + remaining_chars = read_size * 2; + } + } + vt_audio_task (vt, 0); + ticks = ctx_ticks (); + } + if (got_data < 0) + { + if (kill (vt->vtpty.pid, 0) != 0) + { + vt->vtpty.done = 1; + } + } + return got_data; +} + +/******/ + +static const char *keymap_vt52[][2]= +{ + {"up", "\033A" }, + {"down", "\033B" }, + {"right", "\033C" }, + {"left", "\033D" }, +}; + +static const char *keymap_application[][2]= +{ + {"up", "\033OA" }, + {"down", "\033OB" }, + {"right", "\033OC" }, + {"left", "\033OD" }, +}; + +static const char *keymap_general[][2]= +{ + {"up", "\033[A"}, + {"down", "\033[B"}, + {"right", "\033[C"}, + {"left", "\033[D"}, + {"end", "\033[F"}, + {"home", "\033[H"}, + {"shift-up", "\033[1;2A"}, + {"shift-down", "\033[1;2B"}, + {"shift-right", "\033[1;2C"}, + {"shift-left", "\033[1;2D"}, + {"alt-a", "\033a"}, + {"alt-b", "\033b"}, + {"alt-c", "\033c"}, + {"alt-d", "\033d"}, + {"alt-e", "\033e"}, + {"alt-f", "\033f"}, + {"alt-g", "\033g"}, + {"alt-h", "\033h"}, + {"alt-i", "\033i"}, + {"alt-j", "\033j"}, + {"alt-k", "\033k"}, + {"alt-l", "\033l"}, + {"alt-m", "\033m"}, + {"alt-n", "\033n"}, + {"alt-o", "\033o"}, + {"alt-p", "\033p"}, + {"alt-q", "\033q"}, + {"alt-r", "\033r"}, + {"alt-s", "\033s"}, + {"alt-t", "\033t"}, + {"alt-u", "\033u"}, + {"alt-v", "\033v"}, + {"alt-w", "\033w"}, + {"alt-x", "\033x"}, + {"alt-y", "\033y"}, + {"alt-z", "\033z"}, + {"alt- ", "\033 "}, + {"alt-space", "\033 "}, + {"alt-0", "\0330"}, + {"alt-1", "\0331"}, + {"alt-2", "\0332"}, + {"alt-3", "\0333"}, + {"alt-4", "\0334"}, + {"alt-5", "\0335"}, + {"alt-6", "\0336"}, + {"alt-7", "\0337"}, + {"alt-8", "\0338"}, + {"alt-9", "\0339"}, + {"alt-return", "\033\r"}, + {"alt-backspace", "\033\177"}, + {"alt-up", "\033[1;3A"}, + {"alt-down", "\033[1;3B"}, + {"alt-right", "\033[1;3C"}, + {"alt-left", "\033[1;3D"}, + {"shift-alt-up", "\033[1;4A"}, + {"shift-alt-down", "\033[1;4B"}, + {"shift-alt-right","\033[1;4C"}, + {"shift-alt-left", "\033[1;4D"}, + {"control-space", "\000"}, + {"control-up", "\033[1;5A"}, + {"control-down", "\033[1;5B"}, + {"control-right", "\033[1;5C"}, + {"control-left", "\033[1;5D"}, + {"shift-control-up", "\033[1;6A"}, + {"shift-control-down", "\033[1;6B"}, + {"shift-control-right", "\033[1;6C"}, + {"shift-control-left", "\033[1;6D"}, + {"insert", "\033[2~"}, + {"delete", "\033[3~"}, + {"control-delete", "\033[3,5~"}, + {"shift-delete", "\033[3,2~"}, + {"control-shift-delete", "\033[3,6~"}, + {"page-up", "\033[5~"}, + {"page-down", "\033[6~"}, + {"return", "\r"}, + {"shift-tab", "\033Z"}, + {"shift-return", "\r"}, + {"control-return", "\r"}, + {"space", " "}, + {"shift-space", " "}, + {"control-a", "\001"}, + {"control-b", "\002"}, + {"control-c", "\003"}, + {"control-d", "\004"}, + {"control-e", "\005"}, + {"control-f", "\006"}, + {"control-g", "\007"}, + {"control-h", "\010"}, + {"control-i", "\011"}, + {"control-j", "\012"}, + {"control-k", "\013"}, + {"control-l", "\014"}, + {"control-m", "\015"}, + {"control-n", "\016"}, + {"control-o", "\017"}, + {"control-p", "\020"}, + {"control-q", "\021"}, + {"control-r", "\022"}, + {"control-s", "\023"}, + {"control-t", "\024"}, + {"control-u", "\025"}, + {"control-v", "\026"}, + {"control-w", "\027"}, + {"control-x", "\030"}, + {"control-y", "\031"}, + {"control-z", "\032"}, + {"escape", "\033"}, + {"tab", "\t"}, + {"backspace", "\177"}, + {"control-backspace", "\177"}, + {"shift-backspace","\177"}, + {"shift-tab", "\033[Z"}, + + {"control-F1", "\033[>11~"}, + {"control-F2", "\033[>12~"}, + {"control-F3", "\033[>13~"}, + {"control-F4", "\033[>14~"}, + {"control-F5", "\033[>15~"}, + + {"shift-F1", "\033[?11~"}, + {"shift-F2", "\033[?12~"}, + {"shift-F3", "\033[?13~"}, + {"shift-F4", "\033[?14~"}, + {"shift-F5", "\033[?15~"}, + + {"F1", "\033[11~"}, // hold screen // ESC O P + {"F2", "\033[12~"}, // print screen // Q + {"F3", "\033[13~"}, // set-up R + {"F4", "\033[14~"}, // data/talk S + {"F5", "\033[15~"}, // break + {"F6", "\033[17~"}, + {"F7", "\033[18~"}, + {"F8", "\033[19~"}, + {"F9", "\033[20~"}, + {"F10", "\033[21~"}, + {"F11", "\033[22~"}, + {"F12", "\033[23~"}, + {"control-/", "\037"}, + {"shift-control-/", "\037"}, + {"control-[", "\033"}, + {"control-]", "\035"}, + {"shift-control-[", "\033"}, + {"shift-control-]", "\031"}, + {"shift-control-`", "\036"}, + {"control-'", "'"}, + {"shift-control-'", "'"}, + {"control-;", ";"}, + {"shift-control-;", ";"}, + {"control-.", "."}, + {"shift-control-.", "."}, + {"control-,", ","}, + {"shift-control-,", ","}, + {"control-\\", "\034"}, + {"control-1", "1"}, + {"control-3", "\033"}, + {"control-4", "\034"}, + {"control-5", "\035"}, + {"control-6", "\036"}, + {"shift-control-6", "\036"}, + {"control-7", "\037"}, + {"shift-control-7", "\036"}, + {"control-8", "\177"}, + {"control-9", "9"}, + + +}; + +void ctx_client_lock (CtxClient *client); +void ctx_client_unlock (CtxClient *client); + +void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str) +{ + if (vt->ctx_events) + { + if (!strcmp (str, "control-l") ) + { + vt->ctx_events = 0; + return; + } + vt_write (vt, str, strlen (str) ); + vt_write (vt, "\n", 1); + return; + } + if (!strncmp (str, "keyup", 5)) return; + if (!strncmp (str, "keydown", 7)) return; + + if (!strcmp (str, "capslock")) return; + +#if 0 + if (!strstr (str, "-page")) + vt_set_scroll (vt, 0); +#endif + + if (!strcmp (str, "idle") ) + return; + else if (!strcmp (str, "shift-control-home")) + { + vt_set_scroll (vt, vt->scrollback_count); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-end")) + { + int new_scroll = 0; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-down")) + { + int new_scroll = vt_get_scroll (vt) - 1; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control-up")) + { + int new_scroll = vt_get_scroll (vt) + 1; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-page-up") || + !strcmp (str, "shift-control-page-up")) + { + int new_scroll = vt_get_scroll (vt) + vt_get_rows (vt) /2; + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-page-down") || + !strcmp (str, "shift-control-page-down")) + { + int new_scroll = vt_get_scroll (vt) - vt_get_rows (vt) /2; + if (new_scroll < 0) { new_scroll = 0; } + vt_set_scroll (vt, new_scroll); + vt_rev_inc (vt); + return; + } + else if (!strcmp (str, "shift-control--") || + !strcmp (str, "control--") ) + { + float font_size = vt_get_font_size (vt); + //font_size /= 1.15; + font_size -=2;//= roundf (font_size); + if (font_size < 2) { font_size = 2; } + vt_set_font_size (vt, font_size); + vt_set_px_size (vt, vt->width, vt->height); + return; + } + else if (!strcmp (str, "shift-control-=") || + !strcmp (str, "control-=") ) + { + float font_size = vt_get_font_size (vt); + float old = font_size; + //font_size *= 1.15; + // + //font_size = roundf (font_size); + font_size+=2; + + if (old == font_size) { font_size = old+1; } + if (font_size > 200) { font_size = 200; } + vt_set_font_size (vt, font_size); + vt_set_px_size (vt, vt->width, vt->height); + + return; + } + else if (!strcmp (str, "shift-control-r") ) + { + vt_open_log (vt, "/tmp/ctx-vt"); + return; + } + else if (!strcmp (str, "shift-control-l") ) + { + vt_set_local (vt, !vt_get_local (vt) ); + return; + } + else if (!strncmp (str, "mouse-", 5) ) + { + int cw = vt_cw (vt); + int ch = vt_ch (vt); + if (!strncmp (str + 6, "motion", 6) ) + { + int x = 0, y = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + vt_mouse (vt, event, VT_MOUSE_MOTION, 1, x/cw + 1, y/ch + 1, x, y); + } + } + } + else if (!strncmp (str + 6, "press", 5) ) + { + int x = 0, y = 0, b = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_PRESS, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + } + else if (!strncmp (str + 6, "drag", 4) ) + { + int x = 0, y = 0, b = 0; // XXX initialize B + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_DRAG, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + } + else if (!strncmp (str + 6, "release", 7) ) + { + int x = 0, y = 0, b = 0; + char *s = strchr (str, ' '); + if (s) + { + x = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + y = atoi (s); + s = strchr (s + 1, ' '); + if (s) + { + b = atoi (s); + } + vt_mouse (vt, event, VT_MOUSE_RELEASE, b, x/cw + 1, y/ch + 1, x, y); + } + } + //clients[active].drawn_rev = 0; + // queue-draw + } + return; + } + + if (vt->scroll_on_input) + { + vt->scroll = 0.0; + } + + + if (vt->state == vt_state_vt52) + { + for (unsigned int i = 0; i<sizeof (keymap_vt52) /sizeof (keymap_vt52[0]); i++) + if (!strcmp (str, keymap_vt52[i][0]) ) + { str = keymap_vt52[i][1]; goto done; } + } + else + { + if (vt->cursor_key_application) + { + for (unsigned int i = 0; i<sizeof (keymap_application) /sizeof (keymap_application[0]); i++) + if (!strcmp (str, keymap_application[i][0]) ) + { str = keymap_application[i][1]; goto done; } + } + } + + + if (!strcmp (str, "return") ) + { + if (vt->cr_on_lf) + { str = "\r\n"; } + else + { str = "\r"; } + goto done; + } + if (!strcmp (str, "control-space") || + !strcmp (str, "control-`") || + !strcmp (str, "control-2") || + !strcmp (str, "shift-control-2") || + !strcmp (str, "shift-control-space") ) + { + str = "\0\0"; + vt_write (vt, str, 1); + return; + } + for (unsigned int i = 0; i< sizeof (keymap_general) / + sizeof (keymap_general[0]); i++) + if (!strcmp (str, keymap_general[i][0]) ) + { + str = keymap_general[i][1]; + break; + } +done: + if (strlen (str) ) + { + if (vt->local_editing) + { + for (int i = 0; str[i]; i++) + { + vt->state (vt, str[i]); + } + } + else + { + vt_write (vt, str, strlen (str) ); + } + } +} + +void vt_paste (VT *vt, const char *str) +{ + if (vt->bracket_paste) + { + vt_write (vt, "\033[200~", 6); + } + vt_feed_keystring (vt, NULL, str); + if (vt->bracket_paste) + { + vt_write (vt, "\033[201~", 6); + } +} + +const char *vt_find_shell_command (void) +{ + if (access ("/.flatpak-info", F_OK) != -1) + { + static char ret[512]; + char buf[256]; + FILE *fp = popen("flatpak-spawn --host getent passwd $USER|cut -f 7 -d :", "r"); + if (fp) + { + while (fgets (buf, sizeof(buf), fp) != NULL) + { + if (buf[strlen(buf)-1]=='\n') + buf[strlen(buf)-1]=0; + sprintf (ret, "flatpak-spawn --env=TERM=xterm --host %s", buf); + } + pclose (fp); + return ret; + } + } + + if (getenv ("SHELL")) + { + return getenv ("SHELL"); + } + int i; + const char *command = NULL; + struct stat stat_buf; + static char *alts[][2] = + { + {"/bin/bash", "/bin/bash"}, + {"/usr/bin/bash", "/usr/bin/bash"}, + {"/bin/sh", "/bin/sh"}, + {"/usr/bin/sh", "/usr/bin/sh"}, + {NULL, NULL} + }; + for (i = 0; alts[i][0] && !command; i++) + { + lstat (alts[i][0], &stat_buf); + if (S_ISREG (stat_buf.st_mode) || S_ISLNK (stat_buf.st_mode) ) + { command = alts[i][1]; } + } + return command; +} + +static char *string_chop_head (char *orig) /* return pointer to reset after arg */ +{ + int j=0; + int eat=0; /* number of chars to eat at start */ + + if(orig) + { + int got_more; + char *o = orig; + while(o[j] == ' ') + {j++;eat++;} + + if (o[j]=='"') + { + eat++;j++; + while(o[j] != '"' && + o[j] != 0) + j++; + o[j]='\0'; + j++; + } + else if (o[j]=='\'') + { + eat++;j++; + while(o[j] != '\'' && + o[j] != 0) + j++; + o[j]='\0'; + j++; + } + else + { + while(o[j] != ' ' && + o[j] != 0 && + o[j] != ';') + j++; + } + if (o[j] == 0 || + o[j] == ';') + got_more = 0; + else + got_more = 1; + o[j]=0; /* XXX: this is where foo;bar won't work but foo ;bar works*/ + + if(eat) + { + int k; + for (k=0; k<j-eat; k++) + orig[k] = orig[k+eat]; + } + if (got_more) + return &orig[j+1]; + } + return NULL; +} + +void _ctx_add_listen_fd (int fd); +void _ctx_remove_listen_fd (int fd); + +static pid_t +vt_forkpty (int *amaster, + char *aname, + const struct termios *termp, + const struct winsize *winsize) +{ + pid_t pid; + int master = posix_openpt (O_RDWR|O_NOCTTY); + int slave; + + if (master < 0) + return -1; + if (grantpt (master) != 0) + return -1; + if (unlockpt (master) != 0) + return -1; +#if 0 + char name[1024]; + if (ptsname_r (master, name, sizeof(name)-1)) + return -1; +#else + char *name = NULL; + if ((name = ptsname (master)) == NULL) + return -1; +#endif + + slave = open(name, O_RDWR|O_NOCTTY); + + if (termp) tcsetattr(slave, TCSAFLUSH, termp); + if (winsize) ioctl(slave, TIOCSWINSZ, winsize); + + pid = fork(); + if (pid < 0) + { + return pid; + } else if (pid == 0) + { + close (master); + setsid (); + dup2 (slave, STDIN_FILENO); + dup2 (slave, STDOUT_FILENO); + dup2 (slave, STDERR_FILENO); + + close (slave); + return 0; + } + ioctl (slave, TIOCSCTTY, NULL); + close (slave); + *amaster = master; + return pid; +} + +static void vt_run_command (VT *vt, const char *command, const char *term) +{ + struct winsize ws; + //signal (SIGCHLD,signal_child); +#if 0 + int was_pidone = (getpid () == 1); +#else + int was_pidone = 0; // do no special treatment, all child processes belong + // to root +#endif + signal (SIGINT,SIG_DFL); + ws.ws_row = vt->rows; + ws.ws_col = vt->cols; + ws.ws_xpixel = ws.ws_col * vt->cw; + ws.ws_ypixel = ws.ws_row * vt->ch; + vt->vtpty.pid = vt_forkpty (&vt->vtpty.pty, NULL, NULL, &ws); + if (vt->vtpty.pid == 0) + { + int i; + if (was_pidone) + { + if (setuid(1000)) fprintf (stderr, "setuid failed\n"); + } + else + { + for (i = 3; i<768; i++) { close (i); } /*hack, trying to close xcb */ + } + unsetenv ("TERM"); + unsetenv ("COLUMNS"); + unsetenv ("LINES"); + unsetenv ("TERMCAP"); + unsetenv ("COLOR_TERM"); + unsetenv ("COLORTERM"); + unsetenv ("VTE_VERSION"); + unsetenv ("CTX_BACKEND"); + //setenv ("TERM", "ansi", 1); + //setenv ("TERM", "vt102", 1); + //setenv ("TERM", "vt100", 1); + // setenv ("TERM", term?term:"xterm", 1); + setenv ("TERM", term?term:"xterm-256color", 1); + setenv ("COLORTERM", "truecolor", 1); + //setenv ("CTX_VERSION", "0", 1); + setenv ("CTX_BACKEND", "ctx", 1); // speeds up launching of clients + + { + char *cargv[32]; + int cargc; + char *rest, *copy; + copy = calloc (strlen (command)+2, 1); + strcpy (copy, command); + rest = copy; + cargc = 0; + while (rest && cargc < 30 && rest[0] != ';') + { + cargv[cargc++] = rest; + rest = string_chop_head (rest); + } + cargv[cargc] = NULL; + execvp (cargv[0], cargv); + } + exit (0); + } + else if (vt->vtpty.pid < 0) + { + VT_error ("forkpty failed (%s)", command); + return; + } + fcntl(vt->vtpty.pty, F_SETFL, O_NONBLOCK|O_NOCTTY); + _ctx_add_listen_fd (vt->vtpty.pty); +} + +void vt_destroy (VT *vt) +{ + while (vt->lines) + { + vt_line_free (vt->lines->data, 1); + ctx_list_remove (&vt->lines, vt->lines->data); + vt->line_count--; + } + while (vt->scrollback) + { + vt_line_free (vt->scrollback->data, 1); + ctx_list_remove (&vt->scrollback, vt->scrollback->data); + } + if (vt->ctxp) + ctx_parser_free (vt->ctxp); + //if (vt->ctx) + // { ctx_free (vt->ctx); } + free (vt->argument_buf); + ctx_list_remove (&vts, vt); + kill (vt->vtpty.pid, 9); + _ctx_remove_listen_fd (vt->vtpty.pty); + close (vt->vtpty.pty); +#if 1 + if (vt->title) + free (vt->title); +#endif + free (vt); +} + +int vt_get_line_count (VT *vt) +{ + return vt->line_count; +} + +const char *vt_get_line (VT *vt, int no) +{ + if (no >= vt->rows) + { + CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows); + if (!l) + { + return ""; + } + CtxString *str = l->data; + return str->str; + } + else + { + CtxList *l = ctx_list_nth (vt->lines, no); + if (!l) + { + return "-"; + } + CtxString *str = l->data; + return str->str; + } +} + +int vt_line_is_continuation (VT *vt, int no) +{ + if (no >= vt->rows) + { + CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows); + if (!l) + { + return 1; + } + VtLine *line = l->data; + return line->wrapped; + } + else + { + CtxList *l = ctx_list_nth (vt->lines, no); + if (!l) + { + return 1; + } + VtLine *line = l->data; + return line->wrapped; + } +} + +int vt_get_cols (VT *vt) +{ + return vt->cols; +} + +int vt_get_rows (VT *vt) +{ + return vt->rows; +} + +int vt_get_cursor_x (VT *vt) +{ + return vt->cursor_x; +} + +int vt_get_cursor_y (VT *vt) +{ + return vt->cursor_y; +} + +static void draw_braille_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v) +{ + ctx_rectangle (ctx, 0.167 * cw + x + u * cw * 0.5, + y - ch + 0.080 * ch + v * ch * 0.25, + 0.33 *cw, 0.33 * cw); +} + +static void draw_sextant_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v) +{ + ctx_rectangle (ctx, x + u * cw * 0.5, + y - ch + v * ch * 0.3333, + 0.5 *cw, 0.34 * ch); +} + +int vt_special_glyph (Ctx *ctx, VT *vt, float x, float y, int cw, int ch, int unichar) +{ + switch (unichar) + { + case 0x2594: // UPPER_ONE_EIGHT_BLOCK + ctx_begin_path (ctx); + { + float factor = 1.0f/8.0f; + ctx_rectangle (ctx, x, y - ch, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2581: // LOWER_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + { + float factor = 1.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2582: // LOWER_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + { + float factor = 1.0f/4.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2583: // LOWER_THREE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 3.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2585: // LOWER_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 5.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2586: // LOWER_THREE_QUARTERS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 3.0f/4.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2587: // LOWER_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + { + float factor = 7.0f/8.0f; + ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor); + ctx_fill (ctx); + } + return 0; + case 0x2589: // LEFT_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*7/8, ch); + ctx_fill (ctx); + return 0; + case 0x258A: // LEFT_THREE_QUARTERS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*3/4, ch); + ctx_fill (ctx); + return 0; + case 0x258B: // LEFT_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*5/8, ch); + ctx_fill (ctx); + return 0; + case 0x258D: // LEFT_THREE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw*3/8, ch); + ctx_fill (ctx); + return 0; + case 0x258E: // LEFT_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/4, ch); + ctx_fill (ctx); + return 0; + case 0x258F: // LEFT_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/8, ch); + ctx_fill (ctx); + return 0; + case 0x258C: // HALF_LEFT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/2, ch); + ctx_fill (ctx); + return 0; + case 0x2590: // HALF_RIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8f: // VT_RIGHT_SEVEN_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*1/8, y - ch, cw*7/8, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8d: // VT_RIGHT_FIVE_EIGHTS_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*3/8, y - ch, cw*5/8, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8b: // VT_RIGHT_ONE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*3/4, y - ch, cw/4, ch); + ctx_fill (ctx); + return 0; + case 0x1fb8e: // VT_RIGHT_THREE_QUARTER_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*1/4, y - ch, cw*3/4, ch); + ctx_fill (ctx); + return 0; + case 0x2595: // VT_RIGHT_ONE_EIGHT_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw*7/8, y - ch, cw/8, ch); + ctx_fill (ctx); + return 0; + case 0x2580: // HALF_UP_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0x2584: // _HALF_DOWN_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0x2596: // _QUADRANT LOWER LEFT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2597: // _QUADRANT LOWER RIGHT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x+cw/2, y - ch/2, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2598: // _QUADRANT UPPER LEFT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x259D: // _QUADRANT UPPER RIGHT + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x2599: // _QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259A: // _QUADRANT UPPER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259B: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + return 0; + case 0x259C: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x259E: // _QUADRANT UPPER RIGHT AND LOWER LEFT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + return 0; + case 0x259F: // _QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596); + vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597); + return 0; + case 0x2588: // FULL_BLOCK: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_fill (ctx); + return 0; + case 0x2591: // LIGHT_SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.25); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x2592: // MEDIUM_SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.5); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x2593: // DARK SHADE: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch, cw, ch); + ctx_save (ctx); + ctx_global_alpha (ctx, 0.75); + ctx_fill (ctx); + ctx_restore (ctx); + return 0; + case 0x23BA: //HORIZONTAL_SCANLINE-1 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.1 - ch * 0.1, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BB: //HORIZONTAL_SCANLINE-3 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.3 - ch * 0.075, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BC: //HORIZONTAL_SCANLINE-7 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.7 - ch * 0.025, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x23BD: //HORIZONTAL_SCANLINE-9 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch + ch*0.9 + ch * 0.0, + cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2500: //VT_BOX_DRAWINGS_LIGHT_HORIZONTAL // and scanline 5 + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2212: // minus -sign + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw * 0.1, y - ch/2 - ch * 0.1 / 2, cw * 0.8, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2502: // VT_BOX_DRAWINGS_LIGHT_VERTICAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch + 1); + ctx_fill (ctx); + return 0; + case 0x250c: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2510: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2514: //VT_BOX_DRAWINGS_LIGHT_UP_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ch*0.1/2); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2 + ch * 0.1, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x2518: //VT_BOX_DRAWINGS_LIGHT_UP_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ ch*0.1/2); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x251C: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_RIGHT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, cw/2+ch * 0.1, ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + return 0; + case 0x2524: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_LEFT: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1); + ctx_fill (ctx); + return 0; + case 0x252C: // VT_BOX_DRAWINGS_LIGHT_DOWN_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, ch * 0.1, ch/2+ch*0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + return 0; + case 0x2534: // VT_BOX_DRAWINGS_LIGHT_UP_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch /2+ch*0.1/2); + ctx_fill (ctx); + return 0; + case 0x253C: // VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_HORIZONTAL: + ctx_begin_path (ctx); + ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1); + ctx_fill (ctx); + ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch); + ctx_fill (ctx); + return 0; + case 0xe0a0: // PowerLine branch + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x+cw/2, y - 0.15 * ch); + ctx_rel_line_to (ctx, -cw/3, -ch * 0.7); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/3, ch * 0.7); + ctx_line_width (ctx, cw * 0.25); + ctx_stroke (ctx); + ctx_restore (ctx); + break; + // case 0xe0a1: // PowerLine LN + // case 0xe0a2: // PowerLine Lock + case 0xe0b0: // PowerLine left solid + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/2); + ctx_fill (ctx); + return 0; + case 0xe0b1: // PowerLine left line + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch * 0.1); + ctx_rel_line_to (ctx, cw * 0.9, -ch/2 * 0.8); + ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8); + ctx_line_width (ctx, cw * 0.2); + ctx_stroke (ctx); + ctx_restore (ctx); + return 0; + case 0xe0b2: // PowerLine Right solid + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, -ch/2); + ctx_rel_line_to (ctx, cw, -ch/2); + ctx_fill (ctx); + return 0; + case 0xe0b3: // PowerLine right line + ctx_save (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch * 0.1); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8); + ctx_rel_line_to (ctx, cw * 0.9, ch/2 * 0.8); + ctx_line_width (ctx, cw * 0.2); + ctx_stroke (ctx); + ctx_restore (ctx); + return 0; + /* + case 0x1fb70: // left triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_fill (ctx); + return 0; + case 0x1fb72: // right triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x1fb73: // lower triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_fill (ctx); + return 0; + case 0x1fb71: // upper triangular one quarter block + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw, 0); + ctx_fill (ctx); + return 0; + */ + case 0x25E2: // VT_BLACK_LOWER_RIGHT_TRIANGLE: + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x25E3: // VT_BLACK_LOWER_LEFT_TRIANGLE: + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch); + ctx_fill (ctx); + return 0; + case 0x25E4: // tri + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_fill (ctx); + return 0; + case 0x25E5: // tri + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y - ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_fill (ctx); + return 0; + case 0x2800: + case 0x2801: + case 0x2802: + case 0x2803: + case 0x2804: + case 0x2805: + case 0x2806: + case 0x2807: + case 0x2808: + case 0x2809: + case 0x280A: + case 0x280B: + case 0x280C: + case 0x280D: + case 0x280E: + case 0x280F: + case 0x2810: + case 0x2811: + case 0x2812: + case 0x2813: + case 0x2814: + case 0x2815: + case 0x2816: + case 0x2817: + case 0x2818: + case 0x2819: + case 0x281A: + case 0x281B: + case 0x281C: + case 0x281D: + case 0x281E: + case 0x281F: + case 0x2820: + case 0x2821: + case 0x2822: + case 0x2823: + case 0x2824: + case 0x2825: + case 0x2826: + case 0x2827: + case 0x2828: + case 0x2829: + case 0x282A: + case 0x282B: + case 0x282C: + case 0x282D: + case 0x282E: + case 0x282F: + case 0x2830: + case 0x2831: + case 0x2832: + case 0x2833: + case 0x2834: + case 0x2835: + case 0x2836: + case 0x2837: + case 0x2838: + case 0x2839: + case 0x283A: + case 0x283B: + case 0x283C: + case 0x283D: + case 0x283E: + case 0x283F: + ctx_begin_path (ctx); + { + int bit_pattern = unichar - 0x2800; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x2840: + case 0x2841: + case 0x2842: + case 0x2843: + case 0x2844: + case 0x2845: + case 0x2846: + case 0x2847: + case 0x2848: + case 0x2849: + case 0x284A: + case 0x284B: + case 0x284C: + case 0x284D: + case 0x284E: + case 0x284F: + case 0x2850: + case 0x2851: + case 0x2852: + case 0x2853: + case 0x2854: + case 0x2855: + case 0x2856: + case 0x2857: + case 0x2858: + case 0x2859: + case 0x285A: + case 0x285B: + case 0x285C: + case 0x285D: + case 0x285E: + case 0x285F: + case 0x2860: + case 0x2861: + case 0x2862: + case 0x2863: + case 0x2864: + case 0x2865: + case 0x2866: + case 0x2867: + case 0x2868: + case 0x2869: + case 0x286A: + case 0x286B: + case 0x286C: + case 0x286D: + case 0x286E: + case 0x286F: + case 0x2870: + case 0x2871: + case 0x2872: + case 0x2873: + case 0x2874: + case 0x2875: + case 0x2876: + case 0x2877: + case 0x2878: + case 0x2879: + case 0x287A: + case 0x287B: + case 0x287C: + case 0x287D: + case 0x287E: + case 0x287F: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 0, 3); + { + int bit_pattern = unichar - 0x2840; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x2880: + case 0x2881: + case 0x2882: + case 0x2883: + case 0x2884: + case 0x2885: + case 0x2886: + case 0x2887: + case 0x2888: + case 0x2889: + case 0x288A: + case 0x288B: + case 0x288C: + case 0x288D: + case 0x288E: + case 0x288F: + case 0x2890: + case 0x2891: + case 0x2892: + case 0x2893: + case 0x2894: + case 0x2895: + case 0x2896: + case 0x2897: + case 0x2898: + case 0x2899: + case 0x289A: + case 0x289B: + case 0x289C: + case 0x289D: + case 0x289E: + case 0x289F: + case 0x28A0: + case 0x28A1: + case 0x28A2: + case 0x28A3: + case 0x28A4: + case 0x28A5: + case 0x28A6: + case 0x28A7: + case 0x28A8: + case 0x28A9: + case 0x28AA: + case 0x28AB: + case 0x28AC: + case 0x28AD: + case 0x28AE: + case 0x28AF: + case 0x28B0: + case 0x28B1: + case 0x28B2: + case 0x28B3: + case 0x28B4: + case 0x28B5: + case 0x28B6: + case 0x28B7: + case 0x28B8: + case 0x28B9: + case 0x28BA: + case 0x28BB: + case 0x28BC: + case 0x28BD: + case 0x28BE: + case 0x28BF: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 1, 3); + { + int bit_pattern = unichar - 0x2880; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x28C0: + case 0x28C1: + case 0x28C2: + case 0x28C3: + case 0x28C4: + case 0x28C5: + case 0x28C6: + case 0x28C7: + case 0x28C8: + case 0x28C9: + case 0x28CA: + case 0x28CB: + case 0x28CC: + case 0x28CD: + case 0x28CE: + case 0x28CF: + case 0x28D0: + case 0x28D1: + case 0x28D2: + case 0x28D3: + case 0x28D4: + case 0x28D5: + case 0x28D6: + case 0x28D7: + case 0x28D8: + case 0x28D9: + case 0x28DA: + case 0x28DB: + case 0x28DC: + case 0x28DD: + case 0x28DE: + case 0x28DF: + case 0x28E0: + case 0x28E1: + case 0x28E2: + case 0x28E3: + case 0x28E4: + case 0x28E5: + case 0x28E6: + case 0x28E7: + case 0x28E8: + case 0x28E9: + case 0x28EA: + case 0x28EB: + case 0x28EC: + case 0x28ED: + case 0x28EE: + case 0x28EF: + case 0x28F0: + case 0x28F1: + case 0x28F2: + case 0x28F3: + case 0x28F4: + case 0x28F5: + case 0x28F6: + case 0x28F7: + case 0x28F8: + case 0x28F9: + case 0x28FA: + case 0x28FB: + case 0x28FC: + case 0x28FD: + case 0x28FE: + case 0x28FF: + ctx_begin_path (ctx); + draw_braille_bit (ctx, x, y, cw, ch, 0, 3); + draw_braille_bit (ctx, x, y, cw, ch, 1, 3); + { + int bit_pattern = unichar - 0x28C0; + int bit = 0; + int u = 0; + int v = 0; + for (bit = 0; bit < 6; bit++) + { + if (bit_pattern & (1<<bit) ) + { + draw_braille_bit (ctx, x, y, cw, ch, u, v); + } + v++; + if (v > 2) + { + v = 0; + u++; + } + } + } + ctx_fill (ctx); + return 0; + case 0x1fb00: + case 0x1fb01: + case 0x1fb02: + case 0x1fb03: + case 0x1fb04: + case 0x1fb05: + case 0x1fb06: + case 0x1fb07: + case 0x1fb08: + case 0x1fb09: + case 0x1fb0a: + case 0x1fb0b: + case 0x1fb0c: + case 0x1fb0d: + case 0x1fb0e: + case 0x1fb0f: + case 0x1fb10: + case 0x1fb11: + case 0x1fb12: + case 0x1fb13: + case 0x1fb14: + case 0x1fb15: + case 0x1fb16: + case 0x1fb17: + case 0x1fb18: + case 0x1fb19: + case 0x1fb1a: + case 0x1fb1b: + case 0x1fb1c: + case 0x1fb1d: + case 0x1fb1e: + case 0x1fb1f: + case 0x1fb20: + case 0x1fb21: + case 0x1fb22: + case 0x1fb23: + case 0x1fb24: + case 0x1fb25: + case 0x1fb26: + case 0x1fb27: + case 0x1fb28: + case 0x1fb29: + case 0x1fb2a: + case 0x1fb2b: + case 0x1fb2c: + case 0x1fb2d: + case 0x1fb2e: + case 0x1fb2f: + case 0x1fb30: + case 0x1fb31: + case 0x1fb32: + case 0x1fb33: + case 0x1fb34: + case 0x1fb35: + case 0x1fb36: + case 0x1fb37: + case 0x1fb38: + case 0x1fb39: + case 0x1fb3a: + case 0x1fb3b: + + { + ctx_begin_path (ctx); + uint32_t bitmask = (unichar - 0x1fb00) + 1; + if (bitmask > 20) bitmask ++; + if (bitmask > 41) bitmask ++; + int bit = 0; + for (int v = 0; v < 3; v ++) + for (int u = 0; u < 2; u ++, bit ++) + { + if (bitmask & (1<<bit)) + { + draw_sextant_bit (ctx, x, y, cw, ch, u, v); + } + } + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch / 3.0f); + ctx_rel_line_to (ctx, cw/2, ch/3.0f); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f); + ctx_rel_line_to (ctx, cw, ch/3.0f); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f * 2); + ctx_rel_line_to (ctx, cw/2, ch/3.0f * 2); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb3f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f * 2); + ctx_rel_line_to (ctx, cw, ch/3.0f * 2); + ctx_fill (ctx); + return 0; + } + break; + case 0x1fb40: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb41: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw/2, -ch/3.0); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb42: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb43: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0f); + ctx_rel_line_to (ctx, cw/2, -ch/3.0f*2.0f); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb44: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, -ch/3.0 * 2); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb45: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0.0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb46: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb47: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb48: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch/3.0); + ctx_rel_line_to (ctx, 0.0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb49: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, -ch/3.0*2); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw, -ch/3.0*2); + ctx_rel_line_to (ctx, 0.0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch/3 * 2); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb4f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, ch/3 * 2); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb50: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, cw/2, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb51: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, ch/3.0); + ctx_rel_line_to (ctx, 0, ch/3.0); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb52: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb53: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, -ch/3.0); + ctx_fill (ctx); + return 0; + } + case 0x1fb54: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb55: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, -ch/3.0*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb56: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, -ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb57: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb58: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb59: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch/3 * 2); + ctx_fill (ctx); + return 0; + } + case 0x1fb5a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0); + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw, ch/3 * 2); + ctx_fill (ctx); + return 0; + } + case 0x1fb5b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, -cw/2, ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb5c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3); + + ctx_rel_line_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb5d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3 * 2); + ctx_rel_line_to (ctx, -cw/2, ch/3); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb5e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3 * 2); + ctx_rel_line_to (ctx, -cw, ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb5f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw/2, ch/3*2); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb60: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb61: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch); + ctx_rel_line_to (ctx, -cw/2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb62: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw/2, -ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb63: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3); + ctx_rel_line_to (ctx, -cw, -ch/3); + ctx_fill (ctx); + return 0; + } + case 0x1fb64: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch/3*2); + ctx_rel_line_to (ctx, -cw/2, -ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb65: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3*2); + ctx_rel_line_to (ctx, -cw, -ch/3*2); + ctx_fill (ctx); + return 0; + } + case 0x1fb66: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/2, -ch); + ctx_rel_line_to (ctx, cw/2, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch); + ctx_fill (ctx); + return 0; + } + case 0x1fb67: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/3.0*2); + ctx_rel_line_to (ctx, 0, -ch/3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/3.0*2); + ctx_rel_line_to (ctx, -cw, -ch/3.0); + ctx_fill (ctx); + return 0; + } + case 0x1fb68: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb69: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb6a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb6b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6c: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6d: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6e: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw, -ch); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_fill (ctx); + return 0; + } + case 0x1fb6f: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb82: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 2); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb83: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 3); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 3); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb84: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 5); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 5); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb85: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 6); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 6); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb86: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, 0, -ch/8 * 7); + ctx_rel_line_to (ctx, cw, 0); + ctx_rel_move_to (ctx, 0, ch/8 * 7); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb87: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*6, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*2, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*2, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb88: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*5, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*3, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*3, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb89: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*3, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*5, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*5, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb8a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_move_to (ctx, cw/8*2, 0); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_move_to (ctx, cw/8*6, 0); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_move_to (ctx, -cw/8*6, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb97: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch/4); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/4); + ctx_rel_line_to (ctx, -cw, 0); + ctx_close_path (ctx); + ctx_move_to (ctx, 0, -ch/2); + ctx_rel_line_to (ctx, 0, -ch/4); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, 0, ch/4); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb9a: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_move_to (ctx, cw, 0); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, -cw, 0); + ctx_fill (ctx); + return 0; + } + case 0x1fb9b: + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x, y); + ctx_rel_line_to (ctx, 0, -ch); + ctx_rel_line_to (ctx, cw/2, ch/2); + ctx_rel_line_to (ctx, cw/2, -ch/2); + ctx_rel_line_to (ctx, 0, ch); + ctx_rel_line_to (ctx, -cw/2, -ch/2); + ctx_rel_line_to (ctx, -cw/2, ch/2); + ctx_fill (ctx); + return 0; + } + + } + return -1; +} + +void vt_ctx_glyph (Ctx *ctx, VT *vt, float x, float y, int unichar, int bold, float scale_x, float scale_y, float offset_y) +{ + int did_save = 0; + if (unichar <= ' ') + return; + scale_x *= vt->scale_x; + scale_y *= vt->scale_y; + + if (!ctx_renderer_is_term (ctx)) + { + // TODO : use our own special glyphs when glyphs are not passed through + if (!vt_special_glyph (ctx, vt, x, y + offset_y * vt->ch, vt->cw * scale_x, vt->ch * scale_y, unichar) ) + return; + } + + if (scale_x != 1.0 || scale_y != 1.0) + { + if (!did_save) + { + ctx_save (ctx); + did_save = 1; + } + + ctx_translate (ctx, x, y); + ctx_scale (ctx, scale_x, scale_y); + ctx_translate (ctx, -x, -y); + } + if (offset_y != 0.0f) + { + if (!did_save) + { + ctx_save (ctx); + did_save = 1; + } + ctx_translate (ctx, 0, vt->font_size * offset_y); + } + y -= vt->font_size * 0.22; + if (bold + && !ctx_renderer_is_term (ctx) + ) + { + ctx_move_to (ctx, x - vt->font_size/30.0, y); + //ctx_line_width (ctx, vt->font_size/30.0); + ctx_glyph (ctx, unichar, 0); + } + ctx_move_to (ctx, x, y); + ctx_glyph (ctx, unichar, 0); + if (did_save) + ctx_restore (ctx); +} + +//static uint8_t palette[256][3]; + +/* optimized for ANSI ART - and avoidance of human metamers + * among color deficient vision - by distributing and pertubating + * until all 64 combinations - sans self application, have + * likely to be discernable by humans. + */ + + +void vt_ctx_get_color (VT *vt, int no, int intensity, uint8_t *rgba) +{ + uint8_t r = 0, g = 0, b = 0; + if (no < 16 && no >= 0) + { + switch (intensity) + { + case 0: + no = 0; + break; + case 1: + // 15 becomes 7 + if (no == 15) { no = 8; } + else if (no > 8) { no -= 8; } + break; + case 2: + /* give the normal color special treatment, and in really normal + * cirumstances it is the dim variant of foreground that is used + */ + if (no == 15) { no = 7; } + break; + case 3: + case 4: + if (no < 8) + { no += 8; } + break; + default: + break; + } + r = palettes[vt->palette_no][no][0]; + g = palettes[vt->palette_no][no][1]; + b = palettes[vt->palette_no][no][2]; + } + else if (no < 16 + 6*6*6) + { + no = no-16; + b = (no % 6) * 255 / 5; + no /= 6; + g = (no % 6) * 255 / 5; + no /= 6; + r = (no % 6) * 255 / 5; + } + else + { + int gray = no - (16 + 6*6*6); + float val = gray * 255 / 24; + r = g = b = val; + } + rgba[0]=r; + rgba[1]=g; + rgba[2]=b; + rgba[3]=255; +} + +int vt_keyrepeat (VT *vt) +{ + return vt->keyrepeat; +} + +static void vt_flush_bg (VT *vt, Ctx *ctx) +{ + if (vt->bg_active) + { + ctx_rgba8 (ctx, vt->bg_rgba[0], vt->bg_rgba[1], vt->bg_rgba[2], vt->bg_rgba[3]); + ctx_rectangle (ctx, vt->bg_x0, vt->bg_y0, vt->bg_width, vt->bg_height); + ctx_fill (ctx); + vt->bg_active = 0; + } +} + +static void vt_draw_bg (VT *vt, Ctx *ctx, + float x0, float y0, + float width, float height, + uint8_t *rgba) +{ + int same_color = !memcmp(rgba, vt->bg_rgba, 4); + if (vt->bg_active && !same_color) + { + vt_flush_bg (vt, ctx); + } + + if (vt->bg_active && same_color) + { + vt->bg_width += width; + } + else + { + memcpy (vt->bg_rgba, rgba, 4); + vt->bg_active = 1; + vt->bg_x0 = x0; + vt->bg_y0 = y0; + vt->bg_width = width; + vt->bg_height = height; + } +} + +float vt_draw_cell (VT *vt, Ctx *ctx, + int row, int col, // pass 0 to force draw - like + float x0, float y0, // for scrollback visible + uint64_t style, + uint32_t unichar, + int dw, int dh, + int in_smooth_scroll, + int in_select, + int is_fg) +// dw is 0 or 1 +// dh is 0 1 or -1 1 is upper -1 is lower +{ + int on_white = vt->reverse_video; + int color = 0; + int bold = (style & STYLE_BOLD) != 0; + int dim = (style & STYLE_DIM) != 0; + int is_hidden = (style & STYLE_HIDDEN) != 0; + int proportional = (style & STYLE_PROPORTIONAL) != 0; + int fg_set = (style & STYLE_FG_COLOR_SET) != 0; + int bg_intensity = 0; + int fg_intensity = 2; + int reverse = ( (style & STYLE_REVERSE) != 0) ^ in_select; + int blink = ( (style & STYLE_BLINK) != 0); + int blink_fast = ( (style & STYLE_BLINK_FAST) != 0); + int cw = vt->cw; + int ch = vt->ch; + if (proportional) + { + if (vt->font_is_mono) + { + ctx_font (ctx, "regular"); + vt->font_is_mono = 0; + } + cw = ctx_glyph_width (ctx, unichar); + } + else + { + if (vt->font_is_mono == 0) + { + ctx_font (ctx, "mono"); + vt->font_is_mono = 1; + if (col > 1) + { + int x = x0; + int new_cw = cw - ( (x % cw) ); + if (new_cw < cw*3/2) + { new_cw += cw; } + cw = new_cw; + } + } + } + float scale_x = 1.0f; + float scale_y = 1.0f; + float offset_y = 0.0f; + if (dw) + { + scale_x = 2.0f; + } + if (dh) + { + scale_y = 2.0f; + } + if (dh == 1) + { + offset_y = 0.5f; + } + else if (dh == -1) + { + offset_y = 0.0f; + } + if (in_smooth_scroll) + { + offset_y -= vt->scroll_offset / (dh?2:1); + } + cw *= scale_x; + if (blink_fast) + { + if ( (vt->blink_state % 2) == 0) + { blink = 1; } + else + { blink = 0; } + } + else if (blink) + { + if ( (vt->blink_state % 10) < 5) + { blink = 1; } + else + { blink = 0; } + } + /* + from the vt100 technical-manual: + + "Reverse characters [..] normally have dim backgrounds with + black characters so that large white spaces have the same impact + on the viewer's eye as the smaller brighter white areas of + normal characters. Bold and reverse asserted together give a + background of normal intensity. Blink applied to nonreverse + characters causes them to alternate between their usual + intensity and the next lower intensity. (Normal characters vary + between normal and dim intensity. Bold characters vary between + bright and normal intensity.) Blink applied to a reverse + character causes that character to alternate between normal and + reverse video representations of that character." + + This is in contrast with how the truth table appears to be + meant used, since it uses a reverse computed as the xor of + the global screen reverse and the reverse attribute of the + cell. + + To fulfil the more asthethic resulting from implementing the + text, and would be useful to show how the on_bright background + mode of the vt100 actually displays the vttest. + + */ + if (on_white) + { + if (bold) + { + bg_intensity = 2; + fg_intensity = blink?1: 0; + } + else if (dim) + { + bg_intensity = 2; + fg_intensity = blink?3: 1; + } + else + { + bg_intensity = 2; + fg_intensity = blink?1: 0; + } + if (fg_set) + { + fg_intensity = blink?2:3; + } + } + else /* bright on dark */ + { + if (bold) + { + bg_intensity = 0; + fg_intensity = blink?2: 3; + } + else if (dim) + { + bg_intensity = 0; + fg_intensity = blink?0: 1; + } + else + { + bg_intensity = 0; + fg_intensity = blink?1: 2; + } + } + uint8_t bg_rgb[4]= {0,0,0,255}; + uint8_t fg_rgb[4]= {255,255,255,255}; + { + //ctx_begin_path (ctx); + if (style & STYLE_BG24_COLOR_SET) + { + uint64_t temp = style >> 40; + bg_rgb[0] = temp & 0xff; + temp >>= 8; + bg_rgb[1] = temp & 0xff; + temp >>= 8; + bg_rgb[2] = temp & 0xff; +#if 0 + if (dh) + { + bg_rgb[0] = + bg_rgb[1] = + bg_rgb[2] = 30; + } +#endif + } + else + { + if (style & STYLE_BG_COLOR_SET) + { + color = (style >> 40) & 255; + bg_intensity = -1; + vt_ctx_get_color (vt, color, bg_intensity, bg_rgb); + } + else + { + switch (bg_intensity) + { + case 0: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i]; } + break; + case 1: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; } + break; + case 2: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->bg_color[i] * 0.05 + vt->fg_color[i] * 0.95; } + break; + case 3: + for (int i = 0; i <3 ; i++) + { bg_rgb[i] = vt->fg_color[i]; } + break; + } + } + } + } + if (style & STYLE_FG24_COLOR_SET) + { + uint64_t temp = style >> 16; + fg_rgb[0] = temp & 0xff; + temp >>= 8; + fg_rgb[1] = temp & 0xff; + temp >>= 8; + fg_rgb[2] = temp & 0xff; + } + else + { + if ( (style & STYLE_FG_COLOR_SET) == 0) + { + switch (fg_intensity) + { + case 0: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.7 + vt->fg_color[i] * 0.3; } + break; + case 1: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; } + break; + case 2: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->bg_color[i] * 0.20 + vt->fg_color[i] * 0.80; } + break; + case 3: + for (int i = 0; i <3 ; i++) + { fg_rgb[i] = vt->fg_color[i]; } + } + } + else + { + color = (style >> 16) & 255; + vt_ctx_get_color (vt, color, fg_intensity, fg_rgb); + } + } + + if (reverse) + { + for (int c = 0; c < 3; c ++) + { + int t = bg_rgb[c]; + bg_rgb[c] = fg_rgb[c]; + fg_rgb[c] = t; + } + } + + if (is_fg || + ((!on_white) && bg_rgb[0]==0 && bg_rgb[1]==0 && bg_rgb[2]==0) || + ((on_white) && bg_rgb[0]==255 && bg_rgb[1]==255 && bg_rgb[2]==255)) + /* these comparisons are not entirely correct, when on dark background we assume black to + * be default and non-set, even when theme might differ + */ + { + /* skipping draw of background */ + } + else + { + if (dh) + { + vt_draw_bg (vt, ctx, ctx_floorf(x0), + ctx_floorf(y0 - ch - ch * (vt->scroll_offset)), cw, ch, bg_rgb); + } + else + { + vt_draw_bg (vt, ctx, x0, y0 - ch + ch * offset_y, cw, ch, bg_rgb); + } + } + + if (!is_fg) + return cw; + + int italic = (style & STYLE_ITALIC) != 0; + int strikethrough = (style & STYLE_STRIKETHROUGH) != 0; + int overline = (style & STYLE_OVERLINE) != 0; + int underline = (style & STYLE_UNDERLINE) != 0; + int underline_var = (style & STYLE_UNDERLINE_VAR) != 0; + if (dh == 1) + { + underline = underline_var = 0; + } + int double_underline = 0; + int curved_underline = 0; + if (underline_var) + { + if (underline) + { + double_underline = 1; + } + else + { + curved_underline = 1; + } + } + + int has_underline = (underline || double_underline || curved_underline); + + if (unichar == ' ' && !has_underline) + is_hidden = 1; + + if (!is_hidden) + { + + ctx_rgba8 (ctx, fg_rgb[0], fg_rgb[1], fg_rgb[2], 255); + + + if (italic) + { + ctx_save (ctx); + ctx_translate (ctx, (x0 + cw/3), (y0 + vt->ch/2) ); + ctx_scale (ctx, 0.9, 0.9); + ctx_rotate (ctx, 0.15); + ctx_translate (ctx, - (x0 + cw/3), - (y0 + vt->ch/2) ); + } + vt_ctx_glyph (ctx, vt, x0, y0, unichar, bold, scale_x, scale_y, offset_y); + if (italic) + { + ctx_restore (ctx); + } + if (curved_underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05); + ctx_rel_line_to (ctx, (cw+2) /3, vt->ch * 0.1); + ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05); + //ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.050:0.04) ); + ctx_stroke (ctx); + } + else if (double_underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.130 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.030 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.050:0.04) ); + ctx_stroke (ctx); + } + else if (underline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + if (overline) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.94 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + if (strikethrough) + { + ctx_begin_path (ctx); + ctx_move_to (ctx, x0, y0 - vt->font_size * 0.43 - vt->ch * vt->scroll_offset); + ctx_rel_line_to (ctx, cw, 0); + ctx_line_width (ctx, vt->font_size * (style & STYLE_BOLD?0.075:0.05) ); + ctx_stroke (ctx); + } + } + return cw; +} + +int vt_has_blink (VT *vt) +{ + if (!vt) return 0; + return (vt->in_smooth_scroll ? 10 : 0); + //return vt->has_blink + (vt->in_smooth_scroll ? 10 : 0); +} + +//extern int enable_terminal_menu; +// + +//void ctx_set_popup (Ctx *ctx, void (*popup)(Ctx *ctx, void *data), void *popup_data); + +static char *primary = NULL; +static void scrollbar_drag (CtxEvent *event, void *data, void *data2); +static int scrollbar_down = 0; + +void vt_mouse_event (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + CtxClient *client = vt_get_client (vt); + float x = event->x; + float y = event->y; + int device_no = event->device_no; + char buf[128]=""; + if ((!vt->in_alt_screen) && + (event->x > vt->width - vt->cw * 1.5 || scrollbar_down) && + (event->type == CTX_DRAG_MOTION || + event->type == CTX_DRAG_PRESS || + event->type == CTX_DRAG_RELEASE)) + return scrollbar_drag (event, data, data2); + switch (event->type) + { + case CTX_MOTION: + case CTX_DRAG_MOTION: + //if (event->device_no==1) + { + sprintf (buf, "mouse-motion %.0f %.0f %i", x, y, device_no); +// ctx_set_dirty (event->ctx, 1); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); +// vt->rev++; + } + break; + case CTX_DRAG_PRESS: + if (event->device_no==2) + { + if (primary) + { + if (vt) + vt_paste (vt, primary); + } + } + else if (event->device_no==3 && !vt->in_alt_screen) + { + vt->popped = 1; + } + else + { + sprintf (buf, "mouse-press %.0f %.0f %i", x, y, device_no); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); +// ctx_set_dirty (event->ctx, 1); +// vt->rev++; + } + break; + case CTX_DRAG_RELEASE: + if (event->device_no==3 && !vt->in_alt_screen) + { + vt->popped = 0; + } + ctx_set_dirty (event->ctx, 1); + sprintf (buf, "mouse-release %.0f %.0f %i", x, y, device_no); + ctx_client_lock (client); + vt_feed_keystring (vt, event, buf); + ctx_client_unlock (client); + break; + default: + // we should not stop propagation + return; + break; + } + event->stop_propagate = 1; +//vt->rev++; +} +static int scrollbar_focused = 0; +#if 0 +static void scrollbar_enter (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + vt->rev++; + scrollbar_focused = 1; +} + +static void scrollbar_leave (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + vt->rev++; + scrollbar_focused = 0; +} +#endif + +static void scrollbar_drag (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + float disp_lines = vt->rows; + float tot_lines = vt->line_count + vt->scrollback_count; + + vt->scroll = tot_lines - disp_lines - (event->y*1.0/ ctx_client_height (vt->id)) * tot_lines + disp_lines/2; + if (vt->scroll < 0) { vt->scroll = 0.0; } + if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; } + vt->rev++; + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; + + switch (event->type) + { + case CTX_DRAG_PRESS: + scrollbar_down = 1; + break; + case CTX_DRAG_RELEASE: + scrollbar_down = 0; + break; + default: + break; + } +} + +#if 0 +static void scroll_handle_drag (CtxEvent *event, void *data, void *data2) +{ + VT *vt = data; + float tot_lines = vt->line_count + vt->scrollback_count; + if (event->type == CTX_DRAG_MOTION) + { + vt->scroll -= (event->delta_y * tot_lines) / (vt->rows * vt->ch); + } + if (vt->scroll < 0) { vt->scroll = 0.0; } + if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; } + vt->rev++; + event->stop_propagate = 1; +} +#endif + +#if 0 +static void test_popup (Ctx *ctx, void *data) +{ + VT *vt = data; + + float x = ctx_client_x (vt->id); + float y = ctx_client_y (vt->id); + ctx_rectangle (ctx, x, y, 100, 100); + ctx_rgb (ctx, 1,0,0); + ctx_fill (ctx); +} +#endif + +void itk_style_color (Ctx *ctx, const char *name); // only itk fun used in vt + +void vt_use_images (VT *vt, Ctx *ctx) +{ + /* this is a call intended for minimized/shaded fully obscured + * clients to make sure their textures are kept alive + * in the server + */ + //float x0=0; + float y0=0; + //vt->has_blink = 0; + //vt->blink_state++; + + ctx_save (ctx); + + { + /* draw graphics */ + for (int row = ((vt->scroll!=0.0f)?vt->scroll:0); + row < (vt->scroll) + vt->rows * 4; + row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + + if (row >= vt->rows && !vt->in_alt_screen) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + if (line->ctx_copy) + { + ctx_render_ctx_textures (line->ctx_copy, ctx); + } + } + } + } + ctx_restore (ctx); +} + + +void vt_register_events (VT *vt, Ctx *ctx, double x0, double y0) +{ + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_translate (ctx, x0, y0); + ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch); + ctx_listen (ctx, CTX_DRAG, vt_mouse_event, vt, NULL); + ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL); + ctx_begin_path (ctx); + ctx_restore (ctx); +} + +void vt_draw (VT *vt, Ctx *ctx, double x0, double y0) +{ + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_translate (ctx, x0, y0); + x0 = 0; + y0 = 0; + ctx_font (ctx, "mono"); + vt->font_is_mono = 0; + ctx_font_size (ctx, vt->font_size * vt->font_to_cell_scale); + vt->has_blink = 0; + vt->blink_state++; +#if 0 + int cursor_x_px = 0; + int cursor_y_px = 0; + int cursor_w = vt->cw; + int cursor_h = vt->ch; + cursor_x_px = x0 + (vt->cursor_x - 1) * vt->cw; + cursor_y_px = y0 + (vt->cursor_y - 1) * vt->ch; + cursor_w = vt->cw; + cursor_h = vt->ch; +#endif + ctx_save (ctx); + //if (vt->scroll || full) + { + ctx_begin_path (ctx); +#if 1 + ctx_rectangle (ctx, 0, 0, vt->width, //(vt->cols) * vt->cw, + (vt->rows) * vt->ch); + if (vt->reverse_video) + { + itk_style_color (ctx, "terminal-bg-reverse"); + ctx_fill (ctx); + } + else + { + itk_style_color (ctx, "terminal-bg"); + ctx_fill (ctx); + } +#endif + if (vt->scroll != 0.0f) + ctx_translate (ctx, 0.0, vt->ch * vt->scroll); + } + /* draw terminal lines */ + { + for (int row = (vt->scroll!=0.0f)?vt->scroll:0; row < (vt->scroll) + vt->rows; row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + if (row >= vt->rows) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + int r = vt->rows - row; + const char *data = line->string.str; + + vt->bg_active = 0; + for (int is_fg = 0; is_fg < 2; is_fg++) + { + const char *d = data; + float x = x0; + uint64_t style = 0; + uint32_t unichar = 0; + int in_scrolling_region = vt->in_smooth_scroll && + ((r >= vt->margin_top && r <= vt->margin_bottom) || r <= 0); + if (is_fg) + vt_flush_bg (vt, ctx); + + for (int col = 1; col <= vt->cols * 1.33 && x < vt->cols * vt->cw; col++) + { + int c = col; + int real_cw; + int in_selected_region = 0; + //if (vt->in_alt_screen == 0) + { + if (r > vt->select_start_row && r < vt->select_end_row) + { + in_selected_region = 1; + } + else if (r == vt->select_start_row) + { + if (col >= vt->select_start_col) { in_selected_region = 1; } + if (r == vt->select_end_row) + { + if (col > vt->select_end_col) { in_selected_region = 0; } + } + } + else if (r == vt->select_end_row) + { + in_selected_region = 1; + if (col > vt->select_end_col) { in_selected_region = 0; } + } + } + if (vt->select_active == 0) in_selected_region = 0; + style = vt_line_get_style (line, col-1); + unichar = d?ctx_utf8_to_unichar (d) :' '; + + int is_cursor = 0; + if (vt->cursor_x == col && vt->cursor_y == vt->rows - row && vt->cursor_visible) + is_cursor = 1; + + real_cw=vt_draw_cell (vt, ctx, r, c, x, y, style, unichar, + line->double_width, + line->double_height_top?1: + line->double_height_bottom?-1:0, + in_scrolling_region, + in_selected_region ^ is_cursor, is_fg); + if (r == vt->cursor_y && col == vt->cursor_x) + { +#if 0 + cursor_x_px = x; +#endif + } + x+=real_cw; + if (style & STYLE_BLINK || + style & STYLE_BLINK_FAST) + { + vt->has_blink = 1; + } + if (d) + { + d = mrg_utf8_skip (d, 1); + if (!*d) { d = NULL; } + } + } + } +#if 0 + if (line->wrapped) + { + ctx_rectangle (ctx, x0, y, 10, 10); + ctx_rgb (ctx, 1,0,0); + ctx_fill (ctx); + } +#endif + } + } + } + +#if 0 + /* draw cursor (done inline with fg/bg reversing, some cursor styles might need + * additional drawing though + */ + if (vt->cursor_visible) + { + // ctx_rgba (ctx, 0.9, 0.8, 0.0, 0.5333); + ctx_rgba (ctx, 1.0,1.0,1.0,1.0); + ctx_begin_path (ctx); + ctx_rectangle (ctx, + cursor_x_px, cursor_y_px, + cursor_w, cursor_h); + ctx_fill (ctx); + } +#endif + + + { + /* draw graphics */ + for (int row = ((vt->scroll!=0.0f)?vt->scroll:0); row < (vt->scroll) + vt->rows * 4; row ++) + { + CtxList *l = ctx_list_nth (vt->lines, row); + float y = y0 + vt->ch * (vt->rows - row); + + if (row >= vt->rows && !vt->in_alt_screen) + { + l = ctx_list_nth (vt->scrollback, row-vt->rows); + } + + if (l && y <= (vt->rows - vt->scroll) * vt->ch) + { + VtLine *line = l->data; + { + for (int i = 0; i < 4; i++) + { + Image *image = line->images[i]; + if (image) + { + int u = (line->image_col[i]-1) * vt->cw + (line->image_X[i] * vt->cw); + int v = y - vt->ch + (line->image_Y[i] * vt->ch); + // int rows = (image->height + (vt->ch-1) ) /vt->ch; + // + // + if (v + image->height +vt->scroll * vt->ch > 0.0 && + image->width && image->height /* some ghost images appear with these */ + ) + { + ctx_save (ctx); + ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols, + vt->ch * vt->rows); + ctx_clip (ctx); + char texture_n[65]; + + sprintf (texture_n, "vtimg%i", image->eid_no); + ctx_rectangle (ctx, u, v, image->width, image->height); + ctx_translate (ctx, u, v); + + //replace this texture_n with NULL to + // be content addressed - but bit slower + ctx_define_texture (ctx, texture_n, image->width, + image->height, + 0, + image->kitty_format == 32 ? + CTX_FORMAT_RGBA8 : + CTX_FORMAT_RGB8, + image->data, texture_n); + ctx_fill (ctx); + + ctx_restore (ctx); + } + } + } + + if (line->ctx_copy) + { + //fprintf (stderr, " [%i]\n", _ctx_frame (ctx)); + //ctx_render_stream (line->ctx_copy, stderr, 1); + + ctx_begin_path (ctx); + ctx_save (ctx); + ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols, + vt->ch * vt->rows); + ctx_clip (ctx); + ctx_translate (ctx, 0.0, y - vt->ch); + + //(vt->rows-row-1) * (vt->ch) ); + //float factor = vt->cols * vt->cw / 1000.0; + //ctx_scale (ctx, factor, factor); + // + ctx_render_ctx (line->ctx_copy, ctx); + ctx_restore (ctx); + } + } + } + // y -= vt->ch; + } + } + + + for (int i = 0; i < 4; i++) + { + if (vt->leds[i]) + { + ctx_rgba (ctx, .5,1,.5,0.8); + ctx_rectangle (ctx, vt->cw * i + vt->cw * 0.25, vt->ch * 0.25, vt->cw/2, vt->ch/2); + ctx_fill (ctx); + } + } + ctx_restore (ctx); +//#define SCROLL_SPEED 0.25; +#define SCROLL_SPEED 0.001; + if (vt->in_smooth_scroll) + { + if (vt->in_smooth_scroll<0) + { + vt->scroll_offset += SCROLL_SPEED; + if (vt->scroll_offset >= 0.0) + { + vt->scroll_offset = 0; + vt->in_smooth_scroll = 0; + vt->rev++; + } + } + else + { + vt->scroll_offset -= SCROLL_SPEED; + if (vt->scroll_offset <= 0.0) + { + vt->scroll_offset = 0; + vt->in_smooth_scroll = 0; + vt->rev++; + } + } + } + + /* scrollbar */ + if (!vt->in_alt_screen) + { + float disp_lines = vt->rows; + float tot_lines = vt->line_count + vt->scrollback_count; + float offset = (tot_lines - disp_lines - vt->scroll) / tot_lines; + float win_len = disp_lines / tot_lines; + +#if 0 + ctx_rectangle (ctx, (vt->cols *vt->cw), 0, + (vt->width) - (vt->cols * vt->cw), + vt->rows * vt->ch); + ctx_rgb (ctx,1,0,0); + ctx_fill (ctx); +#endif + + ctx_rectangle (ctx, (vt->width) - vt->cw * 1.5, + 0, 1.5 * vt->cw, + vt->rows * vt->ch); + //ctx_listen (ctx, CTX_DRAG, scrollbar_drag, vt, NULL); + //ctx_listen (ctx, CTX_ENTER, scrollbar_enter, vt, NULL); + //ctx_listen (ctx, CTX_LEAVE, scrollbar_leave, vt, NULL); + if (vt->scroll != 0 || scrollbar_focused) + ctx_rgba (ctx, 0.5, 0.5, 0.5, .25); + else + ctx_rgba (ctx, 0.5, 0.5, 0.5, .10); + ctx_fill (ctx); + ctx_round_rectangle (ctx, (vt->width) - vt->cw * 1.5, + offset * vt->rows * vt->ch, (1.5-0.2) * vt->cw, + win_len * vt->rows * vt->ch, + vt->cw * 1.5 /2); + //ctx_listen (ctx, CTX_DRAG, scroll_handle_drag, vt, NULL); + if (vt->scroll != 0 || scrollbar_focused) + ctx_rgba (ctx, 1, 1, 1, .25); + else + ctx_rgba (ctx, 1, 1, 1, .10); + ctx_fill (ctx); + } + + ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch); + ctx_listen (ctx, CTX_DRAG, vt_mouse_event, vt, NULL); + ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL); + ctx_begin_path (ctx); + + ctx_restore (ctx); + + if (vt->popped) + { + //ctx_set_popup (ctx, test_popup, vt); + } +} + + +int vt_is_done (VT *vt) +{ + return vt->vtpty.done; +} + +int vt_get_result (VT *vt) +{ + /* we could block - at least for a while, here..? */ + return vt->result; +} + +void vt_set_scrollback_lines (VT *vt, int scrollback_lines) +{ + vt->scrollback_limit = scrollback_lines; +} + +int vt_get_scrollback_lines (VT *vt) +{ + return vt->scrollback_limit; +} + +void vt_set_scroll (VT *vt, int scroll) +{ + if (vt->scroll == scroll) + return; + vt->scroll = scroll; + if (vt->scroll > ctx_list_length (vt->scrollback) ) + { vt->scroll = ctx_list_length (vt->scrollback); } + if (vt->scroll < 0) + { vt->scroll = 0; } +} + +int vt_get_scroll (VT *vt) +{ + return vt->scroll; +} + +char * +vt_get_selection (VT *vt) +{ + CtxString *str = ctx_string_new (""); + char *ret; + for (int row = vt->select_start_row; row <= vt->select_end_row; row++) + { + const char *line_str = vt_get_line (vt, vt->rows - row); + int col = 1; + for (const char *c = line_str; *c; c = mrg_utf8_skip (c, 1), col ++) + { + if (row == vt->select_end_row && col > vt->select_end_col) + { continue; } + if (row == vt->select_start_row && col < vt->select_start_col) + { continue; } + ctx_string_append_utf8char (str, c); + } + if (row < vt->select_end_row && !vt_line_is_continuation (vt, vt->rows-row-1)) + { + _ctx_string_append_byte (str, '\n'); + } + } + ret = str->str; + ctx_string_free (str, 0); + return ret; +} + +int vt_get_local (VT *vt) +{ + return vt->local_editing; +} + +void vt_set_local (VT *vt, int local) +{ + vt->local_editing = local; +} + +static unsigned long prev_press_time = 0; +static int short_count = 0; + + +void terminal_set_primary (const char *text) +{ + if (primary) free (primary); + primary = NULL; + if (text) primary = strdup (text); +} + +void terminal_long_tap (Ctx *ctx, VT *vt); +static int long_tap_cb_id = 0; +static int single_tap (Ctx *ctx, void *data) +{ +#if 0 // XXX + VT *vt = data; + if (short_count == 0 && !vt->select_active) + terminal_long_tap (ctx, vt); +#endif + return 0; +} + +void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y) +{ + char buf[64]=""; + int button_state = 0; + vt->rev++; + ctx_ticks(); + if ((! (vt->mouse | vt->mouse_all | vt->mouse_drag)) || + (event && (event->state & CTX_MODIFIER_STATE_SHIFT))) + { + // regular mouse select, this is incomplete + // fully ignorant of scrollback for now + // + if (type == VT_MOUSE_PRESS) + { + vt->cursor_down = 1; + vt->select_begin_col = x; + vt->select_begin_row = y - (int)vt->scroll; + vt->select_start_col = x; + vt->select_start_row = y - (int)vt->scroll; + vt->select_end_col = x; + vt->select_end_row = y - (int)vt->scroll; + vt->select_active = 0; + if (long_tap_cb_id) + { + ctx_remove_idle (vt->root_ctx, long_tap_cb_id); + long_tap_cb_id = 0; + } + + if ((ctx_ticks () - prev_press_time) < 1000*300 && + abs(px_x - vt->select_begin_x) + + abs(px_y - vt->select_begin_y) < 8) + { + short_count++; + switch (short_count) + { + case 1: + { + /* extend selection until space, XXX should handle utf8 instead of ascii here! */ + + int hit_space = 0; + + while (vt->select_start_col > 1 && !hit_space) + { + vt->select_start_col --; + char *sel = vt_get_selection (vt); + if (sel[0] == ' ' || sel[0] == '\0') + hit_space = 1; + free (sel); + } + if (hit_space) + vt->select_start_col++; + + hit_space = 0; + while ((hit_space == 0) && + (vt->select_end_col < vt->cols)) + { + vt->select_end_col ++; + char *sel = vt_get_selection (vt); + int len = strlen(sel); + if (sel[len-1]==' ') + hit_space = 1; + free (sel); + } + if (hit_space) + vt->select_end_col--; + + vt->select_active = 1; + + { char *sel = vt_get_selection (vt); + if (sel) + { + terminal_set_primary (sel); + free (sel); + } + } + } + break; + case 2: + vt->select_start_col = 1; + vt->select_end_col = vt->cols; + vt->select_active = 1; + { + char *sel = vt_get_selection (vt); + if (sel){ + terminal_set_primary (sel); + free (sel); + } + } + break; + case 3: + short_count = 0; + vt->select_start_col = + vt->select_end_col = vt->select_begin_col; + vt->select_active = 0; + terminal_set_primary (""); + break; + } + } + else + { + if (vt->root_ctx && short_count == 0) + long_tap_cb_id = ctx_add_timeout (vt->root_ctx, 1000, single_tap, vt); + short_count = 0; + //vt->select_start_col = + //vt->select_end_col = vt->select_begin_col; + } + vt->select_begin_x = px_x; + vt->select_begin_y = px_y; + prev_press_time = ctx_ticks (); + vt->rev++; + } + else if (type == VT_MOUSE_RELEASE) + { + if (long_tap_cb_id) + { + ctx_remove_idle (vt->root_ctx, long_tap_cb_id); + long_tap_cb_id = 0; + } + vt->cursor_down = 0; + } + else if (type == VT_MOUSE_MOTION && vt->cursor_down) + { + int row = y - (int)vt->scroll; + int col = x; + if ((row > vt->select_begin_row) || + ((row == vt->select_begin_row) && (col >= vt->select_begin_col))) + { + vt->select_start_col = vt->select_begin_col; + vt->select_start_row = vt->select_begin_row; + vt->select_end_col = col; + vt->select_end_row = row; + } + else + { + vt->select_start_col = col; + vt->select_start_row = row; + vt->select_end_col = vt->select_begin_col; + vt->select_end_row = vt->select_begin_row; + } + if (vt->select_end_row == vt->select_start_row && + abs (vt->select_begin_x - px_x) < vt->cw/2) + { + vt->select_active = 0; + } + else + { + vt->select_active = 1; + char *selection = vt_get_selection (vt); + if (selection) + { + terminal_set_primary (selection); + free (selection); + } + } + + if (y < 1) + { + vt->scroll += 1.0f; + if (vt->scroll > vt->scrollback_count) + vt->scroll = vt->scrollback_count; + } + else if (y > vt->rows) + { + vt->scroll -= 1.0f; + if (vt->scroll < 0) + vt->scroll = 0.0f; + } + + vt->rev++; + } + return; + } + if (type == VT_MOUSE_MOTION) + { button_state = 3; } + + if (vt->unit_pixels && vt->mouse_decimal) + { + x = px_x; + y = px_y; + } + switch (type) + { + case VT_MOUSE_MOTION: + if (!vt->mouse_all) + return; + if (x==vt->lastx && y==vt->lasty) + return; + vt->lastx = x; + vt->lasty = y; + // sprintf (buf, "\033[<35;%i;%iM", x, y); + break; + case VT_MOUSE_RELEASE: + if (vt->mouse_decimal == 0) + button_state = 3; + break; + case VT_MOUSE_PRESS: + button_state = 0; + break; + case VT_MOUSE_DRAG: // XXX not really used - remove + if (! (vt->mouse_all || vt->mouse_drag) ) + return; + button_state = 32; + break; + } + // todo : mix in ctrl/meta state + if (vt->mouse_decimal) + { + sprintf (buf, "\033[<%i;%i;%i%c", button_state, x, y, type == VT_MOUSE_RELEASE?'m':'M'); + } + else + { + sprintf (buf, "\033[M%c%c%c", button_state + 32, x + 32, y + 32); + } + if (buf[0]) + { + vt_write (vt, buf, strlen (buf) ); + fflush (NULL); + } +} + +pid_t vt_get_pid (VT *vt) +{ + return vt->vtpty.pid; +} + +void vt_set_ctx (VT *vt, Ctx *ctx) +{ + vt->root_ctx = ctx; +} +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#if !__COSMOPOLITAN__ +#include <unistd.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <pty.h> +#include <math.h> +#include <malloc.h> +#include <sys/time.h> +#include <time.h> +#endif + +#define VT_RECORD 0 +extern Ctx *ctx; +#define flag_is_set(a, f) (((a) & (f))!=0) +#define flag_set(a, f) ((a) |= (f)); +#define flag_unset(a, f) ((a) &= ~(f)); + +//#define CTX_x CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0) +//#define CTX_y CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0) +#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0) +#define CTX_lower CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0) +#define CTX_raise CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0) +#define CTX_raise_top CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0) +#define CTX_terminate CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0) +#define CTX_maximize CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0) +#define CTX_unmaximize CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0) +//#define CTX_width CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0) +//#define CTX_title CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0) +#define CTX_title 15643372 +#define CTX_action CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0) +//#define CTX_height CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0) + +void terminal_update_title (const char *title); +int ctx_renderer_is_sdl (Ctx *ctx); +int ctx_renderer_is_fb (Ctx *ctx); +int ctx_renderer_is_term (Ctx *ctx); +void ctx_sdl_set_fullscreen (Ctx *ctx, int val); +int ctx_sdl_get_fullscreen (Ctx *ctx); +float ctx_target_fps = 25.0; +static int ctx_fetched_bytes = 1; + +CtxClient *vt_get_client (VT *vt); + +CtxList *vts = NULL; + +void ctx_clients_signal_child (int signum) +{ + pid_t pid; + int status; + if ( (pid = waitpid (-1, &status, WNOHANG) ) != -1) + { + if (pid) + { + for (CtxList *l = vts; l; l=l->next) + { + VtPty *vt = l->data; + if (vt->pid == pid) + { + vt->done = 1; + //vt->result = status; + } + } + } + } +} + + + +int vt_set_prop (VT *vt, uint32_t key_hash, const char *val) +{ +#if 1 + switch (key_hash) + { + case CTX_title: + { + CtxClient *client = vt_get_client (vt); + if (client) + { + if (client->title) free (client->title); + client->title = strdup (val); + } + } + + break; + } +#else + float fval = strtod (val, NULL); + CtxClient *client = ctx_client_by_id (ct->id); + uint32_t val_hash = ctx_strhash (val, 0); + if (!client) + return 0; + + if (key_hash == ctx_strhash("start_move", 0)) + { + start_moving (client); + moving_client = 1; + return 0; + } + +// set "pcm-hz" "8000" +// set "pcm-bits" "8" +// set "pcm-encoding" "ulaw" +// set "play-pcm" "d41ata312313" +// set "play-pcm-ref" "foo.wav" + +// get "free" +// storage of blobs for referencing when drawing or for playback +// set "foo.wav" "\3\1\1\4\" +// set "fnord.png" "PNG12.4a312" + + switch (key_hash) + { + case CTX_title: ctx_client_set_title (ct->id, val); break; + case CTX_x: client->x = fval; break; + case CTX_y: client->y = fval; break; + case CTX_width: ctx_client_resize (ct->id, fval, client->height); break; + case CTX_height: ctx_client_resize (ct->id, client->width, fval); break; + case CTX_action: + switch (val_hash) + { + case CTX_maximize: ctx_client_maximize (client); break; + case CTX_unmaximize: ctx_client_unmaximize (client); break; + case CTX_lower: ctx_client_lower (client); break; + case CTX_lower_bottom: ctx_client_lower_bottom (client); break; + case CTX_raise: ctx_client_raise (client); break; + case CTX_raise_top: ctx_client_raise_top (client); break; + } + break; + } + ct->rev++; +#endif + return 0; +} + +static float _ctx_font_size = 10.0; + +CtxList *clients = NULL; +CtxClient *active = NULL; +CtxClient *active_tab = NULL; + +static CtxClient *ctx_client_by_id (int id); + +int ctx_client_resize (int id, int width, int height); +void ctx_client_maximize (int id); + +CtxClient *vt_get_client (VT *vt) +{ + for (CtxList *l = clients; l; l =l->next) + { + CtxClient *client = l->data; + if (client->vt == vt) + return client; + } + return NULL; +} + +CtxClient *ctx_client_new (Ctx *ctx, + const char *commandline, + int x, int y, int width, int height, + CtxClientFlags flags) +{ + static int global_id = 0; + float font_size = ctx_get_font_size (ctx); + CtxClient *client = calloc (sizeof (CtxClient), 1); + ctx_list_append (&clients, client); + client->id = global_id++; + client->x = x; + client->y = y; + client->flags = flags; + client->ctx = ctx; + client->width = width; + client->height = height; + + if (ctx_renderer_is_term (ctx)) + { + font_size = 3; + } + + //fprintf (stderr, "client new:%f\n", font_size); +#if CTX_THREADS + mtx_init (&client->mtx, mtx_plain); +#endif + float line_spacing = 2.0f; + client->vt = vt_new (commandline, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0); + vt_set_ctx (client->vt, ctx); + return client; +} + +CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags) +{ + CtxString *string = ctx_string_new (""); + for (int i = 0; argv[i]; i++) + { + char space = ' '; + if (i > 0) + ctx_string_append_data (string, &space, 1); + for (int c = 0; argv[i][c]; c++) + { + switch (argv[i][c]) + { + case '"':ctx_string_append_str (string, "\\\"");break; + case '\'':ctx_string_append_str (string, "\\\'");break; + default:ctx_string_append_data (string, &argv[i][c], 1);break; + } + } + } + CtxClient *ret = ctx_client_new (ctx, string->str, x, y, width, height, flags); + ctx_string_free (string, 1); + return ret; +} + +extern float ctx_shape_cache_rate; +extern int _ctx_max_threads; + +static int focus_follows_mouse = 0; + +static CtxClient *find_active (int x, int y) +{ + CtxClient *ret = NULL; + float titlebar_height = _ctx_font_size; + int resize_border = titlebar_height/2; + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *c = l->data; + if ((c->flags & ITK_CLIENT_MAXIMIZED) && c == active_tab) + if (x > c->x - resize_border && x < c->x+c->width + resize_border && + y > c->y - titlebar_height && y < c->y+c->height + resize_border) + { + ret = c; + } + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *c = l->data; + if (!(c->flags & ITK_CLIENT_MAXIMIZED)) + if (x > c->x - resize_border && x < c->x+c->width + resize_border && + y > c->y - titlebar_height && y < c->y+c->height + resize_border) + { + ret = c; + } + } + return ret; +} + +int id_to_no (int id) +{ + CtxList *l; + int no = 0; + + for (l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->id == id) + return no; + no++; + } + return -1; +} + +void ctx_client_move (int id, int x, int y); +int ctx_client_resize (int id, int w, int h); +void ctx_client_shade_toggle (int id); +float ctx_client_min_y_pos (Ctx *ctx); +float ctx_client_max_y_pos (Ctx *ctx); + +#if 0 +void ensure_layout () +{ + int n_clients = ctx_list_length (clients); + if (n_clients == 1) + { + CtxClient *client = clients->data; + if (client->flags & ITK_CLIENT_MAXIMIZED) + { + ctx_client_move (client->id, 0, 0); + ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx)); + if (active_tab == NULL) + active_tab = client; + } + } + else + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->flags & ITK_CLIENT_MAXIMIZED) + { + ctx_client_move (client->id, 0, client_min_y_pos (ctx)); + ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx) - + ctx_client_min_y_pos (ctx) / 2); // /2 to counter the double titlebar of non-maximized + if (active_tab == NULL) + active_tab = client; + } + } +} +#endif + +static CtxClient *ctx_client_by_id (int id) +{ + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->id == id) + return client; + } + return NULL; +} + +void ctx_client_remove (Ctx *ctx, CtxClient *client) +{ + ctx_client_lock (client); + if (!client->internal) + { + + if (client->vt) + vt_destroy (client->vt); + } + + if (client->title) + free (client->title); + +#if VT_RECORD + if (client->recording) + ctx_free (client->recording); +#endif + + ctx_list_remove (&clients, client); + + if (client == active_tab) + { + active_tab = NULL; + } + + if (ctx) + if (client == active) + { + active = find_active (ctx_pointer_x (ctx), ctx_pointer_y (ctx)); + if (!active) active = clients?clients->data:NULL; + } + + ctx_client_unlock (client); + free (client); + //ensure_layout(); +} + +#if 0 +void ctx_client_remove_by_id (int id) +{ + int no = id_to_no (id); + if (no>=0) + ctx_client_remove (no); +} +#endif + +int ctx_client_height (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->height; +} + +int ctx_client_x (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->x; +} + +int ctx_client_y (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return 0; + return client->y; +} + +void ctx_client_raise_top (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + ctx_list_remove (&clients, client); + ctx_list_append (&clients, client); +} + +void ctx_client_lower_bottom (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + ctx_list_remove (&clients, client); + ctx_list_prepend (&clients, client); +} + + +void ctx_client_iconify (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags |= ITK_CLIENT_ICONIFIED; +} + +int ctx_client_is_iconified (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_ICONIFIED) != 0; +} + +void ctx_client_uniconify (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags &= ~ITK_CLIENT_ICONIFIED; +} + +void ctx_client_maximize (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (client->flags & ITK_CLIENT_MAXIMIZED) + return; + client->flags |= ITK_CLIENT_MAXIMIZED; + client->unmaximized_x = client->x; + client->unmaximized_y = client->y; + client->unmaximized_width = client->width; + client->unmaximized_height = client->height; + + // enforce_layout does the size + //client_resize (id, ctx_width (ctx), ctx_height(ctx) - ctx_client_min_y_pos (ctx)); + + ctx_client_move (id, 0, ctx_client_min_y_pos (client->ctx)); + active_tab = client; +} + +int ctx_client_is_maximized (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_MAXIMIZED) != 0; +} + +void ctx_client_unmaximize (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if ((client->flags & ITK_CLIENT_MAXIMIZED) == 0) + return; + client->flags &= ~ITK_CLIENT_MAXIMIZED; + ctx_client_resize (id, client->unmaximized_width, client->unmaximized_height); + ctx_client_move (id, client->unmaximized_x, client->unmaximized_y); + active_tab = NULL; +} + +void ctx_client_maximized_toggle (int id) +{ + if (ctx_client_is_maximized (id)) + ctx_client_unmaximize (id); + else + ctx_client_maximize (id); +} + + +void ctx_client_shade (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags |= ITK_CLIENT_SHADED; +} + +int ctx_client_is_shaded (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return -1; + return (client->flags & ITK_CLIENT_SHADED) != 0; +} + +void ctx_client_unshade (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + client->flags &= ~ITK_CLIENT_SHADED; +} + +void ctx_client_toggle_maximized (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (ctx_client_is_maximized (id)) + ctx_client_unmaximize (id); + else + ctx_client_maximize (id); +} + +void ctx_client_shade_toggle (int id) +{ + CtxClient *client = ctx_client_by_id (id); + if (!client) return; + if (ctx_client_is_shaded (id)) + ctx_client_shade (id); + else + ctx_client_unshade (id); +} + +void ctx_client_move (int id, int x, int y) +{ + CtxClient *client = ctx_client_by_id (id); + if (client && (client->x != x || client->y != y)) + { + client->x = x; + client->y = y; + vt_rev_inc (client->vt); + } +} + +int ctx_client_resize (int id, int width, int height) +{ + CtxClient *client = ctx_client_by_id (id); + + if (client && ((height != client->height) || (width != client->width) )) + { + client->width = width; + client->height = height; + if (client->vt) + vt_set_px_size (client->vt, width, height); + return 1; + } + return 0; +} + +static void ctx_client_titlebar_drag (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + if (event->type == CTX_DRAG_RELEASE) + { + static int prev_drag_end_time = 0; + if (event->time - prev_drag_end_time < 500) + { + //client_shade_toggle (client->id); + ctx_client_maximized_toggle (client->id); + } + prev_drag_end_time = event->time; + } + + float new_x = client->x + event->delta_x; + float new_y = client->y + event->delta_y; + + float snap_threshold = 8; + + if (ctx_renderer_is_term (event->ctx)) + snap_threshold = 1; + + if (new_y < ctx_client_min_y_pos (event->ctx)) new_y = ctx_client_min_y_pos (event->ctx); + if (new_y > ctx_client_max_y_pos (event->ctx)) new_y = ctx_client_max_y_pos (event->ctx); + + if (fabs (new_x - 0) < snap_threshold) new_x = 0.0; + if (fabs (ctx_width (event->ctx) - (new_x + client->width)) < snap_threshold) new_x = ctx_width (event->ctx) - client->width; + + ctx_client_move (client->id, new_x, new_y); + + //vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + + event->stop_propagate = 1; +} + +static float min_win_dim = 32; + +static void ctx_client_resize_se (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_w = client->width + event->delta_x; + int new_h = client->height + event->delta_y; + if (new_w <= min_win_dim) new_w = min_win_dim; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_e (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_w = client->width + event->delta_x; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, client->height); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_s (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + int new_h = client->height + event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, client->width, new_h); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_n (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_y = client->y + event->delta_y; + int new_h = client->height - event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + ctx_client_resize (client->id, client->width, new_h); + ctx_client_move (client->id, client->x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_ne (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_y = client->y + event->delta_y; + int new_h = client->height - event->delta_y; + int new_w = client->width + event->delta_x; + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, client->x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_sw (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + float new_x = client->x + event->delta_x; + int new_w = client->width - event->delta_x; + int new_h = client->height + event->delta_y; + + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, new_x, client->y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_nw (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + float new_x = client->x + event->delta_x; + float new_y = client->y + event->delta_y; + int new_w = client->width - event->delta_x; + int new_h = client->height - event->delta_y; + if (new_h <= min_win_dim) new_h = min_win_dim; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, new_h); + ctx_client_move (client->id, new_x, new_y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +static void ctx_client_resize_w (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + float new_x = client->x + event->delta_x; + int new_w = client->width - event->delta_x; + if (new_w <= min_win_dim) new_w = min_win_dim; + ctx_client_resize (client->id, new_w, client->height); + ctx_client_move (client->id, new_x, client->y); + if (client->vt) // force redraw + vt_rev_inc (client->vt); + ctx_set_dirty (event->ctx, 1); + + event->stop_propagate = 1; +} + +static void ctx_client_close (CtxEvent *event, void *data, void *data2) +{ + //Ctx *ctx = event->ctx; + CtxClient *client = data; + + // client->do_quit = 1; + + ctx_client_remove (event->ctx, client); + + ctx_set_dirty (event->ctx, 1); + event->stop_propagate = 1; +} + +/********************/ +void vt_use_images (VT *vt, Ctx *ctx); +float _ctx_green = 0.5; + +static void ctx_client_draw (Ctx *ctx, CtxClient *client, float x, float y) +{ + if (client->internal) + { +#if 0 + ctx_save (ctx); + + ctx_translate (ctx, x, y); + int width = client->width; + int height = client->height; + + itk_panel_start (itk, "", 0, 0, width, height); + //itk_seperator (itk); +#if 0 + if (itk_button (itk, "add tab")) + { + add_tab (vt_find_shell_command(), 1); + } +#endif + //itk_sameline (itk); + //itk_toggle (itk, "on screen keyboard", &on_screen_keyboard); + //itk_toggle (itk, "focus follows mouse", &focus_follows_mouse); + itk_slider_float (itk, "CTX_GREEN", &_ctx_green, 0.0, 1.0, 0.5); + itk_ctx_settings (itk); + itk_itk_settings (itk); + + itk_panel_end (itk); + itk_done (itk); + //itk_key_bindings (itk); + + ctx_restore (ctx); +#endif + } + else + { + ctx_client_lock (client); + + int found = 0; + for (CtxList *l2 = clients; l2; l2 = l2->next) + if (l2->data == client) found = 1; + if (found) + { + + int rev = vt_rev (client->vt); +#if VT_RECORD + if (client->drawn_rev != rev) + { + if (!client->recording) + client->recording = ctx_new (); + else + ctx_reset (client->recording); + vt_draw (client->vt, client->recording, 0.0, 0.0); + } + + if (client->recording) + { + ctx_save (ctx); + ctx_translate (ctx, x, y); + ctx_render_ctx (client->recording, ctx); + vt_register_events (client->vt, ctx, 0.0, 0.0); + ctx_restore (ctx); + } +#else + + vt_draw (client->vt, ctx, x, y); + vt_register_events (client->vt, ctx, x, y); +#endif + client->drawn_rev = rev; + ctx_client_unlock (client); + } + } +} + +static void ctx_client_use_images (Ctx *ctx, CtxClient *client) +{ + if (!client->internal) + { + uint32_t rev = vt_rev (client->vt); +#if VT_RECORD + if (client->drawn_rev != rev) + { + if (!client->recording) + client->recording = ctx_new (); + else + ctx_reset (client->recording); + vt_draw (client->vt, client->recording, 0.0, 0.0); + } + + if (client->recording) + { + ctx_save (ctx); + ctx_render_ctx_textures (client->recording, ctx); + ctx_restore (ctx); + } +#else + vt_use_images (client->vt, ctx); +#endif + client->drawn_rev = rev; + } +} + +void ctx_client_lock (CtxClient *client) +{ +#if CTX_THREADS + mtx_lock (&client->mtx); +#endif +} + +void ctx_client_unlock (CtxClient *client) +{ +#if CTX_THREADS + mtx_unlock (&client->mtx); +#endif +} + +#if 0 +void ctx_client_handle_event (Ctx *ctx, CtxEvent *ctx_event, const char *event) +{ + if (!active) + return; + if (active->internal) + return; + VT *vt = active->vt; + CtxClient *client = vt_get_client (vt); + + ctx_client_lock (client); + + if (!strcmp (event, "F11")) + { +#ifndef NO_SDL + if (ctx_renderer_is_sdl (ctx)) + { + ctx_sdl_set_fullscreen (ctx, !ctx_sdl_get_fullscreen (ctx)); + } +#endif + } + else if (!strcmp (event, "shift-return")) + { + vt_feed_keystring (vt, ctx_event, "return"); + } + else if (!strcmp (event, "shift-control-v") ) + { + char *text = ctx_get_clipboard (ctx); + if (text) + { + if (vt) + vt_paste (vt, text); + free (text); + } + } + else if (!strcmp (event, "shift-control-c") && vt) + { + char *text = vt_get_selection (vt); + if (text) + { + ctx_set_clipboard (ctx, text); + free (text); + } + } + else if (!strcmp (event, "shift-control-t") || + ((ctx_renderer_is_fb (ctx) || ctx_renderer_is_term (ctx)) + && !strcmp (event, "control-t") )) + { + //XXX add_tab (vt_find_shell_command(), 1); + } + else if (!strcmp (event, "shift-control-n") ) + { + pid_t pid; + if ( (pid=fork() ) ==0) + { + unsetenv ("CTX_VERSION"); + // execlp (execute_self, execute_self, NULL); + exit (0); + } + } + +#if 0 + else if (!strcmp (event, "alt-1")) switch_to_tab(0); + else if (!strcmp (event, "alt-2")) switch_to_tab(1); + else if (!strcmp (event, "alt-3")) switch_to_tab(2); + else if (!strcmp (event, "alt-4")) switch_to_tab(3); + else if (!strcmp (event, "alt-5")) switch_to_tab(4); + else if (!strcmp (event, "alt-6")) switch_to_tab(5); + else if (!strcmp (event, "alt-7")) switch_to_tab(6); + else if (!strcmp (event, "alt-8")) switch_to_tab(7); + else if (!strcmp (event, "alt-9")) switch_to_tab(8); + else if (!strcmp (event, "alt-0")) switch_to_tab(9); +#endif + else if (!strcmp (event, "shift-control-q") ) + { + ctx_quit (ctx); + } + else if (!strcmp (event, "shift-control-w") ) + { + active->do_quit = 1; + } + else if (!strcmp (event, "shift-control-s") ) + { + if (vt) + { + char *sel = vt_get_selection (vt); + if (sel) + { + vt_feed_keystring (vt, ctx_event, sel); + free (sel); + } + } + } + else + { + if (vt) + vt_feed_keystring (vt, ctx_event, event); + } + ctx_client_unlock (client); +} +#endif + +static int ctx_clients_dirty_count (void) +{ + int changes = 0; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if ((client->drawn_rev != vt_rev (client->vt) ) || + vt_has_blink (client->vt)) + changes++; + } + return changes; +} + +static void ctx_client_titlebar_drag_maximized (CtxEvent *event, void *data, void *data2) +{ + CtxClient *client = data; + + active = active_tab = client; + if (event->type == CTX_DRAG_RELEASE) + { + static int prev_drag_end_time = 0; + if (event->time - prev_drag_end_time < 500) + { + //client_shade_toggle (client->id); + ctx_client_unmaximize (client->id); + ctx_client_raise_top (client->id); + active_tab = NULL; + } + prev_drag_end_time = event->time; + } + ctx_set_dirty (event->ctx, 1); + vt_rev_inc (client->vt); + event->stop_propagate = 1; +} + +float ctx_client_min_y_pos (Ctx *ctx) +{ + return _ctx_font_size * 2; // a titlebar and a panel +} + +float ctx_client_max_y_pos (Ctx *ctx) +{ + return ctx_height (ctx); +} + +void ctx_client_titlebar_draw (Ctx *ctx, CtxClient *client, + float x, float y, float width, float titlebar_height) +{ +#if 0 + ctx_move_to (ctx, x, y + height * 0.8); + if (client == active) + ctx_rgba (ctx, 1, 1,0.4, 1.0); + else + ctx_rgba (ctx, 1, 1,1, 0.8); + ctx_text (ctx, client->title); +#else + ctx_rectangle (ctx, x, y - titlebar_height, + width, titlebar_height); + if (client == active) + itk_style_color (ctx, "titlebar-focused-bg"); + else + itk_style_color (ctx, "titlebar-bg"); + + if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y == titlebar_height) + { + ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag_maximized, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL); + } + else + { + ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL); + } + ctx_fill (ctx); + //ctx_font_size (ctx, itk->font_size);//titlebar_height);// * 0.85); + + if (client == active && + (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y != titlebar_height)) +#if 1 + ctx_rectangle (ctx, x + width - titlebar_height, + y - titlebar_height, titlebar_height, + titlebar_height); +#endif + ctx_rgb (ctx, 1, 0,0); + ctx_listen (ctx, CTX_PRESS, ctx_client_close, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_ARROW); + //ctx_fill (ctx); + ctx_begin_path (ctx); + ctx_move_to (ctx, x + width - titlebar_height * 0.8, y - titlebar_height * 0.22); + if (client == active) + itk_style_color (ctx, "titlebar-focused-close"); + else + itk_style_color (ctx, "titlebar-close"); + ctx_text (ctx, "X"); + + ctx_move_to (ctx, x + width/2, y - titlebar_height * 0.22); + if (client == active) + itk_style_color (ctx, "titlebar-focused-fg"); + else + itk_style_color (ctx, "titlebar-fg"); + + ctx_save (ctx); + ctx_text_align (ctx, CTX_TEXT_ALIGN_CENTER); + if (client->title) + ctx_text (ctx, client->title); + else + ctx_text (ctx, "untitled"); + ctx_restore (ctx); +#endif +} + +#if 0 +static void key_down (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "down %i %s\n", event->unicode, event->string); +} +static void key_up (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "up %i %s\n", event->unicode, event->string); +} +static void key_press (CtxEvent *event, void *data1, void *data2) +{ + fprintf (stderr, "press %i %s\n", event->unicode, event->string); +} +#endif + +int ctx_clients_draw (Ctx *ctx) +{ + _ctx_font_size = ctx_get_font_size (ctx); + float titlebar_height = _ctx_font_size; + int n_clients = ctx_list_length (clients); + + if (active && flag_is_set(active->flags, ITK_CLIENT_MAXIMIZED) && n_clients == 1) + { + ctx_client_draw (ctx, active, 0, 0); + return 0; + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED)) + { + if (client == active_tab) + { + ctx_client_draw (ctx, client, 0, titlebar_height); + } + else + { + ctx_client_use_images (ctx, client); + } + } + } + + { + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + VT *vt = client->vt; + if (vt && !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED)) + { + if (flag_is_set(client->flags, ITK_CLIENT_SHADED)) + { + ctx_client_use_images (ctx, client); + } + else + { + ctx_client_draw (ctx, client, client->x, client->y); + } + + // resize regions + if (client == active && + !flag_is_set(client->flags, ITK_CLIENT_SHADED) && + !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) && + flag_is_set(client->flags, ITK_CLIENT_UI_RESIZABLE)) + { + itk_style_color (ctx, "titlebar-focused-bg"); + + ctx_rectangle (ctx, + client->x, + client->y - titlebar_height * 2, + client->width, titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_n, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_N); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x, + client->y + client->height - titlebar_height, + client->width, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_s, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_S); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width, + client->y - titlebar_height, + titlebar_height, client->height + titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_e, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_E); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y - titlebar_height, + titlebar_height, client->height + titlebar_height); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_w, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_W); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width - titlebar_height, + client->y - titlebar_height * 2, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_ne, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NE); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y - titlebar_height * 2, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_nw, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NW); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x - titlebar_height, + client->y + client->height - titlebar_height, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_sw, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SW); + ctx_begin_path (ctx); + + ctx_rectangle (ctx, + client->x + client->width - titlebar_height, + client->y + client->height - titlebar_height, + titlebar_height * 2, titlebar_height * 2); + ctx_listen (ctx, CTX_DRAG, ctx_client_resize_se, client, NULL); + ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SE); + ctx_begin_path (ctx); + + } + + if (client->flags & ITK_CLIENT_TITLEBAR) + ctx_client_titlebar_draw (ctx, client, client->x, client->y, client->width, titlebar_height); + } + } + } + return 0; +} + +extern int _ctx_enable_hash_cache; + +void vt_audio_task (VT *vt, int click); + +int ctx_input_pending (Ctx *ctx, int timeout); + +int ctx_clients_need_redraw (Ctx *ctx) +{ + int changes = 0; + int follow_mouse = focus_follows_mouse; + CtxList *to_remove = NULL; + //ensure_layout (); + +// if (print_shape_cache_rate) +// fprintf (stderr, "\r%f ", ctx_shape_cache_rate); + + CtxClient *client = find_active (ctx_pointer_x (ctx), + ctx_pointer_y (ctx)); + + if (follow_mouse || ctx_pointer_is_down (ctx, 0) || + ctx_pointer_is_down (ctx, 1) || (active==NULL)) + { + if (client) + { + if (active != client) + { + active = client; + if (follow_mouse == 0 || + (ctx_pointer_is_down (ctx, 0) || + ctx_pointer_is_down (ctx, 1))) + { + //if (client != clients->data) + #if 1 + if ((client->flags & ITK_CLIENT_MAXIMIZED)==0) + { + ctx_list_remove (&clients, client); + ctx_list_append (&clients, client); + } +#endif + } + changes ++; + } + } + } + + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + if (client->vt) + { + if (vt_is_done (client->vt)) + ctx_list_prepend (&to_remove, client); + } + } + for (CtxList *l = to_remove; l; l = l->next) + { + ctx_client_remove (ctx, l->data); + changes++; + } + while (to_remove) + { + ctx_list_remove (&to_remove, to_remove->data); + } + + changes += ctx_clients_dirty_count (); + return changes != 0; +} + +float ctx_avg_bytespeed = 0.0; + +static void ctx_client_handle_events_iteration (Ctx *ctx) +{ + static int fail_safe = 0; + //int n_clients = ctx_list_length (clients); + int pending_data = 0; + long time_start = ctx_ticks (); + int sleep_time = 1000000/ctx_target_fps; + + pending_data = ctx_input_pending (ctx, sleep_time); + + ctx_fetched_bytes = 0; + if (pending_data || fail_safe>100) + { + if (!pending_data)pending_data = 1; + /* record amount of time spent - and adjust time of reading for + * vts? + */ + long int fractional_sleep = sleep_time / pending_data; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + ctx_client_lock (client); + int found = 0; + for (CtxList *l2 = clients; l2; l2 = l2->next) + if (l2->data == client) found = 1; + if (!found) + goto done; + + ctx_fetched_bytes += vt_poll (client->vt, fractional_sleep); + //ctx_fetched_bytes += vt_poll (client->vt, sleep_time); //fractional_sleep); + ctx_client_unlock (client); + } +done: + fail_safe = 0; + } + else + { + fail_safe ++; + for (CtxList *l = clients; l; l = l->next) + { + CtxClient *client = l->data; + vt_audio_task (client->vt, 0); + } + } + + //int got_events = 0; + + //while (ctx_get_event (ctx)) { } +#if 0 + if (changes /*|| pending_data */) + { + ctx_target_fps *= 1.6; + if (ctx_target_fps > 60) ctx_target_fps = 60; + } + else + { + ctx_target_fps = ctx_target_fps * 0.95 + 30.0 * 0.05; + + // 20fps is the lowest where sun 8bit ulaw 8khz works reliably + } + + if (ctx_avg_bytespeed > 1024 * 1024) ctx_target_fps = 10.0; + + if (_ctx_green < 0.4) + ctx_target_fps = 120.0; + else if (_ctx_green > 0.6) + ctx_target_fps = 25.0; + + //ctx_target_fps = 30.0; +#else + ctx_target_fps = 30.0; +#endif + + long time_end = ctx_ticks (); + + int timed = (time_end-time_start); + float bytespeed = ctx_fetched_bytes / ((timed)/ (1000.0f * 1000.0f)); + + ctx_avg_bytespeed = bytespeed * 0.2 + ctx_avg_bytespeed * 0.8; +#if 0 + fprintf (stderr, "%.2fmb/s %i/%i %.2f \r", ctx_avg_bytespeed/1024/1024, ctx_fetched_bytes, timed, ctx_target_fps); +#endif +} + + +static int ctx_clients_handle_events_fun (void *data) +{ + Ctx *ctx = data; + while (!ctx->quit) + { + int n_clients = ctx_list_length (clients); + ctx_client_handle_events_iteration (data); + switch (n_clients) + { + case 0: + usleep (1000 * 10); + break; + case 1: + usleep (1); // letting quit work - and also makes framerate for dump + break; + default: + usleep (0); // the switching between clients should be enough + break; + } + } + return 0; +} + +void ctx_clients_handle_events (Ctx *ctx) +{ +#if CTX_THREADS==0 + ctx_client_handle_events_iteration (ctx); +#else + static thrd_t tid = 0; + if (tid == 0) + { + thrd_create (&tid, (void*)ctx_clients_handle_events_fun, ctx); + } +#endif +} + +#endif /* CTX_VT */ +#endif // __CTX_H__ diff --git a/lib/ctx/fira-mono.ttf b/lib/ctx/fira-mono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3910f17e894307cfb06b09d4565161c5c6f8692f Binary files /dev/null and b/lib/ctx/fira-mono.ttf differ diff --git a/lib/ctx/license-ctx.txt b/lib/ctx/license-ctx.txt new file mode 100644 index 0000000000000000000000000000000000000000..4587919e3a408207b490145d29c0fb54fd4a9c51 --- /dev/null +++ b/lib/ctx/license-ctx.txt @@ -0,0 +1,14 @@ +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/>. + +2002, 2012, 2015, 2019, 2020 Øyvind Kolås <pippin@gimp.org> diff --git a/lib/ctx/meson.build b/lib/ctx/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..a4b660e3914fd9c89d21209a940578bafce37978 --- /dev/null +++ b/lib/ctx/meson.build @@ -0,0 +1,41 @@ +includes = include_directories( + './', +) + +libm_native = meson.get_compiler('c', native: true).find_library('m') + +ctx_fontgen_bin = executable( + 'ctx-fontgen', + 'ctx-fontgen.c', + native: true, + include_directories: includes, + dependencies: libm_native, + c_args: ['-w', '-DNO_LIBCURL'] +) + +ctx_fontgen = generator( + ctx_fontgen_bin, + output: 'font-@BASENAME@.h', + arguments: ['@INPUT@', 'mono', 'ascii-extras'], + capture: true, +) + +font = ctx_fontgen.process('fira-mono.ttf') + +sources = files( + 'ctx.c', +) + +lib = static_library( + 'ctx', + sources, + font, + include_directories: includes, + dependencies: periphdriver, + c_args: ['-O3', '-w'] +) + +libctx = declare_dependency( + include_directories: includes, + link_with: lib, +) diff --git a/lib/ctx/stb_truetype.h b/lib/ctx/stb_truetype.h new file mode 100644 index 0000000000000000000000000000000000000000..62595a15fdf6b2d381d14e1798bb499cd4595480 --- /dev/null +++ b/lib/ctx/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1). +// +// Advancing for the next character: +// Call GlyphHMetrics, and compute 'current_point += SF * advance'. +// +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include <math.h> + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include <math.h> + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <string.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i<lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); + stbtt_uint8 *lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); + stbtt_uint8 *subTableOffsets = lookupTable + 6; + switch(lookupType) { + case 2: { // Pair Adjustment Positioning Subtable + stbtt_int32 sti; + for (sti=0; sti<subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); + stbtt_uint8 *table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(table); + stbtt_uint16 coverageOffset = ttUSHORT(table + 2); + stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); + if (coverageIndex == -1) continue; + + switch (posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(table + 8); + stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); + stbtt_uint8 *pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); + stbtt_uint8 *pairValueArray = pairValueTable + 2; + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + STBTT_assert(coverageIndex < pairSetCount); + STBTT__NOTUSED(pairSetCount); + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i<numEntries; i++) { + stbtt_uint8 *svg_doc = svg_docs + (12 * i); + if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +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. +------------------------------------------------------------------------------ +*/ diff --git a/lib/meson.build b/lib/meson.build index 7f094776ff8feb56a9faa009ac06cf64df2b6804..275264f454224c964a392501a437ae1fb283578f 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -8,6 +8,7 @@ subdir('./vendor/Maxim/MAX77650/') subdir('./vendor/Maxim/MAX86150/') subdir('./vendor/Maxim/rd117_mbed/') subdir('./gfx/') +subdir('./ctx/') subdir('./FreeRTOS/') subdir('./FreeRTOS-Plus/') diff --git a/tools/code-style.sh b/tools/code-style.sh index c90ce11c2e3fe0cea1ad8ca3ea8c9fe120f6a763..525e5fdaca04f52fb6f029cb7b935f5814c5ad19 100755 --- a/tools/code-style.sh +++ b/tools/code-style.sh @@ -46,6 +46,7 @@ formatter_blacklist=( lib/mx25lba/ lib/sdk/ lib/vendor/ + lib/ctx/ctx-fontgen.c openocd/ docker/ Documentation/hawkmoth/