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