compress images in record device
[swftools.git] / lib / devices / record.c
1 /* gfxdevice_record.cc
2
3    Part of the swftools package.
4
5    Copyright (c) 2005 Matthias Kramm <kramm@quiss.org> 
6  
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <math.h>
24 #include "../../config.h"
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <memory.h>
29 #ifdef HAVE_IO_H
30 #include <io.h>
31 #endif
32 #include <zlib.h>
33 #include <string.h>
34 #include <assert.h>
35 #include "../gfxdevice.h"
36 #include "../gfxtools.h"
37 #include "../types.h"
38 #include "../bitio.h"
39 #include "../log.h"
40 #include "../os.h"
41 #include "../png.h"
42 #include "record.h"
43
44 //#define STATS
45
46 typedef struct _state {
47     char*last_string[16];
48     gfxcolor_t last_color[16];
49     gfxmatrix_t last_matrix[16];
50
51 #ifdef STATS
52     int size_matrices;
53     int size_positions;
54     int size_images;
55     int size_lines;
56     int size_colors;
57     int size_fonts;
58     int size_chars;
59 #endif
60 } state_t;
61
62 typedef struct _internal {
63     gfxfontlist_t* fontlist;
64     state_t state;
65
66     writer_t w;
67     int cliplevel;
68     char use_tempfile;
69     char*filename;
70 } internal_t;
71
72 typedef struct _internal_result {
73     char use_tempfile;
74     char*filename;
75     void*data;
76     int length;
77 } internal_result_t;
78
79 #define OP_END 0x00
80 #define OP_SETPARAM 0x01
81 #define OP_STROKE 0x02
82 #define OP_STARTCLIP 0x03
83 #define OP_ENDCLIP 0x04
84 #define OP_FILL 0x05
85 #define OP_FILLBITMAP 0x06
86 #define OP_FILLGRADIENT 0x07
87 #define OP_ADDFONT 0x08
88 #define OP_DRAWCHAR 0x09
89 #define OP_DRAWLINK 0x0a
90 #define OP_STARTPAGE 0x0b
91 #define OP_ENDPAGE 0x0c
92 #define OP_FINISH 0x0d
93
94 #define FLAG_SAME_AS_LAST 0x10
95
96 #define LINE_MOVETO 0x0e
97 #define LINE_LINETO 0x0f
98 #define LINE_SPLINETO 0x10
99
100 /* ----------------- reading/writing of low level primitives -------------- */
101
102 static void dumpLine(writer_t*w, state_t*state, gfxline_t*line)
103 {
104     while(line) {
105         if(line->type == gfx_moveTo) {
106             writer_writeU8(w, LINE_MOVETO);
107             writer_writeDouble(w, line->x);
108             writer_writeDouble(w, line->y);
109 #ifdef STATS
110             state->size_lines += 1+8+8;
111 #endif
112         } else if(line->type == gfx_lineTo) {
113             writer_writeU8(w, LINE_LINETO);
114             writer_writeDouble(w, line->x);
115             writer_writeDouble(w, line->y);
116 #ifdef STATS
117             state->size_lines += 1+8+8;
118 #endif
119         } else if(line->type == gfx_splineTo) {
120             writer_writeU8(w, LINE_SPLINETO);
121             writer_writeDouble(w, line->x);
122             writer_writeDouble(w, line->y);
123             writer_writeDouble(w, line->sx);
124             writer_writeDouble(w, line->sy);
125 #ifdef STATS
126             state->size_lines += 1+8+8+8+8;
127 #endif
128         }
129         line = line->next;
130     }
131     writer_writeU8(w, OP_END);
132 #ifdef STATS
133     state->size_lines += 1;
134 #endif
135 }
136 static gfxline_t* readLine(reader_t*r, state_t*s)
137 {
138     gfxline_t*start = 0, *pos = 0;
139     while(1) {
140         unsigned char op = reader_readU8(r);
141         if(op == OP_END)
142             break;
143         gfxline_t*line = (gfxline_t*)rfx_calloc(sizeof(gfxline_t));
144         if(!start) {
145             start = pos = line;
146         } else {
147             pos->next = line;
148             pos = line;
149         }
150         if(op == LINE_MOVETO) {
151             line->type = gfx_moveTo;
152             line->x = reader_readDouble(r);
153             line->y = reader_readDouble(r);
154         } else if(op == LINE_LINETO) {
155             line->type = gfx_lineTo;
156             line->x = reader_readDouble(r);
157             line->y = reader_readDouble(r);
158         } else if(op == LINE_SPLINETO) {
159             line->type = gfx_splineTo;
160             line->x = reader_readDouble(r);
161             line->y = reader_readDouble(r);
162             line->sx = reader_readDouble(r);
163             line->sy = reader_readDouble(r);
164         }
165     }
166     return start;
167 }
168
169 #define COMPRESS_IMAGES
170 static void dumpImage(writer_t*w, state_t*state, gfximage_t*img)
171 {
172     int oldpos = w->pos;
173     writer_writeU16(w, img->width);
174     writer_writeU16(w, img->height);
175 #ifdef COMPRESS_IMAGES
176     //45.2% images (3055340 bytes) (without filter)
177     //39.9% images (2458904 bytes) (with filter, Z_BEST_SPEED)
178     //35.3% images (2027305 bytes) (with filter, Z_BEST_COMPRESSION)
179     unsigned char*filter = malloc(img->height);
180     int y;
181     gfxcolor_t*image = malloc(img->width*img->height*sizeof(gfxcolor_t));
182     for(y=0;y<img->height;y++) {
183         filter[y] = png_apply_filter_32(
184                 (void*)&image[y*img->width], 
185                 (void*)&img->data[y*img->width], img->width, y);
186     }
187     int size = img->width*img->height;
188     uLongf compressdata_size = compressBound(size*sizeof(gfxcolor_t));
189     void*compressdata = malloc(compressdata_size);
190     compress2(compressdata, &compressdata_size, (void*)image, sizeof(gfxcolor_t)*size, Z_BEST_SPEED);
191     writer_writeU32(w, compressdata_size);
192     w->write(w, filter, img->height);
193     w->write(w, compressdata, compressdata_size);
194     free(image);
195     free(filter);
196 #else
197     w->write(w, img->data, img->width*img->height*sizeof(gfxcolor_t));
198 #endif
199 #ifdef STATS
200     state->size_images += w->pos - oldpos;
201 #endif
202 }
203 static gfximage_t readImage(reader_t*r, state_t*state)
204 {
205     gfximage_t img;
206     img.width = reader_readU16(r);
207     img.height = reader_readU16(r);
208     uLongf size = img.width*img.height*sizeof(gfxcolor_t);
209     img.data = malloc(size);
210 #ifdef COMPRESS_IMAGES
211     uLongf compressdata_size = reader_readU32(r);
212     void*compressdata = malloc(compressdata_size);
213     unsigned char*filter = malloc(img.height);
214     r->read(r, filter, img.height);
215     r->read(r, compressdata, compressdata_size);
216     uncompress((void*)img.data, &size, compressdata, compressdata_size);
217     int y;
218     unsigned char*line = malloc(img.width*sizeof(gfxcolor_t));
219     for(y=0;y<img.height;y++) {
220         png_inverse_filter_32(filter[y], (void*)&img.data[y*img.width], 
221                               y?(void*)&img.data[(y-1)*img.width]:(void*)0, 
222                               line, img.width);
223         memcpy(&img.data[y*img.width], line, img.width*sizeof(gfxcolor_t));
224     }
225     free(filter);
226 #else
227     r->read(r, img.data, size);
228 #endif
229     return img;
230 }
231
232 static void dumpMatrix(writer_t*w, state_t*state, gfxmatrix_t*matrix)
233 {
234     writer_writeDouble(w, matrix->m00);
235     writer_writeDouble(w, matrix->m01);
236     writer_writeDouble(w, matrix->m10);
237     writer_writeDouble(w, matrix->m11);
238     writer_writeDouble(w, matrix->tx);
239     writer_writeDouble(w, matrix->ty);
240 #ifdef STATS
241     state->size_matrices += 6*8;
242 #endif
243 }
244 static gfxmatrix_t readMatrix(reader_t*r, state_t*state)
245 {
246     gfxmatrix_t matrix;
247     matrix.m00 = reader_readDouble(r);
248     matrix.m01 = reader_readDouble(r);
249     matrix.m10 = reader_readDouble(r);
250     matrix.m11 = reader_readDouble(r);
251     matrix.tx = reader_readDouble(r);
252     matrix.ty = reader_readDouble(r);
253     return matrix;
254 }
255 static void dumpXY(writer_t*w, state_t*state, gfxmatrix_t*matrix)
256 {
257     writer_writeDouble(w, matrix->tx);
258     writer_writeDouble(w, matrix->ty);
259 #ifdef STATS
260     state->size_positions += 2*8;
261 #endif
262 }
263 static void readXY(reader_t*r, state_t*state, gfxmatrix_t*m)
264 {
265     m->tx = reader_readDouble(r);
266     m->ty = reader_readDouble(r);
267 }
268
269 static void dumpColor(writer_t*w, state_t*state, gfxcolor_t*color)
270 {
271     writer_writeU8(w, color->r);
272     writer_writeU8(w, color->g);
273     writer_writeU8(w, color->b);
274     writer_writeU8(w, color->a);
275 #ifdef STATS
276     state->size_colors += 4;
277 #endif
278 }
279 static gfxcolor_t readColor(reader_t*r, state_t*state)
280 {
281     gfxcolor_t col;
282     col.r = reader_readU8(r);
283     col.g = reader_readU8(r);
284     col.b = reader_readU8(r);
285     col.a = reader_readU8(r);
286     return col;
287 }
288
289 static void dumpGradient(writer_t*w, state_t*state, gfxgradient_t*gradient)
290 {
291     while(gradient) {
292         writer_writeU8(w, 1);
293         dumpColor(w, state, &gradient->color);
294         writer_writeFloat(w, gradient->pos);
295         gradient = gradient->next;
296     }
297     writer_writeU8(w, 0);
298 }
299 static gfxgradient_t* readGradient(reader_t*r, state_t*state)
300 {
301     gfxgradient_t*start = 0, *pos = 0;
302     while(1) {
303         U8 op = reader_readU8(r);
304         if(!op)
305             break;
306         gfxgradient_t*g = (gfxgradient_t*)rfx_calloc(sizeof(gfxgradient_t));
307         if(!start) {
308             start = pos = g;
309         } else {
310             pos->next = g;
311             pos = g;
312         }
313         g->color = readColor(r, state);
314         g->pos = reader_readFloat(r);
315     }
316     return start;
317 }
318
319 static void dumpCXForm(writer_t*w, state_t*state, gfxcxform_t*c)
320 {
321     if(!c) {
322         writer_writeU8(w, 0);
323     } else {
324         writer_writeU8(w, 1);
325         writer_writeFloat(w, c->rr); writer_writeFloat(w, c->rg); writer_writeFloat(w, c->rb); writer_writeFloat(w, c->ra);
326         writer_writeFloat(w, c->gr); writer_writeFloat(w, c->gg); writer_writeFloat(w, c->gb); writer_writeFloat(w, c->ga);
327         writer_writeFloat(w, c->br); writer_writeFloat(w, c->bg); writer_writeFloat(w, c->bb); writer_writeFloat(w, c->ba);
328         writer_writeFloat(w, c->ar); writer_writeFloat(w, c->ag); writer_writeFloat(w, c->ab); writer_writeFloat(w, c->aa);
329     }
330 }
331 static gfxcxform_t* readCXForm(reader_t*r, state_t*state)
332 {
333     U8 type = reader_readU8(r);
334     if(!type)
335         return 0;
336     gfxcxform_t* c = (gfxcxform_t*)rfx_calloc(sizeof(gfxcxform_t));
337     c->rr = reader_readFloat(r); c->rg = reader_readFloat(r); c->rb = reader_readFloat(r); c->ra = reader_readFloat(r);
338     c->gr = reader_readFloat(r); c->gg = reader_readFloat(r); c->gb = reader_readFloat(r); c->ga = reader_readFloat(r);
339     c->br = reader_readFloat(r); c->bg = reader_readFloat(r); c->bb = reader_readFloat(r); c->ba = reader_readFloat(r);
340     c->ar = reader_readFloat(r); c->ag = reader_readFloat(r); c->ab = reader_readFloat(r); c->aa = reader_readFloat(r);
341     return c;
342 }
343
344 static void dumpFont(writer_t*w, state_t*state, gfxfont_t*font)
345 {
346     int oldpos = w->pos;
347 #ifdef STATS
348     int old_size_lines = state->size_lines;
349 #endif
350     writer_writeString(w, font->id);
351     writer_writeU32(w, font->num_glyphs);
352     writer_writeU32(w, font->max_unicode);
353     writer_writeDouble(w, font->ascent);
354     writer_writeDouble(w, font->descent);
355     int t;
356     for(t=0;t<font->num_glyphs;t++) {
357         dumpLine(w, state, font->glyphs[t].line);
358         writer_writeDouble(w, font->glyphs[t].advance);
359         writer_writeU32(w, font->glyphs[t].unicode);
360         if(font->glyphs[t].name) {
361             writer_writeString(w,font->glyphs[t].name);
362         } else {
363             writer_writeU8(w,0);
364         }
365     }
366     for(t=0;t<font->max_unicode;t++) {
367         writer_writeU32(w, font->unicode2glyph[t]);
368     }
369     writer_writeU32(w, font->kerning_size);
370     for(t=0;t<font->kerning_size;t++) {
371         writer_writeU32(w, font->kerning[t].c1);
372         writer_writeU32(w, font->kerning[t].c2);
373         writer_writeU32(w, font->kerning[t].advance);
374     }
375 #ifdef STATS
376     state->size_lines = old_size_lines;
377     state->size_fonts += w->pos - oldpos;
378 #endif
379 }
380 static gfxfont_t*readFont(reader_t*r, state_t*state)
381 {
382     gfxfont_t* font = (gfxfont_t*)rfx_calloc(sizeof(gfxfont_t));
383     font->id = reader_readString(r);
384     font->num_glyphs = reader_readU32(r);
385     font->max_unicode = reader_readU32(r);
386     font->ascent = reader_readDouble(r);
387     font->descent = reader_readDouble(r);
388     font->glyphs = (gfxglyph_t*)rfx_calloc(sizeof(gfxglyph_t)*font->num_glyphs);
389     font->unicode2glyph = (int*)rfx_calloc(sizeof(font->unicode2glyph[0])*font->max_unicode);
390     int t;
391     for(t=0;t<font->num_glyphs;t++) {
392         font->glyphs[t].line = readLine(r, state);
393         font->glyphs[t].advance = reader_readDouble(r);
394         font->glyphs[t].unicode = reader_readU32(r);
395         font->glyphs[t].name = reader_readString(r);
396         if(!font->glyphs[t].name[0]) {
397             free((void*)(font->glyphs[t].name));
398             font->glyphs[t].name = 0;
399         }
400     }
401     for(t=0;t<font->max_unicode;t++) {
402         font->unicode2glyph[t] = reader_readU32(r);
403     }
404     font->kerning_size = reader_readU32(r);
405     if(font->kerning_size) {
406         font->kerning = malloc(sizeof(gfxkerning_t)*font->kerning_size);
407         for(t=0;t<font->kerning_size;t++) {
408             font->kerning[t].c1 = reader_readU32(r);
409             font->kerning[t].c2 = reader_readU32(r);
410             font->kerning[t].advance = reader_readU32(r);
411         }
412     }
413     return font;
414 }
415
416 /* ----------------- reading/writing of primitives with caching -------------- */
417
418 static char* read_string(reader_t*r, state_t*state, U8 id, U8 flags)
419 {
420     assert(id>=0 && id<16);
421     if(flags&FLAG_SAME_AS_LAST) {
422         assert(state->last_string[id]);
423         return strdup(state->last_string[id]);
424     }
425     char*s = reader_readString(r);
426     state->last_string[id] = strdup(s);
427     return s;
428 }
429 static gfxcolor_t read_color(reader_t*r, state_t*state, U8 id, U8 flags)
430 {
431     assert(id>=0 && id<16);
432     if(flags&FLAG_SAME_AS_LAST)
433         return state->last_color[id];
434     gfxcolor_t c = readColor(r, state);
435     state->last_color[id] = c;
436     return c;
437 }
438 static gfxmatrix_t read_matrix(reader_t*r, state_t*state, U8 id, U8 flags)
439 {
440     assert(id>=0 && id<16);
441     if(flags&FLAG_SAME_AS_LAST) {
442         gfxmatrix_t m = state->last_matrix[id];
443         readXY(r, state, &m);
444         return m;
445     }
446     gfxmatrix_t m = readMatrix(r, state);
447     state->last_matrix[id] = m;
448     return m;
449 }
450
451 /* --------------------------- record device operations ---------------------- */
452
453 static int record_setparameter(struct _gfxdevice*dev, const char*key, const char*value)
454 {
455     internal_t*i = (internal_t*)dev->internal;
456     msg("<trace> record: %08x SETPARAM %s %s\n", dev, key, value);
457     writer_writeU8(&i->w, OP_SETPARAM);
458     writer_writeString(&i->w, key);
459     writer_writeString(&i->w, value);
460     return 1;
461 }
462
463 static void record_stroke(struct _gfxdevice*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit)
464 {
465     internal_t*i = (internal_t*)dev->internal;
466     msg("<trace> record: %08x STROKE\n", dev);
467     writer_writeU8(&i->w, OP_STROKE);
468     writer_writeDouble(&i->w, width);
469     writer_writeDouble(&i->w, miterLimit);
470     dumpColor(&i->w, &i->state, color);
471     writer_writeU8(&i->w, cap_style);
472     writer_writeU8(&i->w, joint_style);
473     dumpLine(&i->w, &i->state, line);
474 }
475
476 static void record_startclip(struct _gfxdevice*dev, gfxline_t*line)
477 {
478     internal_t*i = (internal_t*)dev->internal;
479     msg("<trace> record: %08x STARTCLIP\n", dev);
480     writer_writeU8(&i->w, OP_STARTCLIP);
481     dumpLine(&i->w, &i->state, line);
482     i->cliplevel++;
483 }
484
485 static void record_endclip(struct _gfxdevice*dev)
486 {
487     internal_t*i = (internal_t*)dev->internal;
488     msg("<trace> record: %08x ENDCLIP\n", dev);
489     writer_writeU8(&i->w, OP_ENDCLIP);
490     i->cliplevel--;
491     if(i->cliplevel<0) {
492         msg("<error> record: endclip() without startclip()");
493     }
494 }
495
496 static void record_fill(struct _gfxdevice*dev, gfxline_t*line, gfxcolor_t*color)
497 {
498     internal_t*i = (internal_t*)dev->internal;
499     msg("<trace> record: %08x FILL\n", dev);
500     writer_writeU8(&i->w, OP_FILL);
501     dumpColor(&i->w, &i->state, color);
502     dumpLine(&i->w, &i->state, line);
503 }
504
505 static void record_fillbitmap(struct _gfxdevice*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
506 {
507     internal_t*i = (internal_t*)dev->internal;
508     msg("<trace> record: %08x FILLBITMAP\n", dev);
509     writer_writeU8(&i->w, OP_FILLBITMAP);
510     dumpImage(&i->w, &i->state, img);
511     dumpMatrix(&i->w, &i->state, matrix);
512     dumpLine(&i->w, &i->state, line);
513     dumpCXForm(&i->w, &i->state, cxform);
514 }
515
516 static void record_fillgradient(struct _gfxdevice*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
517 {
518     internal_t*i = (internal_t*)dev->internal;
519     msg("<trace> record: %08x FILLGRADIENT %08x\n", dev, gradient);
520     writer_writeU8(&i->w, OP_FILLGRADIENT);
521     writer_writeU8(&i->w, type);
522     dumpGradient(&i->w, &i->state, gradient);
523     dumpMatrix(&i->w, &i->state, matrix);
524     dumpLine(&i->w, &i->state, line);
525 }
526
527 static void record_addfont(struct _gfxdevice*dev, gfxfont_t*font)
528 {
529     internal_t*i = (internal_t*)dev->internal;
530     msg("<trace> record: %08x ADDFONT %s\n", dev, font->id);
531     if(font && !gfxfontlist_hasfont(i->fontlist, font)) {
532         writer_writeU8(&i->w, OP_ADDFONT);
533         dumpFont(&i->w, &i->state, font);
534         i->fontlist = gfxfontlist_addfont(i->fontlist, font);
535     }
536 }
537
538 static void record_drawchar(struct _gfxdevice*dev, gfxfont_t*font, int glyphnr, gfxcolor_t*color, gfxmatrix_t*matrix)
539 {
540     internal_t*i = (internal_t*)dev->internal;
541     if(font && !gfxfontlist_hasfont(i->fontlist, font)) {
542         record_addfont(dev, font);
543     }
544
545     msg("<trace> record: %08x DRAWCHAR %d\n", glyphnr, dev);
546     const char*font_id = font->id?font->id:"*NULL*";
547     
548     gfxmatrix_t*l = &i->state.last_matrix[OP_DRAWCHAR];
549
550     char same_font = i->state.last_string[OP_DRAWCHAR] && !strcmp(i->state.last_string[OP_DRAWCHAR], font_id);
551     char same_matrix = (l->m00 == matrix->m00) && (l->m01 == matrix->m01) && (l->m10 == matrix->m10) && (l->m11 == matrix->m11);
552     char same_color = !memcmp(color, &i->state.last_color[OP_DRAWCHAR], sizeof(gfxcolor_t));
553     U8 flags = 0;
554     if(same_font && same_matrix && same_color)
555         flags |= FLAG_SAME_AS_LAST;
556
557     writer_writeU8(&i->w, OP_DRAWCHAR|flags);
558     writer_writeU32(&i->w, glyphnr);
559 #ifdef STATS
560     i->state.size_chars += 5;
561 #endif
562
563     if(!(flags&FLAG_SAME_AS_LAST)) {
564         writer_writeString(&i->w, font_id);
565         dumpColor(&i->w, &i->state, color);
566         dumpMatrix(&i->w, &i->state, matrix);
567         i->state.last_string[OP_DRAWCHAR] = strdup(font_id);
568         i->state.last_color[OP_DRAWCHAR] = *color;
569         i->state.last_matrix[OP_DRAWCHAR] = *matrix;
570     } else {
571         dumpXY(&i->w, &i->state, matrix);
572     }
573 }
574
575 static void record_startpage(struct _gfxdevice*dev, int width, int height)
576 {
577     internal_t*i = (internal_t*)dev->internal;
578     msg("<trace> record: %08x STARTPAGE\n", dev);
579     writer_writeU8(&i->w, OP_STARTPAGE);
580     writer_writeU16(&i->w, width);
581     writer_writeU16(&i->w, height);
582 }
583
584 static void record_endpage(struct _gfxdevice*dev)
585 {
586     internal_t*i = (internal_t*)dev->internal;
587     msg("<trace> record: %08x ENDPAGE\n", dev);
588     writer_writeU8(&i->w, OP_ENDPAGE);
589 }
590
591 static void record_drawlink(struct _gfxdevice*dev, gfxline_t*line, const char*action)
592 {
593     internal_t*i = (internal_t*)dev->internal;
594     msg("<trace> record: %08x DRAWLINK\n", dev);
595     writer_writeU8(&i->w, OP_DRAWLINK);
596     dumpLine(&i->w, &i->state, line);
597     writer_writeString(&i->w, action);
598 }
599
600 /* ------------------------------- replaying --------------------------------- */
601
602 static void replay(struct _gfxdevice*dev, gfxdevice_t*out, reader_t*r)
603 {
604     internal_t*i = 0;
605     if(dev) {
606         i = (internal_t*)dev->internal;
607     }
608
609     state_t state;
610     memset(&state, 0, sizeof(state));
611
612     gfxfontlist_t* fontlist = gfxfontlist_create();
613
614     while(1) {
615         unsigned char op;
616         if(r->read(r, &op, 1)!=1)
617             break;
618         unsigned char flags = op&0xf0;
619         op&=0x0f;
620
621         switch(op) {
622             case OP_END:
623                 goto finish;
624             case OP_SETPARAM: {
625                 msg("<trace> replay: SETPARAM");
626                 char*key;
627                 char*value;
628                 key = reader_readString(r);
629                 value = reader_readString(r);
630                 out->setparameter(out, key, value);
631                 free(key);
632                 free(value);
633                 break;
634             }
635             case OP_STARTPAGE: {
636                 msg("<trace> replay: STARTPAGE");
637                 U16 width = reader_readU16(r);
638                 U16 height = reader_readU16(r);
639                 out->startpage(out, width, height);
640                 break;
641             }
642             case OP_ENDPAGE: {
643                 msg("<trace> replay: ENDPAGE");
644                 out->endpage(out);
645                 break;
646             }
647             case OP_FINISH: {
648                 msg("<trace> replay: FINISH");
649                 break;
650             }
651             case OP_STROKE: {
652                 msg("<trace> replay: STROKE");
653                 double width = reader_readDouble(r);
654                 double miterlimit = reader_readDouble(r);
655                 gfxcolor_t color = readColor(r, &state);
656                 gfx_capType captype;
657                 int v = reader_readU8(r);
658                 switch (v) {
659                     case 0: captype = gfx_capButt; break;
660                     case 1: captype = gfx_capRound; break;
661                     case 2: captype = gfx_capSquare; break;
662                 }
663                 gfx_joinType jointtype;
664                 v = reader_readU8(r);
665                 switch (v) {
666                     case 0: jointtype = gfx_joinMiter; break;
667                     case 1: jointtype = gfx_joinRound; break;
668                     case 2: jointtype = gfx_joinBevel; break;
669                 }
670                 gfxline_t* line = readLine(r, &state);
671                 out->stroke(out, line, width, &color, captype, jointtype,miterlimit);
672                 gfxline_free(line);
673                 break;
674             }
675             case OP_STARTCLIP: {
676                 msg("<trace> replay: STARTCLIP");
677                 gfxline_t* line = readLine(r, &state);
678                 out->startclip(out, line);
679                 gfxline_free(line);
680                 break;
681             }
682             case OP_ENDCLIP: {
683                 msg("<trace> replay: ENDCLIP");
684                 out->endclip(out);
685                 break;
686             }
687             case OP_FILL: {
688                 msg("<trace> replay: FILL");
689                 gfxcolor_t color = readColor(r, &state);
690                 gfxline_t* line = readLine(r, &state);
691                 out->fill(out, line, &color);
692                 gfxline_free(line);
693                 break;
694             }
695             case OP_FILLBITMAP: {
696                 msg("<trace> replay: FILLBITMAP");
697                 gfximage_t img = readImage(r, &state);
698                 gfxmatrix_t matrix = readMatrix(r, &state);
699                 gfxline_t* line = readLine(r, &state);
700                 gfxcxform_t* cxform = readCXForm(r, &state);
701                 out->fillbitmap(out, line, &img, &matrix, cxform);
702                 gfxline_free(line);
703                 if(cxform)
704                     free(cxform);
705                 free(img.data);img.data=0;
706                 break;
707             }
708             case OP_FILLGRADIENT: {
709                 msg("<trace> replay: FILLGRADIENT");
710                 gfxgradienttype_t type;
711                 int v = reader_readU8(r);
712                 switch (v) {
713                     case 0: 
714                       type = gfxgradient_radial; break;
715                     case 1:
716                       type = gfxgradient_linear; break;
717                 }  
718                 gfxgradient_t*gradient = readGradient(r, &state);
719                 gfxmatrix_t matrix = readMatrix(r, &state);
720                 gfxline_t* line = readLine(r, &state);
721                 out->fillgradient(out, line, gradient, type, &matrix);
722                 break;
723             }
724             case OP_DRAWLINK: {
725                 msg("<trace> replay: DRAWLINK");
726                 gfxline_t* line = readLine(r, &state);
727                 char* s = reader_readString(r);
728                 out->drawlink(out,line,s);
729                 gfxline_free(line);
730                 free(s);
731                 break;
732             }
733             case OP_ADDFONT: {
734                 msg("<trace> replay: ADDFONT out=%08x(%s)", out, out->name);
735                 gfxfont_t*font = readFont(r, &state);
736                 fontlist = gfxfontlist_addfont(fontlist, font);
737                 out->addfont(out, font);
738                 break;
739             }
740             case OP_DRAWCHAR: {
741                 U32 glyph = reader_readU32(r);
742                 gfxmatrix_t m = {1,0,0, 0,1,0};
743                 char* id = read_string(r, &state, op, flags);
744                 gfxcolor_t color = read_color(r, &state, op, flags);
745                 gfxmatrix_t matrix = read_matrix(r, &state, op, flags);
746
747                 gfxfont_t*font = id?gfxfontlist_findfont(fontlist, id):0;
748                 if(i && !font) {
749                     font = gfxfontlist_findfont(i->fontlist, id);
750                 }
751                 msg("<trace> replay: DRAWCHAR font=%s glyph=%d", id, glyph);
752                 out->drawchar(out, font, glyph, &color, &matrix);
753                 free(id);
754                 break;
755             }
756         }
757     }
758 finish:
759     r->dealloc(r);
760     /* problem: if we just replayed into a device which stores the
761        font for later use (the record device itself is a nice example),
762        then we can't free it yet */
763     //gfxfontlist_free(fontlist, 1);
764     gfxfontlist_free(fontlist, 0);
765 }
766 void gfxresult_record_replay(gfxresult_t*result, gfxdevice_t*device)
767 {
768     internal_result_t*i = (internal_result_t*)result->internal;
769     
770     reader_t r;
771     if(i->use_tempfile) {
772         reader_init_filereader2(&r, i->filename);
773     } else {
774         reader_init_memreader(&r, i->data, i->length);
775     }
776
777     replay(0, device, &r);
778 }
779
780 static void record_result_write(gfxresult_t*r, int filedesc)
781 {
782     internal_result_t*i = (internal_result_t*)r->internal;
783     if(i->data) {
784         write(filedesc, i->data, i->length);
785     }
786 }
787 static int record_result_save(gfxresult_t*r, const char*filename)
788 {
789     internal_result_t*i = (internal_result_t*)r->internal;
790     if(i->use_tempfile) {
791         move_file(i->filename, filename);
792     } else {
793         FILE*fi = fopen(filename, "wb");
794         if(!fi) {
795             fprintf(stderr, "Couldn't open file %s for writing\n", filename);
796             return -1;
797         }
798         fwrite(i->data, i->length, 1, fi);
799         fclose(fi);
800     }
801     return 0;
802 }
803 static void*record_result_get(gfxresult_t*r, const char*name)
804 {
805     internal_result_t*i = (internal_result_t*)r->internal;
806     if(!strcmp(name, "data")) {
807         return i->data;
808     } else if(!strcmp(name, "length")) {
809         return &i->length;
810     }
811     return 0;
812 }
813 static void record_result_destroy(gfxresult_t*r)
814 {
815     internal_result_t*i = (internal_result_t*)r->internal;
816     if(i->data) {
817         free(i->data);i->data = 0;
818     }
819     if(i->filename) {
820         unlink(i->filename);
821         free(i->filename);
822     }
823     free(r->internal);r->internal = 0;
824     free(r);
825 }
826
827 static unsigned char printable(unsigned char a)
828 {
829     if(a<32 || a==127) return '.';
830     else return a;
831 }
832
833 static void hexdumpMem(unsigned char*data, int len)
834 {
835     int t;
836     char ascii[32];
837     for(t=0;t<len;t++) {
838         printf("%02x ", data[t]);
839         ascii[t&15] = printable(data[t]);
840         if((t && ((t&15)==15)) || (t==len-1))
841         {
842             int s,p=((t)&15)+1;
843             ascii[p] = 0;
844             for(s=p-1;s<16;s++) {
845                 printf("   ");
846             }
847             printf(" %s\n", ascii);
848         }
849     }
850 }
851
852 void gfxdevice_record_flush(gfxdevice_t*dev, gfxdevice_t*out)
853 {
854     internal_t*i = (internal_t*)dev->internal;
855     if(out) {
856         if(!i->use_tempfile) {
857             int len=0;
858             void*data = writer_growmemwrite_memptr(&i->w, &len);
859             reader_t r;
860             reader_init_memreader(&r, data, len);
861             replay(dev, out, &r);
862             writer_growmemwrite_reset(&i->w);
863         } else {
864             msg("<fatal> Flushing not supported for file based record device");
865             exit(1);
866         }
867     }
868 }
869
870 static gfxresult_t* record_finish(struct _gfxdevice*dev)
871 {
872     internal_t*i = (internal_t*)dev->internal;
873     msg("<trace> record: %08x END", dev);
874
875     if(i->cliplevel) {
876         msg("<error> Warning: unclosed cliplevels");
877     }
878
879 #ifdef STATS
880     int total = i->w.pos;
881     if(total && i->use_tempfile) {
882         state_t*s = &i->state;
883         msg("<notice> record device finished. stats:");
884         msg("<notice> %4.1f%% matrices (%d bytes)", s->size_matrices*100.0/total, s->size_matrices);
885         msg("<notice> %4.1f%% positions (%d bytes)", s->size_positions*100.0/total, s->size_positions);
886         msg("<notice> %4.1f%% colors (%d bytes)", s->size_colors*100.0/total, s->size_colors);
887         msg("<notice> %4.1f%% lines (%d bytes)", s->size_lines*100.0/total, s->size_lines);
888         msg("<notice> %4.1f%% fonts (%d bytes)", s->size_fonts*100.0/total, s->size_fonts);
889         msg("<notice> %4.1f%% images (%d bytes)", s->size_images*100.0/total, s->size_images);
890         msg("<notice> %4.1f%% characters (%d bytes)", s->size_chars*100.0/total, s->size_chars);
891         msg("<notice> total: %d bytes", total);
892     }
893 #endif
894     
895     writer_writeU8(&i->w, OP_END);
896     
897     gfxfontlist_free(i->fontlist, 0);
898    
899     internal_result_t*ir = (internal_result_t*)rfx_calloc(sizeof(gfxresult_t));
900    
901     ir->use_tempfile = i->use_tempfile;
902     if(i->use_tempfile) {
903         ir->filename = i->filename;
904     } else {
905         ir->data = writer_growmemwrite_getmem(&i->w);
906         ir->length = i->w.pos;
907     }
908     i->w.finish(&i->w);
909
910     gfxresult_t*result= (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
911     result->save = record_result_save;
912     result->get = record_result_get;
913     result->destroy = record_result_destroy;
914     result->internal = ir;
915
916     free(dev->internal);memset(dev, 0, sizeof(gfxdevice_t));
917     
918     return result;
919 }
920
921 void gfxdevice_record_init(gfxdevice_t*dev, char use_tempfile)
922 {
923     internal_t*i = (internal_t*)rfx_calloc(sizeof(internal_t));
924     memset(dev, 0, sizeof(gfxdevice_t));
925     
926     dev->name = "record";
927
928     dev->internal = i;
929   
930     i->use_tempfile = use_tempfile;
931     if(!use_tempfile) {
932         writer_init_growingmemwriter(&i->w, 1048576);
933     } else {
934         char buffer[128];
935         i->filename = strdup(mktempname(buffer, "gfx"));
936         writer_init_filewriter2(&i->w, i->filename);
937     }
938     i->fontlist = gfxfontlist_create();
939     i->cliplevel = 0;
940
941     dev->setparameter = record_setparameter;
942     dev->startpage = record_startpage;
943     dev->startclip = record_startclip;
944     dev->endclip = record_endclip;
945     dev->stroke = record_stroke;
946     dev->fill = record_fill;
947     dev->fillbitmap = record_fillbitmap;
948     dev->fillgradient = record_fillgradient;
949     dev->addfont = record_addfont;
950     dev->drawchar = record_drawchar;
951     dev->drawlink = record_drawlink;
952     dev->endpage = record_endpage;
953     dev->finish = record_finish;
954 }
955