Coverage Report

Created: 2026-03-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/pdf/pdf-device.c
Line
Count
Source
1
// Copyright (C) 2004-2026 Artifex Software, Inc.
2
//
3
// This file is part of MuPDF.
4
//
5
// MuPDF is free software: you can redistribute it and/or modify it under the
6
// terms of the GNU Affero General Public License as published by the Free
7
// Software Foundation, either version 3 of the License, or (at your option)
8
// any later version.
9
//
10
// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
// details.
14
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
#include "mupdf/pdf.h"
25
26
#include <ft2build.h>
27
#include FT_FREETYPE_H
28
#include FT_ADVANCES_H
29
30
#define ALLOWED_TEXT_POS_ERROR (0.001f)
31
32
0
#define ENC_IDENTITY 0
33
0
#define ENC_UNICODE 1
34
35
typedef struct pdf_device pdf_device;
36
37
typedef struct
38
{
39
  /* The first few entries aren't really graphics state things, but
40
   * they are recorded here as they are fundamentally intertwined with
41
   * the push/pulling of the gstates. */
42
  fz_buffer *buf;
43
  void (*on_pop)(fz_context*,pdf_device*,void *);
44
  void *on_pop_arg;
45
46
  /* The graphics state proper */
47
  fz_matrix ctm;
48
  fz_colorspace *colorspace[2];
49
  float color[2][4];
50
  float alpha[2];
51
  fz_stroke_state *stroke_state;
52
  int font;
53
  float font_size;
54
  int text_rendering_mode;
55
  int knockout;
56
} gstate;
57
58
/* The image digest information, object reference, as well as indirect reference
59
 * ID are all stored in doc->resources->image, and so they are maintained
60
 * through the life of the document not just this page level device. As we
61
 * encounter images on a page, we will add to the hash table if they are not
62
 * already present.  When we have an image on a particular page, the resource
63
 * dict will be updated with the proper indirect reference across the document.
64
 * We do need to maintain some information as to what image resources we have
65
 * already specified for this page which is the purpose of image_indices
66
 */
67
68
typedef struct
69
{
70
  float alpha;
71
  int stroke;
72
} alpha_entry;
73
74
typedef struct
75
{
76
  int alpha;
77
  int isolated;
78
  int knockout;
79
  fz_colorspace *colorspace;
80
  pdf_obj *ref;
81
} group_entry;
82
83
struct pdf_device
84
{
85
  fz_device super;
86
87
  pdf_document *doc;
88
  pdf_obj *resources;
89
90
  int in_text;
91
92
  int num_forms;
93
  int num_smasks;
94
95
  int num_gstates;
96
  int max_gstates;
97
  gstate *gstates;
98
99
  int num_imgs;
100
  int max_imgs;
101
  int *image_indices;
102
103
  int num_cid_fonts;
104
  int max_cid_fonts;
105
  fz_font **cid_fonts;
106
  int *cid_fonts_enc;
107
108
  int num_alphas;
109
  int max_alphas;
110
  alpha_entry *alphas;
111
112
  int num_groups;
113
  int max_groups;
114
  group_entry *groups;
115
116
  int num_shadings;
117
};
118
119
0
#define CURRENT_GSTATE(pdev) (&(pdev)->gstates[(pdev)->num_gstates-1])
120
121
/* Helper functions */
122
static void
123
pdf_dev_stroke_state(fz_context *ctx, pdf_device *pdev, const fz_stroke_state *stroke_state)
124
0
{
125
0
  gstate *gs = CURRENT_GSTATE(pdev);
126
127
0
  if (stroke_state == gs->stroke_state)
128
0
    return;
129
0
  if (gs->stroke_state &&
130
0
    !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state))
131
0
    && gs->stroke_state->dash_len == stroke_state->dash_len &&
132
0
      (stroke_state->dash_len == 0 ||
133
0
      !memcmp(&gs->stroke_state->dash_list[0], &stroke_state->dash_list[0], sizeof(stroke_state->dash_list[0]) * stroke_state->dash_len)))
134
0
    return;
135
0
  if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth)
136
0
  {
137
0
    fz_append_printf(ctx, gs->buf, "%g w\n", stroke_state->linewidth);
138
0
  }
139
0
  if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap)
140
0
  {
141
0
    int cap = stroke_state->start_cap;
142
    /* FIXME: Triangle caps aren't supported in pdf */
143
0
    if (cap == FZ_LINECAP_TRIANGLE)
144
0
      cap = FZ_LINECAP_BUTT;
145
0
    fz_append_printf(ctx, gs->buf, "%d J\n", cap);
146
0
  }
147
0
  if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin)
148
0
  {
149
0
    int join = stroke_state->linejoin;
150
0
    if (join == FZ_LINEJOIN_MITER_XPS)
151
0
      join = FZ_LINEJOIN_MITER;
152
0
    fz_append_printf(ctx, gs->buf, "%d j\n", join);
153
0
  }
154
0
  if (!gs->stroke_state || fz_max(1.0f, gs->stroke_state->miterlimit) != fz_max(1.0f, stroke_state->miterlimit))
155
0
  {
156
0
    fz_append_printf(ctx, gs->buf, "%g M\n", fz_max(1.0f, stroke_state->miterlimit));
157
0
  }
158
0
  if (gs->stroke_state == NULL && stroke_state->dash_len == 0)
159
0
  {
160
    /* No stroke details. */
161
0
  }
162
0
  else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len ||
163
0
    memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len))
164
0
  {
165
0
    int i;
166
0
    fz_append_byte(ctx, gs->buf, '[');
167
0
    for (i = 0; i < stroke_state->dash_len; i++)
168
0
    {
169
0
      if (i > 0)
170
0
        fz_append_byte(ctx, gs->buf, ' ');
171
0
      fz_append_printf(ctx, gs->buf, "%g", stroke_state->dash_list[i]);
172
0
    }
173
0
    fz_append_printf(ctx, gs->buf, "]%g d\n", stroke_state->dash_phase);
174
0
  }
175
0
  fz_drop_stroke_state(ctx, gs->stroke_state);
176
0
  gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state);
