/* $XTermId: graphics_regis.c,v 1.126 2020/10/14 19:04:43 tom Exp $ */ /* * Copyright 2014-2019,2020 by Ross Combs * Copyright 2014-2019,2020 by Thomas E. Dickey * * All Rights Reserved * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) 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. * * Except as contained in this notice, the name(s) of the above copyright * holders shall not be used in advertising or otherwise to promote the * sale, use or other dealings in this Software without prior written * authorization. */ #include #include #include #include #include #include #include #include #include #include /* get rid of shadowing warnings (we will not draw Bessel functions) */ #define y1 my_y1 #define y0 my_y0 #define SCALE_FIXED_POINT 16U #undef DEBUG_PARSING #undef DEBUG_ALPHABET_LOOKUP #undef DEBUG_ALPHABETS #undef DEBUG_BEZIER #undef DEBUG_SPLINE_SEGMENTS #undef DEBUG_SPLINE_POINTS #undef DEBUG_SPLINE_WITH_ROTATION #undef DEBUG_SPLINE_WITH_OVERDRAW #undef DEBUG_ARC_POINTS #undef DEBUG_ARC_CENTER #undef DEBUG_ARC_START #undef DEBUG_ARC_END #undef DEBUG_SPECIFIC_CHAR_METRICS #define IS_DEBUG_CHAR(CH) ((CH) == 'W') /* glyphs to dump to terminal */ #undef DEBUG_COMPUTED_FONT_METRICS #undef DEBUG_FONT_NAME #undef DEBUG_FONT_SIZE_SEARCH #undef DEBUG_XFT_GLYPH #undef DEBUG_GLYPH_RETRIEVAL #undef DEBUG_XFT_GLYPH_LOADING #undef DEBUG_LOAD /* controls for extensions over VT3x0 limitations */ #define ENABLE_RGB_COLORSPECS #undef ENABLE_FREE_ROTATION #undef ENABLE_DISTORTIONLESS_ROTATION #define ENABLE_UPLOAD_ALPHABET_FROM_FONT #define ENABLE_UPLOAD_ALPHABET_ZERO #define ENABLE_USER_FONT_SIZE #define ENABLE_VARIABLE_ITALICS #define MIN_ITERATIONS_BEFORE_REFRESH 10U #define MIN_MS_BEFORE_REFRESH 33 /* *INDENT-OFF* */ typedef struct RegisPoint { int x, y; } RegisPoint; typedef struct RegisWriteControls { unsigned pv_multiplier; unsigned pattern; unsigned pattern_multiplier; unsigned invert_pattern; unsigned plane_mask; unsigned write_style; RegisterNum foreground; unsigned shading_enabled; char shading_character; int shading_reference; unsigned shading_reference_dim; unsigned line_width; } RegisWriteControls; typedef struct RegisTextControls { unsigned alphabet_num; unsigned character_set_l; /* default: "(B" (ASCII) */ unsigned character_set_r; /* default: "-@" (Latin-1) */ unsigned character_display_w; unsigned character_display_h; unsigned character_unit_cell_w; unsigned character_unit_cell_h; int character_inc_x; int character_inc_y; int string_rotation; int character_rotation; int slant; /* for italic/oblique */ } RegisTextControls; #define FixedCopy(dst, src, len) strncpy(dst, src, len - 1)[len - 1] = '\0' #define CopyFontname(dst, src) FixedCopy(dst, src, (size_t) REGIS_FONTNAME_LEN) #define MAX_REGIS_PAGES 8U #define MAX_REGIS_ALPHABETS 8U #define REGIS_ALPHABET_NAME_LEN 11U #define REGIS_FONTNAME_LEN 256U /* enough for a 16x24 font (about 100KB) */ #define MAX_REGIS_ALPHABET_BYTES (256U * 16U * 24U) #define MAX_GLYPH_PIXELS 8192U #define MAX_GLYPHS 256U #define INVALID_ALPHABET_NUM ~0U typedef struct RegisAlphabet { unsigned alphabet_num; unsigned pixw, pixh; char name[REGIS_ALPHABET_NAME_LEN]; char fontname[REGIS_FONTNAME_LEN]; int use_font; int loaded[MAX_GLYPHS]; Char *bytes; } RegisAlphabet; typedef struct RegisDataFragment { char const *start; unsigned pos; unsigned len; } RegisDataFragment; /* *INDENT-ON* */ #define POSITION_STACK_SIZE 16U #define DUMMY_STACK_X -32768 #define DUMMY_STACK_Y -32768 #define CURVE_POSITION_ARC_EDGE 0U #define CURVE_POSITION_ARC_CENTER 1U #define CURVE_POSITION_OPEN_CURVE 2U #define CURVE_POSITION_CLOSED_CURVE 3U #define MAX_INPUT_CURVE_POINTS 16U #define MAX_CURVE_POINTS (MAX_INPUT_CURVE_POINTS + 4U) #define MAX_FILL_POINTS 2048U typedef struct RegisParseState { RegisDataFragment input; char *temp; unsigned templen; char command; char option; /* position stack */ int stack_x[POSITION_STACK_SIZE]; int stack_y[POSITION_STACK_SIZE]; unsigned stack_next; /* next empty position */ /* curve options */ int curve_mode; int arclen; int x_points[MAX_CURVE_POINTS]; int y_points[MAX_CURVE_POINTS]; unsigned num_points; /* load options */ char load_name[REGIS_ALPHABET_NAME_LEN]; unsigned load_alphabet; unsigned load_w, load_h; unsigned load_index; unsigned load_glyph; unsigned load_row; /* text options */ unsigned text_tilt_state; } RegisParseState; #define TEXT_TILT_STATE_READY 0U #define TEXT_TILT_STATE_GOT_D 1U #define TEXT_TILT_STATE_GOT_DS 2U #define TEXT_TILT_STATE_GOT_DSD 3U typedef struct RegisGraphicsContext { XtermWidget current_widget; Graphic *destination_graphic; Graphic *display_graphic; int graphics_termid; int x_off, y_off; int x_div, y_div; int width, height; unsigned all_planes; RegisterNum background; char const *builtin_font; RegisAlphabet alphabets[MAX_REGIS_ALPHABETS]; RegisWriteControls persistent_write_controls; RegisWriteControls temporary_write_controls; RegisTextControls persistent_text_controls; RegisTextControls temporary_text_controls; RegisTextControls *current_text_controls; int multi_input_mode; int graphics_output_cursor_x; int graphics_output_cursor_y; unsigned pattern_count; unsigned pattern_bit; int fill_mode; RegisPoint fill_points[MAX_FILL_POINTS]; unsigned fill_point_count; unsigned destination_page; unsigned display_page; int force_refresh; } RegisGraphicsContext; static RegisGraphicsContext persistent_context; static RegisParseState persistent_state; #define MAX_PATTERN_BITS 8U #define WRITE_STYLE_OVERLAY 1U #define WRITE_STYLE_REPLACE 2U #define WRITE_STYLE_COMPLEMENT 3U #define WRITE_STYLE_ERASE 4U #define WRITE_SHADING_REF_Y 0U #define WRITE_SHADING_REF_X 1U #define WRITE_SHADING_REF_NONE 2U /* keypress event example: http://iraf.net/forum/viewtopic.php?showtopic=61692 */ #define MIN2(X, Y) ( (X) < (Y) ? (X) : (Y) ) #define MIN3(X, Y, Z) ( MIN2(MIN2((X), (Y)), MIN2((Y), (Z))) ) #define MAX2(X, Y) ( (X) > (Y) ? (X) : (Y) ) #define MAX3(X, Y, Z) ( MAX2(MAX2((X), (Y)), MAX2((Y), (Z))) ) #define ROT_LEFT_N(V, N) ( (((V) << ((N) & 3U )) & 255U) | \ ((V) >> (8U - ((N) & 3U))) ) #define ROT_LEFT(V) ( (((V) << 1U) & 255U) | ((V) >> 7U) ) /* convert user coordinates to absolute pixel coordinates */ #define SCALE_XCOORD(C, X, S) ( ( (X) * ((C)->width - 1) ) / ( (C)->x_div * (S) ) ) #define SCALE_YCOORD(C, Y, S) ( ( (Y) * ((C)->height - 1) ) / ( (C)->y_div * (S) ) ) #define TRANSLATE_XCOORD(C, X, S) SCALE_XCOORD((C), (X) - (C)->x_off * (S), (S) ) #define TRANSLATE_YCOORD(C, Y, S) SCALE_YCOORD((C), (Y) - (C)->y_off * (S), (S) ) #if 0 /* convert absolute pixel coordinate to user coordinates */ #define SCALE_XPIX(C, X, S) ( ( (X) * ((C)->x_div * (S) ) ) / ((C)->width - 1) ) #define SCALE_YPIX(C, Y, S) ( ( (Y) * ((C)->y_div * (S) ) ) / ((C)->height - 1) ) #define TRANSLATE_XPIX(C, X, S) ( SCALE_XPIX((C), (X), (S) ) + (C)->x_off * (S) ) #define TRANSLATE_YPIX(C, Y, S) ( SCALE_YPIX((C), (Y), (S) ) + (C)->y_off * (S) ) #endif #define READ_PIXEL(C, X, Y) read_pixel((C)->destination_graphic, (X), (Y)) #define DRAW_PIXEL(C, X, Y, COL) draw_solid_pixel((C)->destination_graphic, (X), (Y), (COL)) #define DRAW_ALL(C, COL) \ draw_solid_rectangle((C)->destination_graphic, 0, 0, (C)->width, (C)->height, (COL)) static unsigned get_shade_character_pixel(Char const *pixels, unsigned w, unsigned h, unsigned smaxf, unsigned scale, int slant_dx, int px, int py); static void get_bitmap_of_character(RegisGraphicsContext const *context, int ch, unsigned maxw, unsigned maxh, Char *pixels, unsigned *w, unsigned *h, unsigned max_pixels); static void init_regis_load_state(RegisParseState *state) { state->load_index = MAX_REGIS_ALPHABETS; state->load_w = 8U; state->load_h = 10U; state->load_alphabet = 1U; /* FIXME: is this the correct default */ state->load_name[0] = '\0'; state->load_glyph = (unsigned) (Char) '\0'; state->load_row = 0U; } static void init_regis_parse_state(RegisParseState *state) { state->command = '_'; state->option = '_'; state->stack_next = 0U; state->load_index = MAX_REGIS_ALPHABETS; init_regis_load_state(state); } static int ifloor(double d) { double dl = floor(d); return (int) dl; } static int isqrt(double d) { double dl = sqrt(d); return (int) dl; } static void draw_regis_pixel(RegisGraphicsContext *context, int x, int y, unsigned value) { unsigned color = 0; switch (context->temporary_write_controls.write_style) { case WRITE_STYLE_OVERLAY: /* * Update pixels with foreground when pattern is 1, * don't change when pattern is 0. */ if (!value) { return; } if (context->temporary_write_controls.invert_pattern) { color = context->background; } else { color = context->temporary_write_controls.foreground; } break; case WRITE_STYLE_REPLACE: /* * Update pixels with foreground when pattern is 1, * set to background when pattern is 0. */ { unsigned fg, bg; if (context->temporary_write_controls.invert_pattern) { fg = context->background; bg = context->temporary_write_controls.foreground; } else { fg = context->temporary_write_controls.foreground; bg = context->background; } color = value ? fg : bg; } break; case WRITE_STYLE_COMPLEMENT: /* * Update pixels with background when pattern is 1, * don't change when pattern is 0. */ if (!value) { return; } color = READ_PIXEL(context, x, y); if (color == COLOR_HOLE) color = context->background; color = color ^ context->all_planes; break; case WRITE_STYLE_ERASE: /* Update pixels to foreground. */ if (context->temporary_write_controls.invert_pattern) { color = context->temporary_write_controls.foreground; } else { color = context->background; } break; } if (context->temporary_write_controls.plane_mask != context->all_planes) { unsigned old_color = READ_PIXEL(context, x, y); if (old_color == COLOR_HOLE) old_color = context->background; color = (color & context->temporary_write_controls.plane_mask) | (old_color & ~context->temporary_write_controls.plane_mask); } DRAW_PIXEL(context, x, y, color); } static void shade_pattern_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref, int x, int y) { unsigned value; if (dim == WRITE_SHADING_REF_X) { int delta = x > ref ? 1 : -1; int curr_x; context->pattern_bit = 1U << (((unsigned) y) & 7U); for (curr_x = ref; curr_x != x + delta; curr_x += delta) { value = context->temporary_write_controls.pattern & context->pattern_bit; draw_regis_pixel(context, curr_x, y, value); } } else if (dim == WRITE_SHADING_REF_Y) { int delta = y > ref ? 1 : -1; int curr_y; for (curr_y = ref; curr_y != y + delta; curr_y += delta) { context->pattern_bit = 1U << (((unsigned) curr_y) & 7U); value = context->temporary_write_controls.pattern & context->pattern_bit; draw_regis_pixel(context, x, curr_y, value); } } else { TRACE(("ERROR: shading requested, but there is no reference axis\n")); } } static void shade_char_to_pixel(RegisGraphicsContext *context, Char const *pixels, unsigned w, unsigned h, unsigned dim, int ref, int x, int y) { unsigned xmaxf = context->current_text_controls->character_unit_cell_w; unsigned ymaxf = context->current_text_controls->character_unit_cell_h; unsigned smaxf; unsigned s; unsigned scale; unsigned value; if (xmaxf > ymaxf) { smaxf = ymaxf; s = h; } else { smaxf = xmaxf; s = w; } scale = (s << SCALE_FIXED_POINT) / smaxf; if (dim == WRITE_SHADING_REF_X) { int delta = x > ref ? 1 : -1; int curr_x; for (curr_x = ref; curr_x != x + delta; curr_x += delta) { value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0, curr_x, y); draw_regis_pixel(context, curr_x, y, value); } } else if (dim == WRITE_SHADING_REF_Y) { int delta = y > ref ? 1 : -1; int curr_y; for (curr_y = ref; curr_y != y + delta; curr_y += delta) { value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0, x, curr_y); draw_regis_pixel(context, x, curr_y, value); } } else { TRACE(("ERROR: shading requested, but there is no reference axis\n")); } } static void draw_patterned_pixel(RegisGraphicsContext *context, int x, int y) { if (context->pattern_count >= context->temporary_write_controls.pattern_multiplier) { context->pattern_count = 0U; context->pattern_bit = ROT_LEFT(context->pattern_bit); } context->pattern_count++; draw_regis_pixel(context, x, y, context->temporary_write_controls.pattern & context->pattern_bit); } static void shade_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref, int x, int y) { if (context->temporary_write_controls.shading_character != '\0') { unsigned xmaxf = context->current_text_controls->character_unit_cell_w; unsigned ymaxf = context->current_text_controls->character_unit_cell_h; char ch = context->temporary_write_controls.shading_character; Char pixels[MAX_GLYPH_PIXELS]; unsigned w, h; get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h, MAX_GLYPH_PIXELS); if (w > 0 && h > 0) { shade_char_to_pixel(context, pixels, w, h, dim, ref, x, y); } } else { shade_pattern_to_pixel(context, dim, ref, x, y); } } static void draw_or_save_patterned_pixel(RegisGraphicsContext *context, int x, int y) { if (context->fill_mode == 1) { if (context->fill_point_count >= MAX_FILL_POINTS) { TRACE(("point %d,%d can not be added to filled polygon\n", x, y)); return; } if (context->fill_point_count > 0U && context->fill_points[context->fill_point_count - 1U].x == x && context->fill_points[context->fill_point_count - 1U].y == y) { return; } context->fill_points[context->fill_point_count].x = x; context->fill_points[context->fill_point_count].y = y; context->fill_point_count++; return; } if (context->temporary_write_controls.shading_enabled) { unsigned dim = context->temporary_write_controls.shading_reference_dim; int ref = context->temporary_write_controls.shading_reference; shade_to_pixel(context, dim, ref, x, y); return; } draw_patterned_pixel(context, x, y); } static int sort_points(void const *l, void const *r) { RegisPoint const *const lp = l; RegisPoint const *const rp = r; if (lp->y < rp->y) return -1; if (lp->y > rp->y) return +1; if (lp->x < rp->x) return -1; if (lp->x > rp->x) return +1; return 0; } static void draw_shaded_polygon(RegisGraphicsContext *context) { unsigned p; int old_x, old_y; int inside; Char pixels[MAX_GLYPH_PIXELS]; unsigned w = 1, h = 1; char ch = context->temporary_write_controls.shading_character; unsigned xmaxf = context->current_text_controls->character_unit_cell_w; unsigned ymaxf = context->current_text_controls->character_unit_cell_h; get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h, MAX_GLYPH_PIXELS); if (w < 1U || h < 1U) { return; } qsort(context->fill_points, (size_t) context->fill_point_count, sizeof(context->fill_points[0]), sort_points); old_x = DUMMY_STACK_X; old_y = DUMMY_STACK_Y; inside = 0; for (p = 0U; p < context->fill_point_count; p++) { int new_x = context->fill_points[p].x; int new_y = context->fill_points[p].y; #if 0 printf("got %d,%d (%d,%d) inside=%d\n", new_x, new_y, old_x, old_y, inside); #endif /* * FIXME: This is using pixels to represent lines which loses * information about exact slope and how many lines are present which * causes misbehavior with some inputs (especially complex polygons). * It also takes more room than remembering vertices, but I'd rather * not have to implement line segments for arcs. Maybe store a count * at each vertex instead (doesn't fix the slope problem). */ /* * FIXME: Change this to only draw inside of polygons, and round * points in a uniform direction to avoid overlapping drawing. As an * option we could continue to support drawing the outline. */ if (new_y != old_y) { if (inside) { /* * Just draw the vertical line when there is not a matching * edge on the right side. */ shade_char_to_pixel(context, pixels, w, h, WRITE_SHADING_REF_X, old_x, old_x, old_y); } inside = 1; } else { if (inside) { shade_char_to_pixel(context, pixels, w, h, WRITE_SHADING_REF_X, old_x, new_x, new_y); } if (new_x > old_x + 1) { inside = !inside; } } old_x = new_x; old_y = new_y; } context->destination_graphic->dirty = 1; } static void draw_filled_polygon(RegisGraphicsContext *context) { unsigned p; int old_x, old_y; int inside; qsort(context->fill_points, (size_t) context->fill_point_count, sizeof(context->fill_points[0]), sort_points); old_x = DUMMY_STACK_X; old_y = DUMMY_STACK_Y; inside = 0; for (p = 0U; p < context->fill_point_count; p++) { int new_x = context->fill_points[p].x; int new_y = context->fill_points[p].y; #if 0 printf("got %d,%d (%d,%d) inside=%d\n", new_x, new_y, old_x, old_y, inside); #endif /* * FIXME: This is using pixels to represent lines which loses * information about exact slope and how many lines are present which * causes misbehavior with some inputs (especially complex polygons). * It also takes more room than remembering vertices, but I'd rather * not have to implement line segments for arcs. Maybe store a count * at each vertex instead (doesn't fix the slope problem). */ /* * FIXME: Change this to only draw inside of polygons, and round * points in a uniform direction to avoid overlapping drawing. As an * option we could continue to support drawing the outline. */ if (new_y != old_y) { if (inside) { /* * Just draw the vertical line when there is not a matching * edge on the right side. */ shade_pattern_to_pixel(context, WRITE_SHADING_REF_X, old_x, old_x, old_y); } inside = 1; } else { if (inside) { shade_pattern_to_pixel(context, WRITE_SHADING_REF_X, old_x, new_x, new_y); } if (new_x > old_x + 1) { inside = !inside; } } old_x = new_x; old_y = new_y; } context->destination_graphic->dirty = 1; } static void draw_patterned_line(RegisGraphicsContext *context, int x1, int y1, int x2, int y2) { int x, y; int dx, dy; int dir, diff; dx = abs(x1 - x2); dy = abs(y1 - y2); if (dx > dy) { if (x1 > x2) { int tmp; EXCHANGE(x1, x2, tmp); EXCHANGE(y1, y2, tmp); } if (y1 < y2) dir = 1; else if (y1 > y2) dir = -1; else dir = 0; diff = 0; y = y1; for (x = x1; x <= x2; x++) { if (diff >= dx) { diff -= dx; y += dir; } diff += dy; draw_or_save_patterned_pixel(context, x, y); } } else { if (y1 > y2) { int tmp; EXCHANGE(y1, y2, tmp); EXCHANGE(x1, x2, tmp); } if (x1 < x2) dir = 1; else if (x1 > x2) dir = -1; else dir = 0; diff = 0; x = x1; for (y = y1; y <= y2; y++) { if (diff >= dy) { diff -= dy; x += dir; } diff += dx; draw_or_save_patterned_pixel(context, x, y); } } context->destination_graphic->dirty = 1; } typedef struct { int dxx; int dxy; int dyx; int dyy; } quadmap_coords; static void draw_patterned_arc(RegisGraphicsContext *context, int cx, int cy, int ex, int ey, int a_start, int a_length, int *ex_final, int *ey_final) { const double third = hypot((double) (cx - ex), (double) (cy - ey)); const int radius = (int) third; const int ra = radius; const int rb = radius; const quadmap_coords neg_quadmap[4] = { {-1, 0, 0, +1}, {0, -1, -1, 0}, {+1, 0, 0, -1}, {0, +1, +1, 0}, }; const quadmap_coords pos_quadmap[4] = { {-1, 0, 0, -1}, {0, -1, +1, 0}, {+1, 0, 0, +1}, {0, +1, -1, 0}, }; const quadmap_coords *quadmap; int total_points; int half_degree; int points_start, points_stop; int points; unsigned iterations; long rx, ry; long dx, dy; int x, y; long e2; long error; TRACE(("orig a_length=%d a_start=%d\n", a_length, a_start)); if (a_length == 0) return; if (a_length > 0) { quadmap = pos_quadmap; } else { quadmap = neg_quadmap; if (a_start != 0) a_start = 3600 - a_start; a_length = abs(a_length); } TRACE(("positive a_length=%d a_start=%d\n", a_length, a_start)); rx = -ra; ry = 0; e2 = rb; dx = (2 * rx + 1) * e2 * e2; dy = rx * rx; error = dx + dy; total_points = 0; do { total_points += 4; e2 = 2 * error; if (e2 >= dx) { rx++; dx += 2 * rb * rb; error += dx; } if (e2 <= dy) { ry++; dy += 2 * ra * ra; error += dy; } } while (rx <= 0); /* FIXME: This is apparently not accurate enough because some arcs start or * end a few pixels off. Maybe compare line slopes in the loop below * instead? */ half_degree = total_points * 5; points_start = (total_points * a_start - half_degree) / 3600; points_stop = (total_points * a_start + total_points * a_length + half_degree) / 3600; TRACE(("drawing arc with %d points clockwise from %g degrees for %g degrees (from point %d to %d out of %d)\n", total_points, a_start / 10.0, a_length / 10.0, points_start, points_stop, total_points)); /* FIXME: The four pixels at the cardinal directions are double-drawn. */ points = 0; for (iterations = 0U; iterations < 8U; iterations++) { int q2 = iterations & 0x3; rx = -ra; ry = 0; e2 = rb; dx = (2 * rx + 1) * e2 * e2; dy = rx * rx; error = dx + dy; do { #ifdef DEBUG_ARC_POINTS double rad = atan2( (double) (quadmap[q2].dyx * rx + quadmap[q2].dyy * ry), (double) (quadmap[q2].dxx * rx + quadmap[q2].dxy * ry)); double deg = (360.0 * rad / (2.0 * M_PI)); if (deg < 0.0) deg += 360.0; #endif if (points >= points_start && points <= points_stop) { x = (int) (cx + quadmap[q2].dxx * rx + quadmap[q2].dxy * ry); y = (int) (cy + quadmap[q2].dyx * rx + quadmap[q2].dyy * ry); #ifdef DEBUG_ARC_POINTS TRACE(("drawing point %u at %d,%d (%.5g deg)\n", points, x, y, deg)); #endif draw_or_save_patterned_pixel(context, x, y); if (ex_final) *ex_final = x; if (ey_final) *ey_final = y; } else { #ifdef DEBUG_ARC_POINTS x = (int) (cx + quadmap[q2].dxx * rx + quadmap[q2].dxy * ry); y = (int) (cy + quadmap[q2].dyx * rx + quadmap[q2].dyy * ry); TRACE(("skipping point %u at %d,%d which is outside of range (%.5g deg)\n", points, x, y, deg)); #endif } points++; e2 = 2 * error; if (e2 >= dx) { rx++; dx += 2 * rb * rb; error += dx; } if (e2 <= dy) { ry++; dy += 2 * ra * ra; error += dy; } } while (rx <= 0); } context->destination_graphic->dirty = 1; } /* * The plot* functions are based on optimized rasterization primitives written * by Zingl Alois. * See http://members.chello.at/easyfilter/bresenham.html */ /* * FIXME: * This is a terrible temporary hack. The plot functions below can be adapted * to work like the other rasterization functions but there's no point in doing * that until we know we don't have to write something completely different. */ static RegisGraphicsContext *global_context; static void setPixel(int x, int y) { draw_or_save_patterned_pixel(global_context, x, y); } static void plotLine(int x0, int y0, int x1, int y1) { int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy; /* error value e_xy */ for (;;) { /* loop */ int e2; setPixel(x0, y0); e2 = 2 * err; if (e2 >= dy) { /* e_xy+e_x > 0 */ if (x0 == x1) break; err += dy; x0 += sx; } if (e2 <= dx) { /* e_xy+e_y < 0 */ if (y0 == y1) break; err += dx; y0 += sy; } } } static void plotQuadBezierSeg(int x0, int y0, int x1, int y1, int x2, int y2) { /* plot a limited quadratic Bezier segment */ int sx = x2 - x1; int sy = y2 - y1; long xx = (x0 - x1); /* relative values for checks */ long yy = (y0 - y1); double cur = (double) (xx * sy - yy * sx); /* curvature */ assert(xx * sx <= 0 && yy * sy <= 0); /* sign of gradient must not change */ if (sx * (long) sx + sy * (long) sy > xx * xx + yy * yy) { /* begin with longer part */ x2 = x0; x0 = sx + x1; y2 = y0; y0 = sy + y1; cur = -cur; /* swap P0 P2 */ } if (cur != 0.0) { /* no straight line */ long xy; double dx, dy, err; xx += sx; xx *= (sx = (x0 < x2) ? 1 : -1); /* x step direction */ yy += sy; yy *= (sy = (y0 < y2) ? 1 : -1); /* y step direction */ xy = 2 * xx * yy; xx *= xx; yy *= yy; /* differences 2nd degree */ if (cur * sx * sy < 0) { /* negated curvature? */ xx = -xx; yy = -yy; xy = -xy; cur = -cur; } /* differences 1st degree */ dx = ((4.0 * sy * cur * (x1 - x0)) + (double) xx) - (double) xy; dy = ((4.0 * sx * cur * (y0 - y1)) + (double) yy) - (double) xy; xx += xx; yy += yy; err = dx + dy + (double) xy; /* error 1st step */ do { setPixel(x0, y0); /* plot curve */ if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */ y1 = (2 * err) < dx; /* save value for test of y step */ if ((2 * err) > dy) { x0 += sx; dx -= (double) xy; dy += (double) yy; err += dy; } /* x step */ if (y1) { y0 += sy; dy -= (double) xy; dx += (double) xx; err += dx; } /* y step */ } while (dy < 0 && dx > 0); /* gradient negates -> algorithm fails */ } plotLine(x0, y0, x2, y2); /* plot remaining part to end */ } #if 0 static void plotQuadBezier(int x0, int y0, int x1, int y1, int x2, int y2) { /* plot any quadratic Bezier curve */ int x = x0 - x1; int y = y0 - y1; double t = x0 - 2 * x1 + x2; double r; if ((long) x * (x2 - x1) > 0) { /* horizontal cut at P4? */ if ((long) y * (y2 - y1) > 0) /* vertical cut at P6 too? */ if (fabs((y0 - 2 * y1 + y2) / t * x) > abs(y)) { /* which first? */ x0 = x2; x2 = x + x1; y0 = y2; y2 = y + y1; /* swap points */ } /* now horizontal cut at P4 comes first */ t = (x0 - x1) / t; r = (1 - t) * ((1 - t) * y0 + 2.0 * t * y1) + t * t * y2; /* By(t=P4) */ t = (x0 * x2 - x1 * x1) * t / (x0 - x1); /* gradient dP4/dx=0 */ x = ifloor(t + 0.5); y = ifloor(r + 0.5); r = (y1 - y0) * (t - x0) / (x1 - x0) + y0; /* intersect P3 | P0 P1 */ plotQuadBezierSeg(x0, y0, x, ifloor(r + 0.5), x, y); r = (y1 - y2) * (t - x2) / (x1 - x2) + y2; /* intersect P4 | P1 P2 */ x0 = x1 = x; y0 = y; y1 = ifloor(r + 0.5); /* P0 = P4, P1 = P8 */ } if ((long) (y0 - y1) * (y2 - y1) > 0) { /* vertical cut at P6? */ t = y0 - 2 * y1 + y2; t = (y0 - y1) / t; r = (1 - t) * ((1 - t) * x0 + 2.0 * t * x1) + t * t * x2; /* Bx(t=P6) */ t = (y0 * y2 - y1 * y1) * t / (y0 - y1); /* gradient dP6/dy=0 */ x = ifloor(r + 0.5); y = ifloor(t + 0.5); r = (x1 - x0) * (t - y0) / (y1 - y0) + x0; /* intersect P6 | P0 P1 */ plotQuadBezierSeg(x0, y0, ifloor(r + 0.5), y, x, y); r = (x1 - x2) * (t - y2) / (y1 - y2) + x2; /* intersect P7 | P1 P2 */ x0 = x; x1 = ifloor(r + 0.5); y0 = y1 = y; /* P0 = P6, P1 = P7 */ } plotQuadBezierSeg(x0, y0, x1, y1, x2, y2); /* remaining part */ } #endif static void plotCubicBezierSeg(int x0, int y0, double x1, double y1, double x2, double y2, int x3, int y3) { /* plot limited cubic Bezier segment */ int f, fx, fy, tt; int leg = 1; int sx = x0 < x3 ? 1 : -1; int sy = y0 < y3 ? 1 : -1; /* step direction */ double xc = -fabs(x0 + x1 - x2 - x3); double xa = xc - 4 * sx * (x1 - x2); double xb = sx * (x0 - x1 - x2 + x3); double yc = -fabs(y0 + y1 - y2 - y3); double ya = yc - 4 * sy * (y1 - y2); double yb = sy * (y0 - y1 - y2 + y3); double ab, ac, bc, cb, xx, xy, yy, dx, dy, ex, *pxy; double EP = 0.01; /* check for curve restrains */ /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */ assert((x1 - x0) * (x2 - x3) < EP && ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP)); assert((y1 - y0) * (y2 - y3) < EP && ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP)); if (xa == 0.0 && ya == 0.0) { /* quadratic Bezier */ sx = ifloor((3 * x1 - x0 + 1) / 2); sy = ifloor((3 * y1 - y0 + 1) / 2); /* new midpoint */ plotQuadBezierSeg(x0, y0, sx, sy, x3, y3); return; } x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1; /* line lengths */ x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1; do { /* loop over both ends */ ab = xa * yb - xb * ya; ac = xa * yc - xc * ya; bc = xb * yc - xc * yb; ex = ab * (ab + ac - 3 * bc) + ac * ac; /* P0 part of self-intersection loop? */ f = ((ex > 0.0) ? 1 : isqrt(1 + 1024 / x1)); /* calculate resolution */ ab *= f; ac *= f; bc *= f; ex *= f * f; /* increase resolution */ xy = 9 * (ab + ac + bc) / 8; cb = 8 * (xa - ya); /* init differences of 1st degree */ dx = 27 * (8 * ab * (yb * yb - ya * yc) + ex * (ya + 2 * yb + yc)) / 64 - ya * ya * (xy - ya); dy = 27 * (8 * ab * (xb * xb - xa * xc) - ex * (xa + 2 * xb + xc)) / 64 - xa * xa * (xy + xa); /* init differences of 2nd degree */ xx = 3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) - ya * (3 * ac * (ya + yb) + ya * cb)) / 4; yy = 3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) - xa * (3 * ac * (xa + xb) + xa * cb)) / 4; xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + cb); ac = ya * ya; cb = xa * xa; xy = 3 * (xy + 9 * f * (cb * yb * yc - xb * xc * ac) - 18 * xb * yb * ab) / 8; if (ex < 0) { /* negate values if inside self-intersection loop */ dx = -dx; dy = -dy; xx = -xx; yy = -yy; xy = -xy; ac = -ac; cb = -cb; } /* init differences of 3rd degree */ ab = 6 * ya * ac; ac = -6 * xa * ac; bc = 6 * ya * cb; cb = -6 * xa * cb; dx += xy; ex = dx + dy; dy += xy; /* error of 1st step */ for (pxy = &xy, fx = fy = f; x0 != x3 && y0 != y3;) { setPixel(x0, y0); /* plot curve */ do { /* move sub-steps of one pixel */ if (dx > *pxy || dy < *pxy) goto exit; /* confusing values */ y1 = 2 * ex - dy; /* save value for test of y step */ if (2 * ex >= dx) { /* x sub-step */ fx--; ex += dx += xx; dy += xy += ac; yy += bc; xx += ab; } if (y1 <= 0) { /* y sub-step */ fy--; ex += dy += yy; dx += xy += bc; xx += ac; yy += cb; } } while (fx > 0 && fy > 0); /* pixel complete? */ if (2 * fx <= f) { x0 += sx; fx += f; } /* x step */ if (2 * fy <= f) { y0 += sy; fy += f; } /* y step */ if (pxy == &xy && dx < 0 && dy > 0) pxy = &EP; /* pixel ahead valid */ } exit: EXCHANGE(x0, x3, tt); sx = -sx; xb = -xb; /* swap legs */ EXCHANGE(y0, y3, tt); sy = -sy; yb = -yb; x1 = x2; } while (leg--); /* try other end */ plotLine(x0, y0, x3, y3); /* remaining part in case of cusp or crunode */ } static void plotCubicBezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) { /* plot any cubic Bezier curve */ int n = 0, i = 0; long xc = x0 + x1 - x2 - x3; long xa = xc - 4 * (x1 - x2); long xb = x0 - x1 - x2 + x3; long xd = xb + 4 * (x1 + x2); long yc = y0 + y1 - y2 - y3; long ya = yc - 4 * (y1 - y2); long yb = y0 - y1 - y2 + y3; long yd = yb + 4 * (y1 + y2); double fx0 = x0; double fy0 = y0; double t1 = (double) (xb * xb - xa * xc), t2, t[5]; #ifdef DEBUG_BEZIER printf("plotCubicBezier(%d,%d, %d,%d, %d,%d, %d,%d\n", x0, y0, x1, y1, x2, y2, x3, y3); #endif /* sub-divide curve at gradient sign changes */ if (xa == 0) { /* horizontal */ if (labs(xc) < 2 * labs(xb)) t[n++] = (double) xc / (2.0 * (double) xb); /* one change */ } else if (t1 > 0.0) { /* two changes */ t2 = sqrt(t1); t1 = ((double) xb - t2) / (double) xa; if (fabs(t1) < 1.0) t[n++] = t1; t1 = ((double) xb + t2) / (double) xa; if (fabs(t1) < 1.0) t[n++] = t1; } t1 = (double) (yb * yb - ya * yc); if (ya == 0) { /* vertical */ if (labs(yc) < 2 * labs(yb)) t[n++] = (double) yc / (2.0 * (double) yb); /* one change */ } else if (t1 > 0.0) { /* two changes */ t2 = sqrt(t1); t1 = ((double) yb - t2) / (double) ya; if (fabs(t1) < 1.0) t[n++] = t1; t1 = ((double) yb + t2) / (double) ya; if (fabs(t1) < 1.0) t[n++] = t1; } for (i = 1; i < n; i++) /* bubble sort of 4 points */ if ((t1 = t[i - 1]) > t[i]) { t[i - 1] = t[i]; t[i] = t1; i = 0; } t1 = -1.0; t[n] = 1.0; /* begin / end point */ for (i = 0; i <= n; i++) { /* plot each segment separately */ double fx1, fx2, fx3; double fy1, fy2, fy3; t2 = t[i]; /* sub-divide at t[i-1], t[i] */ fx1 = (t1 * (t1 * (double) xb - (double) (2 * xc)) - t2 * (t1 * (t1 * (double) xa - (double) (2 * xb)) + (double) xc) + (double) xd) / 8 - fx0; fy1 = (t1 * (t1 * (double) yb - (double) (2 * yc)) - t2 * (t1 * (t1 * (double) ya - (double) (2 * yb)) + (double) yc) + (double) yd) / 8 - fy0; fx2 = (t2 * (t2 * (double) xb - (double) (2 * xc)) - t1 * (t2 * (t2 * (double) xa - (double) (2 * xb)) + (double) xc) + (double) xd) / 8 - fx0; fy2 = (t2 * (t2 * (double) yb - (double) (2 * yc)) - t1 * (t2 * (t2 * (double) ya - (double) (2 * yb)) + (double) yc) + (double) yd) / 8 - fy0; fx0 -= fx3 = (t2 * (t2 * ((double) (3 * xb) - t2 * (double) xa) - (double) (3 * xc)) + (double) xd) / 8; fy0 -= fy3 = (t2 * (t2 * ((double) (3 * yb) - t2 * (double) ya) - (double) (3 * yc)) + (double) yd) / 8; x3 = ifloor(fx3 + 0.5); y3 = ifloor(fy3 + 0.5); /* scale bounds to int */ if (fx0 != 0.0) { fx1 *= fx0 = (x0 - x3) / fx0; fx2 *= fx0; } if (fy0 != 0.0) { fy1 *= fy0 = (y0 - y3) / fy0; fy2 *= fy0; } if (x0 != x3 || y0 != y3) /* segment t1 - t2 */ plotCubicBezierSeg(x0, y0, x0 + fx1, y0 + fy1, x0 + fx2, y0 + fy2, x3, y3); x0 = x3; y0 = y3; fx0 = fx3; fy0 = fy3; t1 = t2; } } #if 0 static void plotQuadSpline(int n, int x[], int y[], int skip_segments) { /* plot quadratic spline, destroys input arrays x,y */ #define M_MAX 12 double mi = 1, m[M_MAX]; /* diagonal constants of matrix */ int i, x0, y0, x1, y1, x2, y2; #ifdef DEBUG_SPLINE_SEGMENTS int color = 0; #endif assert(n > 1); /* need at least 3 points P[0]..P[n] */ #ifdef DEBUG_SPLINE_POINTS { int save_pattern; i = 0; global_context->temporary_write_controls.foreground = 11; save_pattern = global_context->temporary_write_controls.pattern; global_context->temporary_write_controls.pattern = 0xff; draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 3600, NULL, NULL); i++; global_context->temporary_write_controls.foreground = 15; for (; i < n; i++) { draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 3600, NULL, NULL); } global_context->temporary_write_controls.foreground = 10; draw_patterned_arc(global_context, x[i], y[n], x[i] + 2, y[i], 0, 3600, NULL, NULL); global_context->temporary_write_controls.pattern = save_pattern; } #endif x2 = x[n]; y2 = y[n]; x[1] = x0 = 8 * x[1] - 2 * x[0]; /* first row of matrix */ y[1] = y0 = 8 * y[1] - 2 * y[0]; for (i = 2; i < n; i++) { /* forward sweep */ if (i - 2 < M_MAX) m[i - 2] = mi = 1.0 / (6.0 - mi); x[i] = x0 = ifloor(8 * x[i] - x0 * mi + 0.5); /* store yi */ y[i] = y0 = ifloor(8 * y[i] - y0 * mi + 0.5); } x1 = ifloor((x0 - 2 * x2) / (5.0 - mi) + 0.5); /* correction last row */ y1 = ifloor((y0 - 2 * y2) / (5.0 - mi) + 0.5); for (i = n - 2; i > 0; i--) { /* back substitution */ if (i <= M_MAX) mi = m[i - 1]; x0 = ifloor((x[i] - x1) * mi + 0.5); /* next corner */ y0 = ifloor((y[i] - y1) * mi + 0.5); #ifdef DEBUG_SPLINE_SEGMENTS color++; global_context->temporary_write_controls.foreground = color; #endif if ((n - 2) - i < skip_segments) plotQuadBezier((x0 + x1) / 2, (y0 + y1) / 2, x1, y1, x2, y2); x2 = (x0 + x1) / 2; x1 = x0; y2 = (y0 + y1) / 2; y1 = y0; } #ifdef DEBUG_SPLINE_SEGMENTS color++; global_context->temporary_write_controls.foreground = color; #endif if (skip_segments > 0) plotQuadBezier(x[0], y[0], x1, y1, x2, y2); } #endif static void plotCubicSpline(int n, int x[], int y[], int skip_first_last) { #define M_MAX 12 double mi = 0.25, m[M_MAX]; /* diagonal constants of matrix */ int x3, y3, x4, y4; int i, x0, y0, x1, y1, x2, y2; #ifdef DEBUG_SPLINE_SEGMENTS RegisterNum color = 0; #endif assert(n > 2); /* need at least 4 points P[0]..P[n] */ #ifdef DEBUG_SPLINE_POINTS { unsigned save_pattern; i = 0; global_context->temporary_write_controls.foreground = 11; save_pattern = global_context->temporary_write_controls.pattern; global_context->temporary_write_controls.pattern = 0xff; draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 3600, NULL, NULL); i++; global_context->temporary_write_controls.foreground = 15; for (; i < n; i++) { draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 3600, NULL, NULL); } global_context->temporary_write_controls.foreground = 10; draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 3600, NULL, NULL); global_context->temporary_write_controls.pattern = save_pattern; } #endif x3 = x[n - 1]; y3 = y[n - 1]; x4 = x[n]; y4 = y[n]; x[1] = x0 = 12 * x[1] - 3 * x[0]; /* first row of matrix */ y[1] = y0 = 12 * y[1] - 3 * y[0]; for (i = 2; i < n; i++) { /* forward sweep */ if (i - 2 < M_MAX) m[i - 2] = mi = 0.25 / (2.0 - mi); x[i] = x0 = ifloor(12 * x[i] - 2 * x0 * mi + 0.5); y[i] = y0 = ifloor(12 * y[i] - 2 * y0 * mi + 0.5); } x2 = ifloor((x0 - 3 * x4) / (7 - 4 * mi) + 0.5); /* correct last row */ /* printf("y0=%d, y4=%d mi=%g\n", y0, y4, mi); */ y2 = ifloor((y0 - 3 * y4) / (7 - 4 * mi) + 0.5); /* printf("y2=%d, y3=%d, y4=%d\n", y2, y3, y4); */ #ifdef DEBUG_SPLINE_SEGMENTS color++; global_context->temporary_write_controls.foreground = color; #endif if (!skip_first_last) plotCubicBezier(x3, y3, (x2 + x4) / 2, (y2 + y4) / 2, x4, y4, x4, y4); if (n - 3 < M_MAX) mi = m[n - 3]; x1 = ifloor((x[n - 2] - 2 * x2) * mi + 0.5); y1 = ifloor((y[n - 2] - 2 * y2) * mi + 0.5); for (i = n - 3; i > 0; i--) { /* back substitution */ if (i <= M_MAX) mi = m[i - 1]; x0 = ifloor((x[i] - 2 * x1) * mi + 0.5); y0 = ifloor((y[i] - 2 * y1) * mi + 0.5); x4 = ifloor((x0 + 4 * x1 + x2 + 3) / 6.0); /* reconstruct P[i] */ y4 = ifloor((y0 + 4 * y1 + y2 + 3) / 6.0); #ifdef DEBUG_SPLINE_SEGMENTS color++; global_context->temporary_write_controls.foreground = color; #endif #define CB_PARM(num) ifloor((num) / 3.0 + 0.5) plotCubicBezier(x4, y4, CB_PARM(2 * x1 + x2), CB_PARM(2 * y1 + y2), CB_PARM(x1 + 2 * x2), CB_PARM(y1 + 2 * y2), x3, y3); x3 = x4; y3 = y4; x2 = x1; y2 = y1; x1 = x0; y1 = y0; } x0 = x[0]; x4 = ifloor((3 * x0 + 7 * x1 + 2 * x2 + 6) / 12.0); /* reconstruct P[1] */ y0 = y[0]; y4 = ifloor((3 * y0 + 7 * y1 + 2 * y2 + 6) / 12.0); #ifdef DEBUG_SPLINE_SEGMENTS global_context->temporary_write_controls.foreground = 4; #endif plotCubicBezier(x4, y4, CB_PARM(2 * x1 + x2), CB_PARM(2 * y1 + y2), CB_PARM(x1 + 2 * x2), CB_PARM(y1 + 2 * y2), x3, y3); #ifdef DEBUG_SPLINE_SEGMENTS color++; global_context->temporary_write_controls.foreground = color; #endif if (!skip_first_last) plotCubicBezier(x0, y0, x0, y0, (x0 + x1) / 2, (y0 + y1) / 2, x4, y4); } static unsigned find_free_alphabet_index(RegisGraphicsContext *context, unsigned alphabet, unsigned pixw, unsigned pixh) { unsigned ii, jj; /* try an exact match */ for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) { if (context->alphabets[ii].alphabet_num == alphabet && context->alphabets[ii].pixw == pixw && context->alphabets[ii].pixh == pixh) { return ii; } } /* otherwise use any empty slot */ for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) { if (context->alphabets[ii].alphabet_num == INVALID_ALPHABET_NUM) { context->alphabets[ii].alphabet_num = alphabet; context->alphabets[ii].pixw = pixw; context->alphabets[ii].pixh = pixh; return ii; } } /* otherwise recycle a slot with a different font size */ for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) { if (context->alphabets[ii].alphabet_num == alphabet) { context->alphabets[ii].pixw = pixw; context->alphabets[ii].pixh = pixh; context->alphabets[ii].name[0] = '\0'; context->alphabets[ii].fontname[0] = '\0'; context->alphabets[ii].use_font = 0; if (context->alphabets[ii].bytes != NULL) { free(context->alphabets[ii].bytes); context->alphabets[ii].bytes = NULL; } for (jj = 0U; jj < MAX_GLYPHS; jj++) { context->alphabets[ii].loaded[jj] = 0; } return ii; } } /* finally just recycle this arbitrary slot */ context->alphabets[0U].alphabet_num = alphabet; context->alphabets[0U].pixw = pixw; context->alphabets[0U].pixh = pixh; context->alphabets[0U].name[0] = '\0'; context->alphabets[0U].fontname[0] = '\0'; context->alphabets[0U].use_font = 0; if (context->alphabets[0U].bytes != NULL) { free(context->alphabets[0U].bytes); context->alphabets[0U].bytes = NULL; } for (jj = 0U; jj < MAX_GLYPHS; jj++) { context->alphabets[0U].loaded[jj] = 0; } return 0U; } #ifdef DEBUG_SPECIFIC_CHAR_METRICS static void dump_bitmap_pixels(Char const *pixels, unsigned w, unsigned h) { unsigned yy, xx; for (yy = 0U; yy < h; yy++) { printf(" "); for (xx = 0U; xx < w; xx++) { if (pixels[yy * w + xx]) { printf("#"); } else { printf("_"); } } printf("\n"); } } #endif #if OPT_RENDERFONT && defined(HAVE_TYPE_FCCHAR32) static int copy_bitmap_from_xft_font(XtermWidget xw, XftFont *font, FcChar32 ch, Char *pixels, unsigned w, unsigned h, unsigned xmin, unsigned ymin) { /* * FIXME: cache: * - the bitmap for the last M characters and target dimensions * - reuse the pixmap object where possible */ Display *display = XtDisplay(xw); Screen *screen = XtScreen(xw); XftColor bg, fg; Pixmap bitmap; XftDraw *draw; XImage *image; GC glyph_gc; unsigned bmw, bmh; unsigned xx, yy; bmw = w + xmin; bmh = h; if (bmw < 1 || bmh < 1) { TRACE(("refusing impossible bitmap size w=%d h=%d xmin=%d ymin=%d for ch='%c'\n", bmw, bmh, xmin, ymin, ch)); return 0; } bitmap = XCreatePixmap(display, DefaultRootWindow(display), bmw, bmh, (unsigned) getVisualDepth(xw)); if (bitmap == None) { TRACE(("unable to create Pixmap for Xft\n")); return 0; } draw = XftDrawCreate(display, bitmap, xw->visInfo->visual, XDefaultColormap(display, XScreenNumberOfScreen(screen))); if (!draw) { TRACE(("unable to create XftDraw\n")); XFreePixmap(display, bitmap); return 0; } bg.pixel = 0UL; bg.color.red = 0; bg.color.green = 0; bg.color.blue = 0; bg.color.alpha = 0x0; XftDrawRect(draw, &bg, 0, 0, bmw, bmh); fg.pixel = 1UL; fg.color.red = 0xffff; fg.color.green = 0xffff; fg.color.blue = 0xffff; fg.color.alpha = 0xffff; XftDrawString32(draw, &fg, font, -(int) xmin, font->ascent - (int) ymin, &ch, 1); glyph_gc = XCreateGC(display, bitmap, 0UL, NULL); if (!glyph_gc) { TRACE(("unable to create GC\n")); XftDrawDestroy(draw); XFreePixmap(display, bitmap); return 0; } XSetForeground(display, glyph_gc, 1UL); XSetBackground(display, glyph_gc, 0UL); image = XGetImage(display, bitmap, 0, 0, w, h, 1UL, XYPixmap); if (!image) { TRACE(("unable to create XImage\n")); XFreeGC(display, glyph_gc); XftDrawDestroy(draw); XFreePixmap(display, bitmap); return 0; } for (yy = 0U; yy < h; yy++) { #ifdef DEBUG_XFT_GLYPH_COPY TRACE(("'%c'[%02u]:", ch, yy)); #endif for (xx = 0U; xx < w; xx++) { unsigned long pix; pix = XGetPixel(image, (int) xx, (int) yy); pixels[yy * w + xx] = (unsigned char) pix; #ifdef DEBUG_XFT_GLYPH_COPY TRACE((" %lu", pix)); #endif } #ifdef DEBUG_XFT_GLYPH_COPY TRACE(("\n")); #endif } XFreeGC(display, glyph_gc); XDestroyImage(image); XftDrawDestroy(draw); XFreePixmap(display, bitmap); return 1; } static void get_xft_glyph_dimensions(XtermWidget xw, XftFont *font, unsigned *w, unsigned *h, unsigned *xmin, unsigned *ymin) { unsigned workw, workh; FcChar32 ch; Char *pixels; Char *pixelp; unsigned yy, xx; unsigned char_count, pixel_count; unsigned real_minx, real_maxx, real_miny, real_maxy; unsigned char_minx, char_maxx, char_miny, char_maxy; /* * For each ASCII or ISO-8859-1 printable code, find out what its * dimensions are. * * We actually render the glyphs and determine the extents ourselves * because the font library can lie by several pixels, and since we are * doing manual character placement in fixed areas the glyph boundary needs * to be accurate. * * Ignore control characters and spaces - their extent information is * misleading. */ /* Our "work area" is just a buffer which should be big enough to hold the * largest glyph even if its size is under-reported by a couple of pixels * in each dimension. */ workw = (unsigned) font->max_advance_width + 2U; if (font->ascent + font->descent > font->height) { workh = (unsigned) (font->ascent + font->descent) + 2U; } else { workh = (unsigned) font->height + 2U; } if (!(pixels = malloc((size_t) (workw * workh)))) { *w = 0U; *h = 0U; #ifdef DEBUG_COMPUTED_FONT_METRICS TRACE(("reported metrics:\n")); TRACE((" %ux%u ascent=%u descent=%u\n", font->max_advance_width, font->height, font->ascent, font->descent)); TRACE(("computed metrics:\n")); TRACE((" (unable to allocate pixel array)\n")); #endif return; } /* FIXME: ch is in UCS32 -- try to support non-ASCII characters */ char_count = 0U; real_minx = workw - 1U; real_maxx = 0U; real_miny = workh - 1U; real_maxy = 0U; for (ch = 33; ch < 256; ++ch) { if (ch >= 127 && ch <= 160) { #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) printf("char: '%c' not in interesting range; ignoring\n", (char) ch); #endif continue; } if (!FcCharSetHasChar(font->charset, ch)) { #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) printf("char: '%c' not in charset; ignoring\n", (char) ch); #endif continue; } if (!copy_bitmap_from_xft_font(xw, font, ch, pixels, workw, workh, 0U, 0U)) { #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) printf("char: '%c' bitmap could not be copied; ignoring\n", (char) ch); #endif continue; } pixel_count = 0U; char_minx = workh - 1U; char_maxx = 0U; char_miny = workh - 1U; char_maxy = 0U; pixelp = pixels; for (yy = 0U; yy < workh; yy++) { for (xx = 0U; xx < workw; xx++) { if (*pixelp++) { if (xx < char_minx) char_minx = xx; else if (xx > char_maxx) char_maxx = xx; if (yy < char_miny) char_miny = yy; else if (yy > char_maxy) char_maxy = yy; pixel_count++; } } } if (pixel_count < 1U) { #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) printf("char: '%c' has no pixels; ignoring\n", (char) ch); #endif continue; } #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) { printf("char: '%c' (%d)\n", (char) ch, ch); printf(" minx: %u\n", char_minx); printf(" maxx: %u\n", char_maxx); printf(" miny: %u\n", char_miny); printf(" maxy: %u\n", char_maxy); dump_bitmap_pixels(pixels, workw, workh); printf("\n"); } #endif if (char_minx < real_minx) real_minx = char_minx; if (char_maxx > real_maxx) real_maxx = char_maxx; if (char_miny < real_miny) real_miny = char_miny; if (char_maxy > real_maxy) real_maxy = char_maxy; char_count++; } free(pixels); if (char_count < 1U) { #ifdef DEBUG_COMPUTED_FONT_METRICS TRACE(("reported metrics:\n")); TRACE((" %ux%u ascent=%u descent=%u\n", font->max_advance_width, font->height, font->ascent, font->descent)); TRACE(("computed metrics:\n")); TRACE((" (no characters found)\n")); #endif *w = 0U; *h = 0U; return; } *w = (unsigned) (1 + real_maxx - real_minx); *h = (unsigned) (1 + real_maxy - real_miny); *xmin = real_minx; *ymin = real_miny; #ifdef DEBUG_COMPUTED_FONT_METRICS printf("reported metrics:\n"); printf(" %ux%u ascent=%u descent=%u\n", font->max_advance_width, font->height, font->ascent, font->descent); printf("computed metrics:\n"); printf(" real_minx=%u real_maxx=%u real_miny=%u real_maxy=%u\n", real_minx, real_maxx, real_miny, real_maxy); printf(" final: %ux%u xmin=%u ymin=%u\n", *w, *h, *xmin, *ymin); #endif } #define FONT_SIZE_CACHE_SIZE 32U /* Find the font pixel size which returns the font which is closest to the given * maxw and maxh without overstepping either dimension. */ static XftFont * find_best_xft_font_size(XtermWidget xw, char const *fontname, unsigned maxw, unsigned maxh, unsigned max_pixels, unsigned *w, unsigned *h, unsigned *xmin, unsigned *ymin) { Display *display = XtDisplay(xw); Screen *screen = XtScreen(xw); XftFont *font; unsigned targeth; unsigned ii, cacheindex; /* FIXME: change cache to just cache the final result and put it in a * wrapper function */ static struct { char fontname[REGIS_FONTNAME_LEN]; unsigned maxw, maxh, max_pixels; unsigned targeth; unsigned w, h; unsigned xmin; unsigned ymin; } cache[FONT_SIZE_CACHE_SIZE]; assert(display); assert(screen); assert(fontname); assert(w); assert(h); assert(xmin); assert(ymin); #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("determining best size of font '%s' for %ux%u glyph with max_pixels=%u\n", fontname, maxw, maxh, max_pixels)); #endif cacheindex = FONT_SIZE_CACHE_SIZE; for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) { if (cache[ii].maxw == maxw && cache[ii].maxh == maxh && cache[ii].max_pixels == max_pixels && strcmp(cache[ii].fontname, fontname) == 0) { cacheindex = ii; break; } } if (cacheindex < FONT_SIZE_CACHE_SIZE) { targeth = cache[cacheindex].targeth; } else { targeth = maxh * 10U + 5U; } for (;;) { if (targeth <= 5U) { TRACE(("Giving up finding suitable Xft font size for \"%s\" at %ux%u.\n", fontname, maxw, maxh)); return NULL; } /* * Xft does a bad job at: * - two-color low-resolution anti-aliased fonts * - non-anti-aliased fonts at low resolution unless a font size is * given (pixel size does not help, and the value of the font size * doesn't appear to matter). * * In those two cases it literally drops pixels, sometimes whole * columns, making the glyphs unreadable and at least ugly even when * readable. */ font = NULL; /* * FIXME: * Also, we need to scale the width and height separately. The * CHAR_WIDTH and CHAR_HEIGHT attributes would seem to be ideal, but * don't appear to have any effect if set. Instead we will manually * scale the bitmap later, which may be very ugly because we won't try * to identify different parts of glyphs or preserve density. */ { XftPattern *pat; XftPattern *match; XftResult status; if ((pat = XftNameParse(fontname))) { #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("trying targeth=%g\n", targeth / 10.0)); #endif XftPatternBuild(pat, #if 0 /* arbitrary value */ XFT_SIZE, XftTypeDouble, 12.0, #endif XFT_PIXEL_SIZE, XftTypeDouble, (double) targeth / 10.0, #if 0 XFT_CHAR_WIDTH, XftTypeInteger, (int) maxw, XFT_CHAR_HEIGHT, XftTypeInteger, (int) (targeth / 10U), #endif XFT_SPACING, XftTypeInteger, XFT_MONO, XFT_SLANT, XftTypeInteger, 0, XFT_ANTIALIAS, XftTypeBool, False, NULL); if ((match = XftFontMatch(display, XScreenNumberOfScreen(screen), pat, &status))) { font = XftFontOpenPattern(display, match); maybeXftCache(xw, font); } XftPatternDestroy(pat); } } if (!font) { #ifdef DEBUG_FONT_SIZE_SEARCH { char buffer[1024]; if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) printf("font name unparsed: \"%s\"\n", buffer); } #endif TRACE(("unable to open a monospaced Xft font matching '%s' with pixelsize %g\n", fontname, targeth / 10.0)); return NULL; } #ifdef DEBUG_FONT_SIZE_SEARCH { char buffer[1024]; if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) { TRACE(("Testing font named \"%s\"\n", buffer)); } else { TRACE(("Testing unknown font\n")); } } #endif if (cacheindex < FONT_SIZE_CACHE_SIZE && targeth == cache[cacheindex].targeth) { *w = cache[cacheindex].w; *h = cache[cacheindex].h; *xmin = cache[cacheindex].xmin; *ymin = cache[cacheindex].ymin; } else { get_xft_glyph_dimensions(xw, font, w, h, xmin, ymin); if (*w < 1 || *h < 1) { #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("got %ux%u dimensions for target size targeth=%d; trying reduced target size\n", *w, *h, targeth)); #endif targeth--; continue; } } #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("checking max=%ux%u targeth=%u.%u\n", maxw, maxh, targeth / 10U, targeth % 10U)); #endif if (*h > maxh) { XftFontClose(display, font); #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("got %ux%u glyph; too tall; reducing target size\n", *w, *h)); #endif if (*h > 2U * maxh) { targeth /= (*h / maxh); } else if (targeth > 10U && *h > maxh + 1U) { targeth -= 10U; } else { targeth--; } continue; } if (*w > maxw) { XftFontClose(display, font); #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("got %ux%u glyph; too wide; reducing target size\n", *w, *h)); #endif if (*w > 2U * maxw) { targeth /= (*w / maxw); } else if (targeth > 10U && *w > maxw + 1U) { targeth -= 10U; } else { targeth--; } continue; } if (*w * *h > max_pixels) { XftFontClose(display, font); #ifdef DEBUG_FONT_SIZE_SEARCH TRACE(("got %ux%u glyph; too many pixels; reducing target size\n", *w, *h)); #endif if (*w * *h > 2U * max_pixels) { unsigned min = *w < *h ? *w : *h; unsigned divisor = (*w * *h) / (max_pixels * min); if (divisor > 1U) { targeth /= divisor; } else if (targeth > 10U) { targeth -= 10U; } else { targeth--; } } else { targeth--; } continue; } #ifdef DEBUG_FONT_NAME { char buffer[1024]; if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) { TRACE(("Final font for \"%s\" max %dx%d is \"%s\"\n", fontname, maxw, maxh, buffer)); } else { TRACE(("Final font for \"%s\" max %dx%d is unknown\n", fontname, maxw, maxh)); } } #endif if (cacheindex == FONT_SIZE_CACHE_SIZE) { for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) { if (cache[ii].maxw == 0U || cache[ii].maxh == 0U || cache[ii].max_pixels == 0U) { CopyFontname(cache[ii].fontname, fontname); cache[ii].maxw = maxw; cache[ii].maxh = maxh; cache[ii].max_pixels = max_pixels; cache[ii].targeth = targeth; cache[ii].w = *w; cache[ii].h = *h; cache[ii].xmin = *xmin; cache[ii].ymin = *ymin; break; } } if (ii == FONT_SIZE_CACHE_SIZE) { ii = targeth % FONT_SIZE_CACHE_SIZE; CopyFontname(cache[ii].fontname, fontname); cache[ii].maxw = maxw; cache[ii].maxh = maxh; cache[ii].max_pixels = max_pixels; cache[ii].targeth = targeth; cache[ii].w = *w; cache[ii].h = *h; cache[ii].xmin = *xmin; cache[ii].ymin = *ymin; } } return font; } } #endif static int get_xft_bitmap_of_character(RegisGraphicsContext const *context, char const *fontname, int ch, unsigned maxw, unsigned maxh, Char *pixels, unsigned max_pixels, unsigned *w, unsigned *h) { /* * See Xft / RENDERFONT stuff in fontutils.c and used in utils.c * Add a separate configuration for ReGIS. */ /* * FIXME: cache: * - reuse the font where possible */ #ifdef XRENDERFONT XtermWidget xw = context->destination_graphic->xw; Display *display = XtDisplay(xw); XftFont *font; unsigned xmin = 0U, ymin = 0U; # ifdef DEBUG_XFT_GLYPH_LOADING TRACE(("trying to load glyph '%c' at max size %dx%d\n", ch, maxw, maxh)); # endif if (!(font = find_best_xft_font_size(xw, fontname, maxw, maxh, max_pixels, w, h, &xmin, &ymin))) { TRACE(("Unable to find suitable Xft font\n")); return 0; } if (*w == 0U || *h == 0U) { TRACE(("empty glyph found for '%c'\n", ch)); XftFontClose(display, font); return 1; } if (!copy_bitmap_from_xft_font(xw, font, CharOf(ch), pixels, *w, *h, xmin, ymin)) { TRACE(("Unable to create bitmap for '%c'\n", ch)); XftFontClose(display, font); return 0; } XftFontClose(display, font); # ifdef DEBUG_XFT_GLYPH_LOADING TRACE(("loaded glyph '%c' at max size %dx%d\n", ch, maxw, maxh)); # endif return 1; #else (void) context; (void) fontname; (void) ch; (void) maxw; (void) maxh; (void) pixels; (void) max_pixels; (void) w; (void) h; TRACE(("Not rendering Xft font for ReGIS (support not compiled in).\n")); return 0; #endif } static unsigned find_best_alphabet_index(RegisGraphicsContext const *context, unsigned minw, unsigned minh, unsigned targetw, unsigned targeth, unsigned max_pixels) { unsigned ii; unsigned bestmatch; unsigned bestw, besth; assert(context); assert(targetw); assert(targeth); assert(max_pixels); bestmatch = MAX_REGIS_ALPHABETS; bestw = 0U; besth = 0U; for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) { if (context->alphabets[ii].alphabet_num == context->current_text_controls->alphabet_num && context->alphabets[ii].pixw >= minw && context->alphabets[ii].pixh >= minh && context->alphabets[ii].pixw <= targetw && context->alphabets[ii].pixh <= targeth && ((context->alphabets[ii].pixw >= bestw && context->alphabets[ii].pixh > besth) || (context->alphabets[ii].pixw > bestw && context->alphabets[ii].pixh >= besth)) && context->alphabets[ii].pixw * context->alphabets[ii].pixh <= max_pixels) { bestmatch = ii; bestw = context->alphabets[ii].pixw; besth = context->alphabets[ii].pixh; } } /* If we can't find one to scale up, look for one to scale down. */ if (bestmatch == MAX_REGIS_ALPHABETS) { bestw = max_pixels; besth = max_pixels; for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) { if (context->alphabets[ii].alphabet_num == context->current_text_controls->alphabet_num && context->alphabets[ii].pixw >= minw && context->alphabets[ii].pixh >= minh && ((context->alphabets[ii].pixw <= bestw && context->alphabets[ii].pixh < besth) || (context->alphabets[ii].pixw < bestw && context->alphabets[ii].pixh <= besth)) && context->alphabets[ii].pixw * context->alphabets[ii].pixh <= max_pixels) { bestmatch = ii; bestw = context->alphabets[ii].pixw; besth = context->alphabets[ii].pixh; } } } #ifdef DEBUG_ALPHABET_LOOKUP if (bestmatch < MAX_REGIS_ALPHABETS) { TRACE(("for target size %ux%u alphabet %u found index %u size %ux%u font=%s\n", targetw, targeth, context->current_text_controls->alphabet_num, bestmatch, bestw, besth, context->alphabets[bestmatch].use_font ? context->alphabets[bestmatch].fontname : "(none)")); } else { TRACE(("for target size %ux%u alphabet %u found no suitable alphabets\n", targetw, targeth, context->current_text_controls->alphabet_num)); } #endif return bestmatch; } #define GLYPH_WIDTH_BYTES(PIXW) ( ((PIXW) + 7U) >> 3U ) static int get_user_bitmap_of_character(RegisGraphicsContext const *context, int ch, unsigned alphabet_index, Char *pixels, unsigned int max_pixels) { const Char *glyph; unsigned w, h; unsigned xx, yy; unsigned byte, bit; assert(context); assert(pixels); if (!context->alphabets[alphabet_index].loaded[(Char) ch]) { TRACE(("BUG: in alphabet %u with alphabet index %u user glyph for '%c' not loaded\n", context->current_text_controls->alphabet_num, alphabet_index, ch)); return 0; } assert(context->alphabets[alphabet_index].bytes); w = context->alphabets[alphabet_index].pixw; h = context->alphabets[alphabet_index].pixh; glyph = &context->alphabets[alphabet_index] .bytes[(Char) ch * GLYPH_WIDTH_BYTES(w) * h]; if (w * h > max_pixels) { TRACE(("in alphabet %u with alphabet index %u user glyph for '%c' is too large: %ux%u (max_pixels=%u)\n", context->current_text_controls->alphabet_num, alphabet_index, ch, w, h, max_pixels)); return 0; } for (yy = 0U; yy < h; yy++) { for (xx = 0U; xx < w; xx++) { byte = yy * GLYPH_WIDTH_BYTES(w) + (xx >> 3U); bit = xx & 7U; pixels[yy * w + xx] = (Char) (((unsigned) glyph[byte] >> (7U - bit)) & 1U); } } return 1; } /* * alphabets * 0 built-in * 1-N custom (max is 3 on VT3X0 -- up to MAX_REGIS_ALPHABETS with xterm) * * built-in 7-bit charsets * (B ASCII * (0 DEC special graphics * (> DEC technical * (A NCR British * (4 NCR Dutch * (5 NCR Finnish * (R NCR French * (9 NCR French Canadian * (K NCR German * (Y NCR Italian * (' NCR Norwegian/Danish * (!6 NCR Portuguese * (Z NCR Spanish * (7 NCR Swedish * (- NCR Swiss * * -@ ??? * * built-in 8-bit charsets * )%5 DEC supplemental graphics * -A ISO Latin-1 supplemental * )< user-preferred supplemental (94 chars) * * defaults * terminal char cell size charsets angle * VT3x0 S1 0:ASCII(94) 0 (positive) * */ static void get_bitmap_of_character(RegisGraphicsContext const *context, int ch, unsigned maxw, unsigned maxh, Char *pixels, unsigned *w, unsigned *h, unsigned max_pixels) { unsigned bestmatch; char const *fontname = NULL; assert(context); assert(w); assert(h); #ifdef DEBUG_GLYPH_RETRIEVAL TRACE(("getting bitmap of glyph %d, current alphabet %d\n", ch, context->current_text_controls->alphabet_num)); #endif if (maxw < 1U || maxh < 1U || max_pixels < 1U) { *w = 0U; *h = 0U; return; } if (context->current_text_controls->alphabet_num == 0) fontname = context->builtin_font; *w = 0U; *h = 0U; bestmatch = find_best_alphabet_index(context, 1U, 1U, maxw, maxh, max_pixels); if (bestmatch < MAX_REGIS_ALPHABETS) { RegisAlphabet const *alpha = &context->alphabets[bestmatch]; #ifdef DEBUG_GLYPH_RETRIEVAL TRACE(("checking user glyph for slot=%u alphabet=%d use_font=%d loaded=%d\n", bestmatch, alpha->alphabet_num, alpha->use_font, alpha->loaded[ch])); #endif if (!alpha->use_font && get_user_bitmap_of_character(context, ch, bestmatch, pixels, max_pixels)) { #ifdef DEBUG_GLYPH_RETRIEVAL TRACE(("found user glyph for alphabet number %d (index %u)\n\n", alpha->alphabet_num, bestmatch)); #endif *w = alpha->pixw; *h = alpha->pixh; return; } if (alpha->use_font) fontname = alpha->fontname; } if (fontname) { #ifdef DEBUG_GLYPH_RETRIEVAL TRACE(("using xft font %s\n", fontname)); #endif if (get_xft_bitmap_of_character(context, fontname, ch, maxw, maxh, pixels, max_pixels, w, h)) { if (*w > maxw) { TRACE(("BUG: Xft glyph is too wide: %ux%u but max is %ux%u\n", *w, *h, maxw, maxh)); } else if (*h > maxh) { TRACE(("BUG: Xft glyph is too tall: %ux%u but max is %ux%u\n", *w, *h, maxw, maxh)); } else if (*w * *h > max_pixels) { TRACE(("BUG: Xft glyph has too many pixels: %u but max is %u\n", *w * *h, max_pixels)); } else { TRACE(("got glyph from \"%s\" for alphabet number %d\n", fontname, context->current_text_controls->alphabet_num)); #ifdef DEBUG_SPECIFIC_CHAR_METRICS if (IS_DEBUG_CHAR(ch)) { printf("got %ux%u Xft bitmap for '%c' target size %ux%u:\n", *w, *h, ch, maxw, maxh); dump_bitmap_pixels(pixels, *w, *h); printf("\n"); } #endif return; } } } TRACE(("unable to load any bitmap for character '%c' in alphabet number %u at %ux%u\n", ch, context->current_text_controls->alphabet_num, maxw, maxh)); /* * The VT3x0 series (and probably earlier ReGIS implementations) use a solid * block glyph for unknown glyphs. */ { unsigned xx, yy; *w = MIN2(8U, maxh); *h = MIN2(10U, maxw); for (yy = 0U; yy < *h; yy++) for (xx = 0U; xx < *w; xx++) pixels[yy * *w + xx] = '\1'; } } #define ROT_SHEAR_SCALE 8192 #define SIGNED_UNSIGNED_MOD(VAL, BASE) ( (((VAL) % (int) (BASE)) + (int) (BASE)) % (int) (BASE) ) static unsigned get_shade_character_pixel(Char const *pixels, unsigned w, unsigned h, unsigned smaxf, unsigned scale, int slant_dx, int px, int py) { unsigned wx, wy; unsigned fx, fy; wx = (unsigned) SIGNED_UNSIGNED_MOD(px - (slant_dx * SIGNED_UNSIGNED_MOD(py, smaxf)) / ROT_SHEAR_SCALE, smaxf); wy = (unsigned) SIGNED_UNSIGNED_MOD(py, smaxf); fx = (wx * scale) >> SCALE_FIXED_POINT; fy = (wy * scale) >> SCALE_FIXED_POINT; if (fx < w && fy < h) { return (unsigned) pixels[fy * w + fx]; } return 0U; } static void draw_character(RegisGraphicsContext *context, int ch, int slant_dx, int rot_shear_x, int rot_shear_y, int x_sign_x, int x_sign_y, int y_sign_x, int y_sign_y) { const unsigned xmaxd = context->current_text_controls->character_display_w; const unsigned ymaxd = context->current_text_controls->character_display_h; const unsigned xmaxf = context->current_text_controls->character_unit_cell_w; const unsigned ymaxf = context->current_text_controls->character_unit_cell_h; unsigned w, h; unsigned xscale, yscale; unsigned fx, fy; unsigned px, py; int sx; int rx, ry; int ox, oy; unsigned pad_left, pad_right; unsigned pad_top, pad_bottom; Char pixels[MAX_GLYPH_PIXELS]; unsigned value; get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h, MAX_GLYPH_PIXELS); if (w < 1 || h < 1) { return; } if (xmaxd > xmaxf) { pad_left = (xmaxd - xmaxf) / 2U; pad_right = (xmaxd - xmaxf) - pad_left; } else { pad_left = 0U; pad_right = 0U; } if (ymaxd > ymaxf) { pad_top = (ymaxd - ymaxf) / 2U; pad_bottom = (ymaxd - ymaxf) - pad_top; } else { pad_top = 0U; pad_bottom = 0U; } xscale = (w << SCALE_FIXED_POINT) / xmaxf; yscale = (h << SCALE_FIXED_POINT) / ymaxf; for (py = 0U; py < ymaxd; py++) { for (px = 0U; px < xmaxd; px++) { if (py < pad_top || px < pad_left || py >= ymaxd - pad_bottom || px >= xmaxd - pad_right) { value = 0U; } else { fx = ((px - pad_left) * xscale) >> SCALE_FIXED_POINT; fy = ((py - pad_top) * yscale) >> SCALE_FIXED_POINT; if (fx < w && fy < h) { value = (unsigned) pixels[fy * w + fx]; } else { value = 0U; } } sx = (int) px + (slant_dx * (int) py) / ROT_SHEAR_SCALE; rx = x_sign_x * sx + x_sign_y * (int) py; ry = y_sign_x * sx + y_sign_y * (int) py; ox = rx + (rot_shear_x * ry) / ROT_SHEAR_SCALE; oy = ry + (rot_shear_y * ox) / ROT_SHEAR_SCALE; ox += (rot_shear_x * oy) / ROT_SHEAR_SCALE; draw_regis_pixel(context, (int) context->graphics_output_cursor_x + ox, (int) context->graphics_output_cursor_y + oy, value); } } } static void move_text(RegisGraphicsContext *context, int dx, int dy) { double total_rotation; int str_invert; int str_shear_x, str_shear_y; int ox, oy; total_rotation = 2.0 * M_PI * context->current_text_controls->string_rotation / 360.0; while (total_rotation > 1.5 * M_PI) { total_rotation -= 2.0 * M_PI; } if (total_rotation > 0.5 * M_PI) { total_rotation -= M_PI; str_invert = -1; } else { str_invert = 1; } str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation)); str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation)); total_rotation = 2.0 * M_PI * context->current_text_controls->character_rotation / 360.0; while (total_rotation > 1.5 * M_PI) { total_rotation -= 2.0 * M_PI; } TRACE(("str_shear: %.5f, %.5f (sign=%d)\n", str_shear_x / (double) ROT_SHEAR_SCALE, str_shear_y / (double) ROT_SHEAR_SCALE, str_invert)); ox = str_invert * dx + (str_shear_x * dy) / ROT_SHEAR_SCALE; oy = str_invert * dy + (str_shear_y * ox) / ROT_SHEAR_SCALE; ox += (str_shear_x * oy) / ROT_SHEAR_SCALE; TRACE(("after pv output updating position %+d,%+d\n", ox, oy)); context->graphics_output_cursor_x += ox; context->graphics_output_cursor_y += oy; return; } #define UPSCALE_TEXT_DIMENSION(D) do { \ *(D) = (unsigned)((double)(*(D)) * M_SQRT2); \ } while (0) static void draw_text(RegisGraphicsContext *context, char const *str) { #ifndef ENABLE_DISTORTIONLESS_ROTATION RegisTextControls *old_text_controls = NULL; static RegisTextControls scratch_text_controls; #endif double total_rotation; size_t ii; int str_invert; int str_shear_x, str_shear_y; int slant_dx; int chr_x_sign_x, chr_x_sign_y; int chr_y_sign_x, chr_y_sign_y; int chr_shear_x, chr_shear_y; int begin_x, begin_y; int rx, ry; int ox, oy; #ifdef DEBUG_ALPHABETS { unsigned n; for (n = 0U; n < MAX_REGIS_ALPHABETS; n++) { printf("alphabet index %u\n", n); if (context->alphabets[n].alphabet_num != INVALID_ALPHABET_NUM) { printf(" alphabet_num=%u\n", context->alphabets[n].alphabet_num); printf(" pixw=%d\n", context->alphabets[n].pixw); printf(" pixh=%d\n", context->alphabets[n].pixh); printf(" name=\"%s\"\n", context->alphabets[n].name); printf(" use_font=%d\n", context->alphabets[n].use_font); printf(" fontname=\"%s\"\n", context->alphabets[n].fontname); printf(" bytes=%p\n", context->alphabets[n].bytes); } } } #endif if (context->current_text_controls->slant <= -75 || context->current_text_controls->slant >= +75) { TRACE(("ERROR: unsupported character slant angle %d\n", context->current_text_controls->slant)); return; } /* FIXME: grab when first entering command */ begin_x = context->graphics_output_cursor_x; begin_y = context->graphics_output_cursor_y; #ifndef ENABLE_DISTORTIONLESS_ROTATION if (context->current_text_controls->character_rotation != 0 && context->current_text_controls->character_rotation != 90 && context->current_text_controls->character_rotation != 180 && context->current_text_controls->character_rotation != 270) { old_text_controls = context->current_text_controls; scratch_text_controls = *context->current_text_controls; UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_display_w); UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_display_h); /* FIXME: Not sure if this is really scaled. The increment seems to * _not_ be scaled. */ UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_unit_cell_w); UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_unit_cell_h); context->current_text_controls = &scratch_text_controls; TRACE(("scaled up text to %dx%d\n", scratch_text_controls.character_display_w, scratch_text_controls.character_display_h)); } #endif total_rotation = 2.0 * M_PI * context->current_text_controls->string_rotation / 360.0; while (total_rotation > 1.5 * M_PI) { total_rotation -= 2.0 * M_PI; } if (total_rotation > 0.5 * M_PI) { total_rotation -= M_PI; str_invert = -1; } else { str_invert = 1; } str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation)); str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation)); total_rotation = 2.0 * M_PI * context->current_text_controls->character_rotation / 360.0; while (total_rotation > 1.5 * M_PI) { total_rotation -= 2.0 * M_PI; } if (total_rotation > 0.5 * M_PI) { total_rotation -= M_PI; chr_x_sign_x = -1; chr_x_sign_y = 0; chr_y_sign_x = 0; chr_y_sign_y = -1; } else { chr_x_sign_x = 1; chr_x_sign_y = 0; chr_y_sign_x = 0; chr_y_sign_y = 1; } chr_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation)); chr_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation)); { const int slant = context->current_text_controls->slant; TRACE(("float version: %.5f\n", tan(2.0 * M_PI * abs(slant) / 360.0))); /* The slant is negative for forward-leaning characters. */ if (slant > 0) { slant_dx = (int) +(tan(2.0 * M_PI * abs(slant) / 360.0) * ROT_SHEAR_SCALE); } else if (slant < 0) { slant_dx = (int) -(tan(2.0 * M_PI * abs(slant) / 360.0) * ROT_SHEAR_SCALE); } else { slant_dx = 0; } TRACE(("string rotation: %d\n", context->current_text_controls->string_rotation)); TRACE(("character rotation: %d\n", context->current_text_controls->character_rotation)); TRACE(("character slant: %d (%.5f pixels per line)\n", slant, slant_dx / (double) ROT_SHEAR_SCALE)); } TRACE(("str_shear: %.5f, %.5f (sign=%d)\n", str_shear_x / (double) ROT_SHEAR_SCALE, str_shear_y / (double) ROT_SHEAR_SCALE, str_invert)); TRACE(("chr_shear: %.5f, %.5f (xsign=%d,%d, ysign=%d,%d)\n", chr_shear_x / (double) ROT_SHEAR_SCALE, chr_shear_y / (double) ROT_SHEAR_SCALE, chr_x_sign_x, chr_x_sign_y, chr_y_sign_x, chr_y_sign_y)); TRACE(("character_inc: %d,%d\n", context->current_text_controls->character_inc_x, context->current_text_controls->character_inc_y)); rx = 0; ry = 0; for (ii = 0U; ii < strlen(str); ii++) { switch (str[ii]) { case '\r': rx = 0; break; case '\n': ry += (int) context->current_text_controls->character_display_h; break; case '\b': rx -= context->current_text_controls->character_inc_x; ry -= context->current_text_controls->character_inc_y; break; case '\t': rx += context->current_text_controls->character_inc_x; ry += context->current_text_controls->character_inc_y; break; default: ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE; oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE; ox += (str_shear_x * oy) / ROT_SHEAR_SCALE; TRACE(("during text output updating position to %d,%d + %+d,%+d for '%c'\n", begin_x, begin_y, ox, oy, str[ii])); context->graphics_output_cursor_x = begin_x + ox; context->graphics_output_cursor_y = begin_y + oy; draw_character(context, str[ii], slant_dx, chr_shear_x, chr_shear_y, chr_x_sign_x, chr_x_sign_y, chr_y_sign_x, chr_y_sign_y); rx += context->current_text_controls->character_inc_x; ry += context->current_text_controls->character_inc_y; } } ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE; oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE; ox += (str_shear_x * oy) / ROT_SHEAR_SCALE; TRACE(("after text output updating position to %d,%d + %+d,%+d\n", begin_x, begin_y, ox, oy)); context->graphics_output_cursor_x = begin_x + ox; context->graphics_output_cursor_y = begin_y + oy; #ifndef ENABLE_DISTORTIONLESS_ROTATION if (context->current_text_controls->character_rotation != 0 && context->current_text_controls->character_rotation != 90 && context->current_text_controls->character_rotation != 180 && context->current_text_controls->character_rotation != 270) { context->current_text_controls = old_text_controls; } #endif context->destination_graphic->dirty = 1; return; } /* * standard character cell sizes * number disp cell unit cell offset * S0 [ 9, 10] [ 8, disp_h] [disp_w, 0] * S1 [ 9, 20] [ 8, disp_h] [disp_w, 0] * S2 [ 18, 30] [ 16, disp_h] [disp_w, 0] * S3 [ 27, 45] [ 24, disp_h] [disp_w, 0] * S4 [ 36, 60] [ 32, disp_h] [disp_w, 0] * S5 [ 45, 75] [ 40, disp_h] [disp_w, 0] * S6 [ 54, 90] [ 48, disp_h] [disp_w, 0] * S7 [ 63,105] [ 56, disp_h] [disp_w, 0] * S8 [ 72,120] [ 64, disp_h] [disp_w, 0] * S9 [ 81,135] [ 72, disp_h] [disp_w, 0] * S10 [ 90,150] [ 80, disp_h] [disp_w, 0] * S11 [ 99,165] [ 88, disp_h] [disp_w, 0] * S12 [108,180] [ 96, disp_h] [disp_w, 0] * S13 [117,195] [104, disp_h] [disp_w, 0] * S14 [126,210] [112, disp_h] [disp_w, 0] * S15 [135,225] [120, disp_h] [disp_w, 0] * S16 [144,240] [128, disp_h] [disp_w, 0] */ static int get_standard_character_size(int standard, unsigned *disp_w, unsigned *disp_h, unsigned *unit_w, unsigned *unit_h, int *off_x, int *off_y) { switch (standard) { case 0: *disp_w = 9U; *disp_h = 10U; *unit_w = 8U; break; case 1: *disp_w = 9U; *disp_h = 20U; *unit_w = 8U; break; case 2: *disp_w = 18U; *disp_h = 30U; *unit_w = 16U; break; case 3: *disp_w = 27U; *disp_h = 45U; *unit_w = 24U; break; case 4: *disp_w = 36U; *disp_h = 60U; *unit_w = 32U; break; case 5: *disp_w = 45U; *disp_h = 75U; *unit_w = 40U; break; case 6: *disp_w = 54U; *disp_h = 90U; *unit_w = 48U; break; case 7: *disp_w = 63U; *disp_h = 105U; *unit_w = 56U; break; case 8: *disp_w = 72U; *disp_h = 120U; *unit_w = 64U; break; case 9: *disp_w = 81U; *disp_h = 135U; *unit_w = 72U; break; case 10: *disp_w = 90U; *disp_h = 150U; *unit_w = 80U; break; case 11: *disp_w = 99U; *disp_h = 165U; *unit_w = 88U; break; case 12: *disp_w = 108U; *disp_h = 180U; *unit_w = 96U; break; case 13: *disp_w = 117U; *disp_h = 195U; *unit_w = 104U; break; case 14: *disp_w = 126U; *disp_h = 210U; *unit_w = 112U; break; case 15: *disp_w = 135U; *disp_h = 225U; *unit_w = 120U; break; case 16: *disp_w = 144U; *disp_h = 240U; *unit_w = 128U; break; default: return 1; } *unit_h = *disp_h; *off_x = (int) *disp_w; *off_y = 0; return 0; } static void init_fragment(RegisDataFragment *fragment, char const *str) { assert(fragment); assert(str); fragment->start = str; fragment->len = (unsigned) strlen(str); fragment->pos = 0U; } static void copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src) { assert(dst); assert(src); dst->start = src->start; dst->len = src->len; dst->pos = src->pos; } static char peek_fragment(RegisDataFragment const *fragment) { assert(fragment); if (fragment->pos < fragment->len) { return fragment->start[fragment->pos]; } return '\0'; } static char pop_fragment(RegisDataFragment *fragment) { assert(fragment); if (fragment->pos < fragment->len) { return fragment->start[fragment->pos++]; } return '\0'; } static char get_fragment(RegisDataFragment const *fragment, unsigned pos) { assert(fragment); if (fragment->pos + pos < fragment->len) { return fragment->start[fragment->pos + pos]; } return '\0'; } #define fragment_length(f) (f)->len static unsigned fragment_remaining(RegisDataFragment const *fragment) { assert(fragment); if (fragment->pos > fragment->len) return 0U; return fragment->len - fragment->pos; } static int fragment_consumed(RegisDataFragment const *fragment) { assert(fragment); return fragment->pos >= fragment->len; } static void fragment_to_string(RegisDataFragment const *fragment, char *out, unsigned outlen) { unsigned remaininglen; unsigned endpos; assert(fragment); assert(out); if (!outlen) return; remaininglen = fragment->len - fragment->pos; if (remaininglen < outlen - 1U) { endpos = remaininglen; } else { endpos = outlen - 1U; } strncpy(out, &fragment->start[fragment->pos], (size_t) endpos); out[endpos] = '\0'; } #define MAX_FRAG 1024 static char const * fragment_to_tempstr(RegisDataFragment const *fragment) { static char tempstr[MAX_FRAG]; assert(fragment); fragment_to_string(fragment, tempstr, MAX_FRAG); return tempstr; } static int skip_regis_whitespace(RegisDataFragment *input) { int skipped = 0; assert(input); while (!fragment_consumed(input)) { char ch = peek_fragment(input); if (ch != ',' && !IsSpace(ch)) { break; } if (ch == '\n') { TRACE(("end of input line\n\n")); } skipped = 1; pop_fragment(input); } if (skipped) return 1; return 0; } static int extract_regis_extent(RegisDataFragment *input, RegisDataFragment *output) { char ch; assert(input); assert(output); output->start = &input->start[input->pos]; output->len = 0U; output->pos = 0U; if (input->pos >= input->len) return 0; ch = input->start[input->pos]; if (ch != '[') return 0; input->pos++; output->start++; /* FIXME: truncate to 16 bit signed integers */ for (; input->pos < input->len; input->pos++, output->len++) { ch = input->start[input->pos]; if (ch == ';') { TRACE(("DATA_ERROR: end of input before closing bracket\n")); break; } if (ch == ']') break; } if (ch == ']') input->pos++; return 1; } static int extract_regis_num(RegisDataFragment *input, RegisDataFragment *output) { char ch = 0; int has_digits = 0; assert(input); assert(output); output->start = &input->start[input->pos]; output->len = 0U; output->pos = 0U; if (input->start[input->pos] == '-' || input->start[input->pos] == '+') { input->pos++; output->len++; } for (; input->pos < input->len; input->pos++, output->len++) { ch = input->start[input->pos]; if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7' && ch != '8' && ch != '9') { break; } has_digits = 1; } /* FIXME: what degenerate forms should be accepted ("E10" "1E" "1e" "1." "1ee10")? */ /* FIXME: the terminal is said to support "floating point values", truncating to int... what do these look like? */ if (has_digits && ch == 'E') { input->pos++; output->len++; for (; input->pos < input->len; input->pos++, output->len++) { ch = input->start[input->pos]; if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7' && ch != '8' && ch != '9') { break; } } } return has_digits; } static int extract_regis_pixelvector(RegisDataFragment *input, RegisDataFragment *output) { char ch; int has_digits; assert(input); assert(output); output->start = &input->start[input->pos]; output->len = 0U; output->pos = 0U; if (input->pos < input->len) { ch = input->start[input->pos]; if (ch == '+' || ch == '-') { input->pos++; output->len++; } } has_digits = 0; for (; input->pos < input->len; input->pos++, output->len++) { ch = input->start[input->pos]; if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7') { break; } has_digits = 1; } return has_digits; } static int extract_regis_command(RegisDataFragment *input, char *command) { char ch; assert(input); assert(command); if (input->pos >= input->len) return 0; ch = input->start[input->pos]; if (ch == '\0' || ch == ';') { return 0; } if (!islower(CharOf(ch)) && !isupper(CharOf(ch)) && ch != '@') { return 0; } *command = ch; input->pos++; return 1; } static int extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen) { char open_quote_ch; char ch; unsigned outlen; assert(input); assert(out); assert(maxlen > 0U); if (input->pos >= input->len) return 0; ch = peek_fragment(input); if (ch != '\'' && ch != '"') return 0; open_quote_ch = ch; outlen = 0U; pop_fragment(input); ch = '\0'; while (!fragment_consumed(input)) { char prev_ch = ch; ch = peek_fragment(input); /* ';' (resync) and '@' (macrograph) are not recognized in strings */ if (prev_ch == open_quote_ch) { if (ch == open_quote_ch) { if (outlen < maxlen) { out[outlen] = ch; } outlen++; pop_fragment(input); ch = '\0'; continue; } if (outlen < maxlen) out[outlen] = '\0'; else out[maxlen] = '\0'; return 1; } if (ch == '\0') break; if (ch != open_quote_ch) { if (outlen < maxlen) out[outlen] = ch; outlen++; } pop_fragment(input); } if (ch == open_quote_ch) { pop_fragment(input); if (outlen < maxlen) out[outlen] = '\0'; else out[maxlen] = '\0'; return 1; } /* FIXME: handle multiple strings concatenated with commas */ TRACE(("DATA_ERROR: end of input before closing quote\n")); return 0; } static int extract_regis_parenthesized_data(RegisDataFragment *input, RegisDataFragment *output) { char ch; char open_quote_ch; int nesting; assert(input); assert(output); output->start = &input->start[input->pos]; output->len = 0U; output->pos = 0U; if (input->pos >= input->len) return 0; ch = input->start[input->pos]; if (ch != '(') return 0; input->pos++; output->start++; nesting = 1; open_quote_ch = '\0'; ch = '\0'; for (; input->pos < input->len; input->pos++, output->len++) { char prev_ch = ch; ch = input->start[input->pos]; if (ch == '\'' || ch == '"') { if (open_quote_ch == '\0') { open_quote_ch = ch; } else { if (ch == prev_ch && prev_ch == open_quote_ch) { ch = '\0'; } else if (ch == open_quote_ch) { open_quote_ch = '\0'; } } continue; } if (open_quote_ch != '\0') continue; if (ch == ';') { TRACE(("leaving parenthesized data nested %d levels deep due to command termination character\n", nesting)); break; } if (ch == '(') nesting++; if (ch == ')') { nesting--; if (nesting == 0) { input->pos++; return 1; } } } TRACE(("DATA_ERROR: end of input before closing paren (%d levels deep)\n", nesting)); return 0; } static int extract_regis_option(RegisDataFragment *input, char *option, RegisDataFragment *output) { char ch; int paren_level, bracket_level; char open_quote_ch; assert(input); assert(option); assert(output); /* LETTER suboptions* value? */ /* * FIXME: what are the rules for using separate parens vs. sharing between * options? */ output->start = &input->start[input->pos]; output->len = 0U; output->pos = 0U; if (input->pos >= input->len) { return 0; } ch = input->start[input->pos]; /* FIXME: are options always letters or are some special characters ok? */ if (ch == ';' || ch == ',' || ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == '"' || ch == '\'' || isdigit(CharOf(ch))) { return 0; } *option = ch; input->pos++; output->start++; paren_level = 0; bracket_level = 0; open_quote_ch = '\0'; for (; input->pos < input->len; input->pos++, output->len++) { ch = input->start[input->pos]; TRACE(("looking at char '%c' in option '%c'\n", ch, *option)); /* FIXME: any special rules for commas? */ /* FIXME: handle escaped quotes */ if (ch == '\'' || ch == '"') { if (open_quote_ch == ch) { open_quote_ch = '\0'; } else { open_quote_ch = ch; } continue; } if (open_quote_ch != '\0') continue; if (ch == '(') { paren_level++; } if (ch == ')') { paren_level--; if (paren_level < 0) { TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n", *option)); return 0; } } if (ch == '[') { bracket_level++; } if (ch == ']') { bracket_level--; if (bracket_level < 0) { TRACE(("DATA_ERROR: found ReGIS option has value with too many close brackets \"%c\"\n", *option)); return 0; } } if (paren_level == 0 && bracket_level == 0) { /* * Top-level commas indicate the end of this option and the start of * another. */ if (ch == ',') break; /* * Top-level command/option/suboption names also indicate the end of * this option. "E" is valid as the exponent indicator in a numeric * parameter. */ if (ch != 'E' && ch != 'e' && ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))) break; } if (ch == ';') break; } if (paren_level != 0) { TRACE(("DATA_ERROR: mismatched parens in argument to ReGIS option \"%c\"\n", *option)); return 0; } if (bracket_level != 0) { TRACE(("DATA_ERROR: mismatched brackets in argument to ReGIS option \"%c\"\n", *option)); return 0; } TRACE(("found ReGIS option and value \"%c\" \"%s\"\n", *option, fragment_to_tempstr(output))); return 1; } static int regis_num_to_int(RegisDataFragment const *input, int *out) { char ch; assert(input); assert(out); /* FIXME: handle exponential notation and rounding */ /* FIXME: check for junk after the number */ ch = peek_fragment(input); if (!isdigit(CharOf(ch)) && ch != '+' && ch != '-') { *out = 0; return 0; } TRACE(("converting \"%s\" to an int\n", fragment_to_tempstr(input))); *out = atoi(fragment_to_tempstr(input)); return 1; } static int load_regis_colorspec(RegisGraphicsContext const *context, RegisDataFragment const *input, short *r_out, short *g_out, short *b_out) { RegisDataFragment colorspec; short r = -1, g = -1, b = -1; short l = -1; int simple; assert(context); assert(input); assert(r_out); assert(g_out); assert(b_out); copy_fragment(&colorspec, input); TRACE(("colorspec option: \"%s\"\n", fragment_to_tempstr(&colorspec))); skip_regis_whitespace(&colorspec); simple = 0; if (fragment_remaining(&colorspec) == 1U) { simple = 1; } else if (fragment_remaining(&colorspec) > 1U) { char after = get_fragment(&colorspec, 1U); if (IsSpace(after)) simple = 1; } if (simple) { char ch = pop_fragment(&colorspec); TRACE(("got ReGIS RGB colorspec pattern '%c' with arguments: \"%s\"\n", ch, fragment_to_tempstr(&colorspec))); switch (ch) { case 'D': case 'd': r = 0; g = 0; b = 0; l = 0; break; case 'R': case 'r': r = 100; g = 0; b = 0; l = 46; break; case 'G': case 'g': r = 0; g = 100; b = 0; l = 50; break; case 'B': case 'b': r = 0; g = 0; b = 100; l = 50; break; case 'C': case 'c': r = 0; g = 100; b = 100; l = 50; break; case 'Y': case 'y': r = 100; g = 100; b = 0; l = 50; break; case 'M': case 'm': r = 100; g = 0; b = 100; l = 50; break; case 'W': case 'w': r = 100; g = 100; b = 100; l = 100; break; default: TRACE(("DATA_ERROR: unknown RGB color name: \"%c\"\n", ch)); return 0; } } else { RegisDataFragment num; int max, val; char comp; short h = -1; short s = -1; while (!fragment_consumed(&colorspec)) { if (skip_regis_whitespace(&colorspec)) continue; comp = pop_fragment(&colorspec); switch (comp) { case ',': /* not sure if this is valid, but it is easy to handle */ continue; case 'H': case 'h': max = 360; comp = 'H'; break; case 'L': case 'l': max = 100; comp = 'L'; break; case 'S': case 's': max = 100; comp = 'S'; break; #ifdef ENABLE_RGB_COLORSPECS case 'R': /* RLogin extension */ case 'r': max = 100; comp = 'R'; break; case 'G': /* RLogin extension */ case 'g': max = 100; comp = 'G'; break; case 'B': /* RLogin extension */ case 'b': max = 100; comp = 'B'; break; #endif default: TRACE(("DATA_ERROR: unrecognized component in colorspec: '%c'\n", comp)); return 0; } skip_regis_whitespace(&colorspec); if (!extract_regis_num(&colorspec, &num)) { TRACE(("DATA_ERROR: expected int after '%c' component in colorspec: \"%s\"\n", comp, fragment_to_tempstr(&colorspec))); return 0; } if (!regis_num_to_int(&num, &val)) { TRACE(("DATA_ERROR: component value %s is not a number\n", fragment_to_tempstr(&num))); return 0; } /* FIXME: error, truncate, wrap, ...? */ if (val < 0 || val > max) { TRACE(("DATA_ERROR: component value %d out of range\n", val)); return 0; } switch (comp) { case 'H': h = (short) val; break; case 'L': l = (short) val; break; case 'S': s = (short) val; break; case 'R': r = (short) val; break; case 'G': g = (short) val; break; case 'B': b = (short) val; break; } } if (h >= 0 && l >= 0 && s >= 0 && r < 0 && g < 0 && b < 0) { TRACE(("found HLS colorspec to be converted: %hd,%hd,%hd\n", h, l, s)); hls2rgb(h, l, s, &r, &g, &b); TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b)); } else if (h < 0 && l < 0 && s < 0 && r >= 0 && g >= 0 && b >= 0) { TRACE(("found RGB colorspec: %hd,%hd,%hd\n", r, g, b)); l = (short) ((MIN3(r, g, b) + MAX3(r, g, b)) / 2); TRACE(("calculated L: %d\n", l)); } else if (h < 0 && l >= 0 && s < 0 && r < 0 && g < 0 && b < 0) { TRACE(("found L colorspec to be converted: %hd,%hd,%hd\n", h, l, s)); hls2rgb(0, l, 0, &r, &g, &b); TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b)); } else { TRACE(("DATA_ERROR: unrecognized colorspec format\n")); return 0; } } /* * The VT240 and VT330 models convert to the closest grayscale value. */ if (context->graphics_termid == 240 || context->graphics_termid == 330) { hls2rgb(0, l, 0, &r, &g, &b); TRACE(("converted to grayscale: %hd,%hd,%hd\n", r, g, b)); } *r_out = r; *g_out = g; *b_out = b; skip_regis_whitespace(&colorspec); if (!fragment_consumed(&colorspec)) { char skip; skip = pop_fragment(&colorspec); (void) skip; /* variable needed only if tracing */ TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS colorspec \"%c\"\n", skip)); } return 1; } static int load_regis_regnum_or_colorspec(RegisGraphicsContext const *context, RegisDataFragment const *input, RegisterNum *out) { int val; RegisDataFragment colorspec; RegisDataFragment num; RegisDataFragment coloroption; copy_fragment(&colorspec, input); TRACE(("looking at colorspec pattern: \"%s\"\n", fragment_to_tempstr(&colorspec))); skip_regis_whitespace(&colorspec); if (extract_regis_num(&colorspec, &num)) { if (!regis_num_to_int(&num, &val)) { TRACE(("DATA_ERROR: colorspec value %s is not a valid register\n", fragment_to_tempstr(&num))); return 0; } if (val < 0) { /* FIXME: error, truncate, wrap, ...? */ TRACE(("DATA_ERROR: ignoring negative colorspec value: %d\n", val)); return 0; } if (val >= (int) context->destination_graphic->valid_registers) { /* FIXME: error, truncate, wrap, ...? */ TRACE(("DATA_ERROR: colorspec value %d is too big; wrapping\n", val)); val %= (int) context->destination_graphic->valid_registers; } TRACE(("colorspec contains index for register %u\n", val)); *out = (RegisterNum) val; skip_regis_whitespace(&colorspec); if (!fragment_consumed(&colorspec)) { char skip; skip = pop_fragment(&colorspec); (void) skip; /* variable needed only if tracing */ TRACE(("DATA_ERROR: unexpected character after register \"%c\"\n", skip)); return 0; } return 1; } if (extract_regis_parenthesized_data(&colorspec, &coloroption)) { short r, g, b; if (!load_regis_colorspec(context, &coloroption, &r, &g, &b)) { TRACE(("unable to parse colorspec\n")); return 0; } *out = find_color_register(context->destination_graphic->color_registers, r, g, b); TRACE(("colorspec maps to closest register %u\n", *out)); return 1; } TRACE(("expected register number or colorspec, but found: \"%s\"\n", fragment_to_tempstr(&colorspec))); return 0; } static int to_scaled_int(char const *num, int scale, int *value) { unsigned long whole, frac; char *end; /* FIXME: handle whitespace? how about trailing junk? */ whole = strtoul(num, &end, 10); if (end[0] == '.') { char temp[5] = "0000"; if (end[1] != '\0') { temp[0] = end[1]; if (end[2] != '\0') { temp[1] = end[2]; if (end[3] != '\0') { temp[2] = end[3]; if (end[4] != '\0') { temp[3] = end[4]; } } } } frac = strtoul(temp, NULL, 10); } else if (end[0] == '\0' || end[0] == ',' || IsSpace(end[0])) { frac = 0; } else { TRACE(("unexpected character %c in number %s\n", end[0], num)); return 0; } *value = (int) (whole * (unsigned) scale + (frac * (unsigned) scale) / 10000); return 1; } static int load_regis_raw_extent(char const *extent, int *relx, int *rely, int *xloc, int *yloc, int scale) { int xsign, ysign; char const *xpart; char const *ypart; xpart = extent; if ((ypart = strchr(extent, ','))) { ypart++; } else { ypart = ""; } while (IsSpace(xpart[0])) xpart++; while (IsSpace(ypart[0])) ypart++; if (xpart[0] == '-') { xsign = -1; xpart++; } else if (xpart[0] == '+') { xsign = +1; xpart++; } else { xsign = 0; } if (ypart[0] == '-') { ysign = -1; ypart++; } else if (ypart[0] == '+') { ysign = +1; ypart++; } else { ysign = 0; } if (xpart[0] == '\0' || xpart[0] == ',') { *relx = 1; *xloc = 0; } else if (xsign == 0) { int val; if (!to_scaled_int(xpart, scale, &val)) return 0; *relx = 0; *xloc = val; } else { int val; if (!to_scaled_int(xpart, scale, &val)) return 0; *relx = 1; *xloc = xsign * val; } if (ypart[0] == '\0') { *rely = 1; *yloc = 0; } else if (ysign == 0) { int val; if (!to_scaled_int(ypart, scale, &val)) return 0; *rely = 0; *yloc = val; } else { int val; if (!to_scaled_int(ypart, scale, &val)) return 0; *rely = 1; *yloc = ysign * val; } return 1; } static int load_regis_mult_extent(char const *extent, int *w, int *h) { int relx, rely; int px, py; if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) { TRACE(("invalid coordinates in extent %s\n", extent)); return 0; } if (relx | rely) { TRACE(("invalid relative value in multiplier extent %s\n", extent)); return 0; } *w = px; *h = py; return 1; } static int load_regis_pixel_extent(char const *extent, int origx, int origy, int *xloc, int *yloc) { int relx, rely; int px, py; if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) { TRACE(("invalid coordinates in extent %s\n", extent)); return 0; } *xloc = px; *yloc = py; if (relx) *xloc += origx; if (rely) *yloc += origy; return 1; } #define COORD_SCALE 1000 static int load_regis_coord_extent(RegisGraphicsContext const *context, char const *extent, int origx, int origy, int *xloc, int *yloc) { int relx, rely; int ux, uy; if (!load_regis_raw_extent(extent, &relx, &rely, &ux, &uy, COORD_SCALE)) { TRACE(("invalid coordinates in extent %s\n", extent)); return 0; } if (relx) { const int px = SCALE_XCOORD(context, ux, COORD_SCALE); TRACE(("converted relative user X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n", ux / (double) COORD_SCALE, px, context->width, context->x_off, context->x_div)); *xloc = origx + px; } else { const int px = TRANSLATE_XCOORD(context, ux, COORD_SCALE); TRACE(("converted absolute user X coord %.03f to absolute pixel X coord %d\n", ux / (double) COORD_SCALE, px)); *xloc = px; } if (rely) { const int py = SCALE_YCOORD(context, uy, COORD_SCALE); TRACE(("converted relative user Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n", uy / (double) COORD_SCALE, py, context->height, context->y_off, context->y_div)); *yloc = origy + py; } else { const int py = TRANSLATE_YCOORD(context, uy, COORD_SCALE); TRACE(("converted absolute user Y coord %.03f to absolute pixel Y coord %d\n", uy / (double) COORD_SCALE, py)); *yloc = py; } return 1; } static int load_regis_raw_pixelvector_digit(char const *pixelvector, unsigned *offset, int *dx, int *dy, int mul) { switch (pixelvector[*offset]) { case '0': *dx += mul; break; case '1': *dx += mul; *dy -= mul; break; case '2': *dy -= mul; break; case '3': *dx -= mul; *dy -= mul; break; case '4': *dx -= mul; break; case '5': *dx -= mul; *dy += mul; break; case '6': *dy += mul; break; case '7': *dx += mul; *dy += mul; break; default: return 0; } (*offset)++; return 1; } static int load_regis_pixel_pixelvector(char const *pixelvector, int mul, int origx, int origy, int *xloc, int *yloc) { int found = 0; int px = 0, py = 0; unsigned offset = 0U; while (load_regis_raw_pixelvector_digit(pixelvector, &offset, &px, &py, mul)) found = 1; if (pixelvector[offset] != '\0') { TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n", &pixelvector[offset])); } *xloc = origx + px; *yloc = origy + py; return found; } static int load_regis_coord_pixelvector(RegisGraphicsContext const *context, char const *pixelvector, int origx, int origy, int *xloc, int *yloc) { const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE); int found = 0; int ux = 0, uy = 0; unsigned offset = 0U; while (load_regis_raw_pixelvector_digit(pixelvector, &offset, &ux, &uy, mul)) found = 1; if (pixelvector[offset] != '\0') { TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n", &pixelvector[offset])); } { const int px = SCALE_XCOORD(context, ux, COORD_SCALE); const int py = SCALE_YCOORD(context, uy, COORD_SCALE); TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n", ux / (double) COORD_SCALE, px, context->width, context->x_off, context->x_div)); *xloc = origx + px; TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n", uy / (double) COORD_SCALE, py, context->height, context->y_off, context->y_div)); *yloc = origy + py; } return found; } static int load_regis_coord_pixelvector_step(RegisGraphicsContext const *context, char const *pixelvector, unsigned *offset, int origx, int origy, int *xloc, int *yloc) { const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE); int found = 0; int ux = 0, uy = 0; if (load_regis_raw_pixelvector_digit(pixelvector, offset, &ux, &uy, mul)) found = 1; if (!found && pixelvector[*offset] != '\0') { TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n", &pixelvector[*offset])); } { const int px = SCALE_XCOORD(context, ux, COORD_SCALE); const int py = SCALE_YCOORD(context, uy, COORD_SCALE); TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n", ux / (double) COORD_SCALE, px, context->width, context->x_off, context->x_div)); *xloc = origx + px; TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n", uy / (double) COORD_SCALE, py, context->height, context->y_off, context->y_div)); *yloc = origy + py; } return found; } static int load_regis_write_control(RegisParseState *state, RegisGraphicsContext const *context, int cur_x, int cur_y, int option, RegisDataFragment *arg, RegisWriteControls *out) { TRACE(("checking write control option \"%c\" with arg \"%s\"\n", option, fragment_to_tempstr(arg))); switch (option) { case 'A': case 'a': TRACE(("write control alternate display method \"%s\"\n", fragment_to_tempstr(arg))); { int val; if (!regis_num_to_int(arg, &val) || val < 0 || val >= 1) { TRACE(("DATA_ERROR: interpreting out of range value as 0 FIXME\n")); break; } if (val == 1) { TRACE(("ERROR: blink display method not supported FIXME\n")); } } break; case 'C': case 'c': TRACE(("write control compliment writing mode \"%s\"\n", fragment_to_tempstr(arg))); out->write_style = WRITE_STYLE_COMPLEMENT; break; case 'E': case 'e': TRACE(("write control erase writing mode \"%s\"\n", fragment_to_tempstr(arg))); out->write_style = WRITE_STYLE_ERASE; break; case 'F': case 'f': TRACE(("write control plane write mask \"%s\"\n", fragment_to_tempstr(arg))); { int val; if (!regis_num_to_int(arg, &val) || val < 0 || val >= (int) context->destination_graphic->valid_registers) { TRACE(("DATA_ERROR: interpreting out of range value as 0 FIXME\n")); out->plane_mask = 0U; } else { out->plane_mask = (unsigned) val; } } break; case 'I': case 'i': TRACE(("write control foreground color \"%s\"\n", fragment_to_tempstr(arg))); if (!load_regis_regnum_or_colorspec(context, arg, &out->foreground)) { TRACE(("DATA_ERROR: write control foreground color specifier not recognized: \"%s\"\n", fragment_to_tempstr(arg))); return 0; } break; case 'L': case 'l': TRACE(("write control line width \"%s\" (FIXME: currently ignored)\n", fragment_to_tempstr(arg))); { int val; if (!regis_num_to_int(arg, &val) || val < 0 || val >= (int) 9) { TRACE(("interpreting out of range value as 1 FIXME\n")); out->line_width = 1U; } else { out->line_width = (unsigned) val; } } break; case 'M': case 'm': TRACE(("write control found pixel multiplication factor \"%s\"\n", fragment_to_tempstr(arg))); { int val; if (!regis_num_to_int(arg, &val) || val <= 0) { TRACE(("interpreting out of range value %d as 1 FIXME\n", val)); out->pv_multiplier = 1U; } else { out->pv_multiplier = (unsigned) val; } } break; case 'N': case 'n': TRACE(("write control negative pattern control \"%s\"\n", fragment_to_tempstr(arg))); { int val; if (!regis_num_to_int(arg, &val)) { val = -1; } switch (val) { default: TRACE(("interpreting out of range value %d as 0 FIXME\n", val)); out->invert_pattern = 0U; break; case 0: out->invert_pattern = 0U; break; case 1: out->invert_pattern = 1U; break; } } break; case 'P': case 'p': TRACE(("write control found pattern control \"%s\"\n", fragment_to_tempstr(arg))); { RegisDataFragment suboptionset; RegisDataFragment suboptionarg; RegisDataFragment item; char suboption; while (!fragment_consumed(arg)) { if (skip_regis_whitespace(arg)) continue; TRACE(("looking for option in \"%s\"\n", fragment_to_tempstr(arg))); if (extract_regis_parenthesized_data(arg, &suboptionset)) { TRACE(("got write pattern suboptionset: \"%s\"\n", fragment_to_tempstr(&suboptionset))); while (!fragment_consumed(&suboptionset)) { skip_regis_whitespace(&suboptionset); if (extract_regis_option(&suboptionset, &suboption, &suboptionarg)) { skip_regis_whitespace(&suboptionarg); TRACE(("inspecting write pattern suboption \"%c\" with value \"%s\"\n", suboption, fragment_to_tempstr(&suboptionarg))); switch (suboption) { case 'M': case 'm': TRACE(("found pattern multiplier \"%s\"\n", fragment_to_tempstr(&suboptionarg))); { RegisDataFragment num; int val; if (extract_regis_num(&suboptionarg, &num)) { if (!regis_num_to_int(&num, &val) || val < 1) { TRACE(("interpreting out of range pattern multiplier \"%s\" as 2 FIXME\n", fragment_to_tempstr(&num))); out->pattern_multiplier = 2U; } else { out->pattern_multiplier = (unsigned) val; } skip_regis_whitespace(&suboptionarg); } if (!fragment_consumed(&suboptionarg)) { TRACE(("DATA_ERROR: unknown content after pattern multiplier \"%s\"\n", fragment_to_tempstr(&suboptionarg))); return 0; } } break; default: TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n", suboption, fragment_to_tempstr(&suboptionarg))); return 0; } continue; } TRACE(("DATA_ERROR: skipping unknown token in pattern control suboptionset (expecting option): \"%s\"\n", fragment_to_tempstr(&suboptionset))); pop_fragment(&suboptionset); } continue; } TRACE(("looking for int in \"%s\"\n", fragment_to_tempstr(arg))); if (extract_regis_num(arg, &item)) { if (peek_fragment(&item) == '0' || peek_fragment(&item) == '1') { unsigned pattern = 0U; unsigned bitcount; TRACE(("converting pattern bits \"%s\"\n", fragment_to_tempstr(&item))); for (bitcount = 0;; bitcount++) { char ch = pop_fragment(&item); if (ch == '\0') break; switch (ch) { case '0': if (bitcount < MAX_PATTERN_BITS) { pattern <<= 1U; } break; case '1': if (bitcount < MAX_PATTERN_BITS) { pattern <<= 1U; pattern |= 1U; } break; default: TRACE(("DATA_ERROR: unknown ReGIS write pattern bit value \"%c\"\n", ch)); return 0; } } if (bitcount > 0U) { unsigned extrabits; for (extrabits = 0; bitcount + extrabits < MAX_PATTERN_BITS; extrabits++) { if (pattern & (1U << (bitcount - 1U))) { pattern <<= 1U; pattern |= 1U; } else { pattern <<= 1U; } } } out->pattern = pattern; } else { int val; TRACE(("converting pattern id \"%s\"\n", fragment_to_tempstr(&item))); if (!regis_num_to_int(&item, &val)) val = -1; switch (val) { /* FIXME: exponential allowed? */ case 0: out->pattern = 0x00; /* solid bg */ break; case 1: out->pattern = 0xff; /* solid fg */ break; case 2: out->pattern = 0xf0; /* dash */ break; case 3: out->pattern = 0xe4; /* dash dot */ break; case 4: out->pattern = 0xaa; /* dot */ break; case 5: out->pattern = 0xea; /* dash dot dot */ break; case 6: out->pattern = 0x88; /* sparse dot */ break; case 7: out->pattern = 0x84; /* asymmetric sparse dot */ break; case 8: out->pattern = 0xc8; /* sparse dash dot */ break; case 9: out->pattern = 0x86; /* sparse dot dash */ break; default: TRACE(("DATA_ERROR: unknown ReGIS standard write pattern \"%d\"\n", val)); return 0; } } TRACE(("final pattern is %02x\n", out->pattern)); continue; } skip_regis_whitespace(arg); TRACE(("DATA_ERROR: skipping unknown token in pattern suboption: \"%s\"\n", fragment_to_tempstr(arg))); pop_fragment(arg); } } break; case 'R': case 'r': TRACE(("write control switch to replacement writing mode \"%s\"\n", fragment_to_tempstr(arg))); out->write_style = WRITE_STYLE_REPLACE; break; case 'S': case 's': TRACE(("write control shading control \"%s\"\n", fragment_to_tempstr(arg))); { RegisDataFragment suboptionset; RegisDataFragment suboptionarg; RegisDataFragment item; char suboption; char shading_character = '\0'; unsigned reference_dim = WRITE_SHADING_REF_Y; /* FIXME: are relative offsets additive? */ int ref_x = cur_x, ref_y = cur_y; int shading_enabled = 0; while (!fragment_consumed(arg)) { if (skip_regis_whitespace(arg)) continue; if (extract_regis_string(arg, state->temp, state->templen)) { TRACE(("found fill char \"%s\"\n", state->temp)); /* FIXME: allow longer strings, ignore extra chars, or treat as error? */ if (strlen(state->temp) != 1) { TRACE(("DATA_ERROR: expected exactly one char in fill string FIXME\n")); return 0; } shading_character = state->temp[0]; shading_enabled = 1; TRACE(("shading character is: '%c' (%d)\n", shading_character, (int) shading_character)); continue; } if (extract_regis_parenthesized_data(arg, &suboptionset)) { skip_regis_whitespace(&suboptionset); TRACE(("got shading control suboptionset: \"%s\"\n", fragment_to_tempstr(&suboptionset))); while (!fragment_consumed(&suboptionset)) { if (skip_regis_whitespace(&suboptionset)) continue; if (extract_regis_option(&suboptionset, &suboption, &suboptionarg)) { TRACE(("inspecting write shading suboption \"%c\" with value \"%s\"\n", suboption, fragment_to_tempstr(&suboptionarg))); switch (suboption) { case 'X': case 'x': TRACE(("found horizontal shading suboption \"%s\"\n", fragment_to_tempstr(&suboptionarg))); if (!fragment_consumed(&suboptionarg)) { TRACE(("DATA_ERROR: unexpected value to horizontal shading suboption FIXME\n")); return 0; } reference_dim = WRITE_SHADING_REF_X; shading_enabled = 1; break; default: TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n", suboption, fragment_to_tempstr(&suboptionarg))); return 0; } continue; } TRACE(("DATA_ERROR: skipping unknown token in shading control suboptionset (expecting option): \"%s\"\n", fragment_to_tempstr(&suboptionset))); pop_fragment(&suboptionset); } continue; } if (extract_regis_extent(arg, &item)) { TRACE(("found extent in shading option curr=%d,%d ref=%d,%d\n", cur_x, cur_y, ref_x, ref_y)); if (!load_regis_coord_extent(context, fragment_to_tempstr(&item), ref_x, ref_y, &ref_x, &ref_y)) { TRACE(("DATA_ERROR: unable to parse extent in write shading option '%c': \"%s\"\n", option, fragment_to_tempstr(&item))); return 0; } TRACE(("shading reference = %d,%d (%s)\n", ref_x, ref_y, ((reference_dim == WRITE_SHADING_REF_X) ? "X" : "Y"))); continue; } if (extract_regis_num(arg, &item)) { if (!regis_num_to_int(&item, &shading_enabled)) { TRACE(("DATA_ERROR: unable to parse int in write shading option '%c': \"%s\"\n", option, fragment_to_tempstr(&item))); return 0; } if (shading_enabled < 0 || shading_enabled > 1) { TRACE(("interpreting out of range value %d as 0 FIXME\n", shading_enabled)); shading_enabled = 0; } TRACE(("shading enabled = %d\n", shading_enabled)); continue; } if (skip_regis_whitespace(arg)) { continue; } TRACE(("DATA_ERROR: skipping unknown token in shade suboption: \"%s\"\n", fragment_to_tempstr(arg))); pop_fragment(arg); } if (shading_enabled) { out->shading_enabled = 1U; out->shading_reference_dim = reference_dim; out->shading_reference = ((reference_dim == WRITE_SHADING_REF_X) ? ref_x : ref_y); out->shading_character = shading_character; TRACE(("final shading state: enabled, dim=%d ref=%d, char=%c\n", out->shading_reference_dim, out->shading_reference, out->shading_character)); } else { /* FIXME: confirm there is no effect if shading isn't enabled * in the same command */ out->shading_enabled = 0U; TRACE(("final shading state: shading disabled\n")); } } break; case 'V': case 'v': TRACE(("write control switch to overlay writing mode \"%s\"\n", fragment_to_tempstr(arg))); out->write_style = WRITE_STYLE_OVERLAY; break; default: TRACE(("DATA_ERROR: ignoring unknown ReGIS write option \"%c\" arg \"%s\"\n", option, fragment_to_tempstr(arg))); return 0; } return 1; } static int load_regis_write_control_set(RegisParseState *state, RegisGraphicsContext const *context, int cur_x, int cur_y, RegisDataFragment *controls, RegisWriteControls *out) { RegisDataFragment optionset; RegisDataFragment arg; char option; while (!fragment_consumed(controls)) { if (skip_regis_whitespace(controls)) continue; if (extract_regis_parenthesized_data(controls, &optionset)) { TRACE(("got write control optionset: \"%s\"\n", fragment_to_tempstr(&optionset))); while (!fragment_consumed(&optionset)) { skip_regis_whitespace(&optionset); if (extract_regis_option(&optionset, &option, &arg)) { skip_regis_whitespace(&arg); TRACE(("got write control option and value: \"%c\" \"%s\"\n", option, fragment_to_tempstr(&arg))); if (!load_regis_write_control(state, context, cur_x, cur_y, option, &arg, out)) { return 0; } continue; } TRACE(("DATA_ERROR: skipping unknown token in write control optionset (expecting option): \"%s\"\n", fragment_to_tempstr(&optionset))); pop_fragment(&optionset); } continue; } TRACE(("DATA_ERROR: skipping unknown token in write controls (expecting optionset): \"%s\"\n", fragment_to_tempstr(controls))); pop_fragment(controls); } return 1; } static void init_regis_write_controls(int graphics_termid, unsigned all_planes, RegisWriteControls *controls) { controls->pv_multiplier = 1U; controls->pattern = 0xff; /* solid */ controls->pattern_multiplier = 2U; controls->invert_pattern = 0U; controls->plane_mask = all_planes; controls->write_style = WRITE_STYLE_OVERLAY; switch (graphics_termid) { case 125: /* FIXME: verify */ case 240: /* FIXME: verify */ case 241: /* FIXME: verify */ case 330: controls->foreground = 3U; break; case 340: default: controls->foreground = 7U; break; case 382: controls->foreground = 1U; /* FIXME: verify */ break; } controls->shading_enabled = 0U; controls->shading_character = '\0'; controls->shading_reference = 0; /* no meaning if shading is disabled */ controls->shading_reference_dim = WRITE_SHADING_REF_NONE; controls->line_width = 1U; /* FIXME: add the rest */ } static void map_regis_graphics_pages(XtermWidget xw, RegisGraphicsContext *context) { const int charrow = 0; const int charcol = 0; unsigned old_display_id = ~0U; if (context->destination_graphic) context->destination_graphic->hidden = 1; if (context->display_graphic) { context->display_graphic->hidden = 1; old_display_id = context->display_graphic->id; } context->destination_graphic = get_new_or_matching_graphic(xw, charrow, charcol, context->width, context->height, context->destination_page); if (context->destination_graphic) { context->destination_graphic->hidden = 1; context->destination_graphic->valid = 1; } context->display_graphic = get_new_or_matching_graphic(xw, charrow, charcol, context->width, context->height, context->display_page); if (context->display_graphic) { context->display_graphic->hidden = 0; if (old_display_id != context->display_graphic->id) { if (!context->display_graphic->valid) { draw_solid_rectangle(context->display_graphic, 0, 0, context->width, context->height, context->background); } context->display_graphic->dirty = 1; context->force_refresh = 1; /* FIXME: This isn't really enough. If there are holes in the new * graphic they should be cleared and set to the text from the same * page. But we don't have pages for text in xterm (the alt buffer * is similar though). */ } context->display_graphic->valid = 1; } TRACE(("using graphics destination=[%d -> %u] display=[%d -> %u]\n", context->destination_page, (context->destination_graphic ? context->destination_graphic->id : 0U), context->display_page, (context->display_graphic ? context->display_graphic->id : 0U))); } static void copy_regis_write_controls(RegisWriteControls const *src, RegisWriteControls *dst) { dst->pv_multiplier = src->pv_multiplier; dst->pattern = src->pattern; dst->pattern_multiplier = src->pattern_multiplier; dst->invert_pattern = src->invert_pattern; dst->foreground = src->foreground; dst->plane_mask = src->plane_mask; dst->write_style = src->write_style; dst->shading_enabled = src->shading_enabled; dst->shading_character = src->shading_character; dst->shading_reference = src->shading_reference; dst->shading_reference_dim = src->shading_reference_dim; dst->line_width = src->line_width; } static void init_regis_text_controls(RegisTextControls *controls) { controls->alphabet_num = 0U; /* built-in */ controls->character_set_l = 0U; /* ASCII */ controls->character_set_r = 0U; /* Latin-1 */ get_standard_character_size(1, &controls->character_display_w, &controls->character_display_h, &controls->character_unit_cell_w, &controls->character_unit_cell_h, &controls->character_inc_x, &controls->character_inc_y); controls->string_rotation = 0; controls->character_rotation = 0; controls->slant = 0; } static void copy_regis_text_controls(RegisTextControls const *src, RegisTextControls *dst) { dst->alphabet_num = src->alphabet_num; dst->character_set_l = src->character_set_l; dst->character_set_r = src->character_set_r; dst->character_display_w = src->character_display_w; dst->character_display_h = src->character_display_h; dst->character_unit_cell_w = src->character_unit_cell_w; dst->character_unit_cell_h = src->character_unit_cell_h; dst->character_inc_x = src->character_inc_x; dst->character_inc_y = src->character_inc_y; dst->string_rotation = src->string_rotation; dst->character_rotation = src->character_rotation; dst->slant = src->slant; } static void init_regis_alphabets(RegisGraphicsContext *context) { unsigned alphabet_index; for (alphabet_index = 0U; alphabet_index < MAX_REGIS_ALPHABETS; alphabet_index++) { context->alphabets[alphabet_index].alphabet_num = INVALID_ALPHABET_NUM; context->alphabets[alphabet_index].pixw = 0U; context->alphabets[alphabet_index].pixh = 0U; context->alphabets[alphabet_index].name[0] = '\0'; context->alphabets[alphabet_index].fontname[0] = '\0'; context->alphabets[alphabet_index].use_font = 0; context->alphabets[alphabet_index].bytes = NULL; } } static void init_regis_graphics_context(int graphics_termid, int width, int height, unsigned max_colors, const char *builtin_font, RegisGraphicsContext *context) { context->destination_graphic = NULL; context->display_graphic = NULL; context->display_page = 0U; context->destination_page = 0U; context->graphics_termid = graphics_termid; /* reset addressing / clear user coordinates */ context->width = width; context->height = height; context->x_off = 0; context->y_off = 0; context->x_div = width - 1; context->y_div = height - 1; /* * Generate a mask covering all valid color register address bits * (but don't bother past 2**16). */ context->all_planes = max_colors; context->all_planes--; context->all_planes |= 1U; context->all_planes |= context->all_planes >> 1U; context->all_planes |= context->all_planes >> 2U; context->all_planes |= context->all_planes >> 4U; context->all_planes |= context->all_planes >> 8U; context->builtin_font = builtin_font; init_regis_write_controls(graphics_termid, context->all_planes, &context->persistent_write_controls); copy_regis_write_controls(&context->persistent_write_controls, &context->temporary_write_controls); init_regis_text_controls(&context->persistent_text_controls); context->current_text_controls = &context->persistent_text_controls; init_regis_alphabets(context); context->multi_input_mode = 0; /* FIXME: coordinates */ /* FIXME: scrolling */ context->background = 0U; /* FIXME: input cursor location */ /* FIXME: input cursor style */ context->graphics_output_cursor_x = 0; context->graphics_output_cursor_y = 0; /* FIXME: output cursor style */ context->force_refresh = 0; } static int parse_regis_command(RegisParseState *state) { char ch; if (!extract_regis_command(&state->input, &ch)) return 0; switch (ch) { case 'C': case 'c': /* Curve * C * (A) # set the arc length in degrees (+ or nothing for * # counter-clockwise, - for clockwise, rounded to the * # closest integer degree) * (B) # begin closed curve sequence (must have at least two * # values; this option can not be nested) * (C) # position is the center, current location is the * # circumference (stays in effect until next command) * (E) # end curve sequence (drawing is performed here) * (S) # begin open curve sequence * (W) # temporary write options (see write command) * [] # center if (C), otherwise point on circumference * []... # if between (B) and (E) * ... # if between (B) and (E) */ TRACE(("found ReGIS command \"%c\" (curve)\n", ch)); state->command = 'c'; state->curve_mode = CURVE_POSITION_ARC_EDGE; state->arclen = 360; state->num_points = 0U; break; case 'F': case 'f': /* Fill * F * (V) # polygon (see vector command) * (C) # curve (see curve command) * (W) # temporary write options (see write command) */ TRACE(("found ReGIS command \"%c\" (filled polygon)\n", ch)); state->command = 'f'; break; case 'L': case 'l': /* Load * L * (A) # set alphabet number or name * (F)"fontname" # load from font (xterm extension) * (S)[w,h] # set glyph size (xterm extension) * "ascii"xx,xx,xx,xx,xx,xx,xx,xx # pixel values */ TRACE(("found ReGIS command \"%c\" (load charset)\n", ch)); state->command = 'l'; break; case 'P': case 'p': /* Position * P * (B) # begin bounded position stack (last point returns to first) * (E) # end position stack * (P) # select graphics page for the input and output cursors * (S) # begin unbounded position stack * (W) # temporary write options (see write command) * # move: 0 == right, 1 == upper right, ..., 7 == lower right * [] # move to position (X, Y, or both) * * Note the stack does not need to be ended before the next command * Note: maximum depth is 16 levels */ TRACE(("found ReGIS command \"%c\" (position)\n", ch)); state->command = 'p'; break; case 'R': case 'r': /* Report * R * (E) # parse error * (I) # set input mode (0 == one-shot, 1 == multiple) (always returns CR) * (L) # current alphabet number and name * (M() # macrograph contents * (M(=) # macrograph storage (free bytes of total bytes) * (P) # absolute output cursor position * (P(I)) # interactive locator mode (in one-shot or multiple mode) * (P(I[xmul,ymul])) # interactive locator mode with arrow key movement multipliers */ TRACE(("found ReGIS command \"%c\" (report status)\n", ch)); state->command = 'r'; break; case 'S': case 's': /* Screen * S * (A[][]) # adjust screen coordinates * (C # 0 (cursor output off), 1 (cursor output on) * (E) # erase to background color, resets shades, curves, and stacks * (F) # print the graphic and erase the screen (DECprint extension) * (H(P)[][) * (I) # set the background to a specific register * (I()) # set the background to the register closest to an "RGB" color * (I(RGB)) # set the background to the register closest to an RGB triplet (RLogin extension) * (I(HLS)) # set the background to the register closest to an HLS triplet * (I(L)) # set the background to the register closest to a grayscale value * (M()...) # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color and grayscale registers) * (M(A)...) # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color registers only) * (M(RGB)...) # 0..100, 0..100, 0..100 (sets color and grayscale registers) (RLogin extension) * (M(ARGB)...) # 0..100, 0..100, 0..100 (sets color registers only) (RLogin extension) * (M(HLS)...) # 0..360, 0..100, 0..100 (sets color and grayscale registers) * (M(AHLS)...) # 0..360, 0..100, 0..100 (sets color registers only) * (M(L)...) # level is 0 ... 100 (sets grayscale registers only) * (P) # 0 (default) or 1 * (S() # scale screen output by scale (default 1, VT125:max=2, VT3x0:unsupported) FIXME * (S(X) # scale screen output horizontally by scale (default 1, VT125:max=2, VT3x0:unsupported) FIXME * (S(Y) # scale screen output vertically by scale (default 1, VT125:max=2, VT3x0:unsupported) FIXME * (T(