1#include "mupdf/fitz.h"
2#include "mupdf/pdf.h"
3
4#include <ft2build.h>
5#include FT_FREETYPE_H
6#include FT_ADVANCES_H
7
8#define ALLOWED_TEXT_POS_ERROR (0.001f)
9
10typedef struct pdf_device_s pdf_device;
11
12typedef struct gstate_s gstate;
13
14struct gstate_s
15{
16 /* The first few entries aren't really graphics state things, but
17 * they are recorded here as they are fundamentally intertwined with
18 * the push/pulling of the gstates. */
19 fz_buffer *buf;
20 void (*on_pop)(fz_context*,pdf_device*,void *);
21 void *on_pop_arg;
22
23 /* The graphics state proper */
24 fz_matrix ctm;
25 fz_colorspace *colorspace[2];
26 float color[2][4];
27 float alpha[2];
28 fz_stroke_state *stroke_state;
29 int font;
30 float font_size;
31 int text_rendering_mode;
32 int knockout;
33};
34
35/* The image digest information, object reference, as well as indirect reference
36 * ID are all stored in doc->resources->image, and so they are maintained
37 * through the life of the document not just this page level device. As we
38 * encounter images on a page, we will add to the hash table if they are not
39 * already present. When we have an image on a particular page, the resource
40 * dict will be updated with the proper indirect reference across the document.
41 * We do need to maintain some information as to what image resources we have
42 * already specified for this page which is the purpose of image_indices
43 */
44
45typedef struct alpha_entry_s alpha_entry;
46
47struct alpha_entry_s
48{
49 float alpha;
50 int stroke;
51};
52
53typedef struct group_entry_s group_entry;
54
55struct group_entry_s
56{
57 int alpha;
58 int isolated;
59 int knockout;
60 fz_colorspace *colorspace;
61 pdf_obj *ref;
62};
63
64struct pdf_device_s
65{
66 fz_device super;
67
68 pdf_document *doc;
69 pdf_obj *resources;
70
71 int in_text;
72
73 int num_forms;
74 int num_smasks;
75
76 int num_gstates;
77 int max_gstates;
78 gstate *gstates;
79
80 int num_imgs;
81 int max_imgs;
82 int *image_indices;
83
84 int num_cid_fonts;
85 int max_cid_fonts;
86 fz_font **cid_fonts;
87
88 int num_alphas;
89 int max_alphas;
90 alpha_entry *alphas;
91
92 int num_groups;
93 int max_groups;
94 group_entry *groups;
95};
96
97#define CURRENT_GSTATE(pdev) (&(pdev)->gstates[(pdev)->num_gstates-1])
98
99/* Helper functions */
100static void
101pdf_dev_stroke_state(fz_context *ctx, pdf_device *pdev, const fz_stroke_state *stroke_state)
102{
103 gstate *gs = CURRENT_GSTATE(pdev);
104
105 if (stroke_state == gs->stroke_state)
106 return;
107 if (gs->stroke_state && !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state)))
108 return;
109 if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth)
110 {
111 fz_append_printf(ctx, gs->buf, "%g w\n", stroke_state->linewidth);
112 }
113 if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap)
114 {
115 int cap = stroke_state->start_cap;
116 /* FIXME: Triangle caps aren't supported in pdf */
117 if (cap == FZ_LINECAP_TRIANGLE)
118 cap = FZ_LINECAP_BUTT;
119 fz_append_printf(ctx, gs->buf, "%d J\n", cap);
120 }
121 if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin)
122 {
123 int join = stroke_state->linejoin;
124 if (join == FZ_LINEJOIN_MITER_XPS)
125 join = FZ_LINEJOIN_MITER;
126 fz_append_printf(ctx, gs->buf, "%d j\n", join);
127 }
128 if (!gs->stroke_state || gs->stroke_state->miterlimit != stroke_state->miterlimit)
129 {
130 fz_append_printf(ctx, gs->buf, "%g M\n", stroke_state->miterlimit);
131 }
132 if (gs->stroke_state == NULL && stroke_state->dash_len == 0)
133 {}
134 else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len ||
135 memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len))
136 {
137 int i;
138 fz_append_byte(ctx, gs->buf, '[');
139 for (i = 0; i < stroke_state->dash_len; i++)
140 {
141 if (i > 0)
142 fz_append_byte(ctx, gs->buf, ' ');
143 fz_append_printf(ctx, gs->buf, "%g", stroke_state->dash_list[i]);
144 }
145 fz_append_printf(ctx, gs->buf, "]%g d\n", stroke_state->dash_phase);
146 }
147 fz_drop_stroke_state(ctx, gs->stroke_state);
148 gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state);
149}
150
151typedef struct
152{
153 fz_context *ctx;
154 fz_buffer *buf;
155} pdf_dev_path_arg;
156
157static void
158pdf_dev_path_moveto(fz_context *ctx, void *arg, float x, float y)
159{
160 fz_buffer *buf = (fz_buffer *)arg;
161 fz_append_printf(ctx, buf, "%g %g m\n", x, y);
162}
163
164static void
165pdf_dev_path_lineto(fz_context *ctx, void *arg, float x, float y)
166{
167 fz_buffer *buf = (fz_buffer *)arg;
168 fz_append_printf(ctx, buf, "%g %g l\n", x, y);
169}
170
171static void
172pdf_dev_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3)
173{
174 fz_buffer *buf = (fz_buffer *)arg;
175 fz_append_printf(ctx, buf, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3);
176}
177
178static void
179pdf_dev_path_close(fz_context *ctx, void *arg)
180{
181 fz_buffer *buf = (fz_buffer *)arg;
182 fz_append_string(ctx, buf, "h\n");
183}
184
185static const fz_path_walker pdf_dev_path_proc =
186{
187 pdf_dev_path_moveto,
188 pdf_dev_path_lineto,
189 pdf_dev_path_curveto,
190 pdf_dev_path_close
191};
192
193static void
194pdf_dev_path(fz_context *ctx, pdf_device *pdev, const fz_path *path)
195{
196 gstate *gs = CURRENT_GSTATE(pdev);
197
198 fz_walk_path(ctx, path, &pdf_dev_path_proc, (void *)gs->buf);
199}
200
201static void
202pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, fz_matrix ctm)
203{
204 fz_matrix inverse;
205 gstate *gs = CURRENT_GSTATE(pdev);
206
207 if (memcmp(&gs->ctm, &ctm, sizeof(ctm)) == 0)
208 return;
209 inverse = fz_invert_matrix(gs->ctm);
210 inverse = fz_concat(ctm, inverse);
211 gs->ctm = ctm;
212 fz_append_printf(ctx, gs->buf, "%M cm\n", &inverse);
213}
214
215static void
216pdf_dev_color(fz_context *ctx, pdf_device *pdev, fz_colorspace *colorspace, const float *color, int stroke, fz_color_params color_params)
217{
218 int diff = 0;
219 int i;
220 int cspace = 0;
221 float rgb[FZ_MAX_COLORS];
222 gstate *gs = CURRENT_GSTATE(pdev);
223
224 if (colorspace == fz_device_gray(ctx))
225 cspace = 1;
226 else if (colorspace == fz_device_rgb(ctx))
227 cspace = 3;
228 else if (colorspace == fz_device_cmyk(ctx))
229 cspace = 4;
230
231 if (cspace == 0)
232 {
233 /* If it's an unknown colorspace, fallback to rgb */
234 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
235 color = rgb;
236 colorspace = fz_device_rgb(ctx);
237 cspace = 3;
238 }
239
240 if (gs->colorspace[stroke] != colorspace)
241 {
242 gs->colorspace[stroke] = colorspace;
243 diff = 1;
244 }
245
246 for (i=0; i < cspace; i++)
247 if (gs->color[stroke][i] != color[i])
248 {
249 gs->color[stroke][i] = color[i];
250 diff = 1;
251 }
252
253 if (diff == 0)
254 return;
255
256 switch (cspace + stroke*8)
257 {
258 case 1:
259 fz_append_printf(ctx, gs->buf, "%g g\n", color[0]);
260 break;
261 case 3:
262 fz_append_printf(ctx, gs->buf, "%g %g %g rg\n", color[0], color[1], color[2]);
263 break;
264 case 4:
265 fz_append_printf(ctx, gs->buf, "%g %g %g %g k\n", color[0], color[1], color[2], color[3]);
266 break;
267 case 1+8:
268 fz_append_printf(ctx, gs->buf, "%g G\n", color[0]);
269 break;
270 case 3+8:
271 fz_append_printf(ctx, gs->buf, "%g %g %g RG\n", color[0], color[1], color[2]);
272 break;
273 case 4+8:
274 fz_append_printf(ctx, gs->buf, "%g %g %g %g K\n", color[0], color[1], color[2], color[3]);
275 break;
276 }
277}
278
279static void
280pdf_dev_alpha(fz_context *ctx, pdf_device *pdev, float alpha, int stroke)
281{
282 int i;
283 pdf_document *doc = pdev->doc;
284 gstate *gs = CURRENT_GSTATE(pdev);
285
286 /* If the alpha is unchanged, nothing to do */
287 if (gs->alpha[stroke] == alpha)
288 return;
289
290 /* Have we sent such an alpha before? */
291 for (i = 0; i < pdev->num_alphas; i++)
292 if (pdev->alphas[i].alpha == alpha && pdev->alphas[i].stroke == stroke)
293 break;
294
295 if (i == pdev->num_alphas)
296 {
297 pdf_obj *o, *ref;
298
299 /* No. Need to make a new one */
300 if (pdev->num_alphas == pdev->max_alphas)
301 {
302 int newmax = pdev->max_alphas * 2;
303 if (newmax == 0)
304 newmax = 4;
305 pdev->alphas = fz_realloc_array(ctx, pdev->alphas, newmax, alpha_entry);
306 pdev->max_alphas = newmax;
307 }
308 pdev->alphas[i].alpha = alpha;
309 pdev->alphas[i].stroke = stroke;
310
311 o = pdf_new_dict(ctx, doc, 1);
312 fz_try(ctx)
313 {
314 char text[32];
315 pdf_dict_put_real(ctx, o, (stroke ? PDF_NAME(CA) : PDF_NAME(ca)), alpha);
316 fz_snprintf(text, sizeof(text), "ExtGState/Alp%d", i);
317 ref = pdf_add_object(ctx, doc, o);
318 pdf_dict_putp_drop(ctx, pdev->resources, text, ref);
319 }
320 fz_always(ctx)
321 {
322 pdf_drop_obj(ctx, o);
323 }
324 fz_catch(ctx)
325 {
326 fz_rethrow(ctx);
327 }
328 pdev->num_alphas++;
329 }
330 fz_append_printf(ctx, gs->buf, "/Alp%d gs\n", i);
331}
332
333static int
334pdf_dev_add_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font)
335{
336 pdf_obj *fres;
337 char text[32];
338 int k;
339 int num;
340
341 /* Check if we already had this one */
342 for (k = 0; k < pdev->num_cid_fonts; k++)
343 if (pdev->cid_fonts[k] == font)
344 return k;
345
346 /* This will add it to the xref if needed */
347 fres = pdf_add_cid_font(ctx, pdev->doc, font);
348
349 /* Not there so add to resources */
350 fz_snprintf(text, sizeof(text), "Font/F%d", pdev->num_cid_fonts);
351 pdf_dict_putp_drop(ctx, pdev->resources, text, fres);
352
353 /* And add index to our list for this page */
354 if (pdev->num_cid_fonts == pdev->max_cid_fonts)
355 {
356 int newmax = pdev->max_cid_fonts * 2;
357 if (newmax == 0)
358 newmax = 4;
359 pdev->cid_fonts = fz_realloc_array(ctx, pdev->cid_fonts, newmax, fz_font*);
360 pdev->max_cid_fonts = newmax;
361 }
362 num = pdev->num_cid_fonts++;
363 pdev->cid_fonts[num] = fz_keep_font(ctx, font);
364 return num;
365}
366
367static void
368pdf_dev_font(fz_context *ctx, pdf_device *pdev, fz_font *font, fz_matrix trm)
369{
370 gstate *gs = CURRENT_GSTATE(pdev);
371 float font_size = fz_matrix_expansion(trm);
372
373 /* If the font is unchanged, nothing to do */
374 if (gs->font >= 0 && pdev->cid_fonts[gs->font] == font && gs->font_size == font_size)
375 return;
376
377 if (fz_font_t3_procs(ctx, font))
378 fz_throw(ctx, FZ_ERROR_GENERIC, "pdf device does not support type 3 fonts");
379 if (fz_font_flags(font)->ft_substitute)
380 fz_throw(ctx, FZ_ERROR_GENERIC, "pdf device does not support substitute fonts");
381 if (!pdf_font_writing_supported(font))
382 fz_throw(ctx, FZ_ERROR_GENERIC, "pdf device does not support font types found in this file");
383
384 gs->font = pdf_dev_add_font_res(ctx, pdev, font);
385 gs->font_size = font_size;
386
387 fz_append_printf(ctx, gs->buf, "/F%d %g Tf\n", gs->font, gs->font_size);
388}
389
390static void
391pdf_dev_push_new_buf(fz_context *ctx, pdf_device *pdev, fz_buffer *buf, void (*on_pop)(fz_context*,pdf_device*,void*), void *on_pop_arg)
392{
393 if (pdev->num_gstates == pdev->max_gstates)
394 {
395 int newmax = pdev->max_gstates*2;
396 pdev->gstates = fz_realloc_array(ctx, pdev->gstates, newmax, gstate);
397 pdev->max_gstates = newmax;
398 }
399 memcpy(&pdev->gstates[pdev->num_gstates], &pdev->gstates[pdev->num_gstates-1], sizeof(*pdev->gstates));
400 fz_keep_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
401 if (buf)
402 pdev->gstates[pdev->num_gstates].buf = buf;
403 else
404 fz_keep_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
405 pdev->gstates[pdev->num_gstates].on_pop = on_pop;
406 pdev->gstates[pdev->num_gstates].on_pop_arg = on_pop_arg;
407 fz_append_string(ctx, pdev->gstates[pdev->num_gstates].buf, "q\n");
408 pdev->num_gstates++;
409}
410
411static void
412pdf_dev_push(fz_context *ctx, pdf_device *pdev)
413{
414 pdf_dev_push_new_buf(ctx, pdev, NULL, NULL, NULL);
415}
416
417static void *
418pdf_dev_pop(fz_context *ctx, pdf_device *pdev)
419{
420 gstate *gs = CURRENT_GSTATE(pdev);
421 void *arg = gs->on_pop_arg;
422
423 fz_append_string(ctx, gs->buf, "Q\n");
424 if (gs->on_pop)
425 gs->on_pop(ctx, pdev, arg);
426 pdev->num_gstates--;
427 fz_drop_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
428 fz_drop_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
429 return arg;
430}
431
432static void
433pdf_dev_text_span(fz_context *ctx, pdf_device *pdev, fz_text_span *span)
434{
435 gstate *gs = CURRENT_GSTATE(pdev);
436 fz_matrix trm, tm, tlm, inv_trm, inv_tm;
437 fz_matrix inv_tfs;
438 fz_point d;
439 float adv;
440 int dx, dy;
441 int i;
442
443 if (span->len == 0)
444 return;
445
446 inv_tfs = fz_scale(1 / gs->font_size, 1 / gs->font_size);
447
448 trm = span->trm;
449 trm.e = span->items[0].x;
450 trm.f = span->items[0].y;
451
452 tm = fz_concat(inv_tfs, trm);
453 tlm = tm;
454
455 inv_tm = fz_invert_matrix(tm);
456 inv_trm = fz_invert_matrix(trm);
457
458 fz_append_printf(ctx, gs->buf, "%M Tm\n[<", &tm);
459
460 for (i = 0; i < span->len; ++i)
461 {
462 fz_text_item *it = &span->items[i];
463 if (it->gid < 0)
464 continue;
465
466 /* transform difference from expected pen position into font units. */
467 d.x = it->x - trm.e;
468 d.y = it->y - trm.f;
469 d = fz_transform_vector(d, inv_trm);
470 dx = (int)(d.x * 1000 + (d.x < 0 ? -0.5f : 0.5f));
471 dy = (int)(d.y * 1000 + (d.y < 0 ? -0.5f : 0.5f));
472
473 trm.e = it->x;
474 trm.f = it->y;
475
476 if (dx != 0 || dy != 0)
477 {
478 if (span->wmode == 0 && dy == 0)
479 fz_append_printf(ctx, gs->buf, ">%d<", -dx);
480 else if (span->wmode == 1 && dx == 0)
481 fz_append_printf(ctx, gs->buf, ">%d<", -dy);
482 else
483 {
484 /* Calculate offset from start of the previous line */
485 tm = fz_concat(inv_tfs, trm);
486 d.x = tm.e - tlm.e;
487 d.y = tm.f - tlm.f;
488 d = fz_transform_vector(d, inv_tm);
489 fz_append_printf(ctx, gs->buf, ">]TJ\n%g %g Td\n[<", d.x, d.y);
490 tlm = tm;
491 }
492 }
493
494 if (fz_font_t3_procs(ctx, span->font))
495 fz_append_printf(ctx, gs->buf, "%02x", it->gid);
496 else
497 fz_append_printf(ctx, gs->buf, "%04x", it->gid);
498
499 adv = fz_advance_glyph(ctx, span->font, it->gid, span->wmode);
500 if (span->wmode == 0)
501 trm = fz_pre_translate(trm, adv, 0);
502 else
503 trm = fz_pre_translate(trm, 0, adv);
504 }
505
506 fz_append_string(ctx, gs->buf, ">]TJ\n");
507}
508
509static void
510pdf_dev_trm(fz_context *ctx, pdf_device *pdev, int trm)
511{
512 gstate *gs = CURRENT_GSTATE(pdev);
513
514 if (gs->text_rendering_mode == trm)
515 return;
516 gs->text_rendering_mode = trm;
517 fz_append_printf(ctx, gs->buf, "%d Tr\n", trm);
518}
519
520static void
521pdf_dev_begin_text(fz_context *ctx, pdf_device *pdev, int trm)
522{
523 pdf_dev_trm(ctx, pdev, trm);
524 if (!pdev->in_text)
525 {
526 gstate *gs = CURRENT_GSTATE(pdev);
527 fz_append_string(ctx, gs->buf, "BT\n");
528 pdev->in_text = 1;
529 }
530}
531
532static void
533pdf_dev_end_text(fz_context *ctx, pdf_device *pdev)
534{
535 gstate *gs = CURRENT_GSTATE(pdev);
536
537 if (!pdev->in_text)
538 return;
539 pdev->in_text = 0;
540 fz_append_string(ctx, gs->buf, "ET\n");
541}
542
543static int
544pdf_dev_new_form(fz_context *ctx, pdf_obj **form_ref, pdf_device *pdev, fz_rect bbox, int isolated, int knockout, float alpha, fz_colorspace *colorspace)
545{
546 pdf_document *doc = pdev->doc;
547 int num;
548 pdf_obj *group_ref = NULL;
549 pdf_obj *group;
550 pdf_obj *form;
551
552 *form_ref = NULL;
553
554 /* Find (or make) a new group with the required options. */
555 for(num = 0; num < pdev->num_groups; num++)
556 {
557 group_entry *g = &pdev->groups[num];
558 if (g->isolated == isolated && g->knockout == knockout && g->alpha == alpha && g->colorspace == colorspace)
559 {
560 group_ref = pdev->groups[num].ref;
561 break;
562 }
563 }
564
565 /* If we didn't find one, make one */
566 if (num == pdev->num_groups)
567 {
568 if (pdev->num_groups == pdev->max_groups)
569 {
570 int newmax = pdev->max_groups * 2;
571 if (newmax == 0)
572 newmax = 4;
573 pdev->groups = fz_realloc_array(ctx, pdev->groups, newmax, group_entry);
574 pdev->max_groups = newmax;
575 }
576 pdev->num_groups++;
577 pdev->groups[num].isolated = isolated;
578 pdev->groups[num].knockout = knockout;
579 pdev->groups[num].alpha = alpha;
580 pdev->groups[num].colorspace = fz_keep_colorspace(ctx, colorspace);
581 pdev->groups[num].ref = NULL;
582 group = pdf_new_dict(ctx, doc, 5);
583 fz_try(ctx)
584 {
585 pdf_dict_put(ctx, group, PDF_NAME(Type), PDF_NAME(Group));
586 pdf_dict_put(ctx, group, PDF_NAME(S), PDF_NAME(Transparency));
587 pdf_dict_put_bool(ctx, group, PDF_NAME(K), knockout);
588 pdf_dict_put_bool(ctx, group, PDF_NAME(I), isolated);
589 switch (fz_colorspace_type(ctx, colorspace))
590 {
591 case FZ_COLORSPACE_GRAY:
592 pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceGray));
593 break;
594 case FZ_COLORSPACE_RGB:
595 pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceRGB));
596 break;
597 case FZ_COLORSPACE_CMYK:
598 pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceCMYK));
599 break;
600 default:
601 break;
602 }
603 group_ref = pdev->groups[num].ref = pdf_add_object(ctx, doc, group);
604 }
605 fz_always(ctx)
606 {
607 pdf_drop_obj(ctx, group);
608 }
609 fz_catch(ctx)
610 {
611 fz_rethrow(ctx);
612 }
613 }
614
615 /* Make us a new Forms object that points to that group, and change
616 * to writing into the buffer for that Forms object. */
617 form = pdf_new_dict(ctx, doc, 4);
618 fz_try(ctx)
619 {
620 pdf_dict_put(ctx, form, PDF_NAME(Subtype), PDF_NAME(Form));
621 pdf_dict_put(ctx, form, PDF_NAME(Group), group_ref);
622 pdf_dict_put_int(ctx, form, PDF_NAME(FormType), 1);
623 pdf_dict_put_rect(ctx, form, PDF_NAME(BBox), bbox);
624 *form_ref = pdf_add_object(ctx, doc, form);
625 }
626 fz_always(ctx)
627 {
628 pdf_drop_obj(ctx, form);
629 }
630 fz_catch(ctx)
631 {
632 fz_rethrow(ctx);
633 }
634
635 /* Insert the new form object into the resources */
636 {
637 char text[32];
638 num = pdev->num_forms++;
639 fz_snprintf(text, sizeof(text), "XObject/Fm%d", num);
640 pdf_dict_putp(ctx, pdev->resources, text, *form_ref);
641 }
642
643 return num;
644}
645
646/* Entry points */
647
648static void
649pdf_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
650 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
651{
652 pdf_device *pdev = (pdf_device*)dev;
653 gstate *gs = CURRENT_GSTATE(pdev);
654
655 pdf_dev_end_text(ctx, pdev);
656 pdf_dev_alpha(ctx, pdev, alpha, 0);
657 pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
658 pdf_dev_ctm(ctx, pdev, ctm);
659 pdf_dev_path(ctx, pdev, path);
660 fz_append_string(ctx, gs->buf, (even_odd ? "f*\n" : "f\n"));
661}
662
663static void
664pdf_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm,
665 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
666{
667 pdf_device *pdev = (pdf_device*)dev;
668 gstate *gs = CURRENT_GSTATE(pdev);
669
670 pdf_dev_end_text(ctx, pdev);
671 pdf_dev_alpha(ctx, pdev, alpha, 1);
672 pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params);
673 pdf_dev_ctm(ctx, pdev, ctm);
674 pdf_dev_stroke_state(ctx, pdev, stroke);
675 pdf_dev_path(ctx, pdev, path);
676 fz_append_string(ctx, gs->buf, "S\n");
677}
678
679static void
680pdf_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
681{
682 pdf_device *pdev = (pdf_device*)dev;
683 gstate *gs;
684
685 pdf_dev_end_text(ctx, pdev);
686 pdf_dev_push(ctx, pdev);
687 pdf_dev_ctm(ctx, pdev, ctm);
688 pdf_dev_path(ctx, pdev, path);
689 gs = CURRENT_GSTATE(pdev);
690 fz_append_string(ctx, gs->buf, (even_odd ? "W* n\n" : "W n\n"));
691}
692
693static void
694pdf_dev_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
695{
696 pdf_device *pdev = (pdf_device*)dev;
697 gstate *gs;
698
699 pdf_dev_end_text(ctx, pdev);
700 pdf_dev_push(ctx, pdev);
701 /* FIXME: Need to push a group, select a pattern (or shading) here,
702 * stroke with the pattern/shading. Then move to defining that pattern
703 * with the next calls to the device interface until the next pop
704 * when we pop the group. */
705 pdf_dev_ctm(ctx, pdev, ctm);
706 pdf_dev_path(ctx, pdev, path);
707 gs = CURRENT_GSTATE(pdev);
708 fz_append_string(ctx, gs->buf, "W n\n");
709}
710
711static void
712pdf_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm,
713 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
714{
715 pdf_device *pdev = (pdf_device*)dev;
716 fz_text_span *span;
717
718 pdf_dev_ctm(ctx, pdev, ctm);
719 pdf_dev_alpha(ctx, pdev, alpha, 0);
720 pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
721
722 for (span = text->head; span; span = span->next)
723 {
724 pdf_dev_begin_text(ctx, pdev, 0);
725 pdf_dev_font(ctx, pdev, span->font, span->trm);
726 pdf_dev_text_span(ctx, pdev, span);
727 }
728}
729
730static void
731pdf_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm,
732 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
733{
734 pdf_device *pdev = (pdf_device*)dev;
735 fz_text_span *span;
736
737 pdf_dev_ctm(ctx, pdev, ctm);
738 pdf_dev_alpha(ctx, pdev, alpha, 1);
739 pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params);
740
741 for (span = text->head; span; span = span->next)
742 {
743 pdf_dev_begin_text(ctx, pdev, 1);
744 pdf_dev_font(ctx, pdev, span->font, span->trm);
745 pdf_dev_text_span(ctx, pdev, span);
746 }
747}
748
749static void
750pdf_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
751{
752 pdf_device *pdev = (pdf_device*)dev;
753 fz_text_span *span;
754
755 pdf_dev_end_text(ctx, pdev);
756 pdf_dev_push(ctx, pdev);
757
758 pdf_dev_ctm(ctx, pdev, ctm);
759
760 for (span = text->head; span; span = span->next)
761 {
762 pdf_dev_begin_text(ctx, pdev, 7);
763 pdf_dev_font(ctx, pdev, span->font, span->trm);
764 pdf_dev_text_span(ctx, pdev, span);
765 }
766}
767
768static void
769pdf_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
770{
771 pdf_device *pdev = (pdf_device*)dev;
772 fz_text_span *span;
773
774 pdf_dev_end_text(ctx, pdev);
775 pdf_dev_push(ctx, pdev);
776
777 pdf_dev_ctm(ctx, pdev, ctm);
778
779 for (span = text->head; span; span = span->next)
780 {
781 pdf_dev_begin_text(ctx, pdev, 7);
782 pdf_dev_font(ctx, pdev, span->font, span->trm);
783 pdf_dev_text_span(ctx, pdev, span);
784 }
785}
786
787static void
788pdf_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
789{
790 pdf_device *pdev = (pdf_device*)dev;
791 fz_text_span *span;
792
793 pdf_dev_ctm(ctx, pdev, ctm);
794
795 for (span = text->head; span; span = span->next)
796 {
797 pdf_dev_begin_text(ctx, pdev, 0);
798 pdf_dev_font(ctx, pdev, span->font, span->trm);
799 pdf_dev_text_span(ctx, pdev, span);
800 }
801}
802
803static void
804pdf_dev_add_image_res(fz_context *ctx, fz_device *dev, pdf_obj *im_res)
805{
806 char text[32];
807 pdf_device *pdev = (pdf_device*)dev;
808 int k;
809 int num;
810
811 /* Check if we already had this one */
812 for (k = 0; k < pdev->num_imgs; k++)
813 {
814 if (pdev->image_indices[k] == pdf_to_num(ctx, im_res))
815 return;
816 }
817
818 /* Not there so add to resources */
819 fz_snprintf(text, sizeof(text), "XObject/Img%d", pdf_to_num(ctx, im_res));
820 pdf_dict_putp(ctx, pdev->resources, text, im_res);
821
822 /* And add index to our list for this page */
823 if (pdev->num_imgs == pdev->max_imgs)
824 {
825 int newmax = pdev->max_imgs * 2;
826 if (newmax == 0)
827 newmax = 4;
828 pdev->image_indices = fz_realloc_array(ctx, pdev->image_indices, newmax, int);
829 pdev->max_imgs = newmax;
830 }
831 num = pdev->num_imgs++;
832 pdev->image_indices[num] = pdf_to_num(ctx, im_res);
833}
834
835static void
836pdf_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
837{
838 pdf_device *pdev = (pdf_device*)dev;
839 pdf_obj *im_res;
840 gstate *gs = CURRENT_GSTATE(pdev);
841
842 pdf_dev_end_text(ctx, pdev);
843 im_res = pdf_add_image(ctx, pdev->doc, image);
844 if (im_res == NULL)
845 {
846 fz_warn(ctx, "pdf_add_image: problem adding image resource");
847 return;
848 }
849
850 fz_try(ctx)
851 {
852 pdf_dev_alpha(ctx, pdev, alpha, 0);
853
854 /* PDF images are upside down, so fiddle the ctm */
855 ctm = fz_pre_scale(ctm, 1, -1);
856 ctm = fz_pre_translate(ctm, 0, -1);
857 pdf_dev_ctm(ctx, pdev, ctm);
858 fz_append_printf(ctx, gs->buf, "/Img%d Do\n", pdf_to_num(ctx, im_res));
859
860 /* Possibly add to page resources */
861 pdf_dev_add_image_res(ctx, dev, im_res);
862 }
863 fz_always(ctx)
864 pdf_drop_obj(ctx, im_res);
865 fz_catch(ctx)
866 fz_rethrow(ctx);
867}
868
869static void
870pdf_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
871{
872 pdf_device *pdev = (pdf_device*)dev;
873
874 /* FIXME */
875 pdf_dev_end_text(ctx, pdev);
876}
877
878static void
879pdf_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
880 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
881{
882 pdf_device *pdev = (pdf_device*)dev;
883 pdf_obj *im_res = NULL;
884 gstate *gs = CURRENT_GSTATE(pdev);
885
886 pdf_dev_end_text(ctx, pdev);
887 im_res = pdf_add_image(ctx, pdev->doc, image);
888 if (im_res == NULL)
889 {
890 fz_warn(ctx, "pdf_add_image: problem adding image resource");
891 return;
892 }
893
894 fz_try(ctx)
895 {
896 fz_append_string(ctx, gs->buf, "q\n");
897 pdf_dev_alpha(ctx, pdev, alpha, 0);
898 pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
899
900 /* PDF images are upside down, so fiddle the ctm */
901 ctm = fz_pre_scale(ctm, 1, -1);
902 ctm = fz_pre_translate(ctm, 0, -1);
903 pdf_dev_ctm(ctx, pdev, ctm);
904 fz_append_printf(ctx, gs->buf, "/Img%d Do Q\n", pdf_to_num(ctx, im_res));
905
906 /* Possibly add to page resources */
907 pdf_dev_add_image_res(ctx, dev, im_res);
908 }
909 fz_always(ctx)
910 pdf_drop_obj(ctx, im_res);
911 fz_catch(ctx)
912 fz_rethrow(ctx);
913}
914
915static void
916pdf_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor)
917{
918 pdf_device *pdev = (pdf_device*)dev;
919
920 /* FIXME */
921 pdf_dev_end_text(ctx, pdev);
922 pdf_dev_push(ctx, pdev);
923}
924
925static void
926pdf_dev_pop_clip(fz_context *ctx, fz_device *dev)
927{
928 pdf_device *pdev = (pdf_device*)dev;
929
930 /* FIXME */
931 pdf_dev_end_text(ctx, pdev);
932 pdf_dev_pop(ctx, pdev);
933}
934
935static void
936pdf_dev_begin_mask(fz_context *ctx, fz_device *dev, fz_rect bbox, int luminosity, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
937{
938 pdf_device *pdev = (pdf_device*)dev;
939 gstate *gs;
940 pdf_obj *smask = NULL;
941 char egsname[32];
942 pdf_obj *egs = NULL;
943 pdf_obj *egss;
944 pdf_obj *form_ref;
945 pdf_obj *color_obj = NULL;
946 int i, n;
947
948 fz_var(smask);
949 fz_var(egs);
950 fz_var(color_obj);
951
952 pdf_dev_end_text(ctx, pdev);
953
954 /* Make a new form to contain the contents of the softmask */
955 pdf_dev_new_form(ctx, &form_ref, pdev, bbox, 0, 0, 1, colorspace);
956
957 fz_try(ctx)
958 {
959 fz_snprintf(egsname, sizeof(egsname), "SM%d", pdev->num_smasks++);
960 egss = pdf_dict_get(ctx, pdev->resources, PDF_NAME(ExtGState));
961 if (!egss)
962 egss = pdf_dict_put_dict(ctx, pdev->resources, PDF_NAME(ExtGState), 10);
963 egs = pdf_dict_puts_dict(ctx, egss, egsname, 1);
964
965 pdf_dict_put(ctx, egs, PDF_NAME(Type), PDF_NAME(ExtGState));
966 smask = pdf_dict_put_dict(ctx, egs, PDF_NAME(SMask), 4);
967
968 pdf_dict_put(ctx, smask, PDF_NAME(Type), PDF_NAME(Mask));
969 pdf_dict_put(ctx, smask, PDF_NAME(S), (luminosity ? PDF_NAME(Luminosity) : PDF_NAME(Alpha)));
970 pdf_dict_put(ctx, smask, PDF_NAME(G), form_ref);
971
972 n = fz_colorspace_n(ctx, colorspace);
973 color_obj = pdf_dict_put_array(ctx, smask, PDF_NAME(BC), n);
974 for (i = 0; i < n; i++)
975 pdf_array_push_real(ctx, color_obj, color[i]);
976
977 gs = CURRENT_GSTATE(pdev);
978 fz_append_printf(ctx, gs->buf, "/SM%d gs\n", pdev->num_smasks-1);
979 }
980 fz_catch(ctx)
981 {
982 pdf_drop_obj(ctx, form_ref);
983 fz_rethrow(ctx);
984 }
985
986 /* Now, everything we get until the end_mask needs to go into a
987 * new buffer, which will be the stream contents for the form. */
988 pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
989}
990
991static void
992pdf_dev_end_mask(fz_context *ctx, fz_device *dev)
993{
994 pdf_device *pdev = (pdf_device*)dev;
995 pdf_document *doc = pdev->doc;
996 gstate *gs = CURRENT_GSTATE(pdev);
997 pdf_obj *form_ref = (pdf_obj *)gs->on_pop_arg;
998
999 /* Here we do part of the pop, but not all of it. */
1000 pdf_dev_end_text(ctx, pdev);
1001 fz_append_string(ctx, gs->buf, "Q\n");
1002 pdf_update_stream(ctx, doc, form_ref, gs->buf, 0);
1003 fz_drop_buffer(ctx, gs->buf);
1004 gs->buf = fz_keep_buffer(ctx, gs[-1].buf);
1005 gs->on_pop_arg = NULL;
1006 pdf_drop_obj(ctx, form_ref);
1007 fz_append_string(ctx, gs->buf, "q\n");
1008}
1009
1010static void
1011pdf_dev_begin_group(fz_context *ctx, fz_device *dev, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
1012{
1013 pdf_device *pdev = (pdf_device*)dev;
1014 pdf_document *doc = pdev->doc;
1015 int num;
1016 pdf_obj *form_ref;
1017 gstate *gs;
1018
1019 pdf_dev_end_text(ctx, pdev);
1020
1021 num = pdf_dev_new_form(ctx, &form_ref, pdev, bbox, isolated, knockout, alpha, cs);
1022
1023 /* Do we have an appropriate blending extgstate already? */
1024 {
1025 char text[32];
1026 pdf_obj *obj;
1027 fz_snprintf(text, sizeof(text), "ExtGState/BlendMode%d", blendmode);
1028 obj = pdf_dict_getp(ctx, pdev->resources, text);
1029 if (obj == NULL)
1030 {
1031 /* No, better make one */
1032 obj = pdf_new_dict(ctx, doc, 2);
1033 pdf_dict_put(ctx, obj, PDF_NAME(Type), PDF_NAME(ExtGState));
1034 pdf_dict_put_name(ctx, obj, PDF_NAME(BM), fz_blendmode_name(blendmode));
1035 pdf_dict_putp_drop(ctx, pdev->resources, text, obj);
1036 }
1037 }
1038
1039 /* Add the call to this group */
1040 gs = CURRENT_GSTATE(pdev);
1041 fz_append_printf(ctx, gs->buf, "/BlendMode%d gs /Fm%d Do\n", blendmode, num);
1042
1043 /* Now, everything we get until the end of group needs to go into a
1044 * new buffer, which will be the stream contents for the form. */
1045 pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
1046}
1047
1048static void
1049pdf_dev_end_group(fz_context *ctx, fz_device *dev)
1050{
1051 pdf_device *pdev = (pdf_device*)dev;
1052 pdf_document *doc = pdev->doc;
1053 gstate *gs = CURRENT_GSTATE(pdev);
1054 fz_buffer *buf = fz_keep_buffer(ctx, gs->buf);
1055 pdf_obj *form_ref;
1056
1057 pdf_dev_end_text(ctx, pdev);
1058 form_ref = (pdf_obj *)pdf_dev_pop(ctx, pdev);
1059 pdf_update_stream(ctx, doc, form_ref, buf, 0);
1060 fz_drop_buffer(ctx, buf);
1061 pdf_drop_obj(ctx, form_ref);
1062}
1063
1064static int
1065pdf_dev_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id)
1066{
1067 pdf_device *pdev = (pdf_device*)dev;
1068
1069 /* FIXME */
1070 pdf_dev_end_text(ctx, pdev);
1071 return 0;
1072}
1073
1074static void
1075pdf_dev_end_tile(fz_context *ctx, fz_device *dev)
1076{
1077 pdf_device *pdev = (pdf_device*)dev;
1078
1079 /* FIXME */
1080 pdf_dev_end_text(ctx, pdev);
1081}
1082
1083static void
1084pdf_dev_close_device(fz_context *ctx, fz_device *dev)
1085{
1086 pdf_device *pdev = (pdf_device*)dev;
1087 pdf_dev_end_text(ctx, pdev);
1088}
1089
1090static void
1091pdf_dev_drop_device(fz_context *ctx, fz_device *dev)
1092{
1093 pdf_device *pdev = (pdf_device*)dev;
1094 int i;
1095
1096 for (i = pdev->num_gstates-1; i >= 0; i--)
1097 {
1098 fz_drop_buffer(ctx, pdev->gstates[i].buf);
1099 fz_drop_stroke_state(ctx, pdev->gstates[i].stroke_state);
1100 }
1101
1102 for (i = pdev->num_cid_fonts-1; i >= 0; i--)
1103 fz_drop_font(ctx, pdev->cid_fonts[i]);
1104
1105 for (i = pdev->num_groups - 1; i >= 0; i--)
1106 {
1107 pdf_drop_obj(ctx, pdev->groups[i].ref);
1108 fz_drop_colorspace(ctx, pdev->groups[i].colorspace);
1109 }
1110
1111 pdf_drop_obj(ctx, pdev->resources);
1112 fz_free(ctx, pdev->cid_fonts);
1113 fz_free(ctx, pdev->image_indices);
1114 fz_free(ctx, pdev->groups);
1115 fz_free(ctx, pdev->alphas);
1116 fz_free(ctx, pdev->gstates);
1117}
1118
1119/*
1120 Create a pdf device. Rendering to the device creates
1121 new pdf content. WARNING: this device is work in progress. It doesn't
1122 currently support all rendering cases.
1123
1124 Note that contents must be a stream (dictionary) to be updated (or
1125 a reference to a stream). Callers should take care to ensure that it
1126 is not an array, and that is it not shared with other objects/pages.
1127*/
1128fz_device *pdf_new_pdf_device(fz_context *ctx, pdf_document *doc, fz_matrix topctm, fz_rect mediabox, pdf_obj *resources, fz_buffer *buf)
1129{
1130 pdf_device *dev = fz_new_derived_device(ctx, pdf_device);
1131
1132 dev->super.close_device = pdf_dev_close_device;
1133 dev->super.drop_device = pdf_dev_drop_device;
1134
1135 dev->super.fill_path = pdf_dev_fill_path;
1136 dev->super.stroke_path = pdf_dev_stroke_path;
1137 dev->super.clip_path = pdf_dev_clip_path;
1138 dev->super.clip_stroke_path = pdf_dev_clip_stroke_path;
1139
1140 dev->super.fill_text = pdf_dev_fill_text;
1141 dev->super.stroke_text = pdf_dev_stroke_text;
1142 dev->super.clip_text = pdf_dev_clip_text;
1143 dev->super.clip_stroke_text = pdf_dev_clip_stroke_text;
1144 dev->super.ignore_text = pdf_dev_ignore_text;
1145
1146 dev->super.fill_shade = pdf_dev_fill_shade;
1147 dev->super.fill_image = pdf_dev_fill_image;
1148 dev->super.fill_image_mask = pdf_dev_fill_image_mask;
1149 dev->super.clip_image_mask = pdf_dev_clip_image_mask;
1150
1151 dev->super.pop_clip = pdf_dev_pop_clip;
1152
1153 dev->super.begin_mask = pdf_dev_begin_mask;
1154 dev->super.end_mask = pdf_dev_end_mask;
1155 dev->super.begin_group = pdf_dev_begin_group;
1156 dev->super.end_group = pdf_dev_end_group;
1157
1158 dev->super.begin_tile = pdf_dev_begin_tile;
1159 dev->super.end_tile = pdf_dev_end_tile;
1160
1161 fz_var(buf);
1162
1163 fz_try(ctx)
1164 {
1165 if (buf)
1166 buf = fz_keep_buffer(ctx, buf);
1167 else
1168 buf = fz_new_buffer(ctx, 256);
1169 dev->doc = doc;
1170 dev->resources = pdf_keep_obj(ctx, resources);
1171 dev->gstates = fz_malloc_struct(ctx, gstate);
1172 dev->gstates[0].buf = buf;
1173 dev->gstates[0].ctm = fz_identity; // XXX
1174 dev->gstates[0].colorspace[0] = fz_device_gray(ctx);
1175 dev->gstates[0].colorspace[1] = fz_device_gray(ctx);
1176 dev->gstates[0].color[0][0] = 1;
1177 dev->gstates[0].color[1][0] = 1;
1178 dev->gstates[0].alpha[0] = 1.0f;
1179 dev->gstates[0].alpha[1] = 1.0f;
1180 dev->gstates[0].font = -1;
1181 dev->num_gstates = 1;
1182 dev->max_gstates = 1;
1183
1184 if (!fz_is_identity(topctm))
1185 fz_append_printf(ctx, buf, "%M cm\n", &topctm);
1186 }
1187 fz_catch(ctx)
1188 {
1189 fz_drop_buffer(ctx, buf);
1190 fz_free(ctx, dev);
1191 fz_rethrow(ctx);
1192 }
1193
1194 return (fz_device*)dev;
1195}
1196
1197/*
1198 Create a device that will record the
1199 graphical operations given to it into a sequence of
1200 pdf operations, together with a set of resources. This
1201 sequence/set pair can then be used as the basis for
1202 adding a page to the document (see pdf_add_page).
1203
1204 doc: The document for which these are intended.
1205
1206 mediabox: The bbox for the created page.
1207
1208 presources: Pointer to a place to put the created
1209 resources dictionary.
1210
1211 pcontents: Pointer to a place to put the created
1212 contents buffer.
1213*/
1214fz_device *pdf_page_write(fz_context *ctx, pdf_document *doc, fz_rect mediabox, pdf_obj **presources, fz_buffer **pcontents)
1215{
1216 fz_matrix pagectm = { 1, 0, 0, -1, -mediabox.x0, mediabox.y1 };
1217 *presources = pdf_new_dict(ctx, doc, 0);
1218 *pcontents = fz_new_buffer(ctx, 0);
1219 return pdf_new_pdf_device(ctx, doc, pagectm, mediabox, *presources, *pcontents);
1220}
1221