177
0
}
178
179
typedef struct
180
{
181
  fz_context *ctx;
182
  fz_buffer *buf;
183
} pdf_dev_path_arg;
184
185
static void
186
pdf_dev_path_moveto(fz_context *ctx, void *arg, float x, float y)
187
0
{
188
0
  fz_buffer *buf = (fz_buffer *)arg;
189
0
  fz_append_printf(ctx, buf, "%g %g m\n", x, y);
190
0
}
191
192
static void
193
pdf_dev_path_lineto(fz_context *ctx, void *arg, float x, float y)
194
0
{
195
0
  fz_buffer *buf = (fz_buffer *)arg;
196
0
  fz_append_printf(ctx, buf, "%g %g l\n", x, y);
197
0
}
198
199
static void
200
pdf_dev_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3)
201
0
{
202
0
  fz_buffer *buf = (fz_buffer *)arg;
203
0
  fz_append_printf(ctx, buf, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3);
204
0
}
205
206
static void
207
pdf_dev_path_close(fz_context *ctx, void *arg)
208
0
{
209
0
  fz_buffer *buf = (fz_buffer *)arg;
210
0
  fz_append_string(ctx, buf, "h\n");
211
0
}
212
213
static const fz_path_walker pdf_dev_path_proc =
214
{
215
  pdf_dev_path_moveto,
216
  pdf_dev_path_lineto,
217
  pdf_dev_path_curveto,
218
  pdf_dev_path_close
219
};
220
221
static void
222
pdf_dev_path(fz_context *ctx, pdf_device *pdev, const fz_path *path)
223
0
{
224
0
  gstate *gs = CURRENT_GSTATE(pdev);
225
0
  fz_rect bounds;
226
227
0
  if (fz_path_is_rect_with_bounds(ctx, path, fz_identity, &bounds))
228
0
  {
229
0
    fz_append_printf(ctx, gs->buf, "%g %g %g %g re\n", bounds.x0, bounds.y0, bounds.x1-bounds.x0, bounds.y1-bounds.y0);
230
0
    return;
231
0
  }
232
233
0
  fz_walk_path(ctx, path, &pdf_dev_path_proc, (void *)gs->buf);
234
0
}
235
236
static void
237
pdf_dev_stroked_path(fz_context *ctx, pdf_device *pdev, const fz_path *path)
238
0
{
239
0
  gstate *gs = CURRENT_GSTATE(pdev);
240
0
  fz_rect bounds;
241
242
0
  if (fz_path_is_closed(ctx, path) && fz_path_is_rect_with_bounds(ctx, path, fz_identity, &bounds))
243
0
  {
244
0
    fz_append_printf(ctx, gs->buf, "%g %g %g %g re\n", bounds.x0, bounds.y0, bounds.x1-bounds.x0, bounds.y1-bounds.y0);
245
0
    return;
246
0
  }
247
248
0
  fz_walk_path(ctx, path, &pdf_dev_path_proc, (void *)gs->buf);
249
0
}
250
251
static void
252
pdf_dev_end_text(fz_context *ctx, pdf_device *pdev)
253
0
{
254
0
  gstate *gs = CURRENT_GSTATE(pdev);
255
256
0
  if (!pdev->in_text)
257
0
    return;
258
0
  pdev->in_text = 0;
259
0
  fz_append_string(ctx, gs->buf, "ET\n");
260
0
}
261
262
static void
263
pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, fz_matrix ctm)
264
0
{
265
0
  fz_matrix inverse;
266
0
  gstate *gs = CURRENT_GSTATE(pdev);
267
268
0
  if (memcmp(&gs->ctm, &ctm, sizeof(ctm)) == 0)
269
0
    return;
270
0
  pdf_dev_end_text(ctx, pdev);
271
0
  inverse = fz_invert_matrix(gs->ctm);
272
0
  inverse = fz_concat(ctm, inverse);
273
0
  gs->ctm = ctm;
274
0
  fz_append_printf(ctx, gs->buf, "%M cm\n", &inverse);
275
0
}
276
277
static void
278
pdf_dev_color(fz_context *ctx, pdf_device *pdev, fz_colorspace *colorspace, const float *color, int stroke, fz_color_params color_params)
279
0
{
280
0
  int diff = 0;
281
0
  int i;
282
0
  int cspace = 0;
283
0
  float rgb[FZ_MAX_COLORS];
284
0
  gstate *gs = CURRENT_GSTATE(pdev);
285
286
0
  if (colorspace == fz_device_gray(ctx))
287
0
    cspace = 1;
288
0
  else if (colorspace == fz_device_rgb(ctx))
289
0
    cspace = 3;
290
0
  else if (colorspace == fz_device_cmyk(ctx))
291
0
    cspace = 4;
292
293
0
  if (cspace == 0)
294
0
  {
295
    /* If it's an unknown colorspace, fallback to rgb */
296
0
    fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
297
0
    color = rgb;
298
0
    colorspace = fz_device_rgb(ctx);
299
0
    cspace = 3;
300
0
  }
301
302
0
  if (gs->colorspace[stroke] != colorspace)
303
0
  {
304
0
    gs->colorspace[stroke] = colorspace;
305
0
    diff = 1;
306
0
  }
307
308
0
  for (i=0; i < cspace; i++)
309
0
    if (gs->color[stroke][i] != color[i])
310
0
    {
311
0
      gs->color[stroke][i] = color[i];
312
0
      diff = 1;
313
0
    }
314
315
0
  if (diff == 0)
316
0
    return;
317
318
0
  switch (cspace + stroke*8)
319
0
  {
320
0
    case 1:
321
0
      fz_append_printf(ctx, gs->buf, "%g g\n", color[0]);
322
0
      break;
323
0
    case 3:
324
0
      fz_append_printf(ctx, gs->buf, "%g %g %g rg\n", color[0], color[1], color[2]);
325
0
      break;
326
0
    case 4:
327
0
      fz_append_printf(ctx, gs->buf, "%g %g %g %g k\n", color[0], color[1], color[2], color[3]);
328
0
      break;
329
0
    case 1+8:
330
0
      fz_append_printf(ctx, gs->buf, "%g G\n", color[0]);
331
0
      break;
332
0
    case 3+8:
333
0
      fz_append_printf(ctx, gs->buf, "%g %g %g RG\n", color[0], color[1], color[2]);
334
0
      break;
335
0
    case 4+8:
336
0
      fz_append_printf(ctx, gs->buf, "%g %g %g %g K\n", color[0], color[1], color[2], color[3]);
337
0
      break;
338
0
  }
339
0
}
340
341
static void
342
pdf_dev_alpha(fz_context *ctx, pdf_device *pdev, float alpha, int stroke)
343
0
{
344
0
  int i;
345
0
  pdf_document *doc = pdev->doc;
346
0
  gstate *gs = CURRENT_GSTATE(pdev);
347
348
  /* If the alpha is unchanged, nothing to do */
349
0
  if (gs->alpha[stroke] == alpha)
350
0
    return;
351
352
0
  gs->alpha[stroke] = alpha;
353
354
  /* Have we sent such an alpha before? */
355
0
  for (i = 0; i < pdev->num_alphas; i++)
356
0
    if (pdev->alphas[i].alpha == alpha && pdev->alphas[i].stroke == stroke)
357
0
      break;
358
359
0
  if (i == pdev->num_alphas)
360
0
  {
361
0
    pdf_obj *o, *ref;
362
363
    /* No. Need to make a new one */
364
0
    if (pdev->num_alphas == pdev->max_alphas)
365
0
    {
366
0
      int newmax = pdev->max_alphas * 2;
367
0
      if (newmax == 0)
368
0
        newmax = 4;
369
0
      pdev->alphas = fz_realloc_array(ctx, pdev->alphas, newmax, alpha_entry);
370
0
      pdev->max_alphas = newmax;
371
0
    }
372
0
    pdev->alphas[i].alpha = alpha;
373
0
    pdev->alphas[i].stroke = stroke;
374
375
0
    o = pdf_new_dict(ctx, doc, 1);
376
0
    fz_try(ctx)
377
0
    {
378
0
      char text[32];
379
0
      pdf_dict_put_real(ctx, o, (stroke ? PDF_NAME(CA) : PDF_NAME(ca)), alpha);
380
0
      fz_snprintf(text, sizeof(text), "ExtGState/Alp%d", i);
381
0
      ref = pdf_add_object(ctx, doc, o);
382
0
      pdf_dict_putp_drop(ctx, pdev->resources, text, ref);
383
0
    }
384
0
    fz_always(ctx)
385
0
    {
386
0
      pdf_drop_obj(ctx, o);
387
0
    }
388
0
    fz_catch(ctx)
389
0
    {
390
0
      fz_rethrow(ctx);
391
0
    }
392
0
    pdev->num_alphas++;
393
0
  }
394
0
  fz_append_printf(ctx, gs->buf, "/Alp%d gs\n", i);
395
0
}
396
397
static int
398
pdf_dev_find_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font)
399
0
{
400
0
  int k;
401
402
  /* Check if we already had this one */
403
0
  for (k = 0; k < pdev->num_cid_fonts; k++)
404
0
    if (pdev->cid_fonts[k] == font)
405
0
      return k;
406
407
0
  return -1;
408
0
}
409
410
static int
411
pdf_dev_add_font_res_imp(fz_context *ctx, pdf_device *pdev, fz_font *font, pdf_obj *fres, int enc)
412
0
{
413
0
  char text[32];
414
0
  int num;
415
416
  /* Not there so add to resources */
417
0
  fz_snprintf(text, sizeof(text), "Font/F%d", pdev->num_cid_fonts);
418
0
  pdf_dict_putp_drop(ctx, pdev->resources, text, fres);
419
420
  /* And add index to our list for this page */
421
0
  if (pdev->num_cid_fonts == pdev->max_cid_fonts)
422
0
  {
423
0
    int newmax = pdev->max_cid_fonts * 2;
424
0
    if (newmax == 0)
425
0
      newmax = 4;
426
0
    pdev->cid_fonts = fz_realloc_array(ctx, pdev->cid_fonts, newmax, fz_font*);
427
0
    pdev->cid_fonts_enc = fz_realloc_array(ctx, pdev->cid_fonts_enc, newmax, int);
428
0
    pdev->max_cid_fonts = newmax;
429
0
  }
430
0
  num = pdev->num_cid_fonts++;
431
0
  pdev->cid_fonts[num] = fz_keep_font(ctx, font);
432
0
  pdev->cid_fonts_enc[num] = enc;
433
0
  return num;
434
0
}
435
436
static int
437
pdf_dev_add_substitute_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font)
438
0
{
439
0
  pdf_obj *fres;
440
0
  int k;
441
442
  /* Check if we already had this one */
443
0
  k = pdf_dev_find_font_res(ctx, pdev, font);
444
0
  if (k >= 0)
445
0
    return k;
446
447
  /* This will add it to the xref if needed */
448
0
  if (font->flags.cjk)
449
0
    fres = pdf_add_cjk_font(ctx, pdev->doc, font, font->flags.cjk_lang, 0, font->flags.is_serif);
450
0
  else
451
0
    fres = pdf_add_substitute_font(ctx, pdev->doc, font);
452
453
  /* And add to the resource dictionary. */
454
0
  return pdf_dev_add_font_res_imp(ctx, pdev, font, fres, ENC_UNICODE);
455
0
}
456
457
static int
458
pdf_dev_add_embedded_font_res(fz_context *ctx, pdf_device *pdev, fz_font *font)
459
0
{
460
0
  pdf_obj *fres;
461
0
  int k;
462
463
  /* Check if we already had this one */
464
0
  k = pdf_dev_find_font_res(ctx, pdev, font);
465
0
  if (k >= 0)
466
0
    return k;
467
468
  /* This will add it to the xref if needed */
469
0
  fres = pdf_add_cid_font(ctx, pdev->doc, font);
470
471
  /* And add to the resource dictionary. */
472
0
  return pdf_dev_add_font_res_imp(ctx, pdev, font, fres, ENC_IDENTITY);
473
0
}
474
475
static void
476
pdf_dev_font(fz_context *ctx, pdf_device *pdev, fz_font *font, fz_matrix trm)
477
0
{
478
0
  gstate *gs = CURRENT_GSTATE(pdev);
479
0
  float font_size = fz_matrix_expansion(trm);
480
481
  /* If the font is unchanged, nothing to do */
482
0
  if (gs->font >= 0 && pdev->cid_fonts[gs->font] == font && gs->font_size == font_size)
483
0
    return;
484
485
  // TODO: vertical wmode
486
487
0
  if (fz_font_t3_procs(ctx, font))
488
0
    fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "pdf device does not support type 3 fonts");
