a09cbbc4d97abea2c9c007b0535e3b257c17101d
[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 static char* read_string(reader_t*r, state_t*state, U8 id, U8 flags)
457 {
458     assert(id>=0 && id<16);
459     if(flags&FLAG_SAME_AS_LAST) {
460         assert(state->last_string[id]);
461         return strdup(state->last_string[id]);
462     }
463     char*s = reader_readString(r);
464     state->last_string[id] = strdup(s);
465     return s;
466 }
467 static gfxcolor_t read_color(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         return state->last_color[id];
472     gfxcolor_t c = readColor(r, state);
473     state->last_color[id] = c;
474     return c;
475 }
476 static gfxmatrix_t read_matrix(reader_t*r, state_t*state, U8 id, U8 flags)
477 {
478     assert(id>=0 && id<16);
479     if(flags&FLAG_SAME_AS_LAST) {
480         gfxmatrix_t m = state->last_matrix[id];
481         readXY(r, state, &m);
482         return m;
483     }
484     gfxmatrix_t m = readMatrix(r, state);
485     state->last_matrix[id] = m;
486     return m;
487 }
488
489 /* --------------------------- record device operations ---------------------- */
490
491 static int record_setparameter(struct _gfxdevice*dev, const char*key, const char*value)
492 {
493     internal_t*i = (internal_t*)dev->internal;
494     msg("<trace> record: %08x SETPARAM %s %s\n", dev, key, value);
495     writer_writeU8(&i->w, OP_SETPARAM);
496     writer_writeString(&i->w, key);
497     writer_writeString(&i->w, value);
498     return 1;
499 }
500
501 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)
502 {
503     internal_t*i = (internal_t*)dev->internal;
504     msg("<trace> record: %08x STROKE\n", dev);
505     writer_writeU8(&i->w, OP_STROKE);
506     writer_writeDouble(&i->w, width);
507     writer_writeDouble(&i->w, miterLimit);
508     dumpColor(&i->w, &i->state, color);
509     writer_writeU8(&i->w, cap_style);
510     writer_writeU8(&i->w, joint_style);
511     dumpLine(&i->w, &i->state, line);
512 }
513
514 static void record_startclip(struct _gfxdevice*dev, gfxline_t*line)
515 {
516     internal_t*i = (internal_t*)dev->internal;
517     msg("<trace> record: %08x STARTCLIP\n", dev);
518     writer_writeU8(&i->w, OP_STARTCLIP);
519     dumpLine(&i->w, &i->state, line);
520     i->cliplevel++;
521 }
522
523 static void record_endclip(struct _gfxdevice*dev)
524 {
525     internal_t*i = (internal_t*)dev->internal;
526     msg("<trace> record: %08x ENDCLIP\n", dev);
527     writer_writeU8(&i->w, OP_ENDCLIP);
528     i->cliplevel--;
529     if(i->cliplevel<0) {
530         msg("<error> record: endclip() without startclip()");
531     }
532 }
533
534 static void record_fill(struct _gfxdevice*dev, gfxline_t*line, gfxcolor_t*color)
535 {
536     internal_t*i = (internal_t*)dev->internal;
537     msg("<trace> record: %08x FILL\n", dev);
538     writer_writeU8(&i->w, OP_FILL);
539     dumpColor(&i->w, &i->state, color);
540     dumpLine(&i->w, &i->state, line);
541 }
542
543 static void record_fillbitmap(struct _gfxdevice*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
544 {
545     internal_t*i = (internal_t*)dev->internal;
546     msg("<trace> record: %08x FILLBITMAP\n", dev);
547     writer_writeU8(&i->w, OP_FILLBITMAP);
548     dumpImage(&i->w, &i->state, img);
549     dumpMatrix(&i->w, &i->state, matrix);
550     dumpLine(&i->w, &i->state, line);
551     dumpCXForm(&i->w, &i->state, cxform);
552 }
553
554 static void record_fillgradient(struct _gfxdevice*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
555 {
556     internal_t*i = (internal_t*)dev->internal;
557     msg("<trace> record: %08x FILLGRADIENT %08x\n", dev, gradient);
558     writer_writeU8(&i->w, OP_FILLGRADIENT);
559     writer_writeU8(&i->w, type);
560     dumpGradient(&i->w, &i->state, gradient);
561     dumpMatrix(&i->w, &i->state, matrix);
562     dumpLine(&i->w, &i->state, line);
563 }
564
565 static void record_addfont(struct _gfxdevice*dev, gfxfont_t*font)
566 {
567     internal_t*i = (internal_t*)dev->internal;
568     msg("<trace> record: %08x ADDFONT %s\n", dev, font->id);
569     if(font && !gfxfontlist_hasfont(i->fontlist, font)) {
570         writer_writeU8(&i->w, OP_ADDFONT);
571         dumpFont(&i->w, &i->state, font);
572         i->fontlist = gfxfontlist_addfont(i->fontlist, font);
573     }
574 }
575
576 static void record_drawchar(struct _gfxdevice*dev, gfxfont_t*font, int glyphnr, gfxcolor_t*color, gfxmatrix_t*matrix)
577 {
578     internal_t*i = (internal_t*)dev->internal;
579     if(font && !gfxfontlist_hasfont(i->fontlist, font)) {
580         record_addfont(dev, font);
581     }
582
583     msg("<trace> record: %08x DRAWCHAR %d\n", glyphnr, dev);
584     const char*font_id = (font&&font->id)?font->id:"*NULL*";
585     
586     gfxmatrix_t*l = &i->state.last_matrix[OP_DRAWCHAR];
587
588     U8 flags = 0;
589     if(!font)
590         flags |= FLAG_ZERO_FONT;
591
592     char same_font = i->state.last_string[OP_DRAWCHAR] && !strcmp(i->state.last_string[OP_DRAWCHAR], font_id);
593     char same_matrix = (l->m00 == matrix->m00) && (l->m01 == matrix->m01) && (l->m10 == matrix->m10) && (l->m11 == matrix->m11);
594     char same_color = !memcmp(color, &i->state.last_color[OP_DRAWCHAR], sizeof(gfxcolor_t));
595
596     /* FIXME
597     if(same_font && same_matrix && same_color)
598         flags |= FLAG_SAME_AS_LAST;
599     */
600
601     writer_writeU8(&i->w, OP_DRAWCHAR|flags);
602     writer_writeU32(&i->w, glyphnr);
603 #ifdef STATS
604     i->state.size_chars += 5;
605 #endif
606
607     if(!(flags&FLAG_SAME_AS_LAST)) {
608         if(!(flags&FLAG_ZERO_FONT))
609             writer_writeString(&i->w, font_id);
610         dumpColor(&i->w, &i->state, color);
611         dumpMatrix(&i->w, &i->state, matrix);
612         i->state.last_string[OP_DRAWCHAR] = strdup(font_id);
613         i->state.last_color[OP_DRAWCHAR] = *color;
614         i->state.last_matrix[OP_DRAWCHAR] = *matrix;
615     } else {
616         dumpXY(&i->w, &i->state, matrix);
617     }
618 }
619
620 static void record_startpage(struct _gfxdevice*dev, int width, int height)
621 {
622     internal_t*i = (internal_t*)dev->internal;
623     msg("<trace> record: %08x STARTPAGE\n", dev);
624     writer_writeU8(&i->w, OP_STARTPAGE);
625     writer_writeU16(&i->w, width);
626     writer_writeU16(&i->w, height);
627 }
628
629 static void record_endpage(struct _gfxdevice*dev)
630 {
631     internal_t*i = (internal_t*)dev->internal;
632     msg("<trace> record: %08x ENDPAGE\n", dev);
633     writer_writeU8(&i->w, OP_ENDPAGE);
634 }
635
636 static void record_drawlink(struct _gfxdevice*dev, gfxline_t*line, const char*action)
637 {
638     internal_t*i = (internal_t*)dev->internal;
639     msg("<trace> record: %08x DRAWLINK\n", dev);
640     writer_writeU8(&i->w, OP_DRAWLINK);
641     dumpLine(&i->w, &i->state, line);
642     writer_writeString(&i->w, action);
643 }
644
645 /* ------------------------------- replaying --------------------------------- */
646
647 static void replay(struct _gfxdevice*dev, gfxdevice_t*out, reader_t*r, gfxfontlist_t**fontlist)
648 {
649     internal_t*i = 0;
650     if(dev) {
651         i = (internal_t*)dev->internal;
652     }
653     gfxfontlist_t*_fontlist=0;
654     if(!fontlist) {
655         fontlist = &_fontlist;
656     }
657
658     state_t state;
659     memset(&state, 0, sizeof(state));
660
661     while(1) {
662         unsigned char op;
663         if(r->read(r, &op, 1)!=1)
664             break;
665         unsigned char flags = op&0xf0;
666         op&=0x0f;
667
668         switch(op) {
669             case OP_END:
670                 goto finish;
671             case OP_SETPARAM: {
672                 msg("<trace> replay: SETPARAM");
673                 char*key;
674                 char*value;
675                 key = reader_readString(r);
676                 value = reader_readString(r);
677                 out->setparameter(out, key, value);
678                 free(key);
679                 free(value);
680                 break;
681             }
682             case OP_STARTPAGE: {
683                 msg("<trace> replay: STARTPAGE");
684                 U16 width = reader_readU16(r);
685                 U16 height = reader_readU16(r);
686                 out->startpage(out, width, height);
687                 break;
688             }
689             case OP_ENDPAGE: {
690                 msg("<trace> replay: ENDPAGE");
691                 out->endpage(out);
692                 break;
693             }
694             case OP_FINISH: {
695                 msg("<trace> replay: FINISH");
696                 break;
697             }
698             case OP_STROKE: {
699                 msg("<trace> replay: STROKE");
700                 double width = reader_readDouble(r);
701                 double miterlimit = reader_readDouble(r);
702                 gfxcolor_t color = readColor(r, &state);
703                 gfx_capType captype;
704                 int v = reader_readU8(r);
705                 switch (v) {
706                     case 0: captype = gfx_capButt; break;
707                     case 1: captype = gfx_capRound; break;
708                     case 2: captype = gfx_capSquare; break;
709                 }
710                 gfx_joinType jointtype;
711                 v = reader_readU8(r);
712                 switch (v) {
713                     case 0: jointtype = gfx_joinMiter; break;
714                     case 1: jointtype = gfx_joinRound; break;
715                     case 2: jointtype = gfx_joinBevel; break;
716                 }
717                 gfxline_t* line = readLine(r, &state);
718                 out->stroke(out, line, width, &color, captype, jointtype,miterlimit);
719                 gfxline_free(line);
720                 break;
721             }
722             case OP_STARTCLIP: {
723                 msg("<trace> replay: STARTCLIP");
724                 gfxline_t* line = readLine(r, &state);
725                 out->startclip(out, line);
726                 gfxline_free(line);
727                 break;
728             }
729             case OP_ENDCLIP: {
730                 msg("<trace> replay: ENDCLIP");
731                 out->endclip(out);
732                 break;
733             }
734             case OP_FILL: {
735                 msg("<trace> replay: FILL");
736                 gfxcolor_t color = readColor(r, &state);
737                 gfxline_t* line = readLine(r, &state);
738                 out->fill(out, line, &color);
739                 gfxline_free(line);
740                 break;
741             }
742             case OP_FILLBITMAP: {
743                 msg("<trace> replay: FILLBITMAP");
744                 gfximage_t img = readImage(r, &state);
745                 gfxmatrix_t matrix = readMatrix(r, &state);
746                 gfxline_t* line = readLine(r, &state);
747                 gfxcxform_t* cxform = readCXForm(r, &state);
748                 out->fillbitmap(out, line, &img, &matrix, cxform);
749                 gfxline_free(line);
750                 if(cxform)
751                     free(cxform);
752                 free(img.data);img.data=0;
753                 break;
754             }
755             case OP_FILLGRADIENT: {
756                 msg("<trace> replay: FILLGRADIENT");
757                 gfxgradienttype_t type;
758                 int v = reader_readU8(r);
759                 switch (v) {
760                     case 0: 
761                       type = gfxgradient_radial; break;
762                     case 1:
763                       type = gfxgradient_linear; break;
764                 }  
765                 gfxgradient_t*gradient = readGradient(r, &state);
766                 gfxmatrix_t matrix = readMatrix(r, &state);
767                 gfxline_t* line = readLine(r, &state);
768                 out->fillgradient(out, line, gradient, type, &matrix);
769                 break;
770             }
771             case OP_DRAWLINK: {
772                 msg("<trace> replay: DRAWLINK");
773                 gfxline_t* line = readLine(r, &state);
774                 char* s = reader_readString(r);
775                 out->drawlink(out,line,s);
776                 gfxline_free(line);
777                 free(s);
778                 break;
779             }
780             case OP_ADDFONT: {
781                 msg("<trace> replay: ADDFONT out=%08x(%s)", out, out->name);
782                 gfxfont_t*font = readFont(r, &state);
783                 if(!gfxfontlist_hasfont(*fontlist, font)) {
784                     *fontlist = gfxfontlist_addfont(*fontlist, font);
785                     out->addfont(out, font);
786                 } else {
787                     gfxfont_free(font);
788                 }
789                 break;
790             }
791             case OP_DRAWCHAR: {
792                 U32 glyph = reader_readU32(r);
793                 gfxmatrix_t m = {1,0,0, 0,1,0};
794                 char* id = 0;
795                 if(!(flags&FLAG_ZERO_FONT))
796                     id = read_string(r, &state, op, flags);
797                 gfxcolor_t color = read_color(r, &state, op, flags);
798                 gfxmatrix_t matrix = read_matrix(r, &state, op, flags);
799
800                 gfxfont_t*font = id?gfxfontlist_findfont(*fontlist, id):0;
801                 if(i && !font) {
802                     font = gfxfontlist_findfont(i->fontlist, id);
803                 }
804                 msg("<trace> replay: DRAWCHAR font=%s glyph=%d", id, glyph);
805                 out->drawchar(out, font, glyph, &color, &matrix);
806                 if(id)
807                     free(id);
808                 break;
809             }
810         }
811     }
812 finish:
813     r->dealloc(r);
814     if(_fontlist)
815         gfxfontlist_free(_fontlist, 0);
816 }
817 void gfxresult_record_replay(gfxresult_t*result, gfxdevice_t*device, gfxfontlist_t**fontlist)
818 {
819     internal_result_t*i = (internal_result_t*)result->internal;
820     
821     reader_t r;
822     if(i->use_tempfile) {
823         reader_init_filereader2(&r, i->filename);
824     } else {
825         reader_init_memreader(&r, i->data, i->length);
826     }
827
828     replay(0, device, &r, fontlist);
829 }
830
831 static void record_result_write(gfxresult_t*r, int filedesc)
832 {
833     internal_result_t*i = (internal_result_t*)r->internal;
834     if(i->data) {
835         write(filedesc, i->data, i->length);
836     }
837 }
838 static int record_result_save(gfxresult_t*r, const char*filename)
839 {
840     internal_result_t*i = (internal_result_t*)r->internal;
841     if(i->use_tempfile) {
842         move_file(i->filename, filename);
843     } else {
844         FILE*fi = fopen(filename, "wb");
845         if(!fi) {
846             fprintf(stderr, "Couldn't open file %s for writing\n", filename);
847             return -1;
848         }
849         fwrite(i->data, i->length, 1, fi);
850         fclose(fi);
851     }
852     return 0;
853 }
854 static void*record_result_get(gfxresult_t*r, const char*name)
855 {
856     internal_result_t*i = (internal_result_t*)r->internal;
857     if(!strcmp(name, "data")) {
858         return i->data;
859     } else if(!strcmp(name, "length")) {
860         return &i->length;
861     }
862     return 0;
863 }
864 static void record_result_destroy(gfxresult_t*r)
865 {
866     internal_result_t*i = (internal_result_t*)r->internal;
867     if(i->data) {
868         free(i->data);i->data = 0;
869     }
870     if(i->filename) {
871         unlink(i->filename);
872         free(i->filename);
873     }
874     free(r->internal);r->internal = 0;
875     free(r);
876 }
877
878 static unsigned char printable(unsigned char a)
879 {
880     if(a<32 || a==127) return '.';
881     else return a;
882 }
883
884 static void hexdumpMem(unsigned char*data, int len)
885 {
886     int t;
887     char ascii[32];
888     for(t=0;t<len;t++) {
889         printf("%02x ", data[t]);
890         ascii[t&15] = printable(data[t]);
891         if((t && ((t&15)==15)) || (t==len-1))
892         {
893             int s,p=((t)&15)+1;
894             ascii[p] = 0;
895             for(s=p-1;s<16;s++) {
896                 printf("   ");
897             }
898             printf(" %s\n", ascii);
899         }
900     }
901 }
902
903 void gfxdevice_record_flush(gfxdevice_t*dev, gfxdevice_t*out, gfxfontlist_t**fontlist)
904 {
905     internal_t*i = (internal_t*)dev->internal;
906     if(out) {
907         if(!i->use_tempfile) {
908             int len=0;
909             void*data = writer_growmemwrite_memptr(&i->w, &len);
910             reader_t r;
911             reader_init_memreader(&r, data, len);
912             replay(dev, out, &r, fontlist);
913             writer_growmemwrite_reset(&i->w);
914         } else {
915             msg("<fatal> Flushing not supported for file based record device");
916             exit(1);
917         }
918     }
919 }
920
921 static gfxresult_t* record_finish(struct _gfxdevice*dev)
922 {
923     internal_t*i = (internal_t*)dev->internal;
924     msg("<trace> record: %08x END", dev);
925
926     if(i->cliplevel) {
927         msg("<error> Warning: unclosed cliplevels");
928     }
929
930 #ifdef STATS
931     int total = i->w.pos;
932     if(total && i->use_tempfile) {
933         state_t*s = &i->state;
934         msg("<notice> record device finished. stats:");
935         msg("<notice> %4.1f%% matrices (%d bytes)", s->size_matrices*100.0/total, s->size_matrices);
936         msg("<notice> %4.1f%% positions (%d bytes)", s->size_positions*100.0/total, s->size_positions);
937         msg("<notice> %4.1f%% colors (%d bytes)", s->size_colors*100.0/total, s->size_colors);
938         msg("<notice> %4.1f%% lines (%d bytes)", s->size_lines*100.0/total, s->size_lines);
939         msg("<notice> %4.1f%% fonts (%d bytes)", s->size_fonts*100.0/total, s->size_fonts);
940         msg("<notice> %4.1f%% images (%d bytes)", s->size_images*100.0/total, s->size_images);
941         msg("<notice> %4.1f%% characters (%d bytes)", s->size_chars*100.0/total, s->size_chars);
942         msg("<notice> total: %d bytes", total);
943     }
944 #endif
945     
946     writer_writeU8(&i->w, OP_END);
947     
948     gfxfontlist_free(i->fontlist, 0);
949    
950     internal_result_t*ir = (internal_result_t*)rfx_calloc(sizeof(gfxresult_t));
951    
952     ir->use_tempfile = i->use_tempfile;
953     if(i->use_tempfile) {
954         ir->filename = i->filename;
955     } else {
956         ir->data = writer_growmemwrite_getmem(&i->w);
957         ir->length = i->w.pos;
958     }
959     i->w.finish(&i->w);
960
961     gfxresult_t*result= (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
962     result->save = record_result_save;
963     result->get = record_result_get;
964     result->destroy = record_result_destroy;
965     result->internal = ir;
966
967     free(dev->internal);memset(dev, 0, sizeof(gfxdevice_t));
968     
969     return result;
970 }
971
972 void gfxdevice_record_init(gfxdevice_t*dev, char use_tempfile)
973 {
974     internal_t*i = (internal_t*)rfx_calloc(sizeof(internal_t));
975     memset(dev, 0, sizeof(gfxdevice_t));
976     
977     dev->name = "record";
978
979     dev->internal = i;
980   
981     i->use_tempfile = use_tempfile;
982     if(!use_tempfile) {
983         writer_init_growingmemwriter(&i->w, 1048576);
984     } else {
985         char buffer[128];
986         i->filename = strdup(mktempname(buffer, "gfx"));
987         writer_init_filewriter2(&i->w, i->filename);
988     }
989     i->fontlist = gfxfontlist_create();
990     i->cliplevel = 0;
991
992     dev->setparameter = record_setparameter;
993     dev->startpage = record_startpage;
994     dev->startclip = record_startclip;
995     dev->endclip = record_endclip;
996     dev->stroke = record_stroke;
997     dev->fill = record_fill;
998     dev->fillbitmap = record_fillbitmap;
999     dev->fillgradient = record_fillgradient;
1000     dev->addfont = record_addfont;
1001     dev->drawchar = record_drawchar;
1002     dev->drawlink = record_drawlink;
1003     dev->endpage = record_endpage;
1004     dev->finish = record_finish;
1005 }
1006