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