489
490
0
  if (fz_font_flags(font)->ft_substitute || !pdf_font_writing_supported(ctx, font))
491
0
    gs->font = pdf_dev_add_substitute_font_res(ctx, pdev, font);
492
0
  else
493
0
    gs->font = pdf_dev_add_embedded_font_res(ctx, pdev, font);
494
495
0
  gs->font_size = font_size;
496
497
0
  fz_append_printf(ctx, gs->buf, "/F%d %g Tf\n", gs->font, gs->font_size);
498
0
}
499
500
static void
501
pdf_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)
502
0
{
503
0
  if (pdev->num_gstates == pdev->max_gstates)
504
0
  {
505
0
    int newmax = pdev->max_gstates*2;
506
0
    pdev->gstates = fz_realloc_array(ctx, pdev->gstates, newmax, gstate);
507
0
    pdev->max_gstates = newmax;
508
0
  }
509
0
  memcpy(&pdev->gstates[pdev->num_gstates], &pdev->gstates[pdev->num_gstates-1], sizeof(*pdev->gstates));
510
0
  fz_keep_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
511
0
  if (buf)
512
0
    pdev->gstates[pdev->num_gstates].buf = buf;
513
0
  else
514
0
    fz_keep_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
515
0
  pdev->gstates[pdev->num_gstates].on_pop = on_pop;
516
0
  pdev->gstates[pdev->num_gstates].on_pop_arg = on_pop_arg;
517
0
  fz_append_string(ctx, pdev->gstates[pdev->num_gstates].buf, "q\n");
518
0
  pdev->num_gstates++;
519
0
}
520
521
static void
522
pdf_dev_push(fz_context *ctx, pdf_device *pdev)
523
0
{
524
0
  pdf_dev_push_new_buf(ctx, pdev, NULL, NULL, NULL);
525
0
}
526
527
static void *
528
pdf_dev_pop(fz_context *ctx, pdf_device *pdev)
529
0
{
530
0
  gstate *gs = CURRENT_GSTATE(pdev);
531
0
  void *arg = gs->on_pop_arg;
532
533
0
  fz_append_string(ctx, gs->buf, "Q\n");
534
0
  if (gs->on_pop)
535
0
    gs->on_pop(ctx, pdev, arg);
536
0
  pdev->num_gstates--;
537
0
  fz_drop_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
538
0
  fz_drop_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
539
0
  return arg;
540
0
}
541
542
static void
543
pdf_dev_text_span(fz_context *ctx, pdf_device *pdev, fz_text_span *span)
544
0
{
545
0
  gstate *gs = CURRENT_GSTATE(pdev);
546
0
  fz_matrix trm, tm, tlm, inv_trm, inv_tm;
547
0
  fz_matrix inv_tfs;
548
0
  fz_point d;
549
0
  float adv;
550
0
  int enc;
551
0
  int dx, dy;
552
0
  int i;
553
554
0
  if (span->len == 0)
555
0
    return;
556
557
0
  inv_tfs = fz_scale(1 / gs->font_size, 1 / gs->font_size);
558
559
0
  trm = span->trm;
560
0
  trm.e = span->items[0].x;
561
0
  trm.f = span->items[0].y;
562
563
0
  tm = fz_concat(inv_tfs, trm);
564
0
  tlm = tm;
565
566
0
  inv_tm = fz_invert_matrix(tm);
567
0
  inv_trm = fz_invert_matrix(trm);
568
569
0
  enc = pdev->cid_fonts_enc[gs->font];
570
571
0
  fz_append_printf(ctx, gs->buf, "%M Tm\n[<", &tm);
572
573
0
  for (i = 0; i < span->len; ++i)
574
0
  {
575
0
    fz_text_item *it = &span->items[i];
576
0
    if (enc == ENC_IDENTITY && it->gid < 0)
577
0
      continue;
578
0
    if (enc == ENC_UNICODE && it->ucs < 0)
579
0
      continue;
580
581
    /* transform difference from expected pen position into font units. */
582
0
    d.x = it->x - trm.e;
583
0
    d.y = it->y - trm.f;
584
0
    d = fz_transform_vector(d, inv_trm);
585
0
    dx = (int)(d.x * 1000 + (d.x < 0 ? -0.5f : 0.5f));
586
0
    dy = (int)(d.y * 1000 + (d.y < 0 ? -0.5f : 0.5f));
587
588
0
    trm.e = it->x;
589
0
    trm.f = it->y;
590
591
0
    if (dx != 0 || dy != 0)
592
0
    {
593
0
      if (span->wmode == 0 && dy == 0)
594
0
        fz_append_printf(ctx, gs->buf, ">%d<", -dx);
595
0
      else if (span->wmode == 1 && dx == 0)
596
0
        fz_append_printf(ctx, gs->buf, ">%d<", -dy);
597
0
      else
598
0
      {
599
        /* Calculate offset from start of the previous line */
600
0
        tm = fz_concat(inv_tfs, trm);
601
0
        d.x = tm.e - tlm.e;
602
0
        d.y = tm.f - tlm.f;
603
0
        d = fz_transform_vector(d, inv_tm);
604
0
        fz_append_printf(ctx, gs->buf, ">]TJ\n%g %g Td\n[<", d.x, d.y);
605
0
        tlm = tm;
606
0
      }
607
0
    }
608
609
0
    if (fz_font_t3_procs(ctx, span->font))
610
0
      fz_append_printf(ctx, gs->buf, "%02x", it->gid);
611
0
    else if (enc == ENC_IDENTITY)
612
0
      fz_append_printf(ctx, gs->buf, "%04x", it->gid);
613
0
    else if (enc == ENC_UNICODE)
614
0
      fz_append_printf(ctx, gs->buf, "%04x", it->ucs);
615
616
0
    if (it->gid != -1)
617
0
    {
618
0
      adv = fz_advance_glyph(ctx, span->font, it->gid, span->wmode);
619
0
      if (span->wmode == 0)
620
0
        trm = fz_pre_translate(trm, adv, 0);
621
0
      else
622
0
        trm = fz_pre_translate(trm, 0, adv);
623
0
    }
624
0
  }
625
626
0
  fz_append_string(ctx, gs->buf, ">]TJ\n");
627
0
}
628
629
static void
630
pdf_dev_trm(fz_context *ctx, pdf_device *pdev, int trm)
631
0
{
632
0
  gstate *gs = CURRENT_GSTATE(pdev);
633
634
0
  if (gs->text_rendering_mode == trm)
635
0
    return;
636
0
  gs->text_rendering_mode = trm;
637
0
  fz_append_printf(ctx, gs->buf, "%d Tr\n", trm);
638
0
}
639
640
static void
641
pdf_dev_begin_text(fz_context *ctx, pdf_device *pdev, int trm)
642
0
{
643
0
  pdf_dev_trm(ctx, pdev, trm);
644
0
  if (!pdev->in_text)
645
0
  {
646
0
    gstate *gs = CURRENT_GSTATE(pdev);
647
0
    fz_append_string(ctx, gs->buf, "BT\n");
648
0
    pdev->in_text = 1;
649
0
  }
650
0
}
651
652
static int
653
pdf_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)
654
0
{
655
0
  pdf_document *doc = pdev->doc;
656
0
  int num;
657
0
  pdf_obj *group_ref = NULL;
658
0
  pdf_obj *group;
659
0
  pdf_obj *form;
660
661
0
  *form_ref = NULL;
662
663
  /* Find (or make) a new group with the required options. */
664
0
  for(num = 0; num < pdev->num_groups; num++)
665
0
  {
666
0
    group_entry *g = &pdev->groups[num];
667
0
    if (g->isolated == isolated && g->knockout == knockout && g->alpha == alpha && g->colorspace == colorspace)
668
0
    {
669
0
      group_ref = pdev->groups[num].ref;
670
0
      break;
671
0
    }
672
0
  }
673
674
  /* If we didn't find one, make one */
675
0
  if (num == pdev->num_groups)
676
0
  {
677
0
    if (pdev->num_groups == pdev->max_groups)
678
0
    {
679
0
      int newmax = pdev->max_groups * 2;
680
0
      if (newmax == 0)
681
0
        newmax = 4;
682
0
      pdev->groups = fz_realloc_array(ctx, pdev->groups, newmax, group_entry);
683
0
      pdev->max_groups = newmax;
684
0
    }
685
0
    pdev->num_groups++;
686
0
    pdev->groups[num].isolated = isolated;
687
0
    pdev->groups[num].knockout = knockout;
688
0
    pdev->groups[num].alpha = alpha;
689
0
    pdev->groups[num].colorspace = fz_keep_colorspace(ctx, colorspace);
690
0
    pdev->groups[num].ref = NULL;
691
0
    group = pdf_new_dict(ctx, doc, 5);
692
0
    fz_try(ctx)
693
0
    {
694
0
      pdf_dict_put(ctx, group, PDF_NAME(Type), PDF_NAME(Group));
695
0
      pdf_dict_put(ctx, group, PDF_NAME(S), PDF_NAME(Transparency));
696
0
      pdf_dict_put_bool(ctx, group, PDF_NAME(K), knockout);
697
0
      pdf_dict_put_bool(ctx, group, PDF_NAME(I), isolated);
698
0
      switch (fz_colorspace_type(ctx, colorspace))
699
0
      {
700
0
      case FZ_COLORSPACE_GRAY:
701
0
        pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceGray));
702
0
        break;
703
0
      case FZ_COLORSPACE_RGB:
704
0
        pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceRGB));
