1 /*
2 * tkimg.c --
3 *
4 * Generic interface to XML parsers.
5 *
6 * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net>
7 *
8 * Zveno Pty Ltd makes this software and associated documentation
9 * available free of charge for any purpose. You may make copies
10 * of the software but you must include all of this notice on any copy.
11 *
12 * Zveno Pty Ltd does not warrant that this software is error free
13 * or fit for any purpose. Zveno Pty Ltd disclaims any liability for
14 * all claims, expenses, losses, damages and costs any user may incur
15 * as a result of using, copying or modifying the software.
16 */
17
18 #include "tkimg.h"
19
20 /*
21 * Prototypes for procedures defined later in this file:
22 */
23
24 /* Variables needed for optional read buffer. See tkimg_ReadBuffer. */
25 #define BUFLEN 4096
26 static int useReadBuf = 0;
27 static int bufStart = -1;
28 static int bufEnd = -1;
29 static char readBuf[BUFLEN];
30
31
32 /*
33 *--------------------------------------------------------------------------
34 * char64 --
35 *
36 * This procedure converts a base64 ascii character into its binary
37 * equivalent. This code is a slightly modified version of the
38 * char64 proc in N. Borenstein's metamail decoder.
39 *
40 * Results:
41 * The binary value, or an error code.
42 *
43 * Side effects:
44 * None.
45 *--------------------------------------------------------------------------
46 */
47
char64(int c)48 static int char64(
49 int c
50 ) {
51 switch(c) {
52 case 'A': return 0; case 'B': return 1; case 'C': return 2;
53 case 'D': return 3; case 'E': return 4; case 'F': return 5;
54 case 'G': return 6; case 'H': return 7; case 'I': return 8;
55 case 'J': return 9; case 'K': return 10; case 'L': return 11;
56 case 'M': return 12; case 'N': return 13; case 'O': return 14;
57 case 'P': return 15; case 'Q': return 16; case 'R': return 17;
58 case 'S': return 18; case 'T': return 19; case 'U': return 20;
59 case 'V': return 21; case 'W': return 22; case 'X': return 23;
60 case 'Y': return 24; case 'Z': return 25; case 'a': return 26;
61 case 'b': return 27; case 'c': return 28; case 'd': return 29;
62 case 'e': return 30; case 'f': return 31; case 'g': return 32;
63 case 'h': return 33; case 'i': return 34; case 'j': return 35;
64 case 'k': return 36; case 'l': return 37; case 'm': return 38;
65 case 'n': return 39; case 'o': return 40; case 'p': return 41;
66 case 'q': return 42; case 'r': return 43; case 's': return 44;
67 case 't': return 45; case 'u': return 46; case 'v': return 47;
68 case 'w': return 48; case 'x': return 49; case 'y': return 50;
69 case 'z': return 51; case '0': return 52; case '1': return 53;
70 case '2': return 54; case '3': return 55; case '4': return 56;
71 case '5': return 57; case '6': return 58; case '7': return 59;
72 case '8': return 60; case '9': return 61; case '+': return 62;
73 case '/': return 63;
74
75 case ' ': case '\t': case '\n': case '\r': case '\f': return IMG_SPACE;
76 case '=': return IMG_PAD;
77 case '\0': return IMG_DONE;
78 default: return IMG_BAD;
79 }
80 }
81
82 /*
83 *--------------------------------------------------------------
84 *
85 * tkimg_ReadBuffer --
86 * Initialize optional read buffer.
87 *
88 * The optional read buffer may be used for compressed image file
89 * formats (ex. RLE), where the file has to be read byte by byte.
90 * This option is only available when reading from an image file,
91 * i.e. "image create -file ..."
92 *
93 * CAUTION:
94 * - Use this option only, when you do NOT use file seeks.
95 * - Use tkimg_ReadBuffer (1) to initialize the read buffer before usage.
96 * Use tkimg_ReadBuffer (0) to switch off the read buffer after usage.
97 *
98 * Results:
99 * None
100 *
101 * Side effects:
102 * Changes the static variables needed for the read buffer.
103 *
104 *--------------------------------------------------------------
105 */
106
tkimg_ReadBuffer(int onOff)107 void tkimg_ReadBuffer(
108 int onOff
109 ) {
110 useReadBuf = onOff;
111 if (onOff) {
112 memset(readBuf, 0, BUFLEN);
113 bufStart = -1;
114 bufEnd = -1;
115 }
116 }
117
118 /*
119 *--------------------------------------------------------------------------
120 * tkimg_Read --
121 *
122 * This procedure returns a buffer from the stream input. This stream
123 * could be anything from a base-64 encoded string to a Channel.
124 *
125 * Results:
126 * The number of characters successfully read from the input
127 *
128 * Side effects:
129 * The tkimg_MFile state could change.
130 *--------------------------------------------------------------------------
131 */
132
tkimg_Read(tkimg_MFile * handle,char * dst,int count)133 int tkimg_Read(
134 tkimg_MFile *handle /* mmdecode "file" handle */,
135 char *dst /* where to put the result */,
136 int count /* number of bytes */
137 ) {
138 int i, c;
139 int bytesRead, bytesToRead;
140 char *dstPtr;
141
142 switch (handle->state) {
143 case IMG_STRING:
144 if ((unsigned int)count > handle->length) {
145 count = handle->length;
146 }
147 if (count) {
148 memcpy(dst, handle->data, count);
149 handle->length -= count;
150 handle->data += count;
151 }
152 return count;
153 case IMG_CHAN:
154 if (!useReadBuf) {
155 return (int) Tcl_Read((Tcl_Channel) handle->data, dst, count);
156 }
157 dstPtr = dst;
158 bytesToRead = count;
159 bytesRead = 0;
160 while (bytesToRead > 0) {
161 #ifdef DEBUG_LOCAL
162 printf ("bytesToRead=%d bytesRead=%d (bufStart=%d bufEnd=%d)\n",
163 bytesToRead, bytesRead, bufStart, bufEnd);
164 #endif
165 if (bufStart < 0) {
166 bufEnd = Tcl_Read((Tcl_Channel)handle->data, readBuf, BUFLEN)-1;
167 #ifdef DEBUG_LOCAL
168 printf ("Reading new %d bytes into buffer "
169 "(bufStart=%d bufEnd=%d)\n",
170 BUFLEN, bufStart, bufEnd);
171 #endif
172 bufStart = 0;
173 if (bufEnd < 0)
174 return bufEnd;
175 }
176 if (bufStart + bytesToRead <= bufEnd +1) {
177 #ifdef DEBUG_LOCAL
178 printf("All in buffer: memcpy %d bytes\n", bytesToRead);
179 #endif
180 /* All bytes already in the buffer. Just copy them to dst. */
181 memcpy(dstPtr, readBuf + bufStart, bytesToRead);
182 bufStart += bytesToRead;
183 if (bufStart > BUFLEN)
184 bufStart = -1;
185 return bytesRead + bytesToRead;
186 } else {
187 #ifdef DEBUG_LOCAL
188 printf("Copy rest of buffer: memcpy %d bytes\n",
189 bufEnd+1-bufStart);
190 #endif
191 memcpy (dstPtr, readBuf + bufStart, bufEnd+1 - bufStart);
192 bytesRead += (bufEnd +1 - bufStart);
193 bytesToRead -= (bufEnd+1 - bufStart);
194 bufStart = -1;
195 dstPtr += bytesRead;
196 }
197 }
198 }
199
200 for(i = 0; i < count && (c = tkimg_Getc(handle)) != IMG_DONE; i++) {
201 *dst++ = c;
202 }
203 return i;
204 }
205
206 /*
207 *--------------------------------------------------------------------------
208 * tkimg_Read2 --
209 *
210 * This procedure returns a buffer from the stream input. This stream
211 * could be anything from a base-64 encoded string to a Channel.
212 *
213 * Results:
214 * The number of characters successfully read from the input
215 *
216 * Side effects:
217 * The tkimg_MFile state could change.
218 *--------------------------------------------------------------------------
219 */
220
tkimg_Read2(tkimg_MFile * handle,char * dst,size_t count)221 size_t tkimg_Read2(
222 tkimg_MFile *handle /* mmdecode "file" handle */,
223 char *dst /* where to put the result */,
224 size_t count /* number of bytes */
225 ) {
226 size_t i;
227 int c;
228 size_t bytesRead, bytesToRead;
229 char *dstPtr;
230
231 switch (handle->state) {
232 case IMG_STRING:
233 if (count > handle->length) {
234 count = handle->length;
235 }
236 if (count) {
237 memcpy(dst, handle->data, count);
238 handle->length -= count;
239 handle->data += count;
240 }
241 return count;
242 case IMG_CHAN:
243 if (!useReadBuf) {
244 return (size_t) Tcl_Read((Tcl_Channel) handle->data, dst, count);
245 }
246 dstPtr = dst;
247 bytesToRead = count;
248 bytesRead = 0;
249 while (bytesToRead > 0) {
250 #ifdef DEBUG_LOCAL
251 printf ("bytesToRead=%d bytesRead=%d (bufStart=%d bufEnd=%d)\n",
252 bytesToRead, bytesRead, bufStart, bufEnd);
253 #endif
254 if (bufStart < 0) {
255 bufEnd = Tcl_Read((Tcl_Channel)handle->data, readBuf, BUFLEN)-1;
256 #ifdef DEBUG_LOCAL
257 printf ("Reading new %d bytes into buffer "
258 "(bufStart=%d bufEnd=%d)\n",
259 BUFLEN, bufStart, bufEnd);
260 #endif
261 bufStart = 0;
262 if (bufEnd < 0)
263 return bufEnd;
264 }
265 if (bufStart + (int)bytesToRead <= bufEnd + 1) {
266 #ifdef DEBUG_LOCAL
267 printf("All in buffer: memcpy %d bytes\n", bytesToRead);
268 #endif
269 /* All bytes already in the buffer. Just copy them to dst. */
270 memcpy(dstPtr, readBuf + bufStart, bytesToRead);
271 bufStart += bytesToRead;
272 if (bufStart > BUFLEN)
273 bufStart = -1;
274 return bytesRead + bytesToRead;
275 } else {
276 #ifdef DEBUG_LOCAL
277 printf("Copy rest of buffer: memcpy %d bytes\n",
278 bufEnd+1-bufStart);
279 #endif
280 memcpy (dstPtr, readBuf + bufStart, bufEnd+1 - bufStart);
281 bytesRead += (bufEnd +1 - bufStart);
282 bytesToRead -= (bufEnd+1 - bufStart);
283 bufStart = -1;
284 dstPtr += bytesRead;
285 }
286 }
287 }
288
289 for(i = 0; i < count && (c = tkimg_Getc(handle)) != IMG_DONE; i++) {
290 *dst++ = c;
291 }
292 return i;
293 }
294
295 /*
296 *--------------------------------------------------------------------------
297 *
298 * tkimg_Getc --
299 *
300 * This procedure returns the next input byte from a stream. This stream
301 * could be anything from a base-64 encoded string to a Channel.
302 *
303 * Results:
304 * The next byte (or IMG_DONE) is returned.
305 *
306 * Side effects:
307 * The tkimg_MFile state could change.
308 *
309 *--------------------------------------------------------------------------
310 */
311
tkimg_Getc(tkimg_MFile * handle)312 int tkimg_Getc(
313 tkimg_MFile *handle /* Input stream handle */
314 ) {
315 int c;
316 int result = 0; /* Initialization needed only to prevent
317 * gcc compiler warning */
318 if (handle->state == IMG_DONE) {
319 return IMG_DONE;
320 }
321
322 if (handle->state == IMG_STRING) {
323 if (!handle->length--) {
324 handle->state = IMG_DONE;
325 return IMG_DONE;
326 }
327 return *handle->data++;
328 }
329
330 do {
331 if (!handle->length--) {
332 handle->state = IMG_DONE;
333 return IMG_DONE;
334 }
335 c = char64(*handle->data++);
336 } while (c == IMG_SPACE);
337
338 if (c > IMG_SPECIAL) {
339 handle->state = IMG_DONE;
340 return IMG_DONE;
341 }
342
343 switch (handle->state++) {
344 case 0:
345 handle->c = c<<2;
346 result = tkimg_Getc(handle);
347 break;
348 case 1:
349 result = handle->c | (c>>4);
350 handle->c = (c&0xF)<<4;
351 break;
352 case 2:
353 result = handle->c | (c>>2);
354 handle->c = (c&0x3)<<6;
355 break;
356 case 3:
357 result = handle->c | c;
358 handle->state = 0;
359 break;
360 }
361 return result;
362 }
363
364 /*
365 *-----------------------------------------------------------------------
366 * tkimg_Write --
367 *
368 * This procedure is invoked to put imaged data into a stream
369 * using tkimg_Putc.
370 *
371 * Results:
372 * The return value is the number of characters "written"
373 *
374 * Side effects:
375 * The base64 handle will change state.
376 *
377 *-----------------------------------------------------------------------
378 */
379
tkimg_Write(tkimg_MFile * handle,const char * src,int count)380 int tkimg_Write(
381 tkimg_MFile *handle /* mmencode "file" handle */,
382 const char *src /* where to get the data */,
383 int count /* number of bytes */
384 ) {
385 int i;
386 int curcount, bufcount;
387
388 if (handle->state == IMG_CHAN) {
389 return Tcl_Write((Tcl_Channel) handle->data, (char *) src, count);
390 }
391 curcount = handle->data - Tcl_DStringValue(handle->buffer);
392 bufcount = curcount + count + count/3 + count/52 + 1024;
393
394 /* make sure that the DString contains enough space */
395 if (bufcount >= (handle->buffer->spaceAvl)) {
396 Tcl_DStringSetLength(handle->buffer, bufcount + 4096);
397 handle->data = Tcl_DStringValue(handle->buffer) + curcount;
398 }
399 /* write the data */
400 for (i=0; (i<count) && (tkimg_Putc(*src++, handle) != IMG_DONE); i++) {
401 /* empty loop body */
402 }
403 return i;
404 }
405
406 /*
407 *-----------------------------------------------------------------------
408 * tkimg_Write2 --
409 *
410 * This procedure is invoked to put imaged data into a stream
411 * using tkimg_Putc.
412 *
413 * Results:
414 * The return value is the number of characters "written"
415 *
416 * Side effects:
417 * The base64 handle will change state.
418 *
419 *-----------------------------------------------------------------------
420 */
421
tkimg_Write2(tkimg_MFile * handle,const char * src,size_t count)422 size_t tkimg_Write2(
423 tkimg_MFile *handle /* mmencode "file" handle */,
424 const char *src /* where to get the data */,
425 size_t count /* number of bytes */
426 ) {
427 size_t i, curcount, bufcount;
428
429 if (handle->state == IMG_CHAN) {
430 return Tcl_Write((Tcl_Channel) handle->data, (char *) src, count);
431 }
432 curcount = handle->data - Tcl_DStringValue(handle->buffer);
433 bufcount = curcount + count + count/3 + count/52 + 1024;
434
435 /* make sure that the DString contains enough space */
436 if (bufcount >= (size_t)(handle->buffer->spaceAvl)) {
437 Tcl_DStringSetLength(handle->buffer, bufcount + 4096);
438 handle->data = Tcl_DStringValue(handle->buffer) + curcount;
439 }
440 /* write the data */
441 for (i=0; (i<count) && (tkimg_Putc(*src++, handle) != IMG_DONE); i++) {
442 /* empty loop body */
443 }
444 return i;
445 }
446
447 /*
448 *-----------------------------------------------------------------------
449 *
450 * tkimg_Putc --
451 *
452 * This procedure encodes and writes the next byte to a base64
453 * encoded string.
454 *
455 * Results:
456 * The written byte is returned.
457 *
458 * Side effects:
459 * the base64 handle will change state.
460 *
461 *-----------------------------------------------------------------------
462 */
463
464 static char const base64_table[64] = {
465 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
466 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
467 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
468 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
469 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
470 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
471 'w', 'x', 'y', 'z', '0', '1', '2', '3',
472 '4', '5', '6', '7', '8', '9', '+', '/'
473 };
474
tkimg_Putc(int c,tkimg_MFile * handle)475 int tkimg_Putc(
476 int c /* character to be written */,
477 tkimg_MFile *handle /* handle containing decoder data and state */
478 ) {
479 /* In fact, here should be checked first if the dynamic
480 * string contains enough space for the next character.
481 * This would be very expensive to do for each character.
482 * Therefore we just allocate 1024 bytes immediately in
483 * the beginning and also take a 1024 bytes margin inside
484 * every tkimg_Write. At least this check is done then only
485 * every 256 bytes, which is much faster. Because the GIF
486 * header is less than 1024 bytes and pixel data is
487 * written in 256 byte portions, this should be safe.
488 */
489
490 if (c == IMG_DONE) {
491 switch(handle->state) {
492 case 0:
493 break;
494 case 1:
495 *handle->data++ = base64_table[(handle->c<<4)&63];
496 *handle->data++ = '='; *handle->data++ = '='; break;
497 case 2:
498 *handle->data++ = base64_table[(handle->c<<2)&63];
499 *handle->data++ = '='; break;
500 default:
501 handle->state = IMG_DONE;
502 return IMG_DONE;
503 }
504 Tcl_DStringSetLength(handle->buffer,
505 (handle->data) - Tcl_DStringValue(handle->buffer));
506 handle->state = IMG_DONE;
507 return IMG_DONE;
508 }
509
510 if (handle->state == IMG_CHAN) {
511 char ch = (char) c;
512 return (Tcl_Write((Tcl_Channel) handle->data, &ch, 1)+1>1) ? c : IMG_DONE;
513 }
514
515 c &= 0xff;
516 switch (handle->state++) {
517 case 0:
518 *handle->data++ = base64_table[(c>>2)&63]; break;
519 case 1:
520 c |= handle->c << 8;
521 *handle->data++ = base64_table[(c>>4)&63]; break;
522 case 2:
523 handle->state = 0;
524 c |= handle->c << 8;
525 *handle->data++ = base64_table[(c>>6)&63];
526 *handle->data++ = base64_table[c&63]; break;
527 }
528 handle->c = c;
529 if (handle->length++ > 52) {
530 handle->length = 0;
531 *handle->data++ = '\n';
532 }
533 return c & 0xff;
534 };
535
536 /*
537 *-------------------------------------------------------------------------
538 * tkimg_WriteInit --
539 * This procedure initializes a base64 decoder handle for writing
540 *
541 * Results:
542 * none
543 *
544 * Side effects:
545 * the base64 handle is initialized
546 *
547 *-------------------------------------------------------------------------
548 */
549
tkimg_WriteInit(Tcl_DString * buffer,tkimg_MFile * handle)550 void tkimg_WriteInit(
551 Tcl_DString *buffer,
552 tkimg_MFile *handle /* mmencode "file" handle */
553 ) {
554 Tcl_DStringSetLength(buffer, buffer->spaceAvl);
555 handle->buffer = buffer;
556 handle->data = Tcl_DStringValue(buffer);
557 handle->state = 0;
558 handle->length = 0;
559 }
560
561 /*
562 *-------------------------------------------------------------------------
563 * tkimg_ReadInit --
564 * This procedure initializes a base64 decoder handle for reading.
565 *
566 * Results:
567 * none
568 *
569 * Side effects:
570 * the base64 handle is initialized
571 *
572 *-------------------------------------------------------------------------
573 */
574
tkimg_ReadInit(Tcl_Obj * data,int c,tkimg_MFile * handle)575 int tkimg_ReadInit(
576 Tcl_Obj *data /* string containing initial mmencoded data */,
577 int c,
578 tkimg_MFile *handle /* mmdecode "file" handle */
579 ) {
580 size_t length;
581 handle->data = (char *) tkimg_GetByteArrayFromObj2(data, &length);
582 handle->length = length;
583 if (*handle->data == c) {
584 handle->state = IMG_STRING;
585 return 1;
586 }
587 c = base64_table[(c>>2)&63];
588
589 while ((handle->length) && (char64(*handle->data) == IMG_SPACE)) {
590 handle->data++;
591 handle->length--;
592 }
593 if (c != *handle->data) {
594 handle->state = IMG_DONE;
595 return 0;
596 }
597 handle->state = 0;
598 return 1;
599 }
600
601 /*
602 *----------------------------------------------------------------------
603 *
604 * tkimg_OpenFileChannel --
605 *
606 * Open a file channel in binary mode. If permissions is 0, the
607 * file will be opened in read mode, otherwise in write mode.
608 *
609 * Results:
610 * The same as Tcl_OpenFileChannel, only the file will
611 * always be opened in binary mode without encoding.
612 *
613 * Side effects:
614 * If function fails, an error message will be left in the
615 * interpreter.
616 *
617 *----------------------------------------------------------------------
618 */
619
tkimg_OpenFileChannel(Tcl_Interp * interp,const char * fileName,int permissions)620 Tcl_Channel tkimg_OpenFileChannel(
621 Tcl_Interp *interp,
622 const char *fileName,
623 int permissions
624 ) {
625 Tcl_Channel chan = Tcl_OpenFileChannel(interp, fileName,
626 permissions?"w":"r", permissions);
627 if (!chan) {
628 return NULL;
629 }
630 if (Tcl_SetChannelOption(interp, chan, "-buffersize", "131072") != TCL_OK) {
631 Tcl_Close(interp, chan);
632 return NULL;
633 }
634 if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) {
635 Tcl_Close(interp, chan);
636 return NULL;
637 }
638 return chan;
639 }
640