"Fossies" - the Fresh Open Source Software Archive

Member "xterm-379/graphics_sixel.c" (10 Oct 2022, 20940 Bytes) of package /linux/misc/xterm-379.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "graphics_sixel.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 373_vs_374.

    1 /* $XTermId: graphics_sixel.c,v 1.38 2022/10/10 15:09:41 tom Exp $ */
    2 
    3 /*
    4  * Copyright 2014-2021,2022 by Ross Combs
    5  * Copyright 2014-2021,2022 by Thomas E. Dickey
    6  *
    7  *                         All Rights Reserved
    8  *
    9  * Permission is hereby granted, free of charge, to any person obtaining a
   10  * copy of this software and associated documentation files (the
   11  * "Software"), to deal in the Software without restriction, including
   12  * without limitation the rights to use, copy, modify, merge, publish,
   13  * distribute, sublicense, and/or sell copies of the Software, and to
   14  * permit persons to whom the Software is furnished to do so, subject to
   15  * the following conditions:
   16  *
   17  * The above copyright notice and this permission notice shall be included
   18  * in all copies or substantial portions of the Software.
   19  *
   20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   23  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
   24  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   25  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   26  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   27  *
   28  * Except as contained in this notice, the name(s) of the above copyright
   29  * holders shall not be used in advertising or otherwise to promote the
   30  * sale, use or other dealings in this Software without prior written
   31  * authorization.
   32  */
   33 
   34 #include <xterm.h>
   35 
   36 #include <stdio.h>
   37 #include <ctype.h>
   38 #include <stdlib.h>
   39 
   40 #include <data.h>
   41 #include <VTparse.h>
   42 #include <ptyx.h>
   43 
   44 #include <assert.h>
   45 #include <graphics.h>
   46 #include <graphics_sixel.h>
   47 
   48 /***====================================================================***/
   49 /*
   50  * Parse numeric parameters which have the operator as a prefix rather than a
   51  * suffix as in ANSI format.
   52  *
   53  *  #             0
   54  *  #1            1
   55  *  #1;           1
   56  *  "1;2;640;480  4
   57  *  #1;2;0;0;0    5
   58  */
   59 static void
   60 parse_prefixedtype_params(ANSI *params, const char **string)
   61 {
   62     const char *cp = *string;
   63     ParmType nparam = 0;
   64     int last_empty = 1;
   65 
   66     memset(params, 0, sizeof(*params));
   67     params->a_final = CharOf(*cp);
   68     if (*cp != '\0')
   69     cp++;
   70 
   71     while (*cp != '\0') {
   72     Char ch = CharOf(*cp);
   73 
   74     if (isdigit(ch)) {
   75         last_empty = 0;
   76         if (nparam < NPARAM) {
   77         params->a_param[nparam] =
   78             (ParmType) ((params->a_param[nparam] * 10)
   79                 + (ch - '0'));
   80         }
   81     } else if (ch == ';') {
   82         last_empty = 1;
   83         nparam++;
   84     } else if (ch == ' ' || ch == '\r' || ch == '\n') {
   85         /* EMPTY */ ;
   86     } else {
   87         break;
   88     }
   89     cp++;
   90     }
   91 
   92     *string = cp;
   93     if (!last_empty)
   94     nparam++;
   95     if (nparam > NPARAM)
   96     params->a_nparam = NPARAM;
   97     else
   98     params->a_nparam = nparam;
   99 }
  100 
  101 typedef struct {
  102     RegisterNum current_register;
  103     RegisterNum background; /* current background color register or hole */
  104     int aspect_vertical;
  105     int aspect_horizontal;
  106     int declared_width;     /* size as reported by the application */
  107     int declared_height;    /* size as reported by the application */
  108     int row;            /* context used during parsing */
  109     int col;            /* context used during parsing */
  110 } SixelContext;
  111 
  112 /* SIXEL SCROLLING, which is on by default in VT3xx terminals, can be
  113  * turned off to better emulate VT2xx terminals by setting Sixel
  114  * Display Mode (DECSDM)
  115  *
  116  *      SIXEL DISPLAY MODE  SIXEL SCROLLING
  117  * VT125    Always on       Unsupported
  118  * VT240    Always on       Unsupported
  119  * VT241    Always on       Unsupported
  120  * VT330    Available via DECSDM    Default mode
  121  * VT382    Available via DECSDM    Default mode
  122  * VT340    Available via DECSDM    Default mode
  123  * VK100/GIGI   No sixel support    No sixel support
  124  *
  125  * dxterm (DECterm) emulated a VT100 series terminal, and supported sixels
  126  * according to 1995 posting to comp.os.vms:
  127  *  https://groups.google.com/g/comp.os.vms/c/XAUMmLtC8Yk
  128  * though not DRCS according to
  129  *  http://odl.sysworks.biz/disk$axpdocdec023/office/dwmot126/vmsdw126/relnotes/6470pro_004.html
  130  */
  131 
  132 static void
  133 init_sixel_background(Graphic *graphic, SixelContext const *context)
  134 {
  135     RegisterNum *source;
  136     RegisterNum *target;
  137     size_t length;
  138     int r, c;
  139 
  140     TRACE(("initializing sixel background to size=%dx%d bgcolor=%hu\n",
  141        context->declared_width,
  142        context->declared_height,
  143        context->background));
  144 
  145     if (context->background == COLOR_HOLE)
  146     return;
  147 
  148     source = graphic->pixels;
  149     for (c = 0; c < graphic->actual_width; c++) {
  150     source[c] = context->background;
  151     }
  152     target = source;
  153     length = (size_t) graphic->actual_width * sizeof(*target);
  154     for (r = 1; r < graphic->actual_height; r++) {
  155     target += graphic->max_width;
  156     memcpy(target, source, length);
  157     }
  158     graphic->color_registers_used[context->background] = 1;
  159 }
  160 
  161 #define ValidColumn(graphic, context) \
  162     ((context)->col >= 0 && \
  163      (context)->col < (graphic)->max_width)
  164 
  165 static Boolean
  166 set_sixel(Graphic *graphic, SixelContext const *context, int sixel)
  167 {
  168     const int mh = graphic->max_height;
  169     const int mw = graphic->max_width;
  170     const RegisterNum color = context->current_register;
  171     int pix;
  172     int pix_row = context->row;
  173     int pix_col = context->col + (pix_row * mw);
  174 
  175     TRACE2(("drawing sixel at pos=%d,%d color=%hu (hole=%d, [%d,%d,%d])\n",
  176         context->col,
  177         context->row,
  178         color,
  179         color == COLOR_HOLE,
  180         ((color != COLOR_HOLE)
  181          ? (unsigned) graphic->color_registers[color].r : 0U),
  182         ((color != COLOR_HOLE)
  183          ? (unsigned) graphic->color_registers[color].g : 0U),
  184         ((color != COLOR_HOLE)
  185          ? (unsigned) graphic->color_registers[color].b : 0U)));
  186     for (pix = 0; pix < 6; pix++, pix_row++, pix_col += mw) {
  187     if (pix_row >= 0 &&
  188         pix_row < mh) {
  189         if (sixel & (1 << pix)) {
  190         if (context->col >= graphic->actual_width) {
  191             graphic->actual_width = context->col + 1;
  192         }
  193         if (pix_row >= graphic->actual_height) {
  194             graphic->actual_height = pix_row + 1;
  195         }
  196         SetSpixel(graphic, pix_col, color);
  197         }
  198     } else {
  199         TRACE(("sixel pixel %d out of bounds\n", pix));
  200         return False;
  201     }
  202     }
  203     return True;
  204 }
  205 
  206 static void
  207 update_sixel_aspect(SixelContext const *context, Graphic *graphic)
  208 {
  209     /* We want to keep the ratio accurate but would like every pixel to have
  210      * the same size so keep these as whole numbers.
  211      */
  212     /* FIXME: DEC terminals had pixels about twice as tall as they were wide,
  213      * and it seems the VT125 and VT24x only used data from odd graphic rows.
  214      * This means it basically cancels out if we ignore both, except that
  215      * the even rows of pixels may not be written by the application such that
  216      * they are suitable for display.  In practice this doesn't seem to be
  217      * an issue but I have very few test files/programs.
  218      */
  219     if (context->aspect_vertical < context->aspect_horizontal) {
  220     graphic->pixw = 1;
  221     graphic->pixh = ((context->aspect_vertical
  222               + context->aspect_horizontal - 1)
  223              / context->aspect_horizontal);
  224     } else {
  225     graphic->pixw = ((context->aspect_horizontal
  226               + context->aspect_vertical - 1)
  227              / context->aspect_vertical);
  228     graphic->pixh = 1;
  229     }
  230     TRACE(("sixel aspect ratio: an=%d ad=%d -> pixw=%d pixh=%d\n",
  231        context->aspect_vertical,
  232        context->aspect_horizontal,
  233        graphic->pixw,
  234        graphic->pixh));
  235 }
  236 
  237 static int
  238 finished_parsing(XtermWidget xw, Graphic *graphic)
  239 {
  240     TScreen *screen = TScreenOf(xw);
  241 
  242     /* Update the screen scrolling and do a refresh.
  243      * The refresh may not cover the whole graphic.
  244      */
  245     if (screen->scroll_amt)
  246     FlushScroll(xw);
  247 
  248     if (SixelScrolling(xw)) {
  249     int new_row, new_col;
  250 
  251     if (screen->sixel_scrolls_right) {
  252         new_row = (graphic->charrow
  253                + (((graphic->actual_height * graphic->pixh)
  254                + FontHeight(screen) - 1)
  255               / FontHeight(screen))
  256                - 1);
  257         new_col = (graphic->charcol
  258                + (((graphic->actual_width * graphic->pixw)
  259                + FontWidth(screen) - 1)
  260               / FontWidth(screen)));
  261     } else {
  262         /* NOTE: XTerm follows the VT382 behavior in text cursor
  263          * placement.  The VT382's vertical position appears to be
  264          * truncated (rounded toward zero) after converting to character
  265          * row.  While rounding up is more often what is desired, so as to
  266          * not overwrite the image, doing so automatically would cause text
  267          * or graphics to scroll off the top of the screen.  Therefore,
  268          * applications must add their own newline character, if desired,
  269          * after a sixel image.
  270          *
  271          * FIXME: The VT340 also rounds down, but it seems to have a
  272          * strange behavior where, on rare occasions, two newlines are
  273          * required to advance beyond the end of the image.  This appears
  274          * to be a firmware bug, but it should be added as an option for
  275          * compatibility.
  276          */
  277         new_row = (graphic->charrow - 1
  278                + (((graphic->actual_height * graphic->pixh)
  279                + FontHeight(screen) - 1)
  280               / FontHeight(screen)));
  281         new_col = graphic->charcol;
  282     }
  283 
  284     TRACE(("setting text position after %dx%d\t%.1f start (%d %d): cursor (%d,%d)\n",
  285            graphic->actual_width * graphic->pixw,
  286            graphic->actual_height * graphic->pixh,
  287            ((double) graphic->charrow
  288         + ((double) (graphic->actual_height * graphic->pixh)
  289            / (double) FontHeight(screen))),
  290            graphic->charrow,
  291            graphic->charcol,
  292            new_row, new_col));
  293 
  294     if (new_col > screen->rgt_marg) {
  295         new_col = screen->lft_marg;
  296         new_row++;
  297         TRACE(("column past left margin, overriding to row=%d col=%d\n",
  298            new_row, new_col));
  299     }
  300 
  301     while (new_row > screen->bot_marg) {
  302         xtermScroll(xw, 1);
  303         new_row--;
  304         TRACE(("bottom row was past screen.  new start row=%d, cursor row=%d\n",
  305            graphic->charrow, new_row));
  306     }
  307 
  308     if (new_row < 0) {
  309         /* FIXME: this was triggering, now it isn't */
  310         TRACE(("new row is going to be negative (%d); skipping position update!",
  311            new_row));
  312     } else {
  313         set_cur_row(screen, new_row);
  314         set_cur_col(screen, new_col <= screen->rgt_marg ? new_col : screen->rgt_marg);
  315     }
  316     }
  317 
  318     graphic->dirty = True;
  319     refresh_modified_displayed_graphics(xw);
  320 
  321     TRACE(("DONE parsed sixel data\n"));
  322     dump_graphic(graphic);
  323     return 0;
  324 }
  325 
  326 /*
  327  * Interpret sixel graphics sequences.
  328  *
  329  * Resources:
  330  *  http://vt100.net/docs/vt3xx-gp/chapter14.html
  331  *  ftp://ftp.cs.utk.edu/pub/shuford/terminal/sixel_graphics_news.txt
  332  *  ftp://ftp.cs.utk.edu/pub/shuford/terminal/all_about_sixels.txt
  333  */
  334 int
  335 parse_sixel(XtermWidget xw, ANSI *params, char const *string)
  336 {
  337     TScreen *screen = TScreenOf(xw);
  338     Graphic *graphic;
  339     SixelContext context;
  340 
  341     switch (screen->terminal_id) {
  342     case 240:
  343     case 241:
  344     case 330:
  345     case 340:
  346     context.aspect_vertical = 2;
  347     context.aspect_horizontal = 1;
  348     break;
  349     case 382:
  350     context.aspect_vertical = 1;
  351     context.aspect_horizontal = 1;
  352     break;
  353     default:
  354     context.aspect_vertical = 2;
  355     context.aspect_horizontal = 1;
  356     break;
  357     }
  358 
  359     context.declared_width = 0;
  360     context.declared_height = 0;
  361 
  362     context.row = 0;
  363     context.col = 0;
  364 
  365     /* default isn't white on the VT240, but not sure what it is */
  366     context.current_register = 3;   /* FIXME: using green, but not sure what it should be */
  367 
  368     if (SixelScrolling(xw)) {
  369     TRACE(("sixel scrolling enabled: inline positioning for graphic at %d,%d\n",
  370            screen->cur_row, screen->cur_col));
  371     graphic = get_new_graphic(xw, screen->cur_row, screen->cur_col, 0U);
  372     } else {
  373     TRACE(("sixel scrolling disabled: inline positioning for graphic at %d,%d\n",
  374            0, 0));
  375     graphic = get_new_graphic(xw, 0, 0, 0U);
  376     }
  377 
  378     {
  379     int Pmacro = params->a_param[0];
  380     int Pbgmode = params->a_param[1];
  381     int Phgrid = params->a_param[2];
  382     int Pan = params->a_param[3];
  383     int Pad = params->a_param[4];
  384     int Ph = params->a_param[5];
  385     int Pv = params->a_param[6];
  386 
  387     (void) Phgrid;
  388 
  389     TRACE(("sixel bitmap graphics sequence: params=%d (Pmacro=%d Pbgmode=%d Phgrid=%d) scroll_amt=%d\n",
  390            params->a_nparam,
  391            Pmacro,
  392            Pbgmode,
  393            Phgrid,
  394            screen->scroll_amt));
  395 
  396     switch (params->a_nparam) {
  397     case 7:
  398         if (Pan == 0 || Pad == 0) {
  399         TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
  400         return -1;
  401         }
  402         context.aspect_vertical = Pan;
  403         context.aspect_horizontal = Pad;
  404 
  405         if (Ph <= 0 || Pv <= 0) {
  406         TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
  407                Ph, Pv));
  408         return -1;
  409         }
  410         if (Ph > graphic->max_width || Pv > graphic->max_height) {
  411         TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
  412                Ph, Pv));
  413         return -1;
  414         }
  415         context.declared_width = Ph;
  416         context.declared_height = Pv;
  417         if (context.declared_width > graphic->actual_width) {
  418         graphic->actual_width = context.declared_width;
  419         }
  420         if (context.declared_height > graphic->actual_height) {
  421         graphic->actual_height = context.declared_height;
  422         }
  423         break;
  424     case 3:
  425     case 2:
  426     case 1:
  427         switch (Pmacro) {
  428         case 0:
  429         /* keep default aspect settings */
  430         break;
  431         case 1:
  432         case 5:
  433         case 6:
  434         context.aspect_vertical = 2;
  435         context.aspect_horizontal = 1;
  436         break;
  437         case 2:
  438         context.aspect_vertical = 5;
  439         context.aspect_horizontal = 1;
  440         break;
  441         case 3:
  442         case 4:
  443         context.aspect_vertical = 3;
  444         context.aspect_horizontal = 1;
  445         break;
  446         case 7:
  447         case 8:
  448         case 9:
  449         context.aspect_vertical = 1;
  450         context.aspect_horizontal = 1;
  451         break;
  452         default:
  453         TRACE(("DATA_ERROR: unknown sixel macro mode parameter\n"));
  454         return -1;
  455         }
  456         break;
  457     case 0:
  458         break;
  459     default:
  460         TRACE(("DATA_ERROR: unexpected parameter count (found %d)\n", params->a_nparam));
  461         return -1;
  462     }
  463 
  464     if (Pbgmode == 1) {
  465         context.background = COLOR_HOLE;
  466     } else {
  467         /* FIXME: is the default background register always zero?  what about in light background mode? */
  468         context.background = 0;
  469     }
  470 
  471     /* Ignore the grid parameter because it seems only printers paid attention to it.
  472      * The VT3xx was always 0.0195 cm.
  473      */
  474     }
  475 
  476     update_sixel_aspect(&context, graphic);
  477 
  478     for (;;) {
  479     Char ch = CharOf(*string);
  480     if (ch == '\0')
  481         break;
  482 
  483     if (ch >= 0x3f && ch <= 0x7e) {
  484         int sixel = ch - 0x3f;
  485         TRACE(("sixel=%x (%c)\n", sixel, (char) ch));
  486         if (!graphic->valid) {
  487         init_sixel_background(graphic, &context);
  488         graphic->valid = True;
  489         }
  490         if (sixel) {
  491         if (!ValidColumn(graphic, &context) ||
  492             !set_sixel(graphic, &context, sixel)) {
  493             context.col = 0;
  494             break;
  495         }
  496         }
  497         context.col++;
  498     } else if (ch == '$') { /* DECGCR */
  499         /* ignore DECCRNLM in sixel mode */
  500         TRACE(("sixel CR\n"));
  501         context.col = 0;
  502     } else if (ch == '-') { /* DECGNL */
  503         int scroll_lines;
  504         TRACE(("sixel NL\n"));
  505         scroll_lines = 0;
  506         while (graphic->charrow - scroll_lines +
  507            (((context.row + Min(6, graphic->actual_height - context.row))
  508              * graphic->pixh
  509              + FontHeight(screen) - 1)
  510             / FontHeight(screen)) > screen->bot_marg) {
  511         scroll_lines++;
  512         }
  513         context.col = 0;
  514         context.row += 6;
  515         /* If we hit the bottom margin on the graphics page (well, we just use the
  516          * text margin for now), the behavior is to either scroll or to discard
  517          * the remainder of the graphic depending on this setting.
  518          */
  519         if (scroll_lines > 0) {
  520         if (SixelScrolling(xw)) {
  521             Display *display = screen->display;
  522             xtermScroll(xw, scroll_lines);
  523             XSync(display, False);
  524             TRACE(("graphic scrolled the screen %d lines. screen->scroll_amt=%d screen->topline=%d, now starting row is %d\n",
  525                scroll_lines,
  526                screen->scroll_amt,
  527                screen->topline,
  528                graphic->charrow));
  529         } else {
  530             break;
  531         }
  532         }
  533     } else if (ch == '!') { /* DECGRI */
  534         int Pcount;
  535         const char *start;
  536         int sixel;
  537 
  538         start = ++string;
  539         for (;;) {
  540         ch = CharOf(*string);
  541         if (!(isdigit(ch) || isspace(ch)))
  542             break;
  543         string++;
  544         }
  545         if (ch == '\0') {
  546         TRACE(("DATA_ERROR: sixel data string terminated in the middle of a repeat operator\n"));
  547         return finished_parsing(xw, graphic);
  548         }
  549         if (string == start) {
  550         TRACE(("DATA_ERROR: sixel data string contains a repeat operator with empty count\n"));
  551         return finished_parsing(xw, graphic);
  552         }
  553         Pcount = atoi(start);
  554         sixel = ch - 0x3f;
  555         TRACE(("sixel repeat operator: sixel=%d (%c), count=%d\n",
  556            sixel, (char) ch, Pcount));
  557         if (!graphic->valid) {
  558         init_sixel_background(graphic, &context);
  559         graphic->valid = True;
  560         }
  561         if (sixel) {
  562         int i;
  563         for (i = 0; i < Pcount; i++) {
  564             if (ValidColumn(graphic, &context) &&
  565             set_sixel(graphic, &context, sixel)) {
  566             context.col++;
  567             } else {
  568             context.col = 0;
  569             break;
  570             }
  571         }
  572         } else {
  573         context.col += Pcount;
  574         }
  575     } else if (ch == '#') { /* DECGCI */
  576         ANSI color_params;
  577         int Pregister;
  578 
  579         parse_prefixedtype_params(&color_params, &string);
  580         Pregister = color_params.a_param[0];
  581         if (Pregister >= (int) graphic->valid_registers) {
  582         TRACE(("DATA_WARNING: sixel color operator uses out-of-range register %d\n", Pregister));
  583         /* FIXME: supposedly the DEC terminals wrapped register indices -- verify */
  584         while (Pregister >= (int) graphic->valid_registers)
  585             Pregister -= (int) graphic->valid_registers;
  586         TRACE(("DATA_WARNING: converted to %d\n", Pregister));
  587         }
  588 
  589         if (color_params.a_nparam > 2 && color_params.a_nparam <= 5) {
  590         int Pspace = color_params.a_param[1];
  591         int Pc1 = color_params.a_param[2];
  592         int Pc2 = color_params.a_param[3];
  593         int Pc3 = color_params.a_param[4];
  594         short r, g, b;
  595 
  596         TRACE(("sixel set color register=%d space=%d color=[%d,%d,%d] (nparams=%d)\n",
  597                Pregister, Pspace, Pc1, Pc2, Pc3, color_params.a_nparam));
  598 
  599         switch (Pspace) {
  600         case 1: /* HLS */
  601             if (Pc1 > 360 || Pc2 > 100 || Pc3 > 100) {
  602             TRACE(("DATA_ERROR: sixel set color operator uses out-of-range HLS color coordinates %d,%d,%d\n",
  603                    Pc1, Pc2, Pc3));
  604             return finished_parsing(xw, graphic);
  605             }
  606             hls2rgb(Pc1, Pc2, Pc3, &r, &g, &b);
  607             break;
  608         case 2: /* RGB */
  609             if (Pc1 > 100 || Pc2 > 100 || Pc3 > 100) {
  610             TRACE(("DATA_ERROR: sixel set color operator uses out-of-range RGB color coordinates %d,%d,%d\n",
  611                    Pc1, Pc2, Pc3));
  612             return finished_parsing(xw, graphic);
  613             }
  614             r = (short) Pc1;
  615             g = (short) Pc2;
  616             b = (short) Pc3;
  617             break;
  618         default:    /* unknown */
  619             TRACE(("DATA_ERROR: sixel set color operator uses unknown color space %d\n", Pspace));
  620             return finished_parsing(xw, graphic);
  621         }
  622         update_color_register(graphic,
  623                       (RegisterNum) Pregister,
  624                       r, g, b);
  625         } else if (color_params.a_nparam == 1) {
  626         TRACE(("sixel switch to color register=%d (nparams=%d)\n",
  627                Pregister, color_params.a_nparam));
  628         context.current_register = (RegisterNum) Pregister;
  629         } else {
  630         TRACE(("DATA_ERROR: sixel switch color operator with unexpected parameter count (nparams=%d)\n", color_params.a_nparam));
  631         return finished_parsing(xw, graphic);
  632         }
  633         continue;
  634     } else if (ch == '"') /* DECGRA */  {
  635         ANSI raster_params;
  636 
  637         parse_prefixedtype_params(&raster_params, &string);
  638         if (raster_params.a_nparam < 2) {
  639         TRACE(("DATA_ERROR: sixel raster attribute operator with incomplete parameters (found %d, expected 2 or 4)\n", raster_params.a_nparam));
  640         return finished_parsing(xw, graphic);
  641         } {
  642         int Pan = raster_params.a_param[0];
  643         int Pad = raster_params.a_param[1];
  644         TRACE(("sixel raster attribute with h:w=%d:%d\n", Pan, Pad));
  645         if (Pan == 0 || Pad == 0) {
  646             TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
  647             return finished_parsing(xw, graphic);
  648         }
  649         context.aspect_vertical = Pan;
  650         context.aspect_horizontal = Pad;
  651         update_sixel_aspect(&context, graphic);
  652         }
  653 
  654         if (raster_params.a_nparam >= 4) {
  655         int Ph = raster_params.a_param[2];
  656         int Pv = raster_params.a_param[3];
  657 
  658         TRACE(("sixel raster attribute with h=%d v=%d\n", Ph, Pv));
  659         if (Ph <= 0 || Pv <= 0) {
  660             TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
  661                Ph, Pv));
  662             return finished_parsing(xw, graphic);
  663         }
  664         if (Ph > graphic->max_width || Pv > graphic->max_height) {
  665             TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
  666                Ph, Pv));
  667             return finished_parsing(xw, graphic);
  668         }
  669         context.declared_width = Ph;
  670         context.declared_height = Pv;
  671         if (context.declared_width > graphic->actual_width) {
  672             graphic->actual_width = context.declared_width;
  673         }
  674         if (context.declared_height > graphic->actual_height) {
  675             graphic->actual_height = context.declared_height;
  676         }
  677         }
  678 
  679         continue;
  680     } else if (ch == ' ' || ch == '\r' || ch == '\n') {
  681         /* EMPTY */ ;
  682     } else {
  683         TRACE(("DATA_ERROR: skipping unknown sixel command %04x (%c)\n",
  684            (int) ch, ch));
  685     }
  686 
  687     string++;
  688     }
  689 
  690     return finished_parsing(xw, graphic);
  691 }