705
0
        break;
706
0
      case FZ_COLORSPACE_CMYK:
707
0
        pdf_dict_put(ctx, group, PDF_NAME(CS), PDF_NAME(DeviceCMYK));
708
0
        break;
709
0
      default:
710
0
        break;
711
0
      }
712
0
      group_ref = pdev->groups[num].ref = pdf_add_object(ctx, doc, group);
713
0
    }
714
0
    fz_always(ctx)
715
0
    {
716
0
      pdf_drop_obj(ctx, group);
717
0
    }
718
0
    fz_catch(ctx)
719
0
    {
720
0
      fz_rethrow(ctx);
721
0
    }
722
0
  }
723
724
  /* Make us a new Forms object that points to that group, and change
725
   * to writing into the buffer for that Forms object. */
726
0
  form = pdf_new_dict(ctx, doc, 4);
727
0
  fz_try(ctx)
728
0
  {
729
0
    pdf_dict_put(ctx, form, PDF_NAME(Subtype), PDF_NAME(Form));
730
0
    pdf_dict_put(ctx, form, PDF_NAME(Group), group_ref);
731
0
    pdf_dict_put_int(ctx, form, PDF_NAME(FormType), 1);
732
0
    pdf_dict_put_rect(ctx, form, PDF_NAME(BBox), bbox);
733
0
    *form_ref = pdf_add_object(ctx, doc, form);
734
0
  }
735
0
  fz_always(ctx)
736
0
  {
737
0
    pdf_drop_obj(ctx, form);
738
0
  }
739
0
  fz_catch(ctx)
740
0
  {
741
0
    fz_rethrow(ctx);
742
0
  }
743
744
  /* Insert the new form object into the resources */
745
0
  {
746
0
    char text[32];
747
0
    num = pdev->num_forms++;
748
0
    fz_snprintf(text, sizeof(text), "XObject/Fm%d", num);
749
0
    pdf_dict_putp(ctx, pdev->resources, text, *form_ref);
750
0
  }
751
752
0
  return num;
753
0
}
754
755
/* Entry points */
756
757
static void
758
pdf_dev_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
759
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
760
0
{
761
0
  pdf_device *pdev = (pdf_device*)dev;
762
0
  gstate *gs = CURRENT_GSTATE(pdev);
763
764
0
  pdf_dev_end_text(ctx, pdev);
765
0
  pdf_dev_alpha(ctx, pdev, alpha, 0);
766
0
  pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
767
0
  pdf_dev_ctm(ctx, pdev, ctm);
768
0
  pdf_dev_path(ctx, pdev, path);
769
0
  fz_append_string(ctx, gs->buf, (even_odd ? "f*\n" : "f\n"));
770
0
}
771
772
static void
773
pdf_dev_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm,
774
  fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
775
0
{
776
0
  pdf_device *pdev = (pdf_device*)dev;
777
0
  gstate *gs = CURRENT_GSTATE(pdev);
778
779
0
  pdf_dev_end_text(ctx, pdev);
780
0
  pdf_dev_alpha(ctx, pdev, alpha, 1);
781
0
  pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params);
782
0
  pdf_dev_ctm(ctx, pdev, ctm);
783
0
  pdf_dev_stroke_state(ctx, pdev, stroke);
784
0
  pdf_dev_stroked_path(ctx, pdev, path);
785
0
  fz_append_string(ctx, gs->buf, "S\n");
