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