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