786
0
}
787
788
static void
789
pdf_dev_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
790
0
{
791
0
  pdf_device *pdev = (pdf_device*)dev;
792
0
  gstate *gs;
793
794
0
  pdf_dev_end_text(ctx, pdev);
795
0
  pdf_dev_push(ctx, pdev);
796
0
  pdf_dev_ctm(ctx, pdev, ctm);
797
0
  pdf_dev_path(ctx, pdev, path);
798
0
  gs = CURRENT_GSTATE(pdev);
799
0
  fz_append_string(ctx, gs->buf, (even_odd ? "W* n\n" : "W n\n"));
800
0
}
801
802
static void
803
pdf_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)
804
0
{
805
0
  pdf_device *pdev = (pdf_device*)dev;
806
0
  gstate *gs;
807
808
0
  pdf_dev_end_text(ctx, pdev);
809
0
  pdf_dev_push(ctx, pdev);
810
  /* FIXME: Need to push a group, select a pattern (or shading) here,
811
   * stroke with the pattern/shading. Then move to defining that pattern
812
   * with the next calls to the device interface until the next pop
813
   * when we pop the group. */
814
0
  pdf_dev_ctm(ctx, pdev, ctm);
815
0
  pdf_dev_stroked_path(ctx, pdev, path);
816
0
  gs = CURRENT_GSTATE(pdev);
817
0
  fz_append_string(ctx, gs->buf, "W n\n");
818
0
}
819
820
static void
821
pdf_dev_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm,
822
    fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
823
0
{
824
0
  pdf_device *pdev = (pdf_device*)dev;
825
0
  fz_text_span *span;
826
827
0
  pdf_dev_ctm(ctx, pdev, ctm);
828
0
  pdf_dev_alpha(ctx, pdev, alpha, 0);
829
0
  pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
830
831
0
  for (span = text->head; span; span = span->next)
832
0
  {
833
0
    pdf_dev_begin_text(ctx, pdev, 0);
834
0
    pdf_dev_font(ctx, pdev, span->font, span->trm);
835
0
    pdf_dev_text_span(ctx, pdev, span);
836
0
  }
837
0
}
838
839
static void
840
pdf_dev_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm,
841
    fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
842
0
{
843
0
  pdf_device *pdev = (pdf_device*)dev;
844
0
  fz_text_span *span;
845
846
0
  pdf_dev_ctm(ctx, pdev, ctm);
847
0
  pdf_dev_alpha(ctx, pdev, alpha, 1);
848
0
  pdf_dev_color(ctx, pdev, colorspace, color, 1, color_params);
849
0
  pdf_dev_stroke_state(ctx, pdev, stroke);
850
851
0
  for (span = text->head; span; span = span->next)
852
0
  {
853
0
    pdf_dev_begin_text(ctx, pdev, 1);
854
0
    pdf_dev_font(ctx, pdev, span->font, span->trm);
855
0
    pdf_dev_text_span(ctx, pdev, span);
856
0
  }
857
0
}
858
859
static void
860
pdf_dev_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
861
0
{
862
0
  pdf_device *pdev = (pdf_device*)dev;
863
0
  fz_text_span *span;
864
865
0
  pdf_dev_end_text(ctx, pdev);
866
0
  pdf_dev_push(ctx, pdev);
867
868
0
  pdf_dev_ctm(ctx, pdev, ctm);
869
870
0
  for (span = text->head; span; span = span->next)
871
0
  {
872
0
    pdf_dev_begin_text(ctx, pdev, 7);
873
0
    pdf_dev_font(ctx, pdev, span->font, span->trm);
874
0
    pdf_dev_text_span(ctx, pdev, span);
875
0
  }
876
0
}
877
878
static void
879
pdf_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)
880
0
{
881
0
  pdf_device *pdev = (pdf_device*)dev;
882
0
  fz_text_span *span;
883
884
0
  pdf_dev_end_text(ctx, pdev);
885
0
  pdf_dev_push(ctx, pdev);
886
887
0
  pdf_dev_ctm(ctx, pdev, ctm);
888
889
0
  for (span = text->head; span; span = span->next)
890
0
  {
891
0
    pdf_dev_begin_text(ctx, pdev, 7);
892
0
    pdf_dev_font(ctx, pdev, span->font, span->trm);
893
0
    pdf_dev_text_span(ctx, pdev, span);
894
0
  }
895
0
}
896
897
static void
898
pdf_dev_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
899
0
{
900
0
  pdf_device *pdev = (pdf_device*)dev;
901
0
  fz_text_span *span;
902
903
0
  pdf_dev_ctm(ctx, pdev, ctm);
904
905
0
  for (span = text->head; span; span = span->next)
906
0
  {
907
0
    pdf_dev_begin_text(ctx, pdev, 0);
908
0
    pdf_dev_font(ctx, pdev, span->font, span->trm);
909
0
    pdf_dev_text_span(ctx, pdev, span);
910
0
  }
911
0
}
912
913
static void
914
pdf_dev_add_image_res(fz_context *ctx, fz_device *dev, pdf_obj *im_res)
915
0
{
916
0
  char text[32];
917
0
  pdf_device *pdev = (pdf_device*)dev;
918
0
  int k;
919
0
  int num;
920
921
  /* Check if we already had this one */
922
0
  for (k = 0; k < pdev->num_imgs; k++)
923
0
  {
924
0
    if (pdev->image_indices[k] == pdf_to_num(ctx, im_res))
925
0
      return;
926
0
  }
927
928
  /* Not there so add to resources */
929
0
  fz_snprintf(text, sizeof(text), "XObject/Img%d", pdf_to_num(ctx, im_res));
930
0
  pdf_dict_putp(ctx, pdev->resources, text, im_res);
931
932
  /* And add index to our list for this page */
933
0
  if (pdev->num_imgs == pdev->max_imgs)
934
0
  {
935
0
    int newmax = pdev->max_imgs * 2;
936
0
    if (newmax == 0)
937
0
      newmax = 4;
938
0
    pdev->image_indices = fz_realloc_array(ctx, pdev->image_indices, newmax, int);
939
0
    pdev->max_imgs = newmax;
940
0
  }
941
0
  num = pdev->num_imgs++;
942
0
  pdev->image_indices[num] = pdf_to_num(ctx, im_res);
943
0
}
944
945
static void
946
pdf_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
947
0
{
948
0
  pdf_device *pdev = (pdf_device*)dev;
949
0
  pdf_obj *im_res;
950
0
  gstate *gs = CURRENT_GSTATE(pdev);
951
952
0
  pdf_dev_end_text(ctx, pdev);
953
0
  im_res = pdf_add_image(ctx, pdev->doc, image);
954
0
  if (im_res == NULL)
955
0
  {
956
0
    fz_warn(ctx, "pdf_add_image: problem adding image resource");
957
0
    return;
958
0
  }
959
960
0
  fz_try(ctx)
961
0
  {
962
0
    pdf_dev_alpha(ctx, pdev, alpha, 0);
963
964
    /* PDF images are upside down, so fiddle the ctm */
965
0
    ctm = fz_pre_scale(ctm, 1, -1);
966
0
    ctm = fz_pre_translate(ctm, 0, -1);
967
0
    pdf_dev_ctm(ctx, pdev, ctm);
968
0
    fz_append_printf(ctx, gs->buf, "/Img%d Do\n", pdf_to_num(ctx, im_res));
969
970
    /* Possibly add to page resources */
971
0
    pdf_dev_add_image_res(ctx, dev, im_res);
972
0
  }
973
0
  fz_always(ctx)
974
0
    pdf_drop_obj(ctx, im_res);
975
0
  fz_catch(ctx)
976
0
    fz_rethrow(ctx);
977
0
}
978
979
static void
980
pdf_dev_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
981
0
{
982
0
  pdf_device *pdev = (pdf_device*)dev;
983
0
  pdf_document *doc = pdev->doc;
984
0
  gstate *gs = CURRENT_GSTATE(pdev);
985
0
  pdf_obj *func_ref = NULL;
986
0
  pdf_obj *shade_ref = NULL;
987
0
  pdf_obj *func_dict = NULL;
988
0
  pdf_obj *shade_dict = NULL;
989
0
  fz_buffer *sample_buf = NULL;
990
0
  fz_colorspace *src_cs;
991
0
  fz_colorspace *dst_cs;
992
0
  pdf_obj *shadings = NULL;
993
0
  int src_n, dst_n;
994
0
  int i, k;
995
0
  int shading_type;
996
0
  char name_buf[32];
997
0
  float converted[FZ_MAX_COLORS];
998
999
0
  fz_var(func_ref);
1000
0
  fz_var(shade_ref);
1001
0
  fz_var(func_dict);
1002
0
  fz_var(shade_dict);
1003
0
  fz_var(sample_buf);
1004
1005
0
  if (shade == NULL || shade->function_stride <= 0 || shade->function == NULL)
1006
0
  {
1007
0
    fz_warn(ctx, "invalid shading passed to pdf device");
1008
0
    return;
1009
0
  }
1010
1011
0
  if (shade->type == FZ_LINEAR)
1012
0
    shading_type = 2;
1013
0
  else if (shade->type == FZ_RADIAL)
1014
0
    shading_type = 3;
1015
0
  else
1016
0
  {
1017
0
    fz_warn(ctx, "pdf device only supports linear/radial shadings for fill_shade");
1018
0
    return;
1019
0
  }
1020
1021
0
  src_cs = shade->colorspace ? shade->colorspace : fz_device_gray(ctx);
1022
0
  src_n = fz_colorspace_n(ctx, src_cs);
1023
0
  if (shade->function_stride < src_n)
1024
0
  {
1025
0
    fz_warn(ctx, "pdf device shading function stride too small");
1026
0
    return;
1027
0
  }
1028
1029
  /* Restrict emitted PDF shading colorspace to device spaces,
1030
   * and convert all other colorspaces to DeviceRGB. */
1031
0
  if (src_cs == fz_device_gray(ctx))
1032
0
  {
1033
0
    dst_cs = fz_device_gray(ctx);
1034
0
    dst_n = 1;
1035
0
  }
1036
0
  else if (src_cs == fz_device_cmyk(ctx))
1037
0
  {
1038
0
    dst_cs = fz_device_cmyk(ctx);
1039
0
    dst_n = 4;
1040
0
  }
1041
0
  else
1042
0
  {
1043
0
    dst_cs = fz_device_rgb(ctx);
1044
0
    dst_n = 3;
1045
0
  }
1046
1047
0
  pdf_dev_end_text(ctx, pdev);
1048
0
  pdf_dev_alpha(ctx, pdev, alpha, 0);
1049
0
  pdf_dev_ctm(ctx, pdev, fz_concat(shade->matrix, ctm));
1050
1051
0
  fz_try(ctx)
1052
0
  {
1053
    /* Build sampled function from MuPDF's canonical 256-entry function table. */
1054
0
    sample_buf = fz_new_buffer(ctx, 256 * dst_n);
1055
0
    for (i = 0; i < 256; ++i)
1056
0
    {
1057
0
      const float *src = &shade->function[i * shade->function_stride];
1058
0
      const float *out = src;
1059
1060
0
      if (src_n != dst_n || src_cs != dst_cs)
1061
0
      {
1062
0
        fz_convert_color(ctx, src_cs, src, dst_cs, converted, NULL, fz_default_color_params);
1063
0
        out = converted;
1064
0
      }
1065
1066
0
      for (k = 0; k < dst_n; ++k)
1067
0
      {
1068
0
        float v = out[k];
1069
0
        int b = fz_clampi((int)(v * 255.0f + 0.5f), 0, 255);
1070
0
        fz_append_byte(ctx, sample_buf, (unsigned char)b);
1071
0
      }
1072
0
    }
1073
1074
0
    func_dict = pdf_new_dict(ctx, doc, 6);
1075
0
    pdf_dict_put_int(ctx, func_dict, PDF_NAME(FunctionType), 0);
1076
0
    pdf_dict_put_int(ctx, func_dict, PDF_NAME(BitsPerSample), 8);
1077
0
    {
1078
0
      pdf_obj *domain = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Domain), 2);
1079
0
      pdf_array_push_int(ctx, domain, 0);
1080
0
      pdf_array_push_int(ctx, domain, 1);
1081
0
    }
