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