8f7b40a3a6e73d6821b2f2b6bd4d326a10b09f46
[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     if(same_font && same_matrix && same_color)
596         flags |= FLAG_SAME_AS_LAST;
597
598     writer_writeU8(&i->w, OP_DRAWCHAR|flags);
599     writer_writeU32(&i->w, glyphnr);
600 #ifdef STATS
601     i->state.size_chars += 5;
602 #endif
603
604     if(!(flags&FLAG_SAME_AS_LAST)) {
605         if(!(flags&FLAG_ZERO_FONT))
606             writer_writeString(&i->w, font_id);
607         dumpColor(&i->w, &i->state, color);
608         dumpMatrix(&i->w, &i->state, matrix);
609         i->state.last_string[OP_DRAWCHAR] = strdup(font_id);
610         i->state.last_color[OP_DRAWCHAR] = *color;
611         i->state.last_matrix[OP_DRAWCHAR] = *matrix;
612     } else {
613         dumpXY(&i->w, &i->state, matrix);
614     }
615 }
616
617 static void record_startpage(struct _gfxdevice*dev, int width, int height)
618 {
619     internal_t*i = (internal_t*)dev->internal;
620     msg("<trace> record: %08x STARTPAGE\n", dev);
621     writer_writeU8(&i->w, OP_STARTPAGE);
622     writer_writeU16(&i->w, width);
623     writer_writeU16(&i->w, height);
624 }
625
626 static void record_endpage(struct _gfxdevice*dev)
627 {
628     internal_t*i = (internal_t*)dev->internal;
629     msg("<trace> record: %08x ENDPAGE\n", dev);
630     writer_writeU8(&i->w, OP_ENDPAGE);
631 }
632
633 static void record_drawlink(struct _gfxdevice*dev, gfxline_t*line, const char*action)
634 {
635     internal_t*i = (internal_t*)dev->internal;
636     msg("<trace> record: %08x DRAWLINK\n", dev);
637     writer_writeU8(&i->w, OP_DRAWLINK);
638     dumpLine(&i->w, &i->state, line);
639     writer_writeString(&i->w, action);
640 }
641
642 /* ------------------------------- replaying --------------------------------- */
643
644 static void replay(struct _gfxdevice*dev, gfxdevice_t*out, reader_t*r)
645 {
646     internal_t*i = 0;
647     if(dev) {
648         i = (internal_t*)dev->internal;
649     }
650
651     state_t state;
652     memset(&state, 0, sizeof(state));
653
654     gfxfontlist_t* fontlist = gfxfontlist_create();
655
656     while(1) {
657         unsigned char op;
658         if(r->read(r, &op, 1)!=1)
659             break;
660         unsigned char flags = op&0xf0;
661         op&=0x0f;
662
663         switch(op) {
664             case OP_END:
665                 goto finish;
666             case OP_SETPARAM: {
667                 msg("<trace> replay: SETPARAM");
668                 char*key;
669                 char*value;
670                 key = reader_readString(r);
671                 value = reader_readString(r);
672                 out->setparameter(out, key, value);
673                 free(key);
674                 free(value);
675                 break;
676             }
677             case OP_STARTPAGE: {
678                 msg("<trace> replay: STARTPAGE");
679                 U16 width = reader_readU16(r);
680                 U16 height = reader_readU16(r);
681                 out->startpage(out, width, height);
682                 break;
683             }
684             case OP_ENDPAGE: {
685                 msg("<trace> replay: ENDPAGE");
686                 out->endpage(out);
687                 break;
688             }
689             case OP_FINISH: {
690                 msg("<trace> replay: FINISH");
691                 break;
692             }
693             case OP_STROKE: {
694                 msg("<trace> replay: STROKE");
695                 double width = reader_readDouble(r);
696                 double miterlimit = reader_readDouble(r);
697                 gfxcolor_t color = readColor(r, &state);
698                 gfx_capType captype;
699                 int v = reader_readU8(r);
700                 switch (v) {
701                     case 0: captype = gfx_capButt; break;
702                     case 1: captype = gfx_capRound; break;
703                     case 2: captype = gfx_capSquare; break;
704                 }
705                 gfx_joinType jointtype;
706                 v = reader_readU8(r);
707                 switch (v) {
708                     case 0: jointtype = gfx_joinMiter; break;
709                     case 1: jointtype = gfx_joinRound; break;
710                     case 2: jointtype = gfx_joinBevel; break;
711                 }
712                 gfxline_t* line = readLine(r, &state);
713                 out->stroke(out, line, width, &color, captype, jointtype,miterlimit);
714                 gfxline_free(line);
715                 break;
716             }
717             case OP_STARTCLIP: {
718                 msg("<trace> replay: STARTCLIP");
719                 gfxline_t* line = readLine(r, &state);
720                 out->startclip(out, line);
721                 gfxline_free(line);
722                 break;
723             }
724             case OP_ENDCLIP: {
725                 msg("<trace> replay: ENDCLIP");
726                 out->endclip(out);
727                 break;
728             }
729             case OP_FILL: {
730                 msg("<trace> replay: FILL");
731                 gfxcolor_t color = readColor(r, &state);
732                 gfxline_t* line = readLine(r, &state);
733                 out->fill(out, line, &color);
734                 gfxline_free(line);
735                 break;
736             }
737             case OP_FILLBITMAP: {
738                 msg("<trace> replay: FILLBITMAP");
739                 gfximage_t img = readImage(r, &state);
740                 gfxmatrix_t matrix = readMatrix(r, &state);
741                 gfxline_t* line = readLine(r, &state);
742                 gfxcxform_t* cxform = readCXForm(r, &state);
743                 out->fillbitmap(out, line, &img, &matrix, cxform);
744                 gfxline_free(line);
745                 if(cxform)
746                     free(cxform);
747                 free(img.data);img.data=0;
748                 break;
749             }
750             case OP_FILLGRADIENT: {
751                 msg("<trace> replay: FILLGRADIENT");
752                 gfxgradienttype_t type;
753                 int v = reader_readU8(r);
754                 switch (v) {
755                     case 0: 
756                       type = gfxgradient_radial; break;
757                     case 1:
758                       type = gfxgradient_linear; break;
759                 }  
760                 gfxgradient_t*gradient = readGradient(r, &state);
761                 gfxmatrix_t matrix = readMatrix(r, &state);
762                 gfxline_t* line = readLine(r, &state);
763                 out->fillgradient(out, line, gradient, type, &matrix);
764                 break;
765             }
766             case OP_DRAWLINK: {
767                 msg("<trace> replay: DRAWLINK");
768                 gfxline_t* line = readLine(r, &state);
769                 char* s = reader_readString(r);
770                 out->drawlink(out,line,s);
771                 gfxline_free(line);
772                 free(s);
773                 break;
774             }
775             case OP_ADDFONT: {
776                 msg("<trace> replay: ADDFONT out=%08x(%s)", out, out->name);
777                 gfxfont_t*font = readFont(r, &state);
778                 fontlist = gfxfontlist_addfont(fontlist, font);
779                 out->addfont(out, font);
780                 break;
781             }
782             case OP_DRAWCHAR: {
783                 U32 glyph = reader_readU32(r);
784                 gfxmatrix_t m = {1,0,0, 0,1,0};
785                 char* id = 0;
786                 if(!(flags&FLAG_ZERO_FONT))
787                     id = read_string(r, &state, op, flags);
788                 gfxcolor_t color = read_color(r, &state, op, flags);
789                 gfxmatrix_t matrix = read_matrix(r, &state, op, flags);
790
791                 gfxfont_t*font = id?gfxfontlist_findfont(fontlist, id):0;
792                 if(i && !font) {
793                     font = gfxfontlist_findfont(i->fontlist, id);
794                 }
795                 msg("<trace> replay: DRAWCHAR font=%s glyph=%d", id, glyph);
796                 out->drawchar(out, font, glyph, &color, &matrix);
797                 if(id)
798                     free(id);
799                 break;
800             }
801         }
802     }
803 finish:
804     r->dealloc(r);
805     /* problem: if we just replayed into a device which stores the
806        font for later use (the record device itself is a nice example),
807        then we can't free it yet */
808     //gfxfontlist_free(fontlist, 1);
809     gfxfontlist_free(fontlist, 0);
810 }
811 void gfxresult_record_replay(gfxresult_t*result, gfxdevice_t*device)
812 {
813     internal_result_t*i = (internal_result_t*)result->internal;
814     
815     reader_t r;
816     if(i->use_tempfile) {
817         reader_init_filereader2(&r, i->filename);
818     } else {
819         reader_init_memreader(&r, i->data, i->length);
820     }
821
822     replay(0, device, &r);
823 }
824
825 static void record_result_write(gfxresult_t*r, int filedesc)
826 {
827     internal_result_t*i = (internal_result_t*)r->internal;
828     if(i->data) {
829         write(filedesc, i->data, i->length);
830     }
831 }
832 static int record_result_save(gfxresult_t*r, const char*filename)
833 {
834     internal_result_t*i = (internal_result_t*)r->internal;
835     if(i->use_tempfile) {
836         move_file(i->filename, filename);
837     } else {
838         FILE*fi = fopen(filename, "wb");
839         if(!fi) {
840             fprintf(stderr, "Couldn't open file %s for writing\n", filename);
841             return -1;
842         }
843         fwrite(i->data, i->length, 1, fi);
844         fclose(fi);
845     }
846     return 0;
847 }
848 static void*record_result_get(gfxresult_t*r, const char*name)
849 {
850     internal_result_t*i = (internal_result_t*)r->internal;
851     if(!strcmp(name, "data")) {
852         return i->data;
853     } else if(!strcmp(name, "length")) {
854         return &i->length;
855     }
856     return 0;
857 }
858 static void record_result_destroy(gfxresult_t*r)
859 {
860     internal_result_t*i = (internal_result_t*)r->internal;
861     if(i->data) {
862         free(i->data);i->data = 0;
863     }
864     if(i->filename) {
865         unlink(i->filename);
866         free(i->filename);
867     }
868     free(r->internal);r->internal = 0;
869     free(r);
870 }
871
872 static unsigned char printable(unsigned char a)
873 {
874     if(a<32 || a==127) return '.';
875     else return a;
876 }
877
878 static void hexdumpMem(unsigned char*data, int len)
879 {
880     int t;
881     char ascii[32];
882     for(t=0;t<len;t++) {
883         printf("%02x ", data[t]);
884         ascii[t&15] = printable(data[t]);
885         if((t && ((t&15)==15)) || (t==len-1))
886         {
887             int s,p=((t)&15)+1;
888             ascii[p] = 0;
889             for(s=p-1;s<16;s++) {
890                 printf("   ");
891             }
892             printf(" %s\n", ascii);
893         }
894     }
895 }
896
897 void gfxdevice_record_flush(gfxdevice_t*dev, gfxdevice_t*out)
898 {
899     internal_t*i = (internal_t*)dev->internal;
900     if(out) {
901         if(!i->use_tempfile) {
902             int len=0;
903             void*data = writer_growmemwrite_memptr(&i->w, &len);
904             reader_t r;
905             reader_init_memreader(&r, data, len);
906             replay(dev, out, &r);
907             writer_growmemwrite_reset(&i->w);
908         } else {
909             msg("<fatal> Flushing not supported for file based record device");
910             exit(1);
911         }
912     }
913 }
914
915 static gfxresult_t* record_finish(struct _gfxdevice*dev)
916 {
917     internal_t*i = (internal_t*)dev->internal;
918     msg("<trace> record: %08x END", dev);
919
920     if(i->cliplevel) {
921         msg("<error> Warning: unclosed cliplevels");
922     }
923
924 #ifdef STATS
925     int total = i->w.pos;
926     if(total && i->use_tempfile) {
927         state_t*s = &i->state;
928         msg("<notice> record device finished. stats:");
929         msg("<notice> %4.1f%% matrices (%d bytes)", s->size_matrices*100.0/total, s->size_matrices);
930         msg("<notice> %4.1f%% positions (%d bytes)", s->size_positions*100.0/total, s->size_positions);
931         msg("<notice> %4.1f%% colors (%d bytes)", s->size_colors*100.0/total, s->size_colors);
932         msg("<notice> %4.1f%% lines (%d bytes)", s->size_lines*100.0/total, s->size_lines);
933         msg("<notice> %4.1f%% fonts (%d bytes)", s->size_fonts*100.0/total, s->size_fonts);
934         msg("<notice> %4.1f%% images (%d bytes)", s->size_images*100.0/total, s->size_images);
935         msg("<notice> %4.1f%% characters (%d bytes)", s->size_chars*100.0/total, s->size_chars);
936         msg("<notice> total: %d bytes", total);
937     }
938 #endif
939     
940     writer_writeU8(&i->w, OP_END);
941     
942     gfxfontlist_free(i->fontlist, 0);
943    
944     internal_result_t*ir = (internal_result_t*)rfx_calloc(sizeof(gfxresult_t));
945    
946     ir->use_tempfile = i->use_tempfile;
947     if(i->use_tempfile) {
948         ir->filename = i->filename;
949     } else {
950         ir->data = writer_growmemwrite_getmem(&i->w);
951         ir->length = i->w.pos;
952     }
953     i->w.finish(&i->w);
954
955     gfxresult_t*result= (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
956     result->save = record_result_save;
957     result->get = record_result_get;
958     result->destroy = record_result_destroy;
959     result->internal = ir;
960
961     free(dev->internal);memset(dev, 0, sizeof(gfxdevice_t));
962     
963     return result;
964 }
965
966 void gfxdevice_record_init(gfxdevice_t*dev, char use_tempfile)
967 {
968     internal_t*i = (internal_t*)rfx_calloc(sizeof(internal_t));
969     memset(dev, 0, sizeof(gfxdevice_t));
970     
971     dev->name = "record";
972
973     dev->internal = i;
974   
975     i->use_tempfile = use_tempfile;
976     if(!use_tempfile) {
977         writer_init_growingmemwriter(&i->w, 1048576);
978     } else {
979         char buffer[128];
980         i->filename = strdup(mktempname(buffer, "gfx"));
981         writer_init_filewriter2(&i->w, i->filename);
982     }
983     i->fontlist = gfxfontlist_create();
984     i->cliplevel = 0;
985
986     dev->setparameter = record_setparameter;
987     dev->startpage = record_startpage;
988     dev->startclip = record_startclip;
989     dev->endclip = record_endclip;
990     dev->stroke = record_stroke;
991     dev->fill = record_fill;
992     dev->fillbitmap = record_fillbitmap;
993     dev->fillgradient = record_fillgradient;
994     dev->addfont = record_addfont;
995     dev->drawchar = record_drawchar;
996     dev->drawlink = record_drawlink;
997     dev->endpage = record_endpage;
998     dev->finish = record_finish;
999 }
1000