1082
0
    {
1083
0
      pdf_obj *range = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Range), dst_n * 2);
1084
0
      for (k = 0; k < dst_n; ++k)
1085
0
      {
1086
0
        pdf_array_push_int(ctx, range, 0);
1087
0
        pdf_array_push_int(ctx, range, 1);
1088
0
      }
1089
0
    }
1090
0
    {
1091
0
      pdf_obj *size = pdf_dict_put_array(ctx, func_dict, PDF_NAME(Size), 1);
1092
0
      pdf_array_push_int(ctx, size, 256);
1093
0
    }
1094
1095
0
    func_ref = pdf_add_stream(ctx, doc, sample_buf, func_dict, 0);
1096
1097
0
    shade_dict = pdf_new_dict(ctx, doc, 6);
1098
0
    pdf_dict_put_int(ctx, shade_dict, PDF_NAME(ShadingType), shading_type);
1099
0
    if (dst_cs == fz_device_gray(ctx))
1100
0
      pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceGray));
1101
0
    else if (dst_cs == fz_device_cmyk(ctx))
1102
0
      pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceCMYK));
1103
0
    else
1104
0
      pdf_dict_put(ctx, shade_dict, PDF_NAME(ColorSpace), PDF_NAME(DeviceRGB));
1105
0
    pdf_dict_put(ctx, shade_dict, PDF_NAME(Function), func_ref);
1106
0
    {
1107
0
      pdf_obj *coords = pdf_dict_put_array(ctx, shade_dict, PDF_NAME(Coords), (shading_type == 2) ? 4 : 6);
1108
0
      pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][0]);
1109
0
      pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][1]);
1110
0
      if (shading_type == 3)
1111
0
        pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[0][2]);
1112
0
      pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][0]);
1113
0
      pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][1]);
1114
0
      if (shading_type == 3)
1115
0
        pdf_array_push_real(ctx, coords, shade->u.l_or_r.coords[1][2]);
1116
0
    }
1117
0
    {
1118
0
      pdf_obj *extend = pdf_dict_put_array(ctx, shade_dict, PDF_NAME(Extend), 2);
1119
0
      pdf_array_push_bool(ctx, extend, shade->u.l_or_r.extend[0]);
1120
0
      pdf_array_push_bool(ctx, extend, shade->u.l_or_r.extend[1]);
1121
0
    }
1122
1123
0
    shade_ref = pdf_add_object(ctx, doc, shade_dict);
1124
1125
0
    fz_snprintf(name_buf, sizeof(name_buf), "Sh%d", pdev->num_shadings++);
1126
0
    shadings = pdf_dict_get(ctx, pdev->resources, PDF_NAME(Shading));
1127
0
    if (!pdf_is_dict(ctx, shadings))
1128
0
    {
1129
0
      shadings = pdf_new_dict(ctx, doc, 4);
1130
0
      pdf_dict_put_drop(ctx, pdev->resources, PDF_NAME(Shading), shadings);
1131
0
      shadings = pdf_dict_get(ctx, pdev->resources, PDF_NAME(Shading));
1132
0
    }
1133
0
    pdf_dict_puts(ctx, shadings, name_buf, shade_ref);
1134
1135
0
    fz_append_printf(ctx, gs->buf, "/%s sh\n", name_buf);
1136
0
  }
1137
0
  fz_always(ctx)
1138
0
  {
1139
0
    fz_drop_buffer(ctx, sample_buf);
1140
0
    pdf_drop_obj(ctx, func_dict);
1141
0
    pdf_drop_obj(ctx, shade_dict);
1142
0
    pdf_drop_obj(ctx, func_ref);
1143
0
    pdf_drop_obj(ctx, shade_ref);
1144
0
  }
1145
0
  fz_catch(ctx)
1146
0
  {
1147
0
    fz_rethrow(ctx);
1148
0
  }
1149
0
}
1150
1151
static void
1152
pdf_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
1153
    fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
1154
0
{
1155
0
  pdf_device *pdev = (pdf_device*)dev;
1156
0
  pdf_obj *im_res = NULL;
1157
0
  gstate *gs = CURRENT_GSTATE(pdev);
1158
1159
0
  pdf_dev_end_text(ctx, pdev);
1160
0
  im_res = pdf_add_image(ctx, pdev->doc, image);
1161
0
  if (im_res == NULL)
1162
0
  {
1163
0
    fz_warn(ctx, "pdf_add_image: problem adding image resource");
1164
0
    return;
1165
0
  }
1166
1167
0
  fz_try(ctx)
1168
0
  {
1169
0
    fz_append_string(ctx, gs->buf, "q\n");
1170
0
    pdf_dev_alpha(ctx, pdev, alpha, 0);
1171
0
    pdf_dev_color(ctx, pdev, colorspace, color, 0, color_params);
1172
1173
    /* PDF images are upside down, so fiddle the ctm */
1174
0
    ctm = fz_pre_scale(ctm, 1, -1);
1175
0
    ctm = fz_pre_translate(ctm, 0, -1);
1176
0
    pdf_dev_ctm(ctx, pdev, ctm);
1177
0
    fz_append_printf(ctx, gs->buf, "/Img%d Do Q\n", pdf_to_num(ctx, im_res));
1178
1179
    /* Possibly add to page resources */
1180
0
    pdf_dev_add_image_res(ctx, dev, im_res);
1181
0
  }
1182
0
  fz_always(ctx)
1183
0
    pdf_drop_obj(ctx, im_res);
1184
0
  fz_catch(ctx)
1185
0
    fz_rethrow(ctx);
1186
0
}
1187
1188
static void
1189
pdf_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor)
1190
0
{
1191
0
  pdf_device *pdev = (pdf_device*)dev;
1192
1193
0
  fz_warn(ctx, "the pdf device does not support image masks; output may be incomplete");
1194
1195
  /* FIXME */
1196
0
  pdf_dev_end_text(ctx, pdev);
1197
0
  pdf_dev_push(ctx, pdev);
1198
0
}
1199
1200
static void
1201
pdf_dev_pop_clip(fz_context *ctx, fz_device *dev)
1202
0
{
1203
0
  pdf_device *pdev = (pdf_device*)dev;
1204
1205
  /* FIXME */
1206
0
  pdf_dev_end_text(ctx, pdev);
1207
0
  pdf_dev_pop(ctx, pdev);
1208
0
}
1209
1210
static void
1211
pdf_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)
1212
0
{
1213
0
  pdf_device *pdev = (pdf_device*)dev;
1214
0
  gstate *gs;
1215
0
  pdf_obj *smask = NULL;
1216
0
  char egsname[32];
1217
0
  pdf_obj *egs = NULL;
1218
0
  pdf_obj *egss;
1219
0
  pdf_obj *form_ref;
1220
0
  pdf_obj *color_obj = NULL;
1221
0
  int i, n;
1222
1223
0
  fz_var(smask);
1224
0
  fz_var(egs);
1225
0
  fz_var(color_obj);
1226
1227
0
  pdf_dev_end_text(ctx, pdev);
1228
1229
0
  pdf_dev_ctm(ctx, pdev, fz_identity);
1230
1231
  /* Make a new form to contain the contents of the softmask */
1232
0
  pdf_dev_new_form(ctx, &form_ref, pdev, bbox, 0, 0, 1, colorspace);
1233
1234
0
  fz_try(ctx)
1235
0
  {
1236
0
    fz_snprintf(egsname, sizeof(egsname), "SM%d", pdev->num_smasks++);
1237
0
    egss = pdf_dict_get(ctx, pdev->resources, PDF_NAME(ExtGState));
1238
0
    if (!egss)
1239
0
      egss = pdf_dict_put_dict(ctx, pdev->resources, PDF_NAME(ExtGState), 10);
1240
0
    egs = pdf_dict_puts_dict(ctx, egss, egsname, 1);
1241
1242
0
    pdf_dict_put(ctx, egs, PDF_NAME(Type), PDF_NAME(ExtGState));
1243
0
    smask = pdf_dict_put_dict(ctx, egs, PDF_NAME(SMask), 4);
1244
1245
0
    pdf_dict_put(ctx, smask, PDF_NAME(Type), PDF_NAME(Mask));
1246
0
    pdf_dict_put(ctx, smask, PDF_NAME(S), (luminosity ? PDF_NAME(Luminosity) : PDF_NAME(Alpha)));
1247
0
    pdf_dict_put(ctx, smask, PDF_NAME(G), form_ref);
1248
1249
0
    n = fz_colorspace_n(ctx, colorspace);
1250
0
    color_obj = pdf_dict_put_array(ctx, smask, PDF_NAME(BC), n);
1251
0
    for (i = 0; i < n; i++)
1252
0
      pdf_array_push_real(ctx, color_obj, color[i]);
1253
1254
0
    gs = CURRENT_GSTATE(pdev);
1255
0
    fz_append_printf(ctx, gs->buf, "/SM%d gs\n", pdev->num_smasks-1);
1256
0
  }
1257
0
  fz_catch(ctx)
1258
0
  {
1259
0
    pdf_drop_obj(ctx, form_ref);
1260
0
    fz_rethrow(ctx);
1261
0
  }
1262
1263
  /* Now, everything we get until the end_mask needs to go into a
1264
   * new buffer, which will be the stream contents for the form. */
1265
0
  pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
1266
0
}
1267
1268
static void
1269
pdf_dev_end_mask(fz_context *ctx, fz_device *dev, fz_function *tr)
1270
0
{
1271
0
  pdf_device *pdev = (pdf_device*)dev;
1272
0
  pdf_document *doc = pdev->doc;
1273
0
  gstate *gs = CURRENT_GSTATE(pdev);
1274
0
  pdf_obj *form_ref = (pdf_obj *)gs->on_pop_arg;
1275
1276
0
  if (tr)
1277
0
    fz_warn(ctx, "Ignoring Transfer function");
1278
1279
  /* Here we do part of the pop, but not all of it. */
1280
0
  pdf_dev_end_text(ctx, pdev);
1281
0
  fz_append_string(ctx, gs->buf, "Q\n");
1282
0
  pdf_update_stream(ctx, doc, form_ref, gs->buf, 0);
1283
0
  fz_drop_buffer(ctx, gs->buf);
1284
0
  gs->buf = fz_keep_buffer(ctx, gs[-1].buf);
1285
0
  gs->on_pop_arg = NULL;
1286
0
  pdf_drop_obj(ctx, form_ref);
1287
0
  fz_append_string(ctx, gs->buf, "q\n");
1288
0
}
1289
1290
static void
1291
pdf_dev_begin_group(fz_context *ctx, fz_device *dev, fz_rect bbox, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
1292
0
{
1293
0
  pdf_device *pdev = (pdf_device*)dev;
1294
0
  int num;
1295
0
  pdf_obj *form_ref;
1296
0
  gstate *gs;
1297
1298
0
  pdf_dev_end_text(ctx, pdev);
1299
1300
0
  pdf_dev_ctm(ctx, pdev, fz_identity);
1301
1302
0
  num = pdf_dev_new_form(ctx, &form_ref, pdev, bbox, isolated, knockout, alpha, cs);
1303
1304
  /* Do we have an appropriate blending extgstate already? */
1305
0
  {
1306
0
    char text[32];
1307
0
    pdf_obj *obj;
1308
0
    pdf_obj *egs = pdf_dict_get(ctx, pdev->resources, PDF_NAME(ExtGState));
1309
0
    if (egs == NULL)
1310
0
      egs = pdf_dict_put_dict(ctx, pdev->resources, PDF_NAME(ExtGState), 4);
1311
0
    fz_snprintf(text, sizeof(text), "BlendMode%d", blendmode);
1312
0
    obj = pdf_dict_gets(ctx, egs, text);
1313
0
    if (obj == NULL)
1314
0
    {
1315
      /* No, better make one */
1316
0
      obj = pdf_dict_puts_dict(ctx, egs, text, 2);
1317
0
      pdf_dict_put(ctx, obj, PDF_NAME(Type), PDF_NAME(ExtGState));
1318
0
      pdf_dict_put_name(ctx, obj, PDF_NAME(BM), fz_blendmode_name(blendmode));
1319
0
    }
1320
0
  }
1321
1322
  /* Add the call to this group */
1323
0
  gs = CURRENT_GSTATE(pdev);
1324
0
  fz_append_printf(ctx, gs->buf, "/BlendMode%d gs /Fm%d Do\n", blendmode, num);
1325
1326
  /* Now, everything we get until the end of group needs to go into a
1327
   * new buffer, which will be the stream contents for the form. */
1328
0
  pdf_dev_push_new_buf(ctx, pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
1329
0
}
1330
1331
static void
1332
pdf_dev_end_group(fz_context *ctx, fz_device *dev)
1333
0
{
1334
0
  pdf_device *pdev = (pdf_device*)dev;
1335
0
  pdf_document *doc = pdev->doc;
1336
0
  gstate *gs = CURRENT_GSTATE(pdev);
1337
0
  fz_buffer *buf = fz_keep_buffer(ctx, gs->buf);
1338
0
  pdf_obj *form_ref;
1339
1340
0
  pdf_dev_end_text(ctx, pdev);
1341
0
  form_ref = (pdf_obj *)pdf_dev_pop(ctx, pdev);
1342
0
  pdf_update_stream(ctx, doc, form_ref, buf, 0);
1343
0
  fz_drop_buffer(ctx, buf);
1344
0
  pdf_drop_obj(ctx, form_ref);
1345
0
}
1346
1347
static int
1348
pdf_dev_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id, int doc_id)
1349
0
{
1350
0
  pdf_device *pdev = (pdf_device*)dev;
1351
1352
0
  fz_warn(ctx, "tiled patterns are not supported in the pdf-write device");
1353
1354
  /* FIXME */
1355
0
  pdf_dev_end_text(ctx, pdev);
1356
0
  return 0;
1357
0
}
1358
1359
static void
1360
pdf_dev_end_tile(fz_context *ctx, fz_device *dev)
1361
0
{
1362
0
  pdf_device *pdev = (pdf_device*)dev;
1363
1364
  /* FIXME */
1365
0
  pdf_dev_end_text(ctx, pdev);
1366
0
}
1367
1368
static void
1369
pdf_dev_close_device(fz_context *ctx, fz_device *dev)
1370
0
{
1371
0
  pdf_device *pdev = (pdf_device*)dev;
1372
0
  pdf_dev_end_text(ctx, pdev);
1373
0
}
1374
1375
static void
1376
pdf_dev_drop_device(fz_context *ctx, fz_device *dev)
1377
0
{
1378
0
  pdf_device *pdev = (pdf_device*)dev;
1379
0
  int i;
1380
1381
0
  for (i = pdev->num_gstates-1; i >= 0; i--)
1382
0
  {
1383
0
    fz_drop_buffer(ctx, pdev->gstates[i].buf);
1384
0
    fz_drop_stroke_state(ctx, pdev->gstates[i].stroke_state);
1385
0
  }
1386
1387
0
  for (i = pdev->num_cid_fonts-1; i >= 0; i--)
1388
0
    fz_drop_font(ctx, pdev->cid_fonts[i]);
1389
1390
0
  for (i = pdev->num_groups - 1; i >= 0; i--)
1391
0
  {
1392
0
    pdf_drop_obj(ctx, pdev->groups[i].ref);
1393
0
    fz_drop_colorspace(ctx, pdev->groups[i].colorspace);
1394
0
  }
1395
1396
0
  pdf_drop_obj(ctx, pdev->resources);
1397
0
  fz_free(ctx, pdev->cid_fonts);
1398
0
  fz_free(ctx, pdev->cid_fonts_enc);
1399
0
  fz_free(ctx, pdev->image_indices);
1400
0
  fz_free(ctx, pdev->groups);
1401
0
  fz_free(ctx, pdev->alphas);
1402
0
  fz_free(ctx, pdev->gstates);
1403
0
  pdf_drop_document(ctx, pdev->doc);
1404
0
}
1405
1406
fz_device *pdf_new_pdf_device(fz_context *ctx, pdf_document *doc, fz_matrix topctm, pdf_obj *resources, fz_buffer *buf)
1407
0
{
1408
0
  pdf_device *dev = fz_new_derived_device(ctx, pdf_device);
1409
1410
0
  dev->super.hints = FZ_NO_TILING;
1411
1412
0
  dev->super.close_device = pdf_dev_close_device;
1413
0
  dev->super.drop_device = pdf_dev_drop_device;
1414
1415
0
  dev->super.fill_path = pdf_dev_fill_path;
1416
0
  dev->super.stroke_path = pdf_dev_stroke_path;
1417
0
  dev->super.clip_path = pdf_dev_clip_path;
1418
0
  dev->super.clip_stroke_path = pdf_dev_clip_stroke_path;
1419
1420
0
  dev->super.fill_text = pdf_dev_fill_text;
1421
0
  dev->super.stroke_text = pdf_dev_stroke_text;
1422
0
  dev->super.clip_text = pdf_dev_clip_text;
1423
0
  dev->super.clip_stroke_text = pdf_dev_clip_stroke_text;
1424
0
  dev->super.ignore_text = pdf_dev_ignore_text;
1425
1426
0
  dev->super.fill_shade = pdf_dev_fill_shade;
1427
0
  dev->super.fill_image = pdf_dev_fill_image;
1428
0
  dev->super.fill_image_mask = pdf_dev_fill_image_mask;
1429
0
  dev->super.clip_image_mask = pdf_dev_clip_image_mask;
1430
1431
0
  dev->super.pop_clip = pdf_dev_pop_clip;
1432
1433
0
  dev->super.begin_mask = pdf_dev_begin_mask;
1434
0
  dev->super.end_mask = pdf_dev_end_mask;
1435
0
  dev->super.begin_group = pdf_dev_begin_group;
1436
0
  dev->super.end_group = pdf_dev_end_group;
1437
1438
0
  dev->super.begin_tile = pdf_dev_begin_tile;
1439
0
  dev->super.end_tile = pdf_dev_end_tile;
1440
1441
0
  fz_var(buf);
1442
1443
0
  fz_try(ctx)
1444
0
  {
1445
0
    dev->doc = pdf_keep_document(ctx, doc);
1446
0
    dev->resources = pdf_keep_obj(ctx, resources);
1447
0
    dev->gstates = fz_malloc_struct(ctx, gstate);
1448
0
    if (buf)
1449
0
      dev->gstates[0].buf = fz_keep_buffer(ctx, buf);
1450
0
    else
1451
0
      dev->gstates[0].buf = fz_new_buffer(ctx, 256);
1452
0
    dev->gstates[0].ctm = fz_identity; // XXX
1453
0
    dev->gstates[0].colorspace[0] = fz_device_gray(ctx);
1454
0
    dev->gstates[0].colorspace[1] = fz_device_gray(ctx);
1455
0
    dev->gstates[0].color[0][0] = 0;
1456
0
    dev->gstates[0].color[1][0] = 0;
1457
0
    dev->gstates[0].alpha[0] = 1.0f;
1458
0
    dev->gstates[0].alpha[1] = 1.0f;
1459
0
    dev->gstates[0].font = -1;
1460
0
    dev->num_gstates = 1;
1461
0
    dev->max_gstates = 1;
1462
0
    dev->num_shadings = 0;
1463
1464
0
    if (!fz_is_identity(topctm))
1465
0
      fz_append_printf(ctx, dev->gstates[0].buf, "%M cm\n", &topctm);
1466
0
  }
1467
0
  fz_catch(ctx)
1468
0
  {
1469
0
    fz_drop_device(ctx, &dev->super);
1470
0
    fz_rethrow(ctx);
1471
0
  }
1472
1473
0
  return (fz_device*)dev;
1474
0
}
1475
1476
fz_device *pdf_page_write(fz_context *ctx, pdf_document *doc, fz_rect mediabox, pdf_obj **presources, fz_buffer **pcontents)
1477
0
{
1478
0
  fz_matrix pagectm = { 1, 0, 0, -1, -mediabox.x0, mediabox.y1 };
1479
0
  if (!*presources)
1480
0
    *presources = pdf_new_dict(ctx, doc, 0);
1481
0
  if (!*pcontents)
1482
0
    *pcontents = fz_new_buffer(ctx, 0);
1483
0
  return pdf_new_pdf_device(ctx, doc, pagectm, *presources, *pcontents);
1484
0
}