Coverage Report

Created: 2025-01-11 06:55

/src/mupdf/source/fitz/output-pclm.c
Line
Count
Source (jump to first uncovered line)
1
// Copyright (C) 2004-2021 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
25
#include <string.h>
26
#include <limits.h>
27
28
const char *fz_pclm_write_options_usage =
29
  "PCLm output options:\n"
30
  "\tcompression=none: No compression (default)\n"
31
  "\tcompression=flate: Flate compression\n"
32
  "\tstrip-height=N: Strip height (default 16)\n"
33
  "\n";
34
35
fz_pclm_options *
36
fz_parse_pclm_options(fz_context *ctx, fz_pclm_options *opts, const char *args)
37
0
{
38
0
  const char *val;
39
40
0
  memset(opts, 0, sizeof *opts);
41
42
0
  if (fz_has_option(ctx, args, "compression", &val))
43
0
  {
44
0
    if (fz_option_eq(val, "none"))
45
0
      opts->compress = 0;
46
0
    else if (fz_option_eq(val, "flate"))
47
0
      opts->compress = 1;
48
0
    else
49
0
      fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unsupported PCLm compression %s (none, or flate only)", val);
50
0
  }
51
0
  if (fz_has_option(ctx, args, "strip-height", &val))
52
0
  {
53
0
    int i = fz_atoi(val);
54
0
    if (i <= 0)
55
0
      fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unsupported PCLm strip height %d (suggest 16)", i);
56
0
    opts->strip_height = i;
57
0
  }
58
59
0
  return opts;
60
0
}
61
62
void
63
fz_write_pixmap_as_pclm(fz_context *ctx, fz_output *out, const fz_pixmap *pixmap, const fz_pclm_options *pclm)
64
0
{
65
0
  fz_band_writer *writer;
66
67
0
  if (!pixmap || !out)
68
0
    return;
69
70
0
  writer = fz_new_pclm_band_writer(ctx, out, pclm);
71
0
  fz_try(ctx)
72
0
  {
73
0
    fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, pixmap->xres, pixmap->yres, 0, pixmap->colorspace, pixmap->seps);
74
0
    fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples);
75
0
    fz_close_band_writer(ctx, writer);
76
0
  }
77
0
  fz_always(ctx)
78
0
    fz_drop_band_writer(ctx, writer);
79
0
  fz_catch(ctx)
80
0
    fz_rethrow(ctx);
81
0
}
82
83
typedef struct pclm_band_writer_s
84
{
85
  fz_band_writer super;
86
  fz_pclm_options options;
87
88
  int obj_num;
89
  int xref_max;
90
  int64_t *xref;
91
  int pages;
92
  int page_max;
93
  int *page_obj;
94
  unsigned char *stripbuf;
95
  unsigned char *compbuf;
96
  size_t complen;
97
} pclm_band_writer;
98
99
static int
100
new_obj(fz_context *ctx, pclm_band_writer *writer)
101
0
{
102
0
  int64_t pos = fz_tell_output(ctx, writer->super.out);
103
104
0
  if (writer->obj_num >= writer->xref_max)
105
0
  {
106
0
    int new_max = writer->xref_max * 2;
107
0
    if (new_max < writer->obj_num + 8)
108
0
      new_max = writer->obj_num + 8;
109
0
    writer->xref = fz_realloc_array(ctx, writer->xref, new_max, int64_t);
110
0
    writer->xref_max = new_max;
111
0
  }
112
113
0
  writer->xref[writer->obj_num] = pos;
114
115
0
  return writer->obj_num++;
116
0
}
117
118
static void
119
pclm_write_header(fz_context *ctx, fz_band_writer *writer_, fz_colorspace *cs)
120
0
{
121
0
  pclm_band_writer *writer = (pclm_band_writer *)writer_;
122
0
  fz_output *out = writer->super.out;
123
0
  int w = writer->super.w;
124
0
  int h = writer->super.h;
125
0
  int n = writer->super.n;
126
0
  int s = writer->super.s;
127
0
  int a = writer->super.alpha;
128
0
  int xres = writer->super.xres;
129
0
  int yres = writer->super.yres;
130
0
  int sh = writer->options.strip_height;
131
0
  int strips = (h + sh-1)/sh;
132
0
  int i;
133
0
  size_t len;
134
0
  unsigned char *data;
135
0
  fz_buffer *buf = NULL;
136
137
0
  if (a != 0)
138
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "PCLm cannot write alpha channel");
139
0
  if (s != 0)
140
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "PCLm cannot write spot colors");
141
0
  if (n != 3 && n != 1)
142
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "PCLm expected to be Grayscale or RGB");
143
144
0
  fz_free(ctx, writer->stripbuf);
145
0
  writer->stripbuf = NULL;
146
0
  fz_free(ctx, writer->compbuf);
147
0
  writer->compbuf = NULL;
148
0
  writer->stripbuf = Memento_label(fz_malloc(ctx, (size_t)w * sh * n), "pclm_stripbuf");
149
0
  writer->complen = fz_deflate_bound(ctx, (size_t)w * sh * n);
150
0
  writer->compbuf = Memento_label(fz_malloc(ctx, writer->complen), "pclm_compbuf");
151
152
  /* Send the file header on the first page */
153
0
  if (writer->pages == 0)
154
0
    fz_write_string(ctx, out, "%PDF-1.4\n%PCLm-1.0\n");
155
156
0
  if (writer->page_max <= writer->pages)
157
0
  {
158
0
    int new_max = writer->page_max * 2;
159
0
    if (new_max == 0)
160
0
      new_max = writer->pages + 8;
161
0
    writer->page_obj = fz_realloc_array(ctx, writer->page_obj, new_max, int);
162
0
    writer->page_max = new_max;
163
0
  }
164
0
  writer->page_obj[writer->pages] = writer->obj_num;
165
0
  writer->pages++;
166
167
  /* Send the Page Object */
168
0
  fz_write_printf(ctx, out, "%d 0 obj\n<<\n/Type /Page\n/Parent 2 0 R\n/Resources <<\n/XObject <<\n", new_obj(ctx, writer));
169
0
  for (i = 0; i < strips; i++)
170
0
    fz_write_printf(ctx, out, "/Image%d %d 0 R\n", i, writer->obj_num + 1 + i);
171
0
  fz_write_printf(ctx, out, ">>\n>>\n/MediaBox[ 0 0 %g %g ]\n/Contents [ %d 0 R ]\n>>\nendobj\n",
172
0
    w * 72.0f / xres, h * 72.0f / yres, writer->obj_num);
173
174
  /* And the Page contents */
175
  /* We need the length to this, so write to a buffer first */
176
0
  fz_var(buf);
177
0
  fz_try(ctx)
178
0
  {
179
0
    buf = fz_new_buffer(ctx, 0);
180
0
    fz_append_printf(ctx, buf, "%g 0 0 %g 0 0 cm\n", 72.0f/xres, 72.0f/yres);
181
0
    for (i = 0; i < strips; i++)
182
0
    {
183
0
      int at = h - (i+1)*sh;
184
0
      int this_sh = sh;
185
0
      if (at < 0)
186
0
      {
187
0
        this_sh += at;
188
0
        at = 0;
189
0
      }
190
0
      fz_append_printf(ctx, buf, "/P <</MCID 0>> BDC q\n%d 0 0 %d 0 %d cm\n/Image%d Do Q\n",
191
0
        w, this_sh, at, i);
192
0
    }
193
0
    len = fz_buffer_storage(ctx, buf, &data);
194
0
    fz_write_printf(ctx, out, "%d 0 obj\n<<\n/Length %zd\n>>\nstream\n", new_obj(ctx, writer), len);
195
0
    fz_write_data(ctx, out, data, len);
196
0
    fz_drop_buffer(ctx, buf);
197
0
    buf = NULL;
198
0
    fz_write_string(ctx, out, "\nendstream\nendobj\n");
199
0
  }
200
0
  fz_catch(ctx)
201
0
  {
202
0
    fz_drop_buffer(ctx, buf);
203
0
    fz_rethrow(ctx);
204
0
  }
205
0
}
206
207
static void
208
flush_strip(fz_context *ctx, pclm_band_writer *writer, int fill)
209
0
{
210
0
  unsigned char *data = writer->stripbuf;
211
0
  fz_output *out = writer->super.out;
212
0
  int w = writer->super.w;
213
0
  int n = writer->super.n;
214
0
  size_t len = (size_t)w*n*fill;
215
216
  /* Buffer is full, compress it and write it. */
217
0
  if (writer->options.compress)
218
0
  {
219
0
    size_t destLen = writer->complen;
220
0
    fz_deflate(ctx, writer->compbuf, &destLen, data, len, FZ_DEFLATE_DEFAULT);
221
0
    len = destLen;
222
0
    data = writer->compbuf;
223
0
  }
224
0
  fz_write_printf(ctx, out, "%d 0 obj\n<<\n/Width %d\n/ColorSpace /Device%s\n/Height %d\n%s/Subtype /Image\n",
225
0
    new_obj(ctx, writer), w, n == 1 ? "Gray" : "RGB", fill, writer->options.compress ? "/Filter /FlateDecode\n" : "");
226
0
  fz_write_printf(ctx, out, "/Length %zd\n/Type /XObject\n/BitsPerComponent 8\n>>\nstream\n", len);
227
0
  fz_write_data(ctx, out, data, len);
228
0
  fz_write_string(ctx, out, "\nendstream\nendobj\n");
229
0
}
230
231
static void
232
pclm_write_band(fz_context *ctx, fz_band_writer *writer_, int stride, int band_start, int band_height, const unsigned char *sp)
233
0
{
234
0
  pclm_band_writer *writer = (pclm_band_writer *)writer_;
235
0
  fz_output *out = writer->super.out;
236
0
  int w = writer->super.w;
237
0
  int h = writer->super.h;
238
0
  int n = writer->super.n;
239
0
  int sh = writer->options.strip_height;
240
0
  int line;
241
242
0
  if (!out)
243
0
    return;
244
245
0
  for (line = 0; line < band_height; line++)
246
0
  {
247
0
    int dstline = (band_start+line) % sh;
248
0
    memcpy(writer->stripbuf + (size_t)w*n*dstline,
249
0
         sp + (size_t)line * w * n,
250
0
         (size_t)w * n);
251
0
    if (dstline+1 == sh)
252
0
      flush_strip(ctx, writer, dstline+1);
253
0
  }
254
255
0
  if (band_start + band_height == h && h % sh != 0)
256
0
    flush_strip(ctx, writer, h % sh);
257
0
}
258
259
static void
260
pclm_write_trailer(fz_context *ctx, fz_band_writer *writer_)
261
0
{
262
0
}
263
264
static void
265
pclm_close_band_writer(fz_context *ctx, fz_band_writer *writer_)
266
0
{
267
0
  pclm_band_writer *writer = (pclm_band_writer *)writer_;
268
0
  fz_output *out = writer->super.out;
269
0
  int i;
270
271
  /* We actually do the trailer writing in the close */
272
0
  if (writer->xref_max > 2)
273
0
  {
274
0
    int64_t t_pos;
275
276
    /* Catalog */
277
0
    writer->xref[1] = fz_tell_output(ctx, out);
278
0
    fz_write_printf(ctx, out, "1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n");
279
280
    /* Page table */
281
0
    writer->xref[2] = fz_tell_output(ctx, out);
282
0
    fz_write_printf(ctx, out, "2 0 obj\n<<\n/Count %d\n/Kids [ ", writer->pages);
283
284
0
    for (i = 0; i < writer->pages; i++)
285
0
      fz_write_printf(ctx, out, "%d 0 R ", writer->page_obj[i]);
286
0
    fz_write_string(ctx, out, "]\n/Type /Pages\n>>\nendobj\n");
287
288
    /* Xref */
289
0
    t_pos = fz_tell_output(ctx, out);
290
0
    fz_write_printf(ctx, out, "xref\n0 %d\n0000000000 65535 f \n", writer->obj_num);
291
0
    for (i = 1; i < writer->obj_num; i++)
292
0
      fz_write_printf(ctx, out, "%010zd 00000 n \n", writer->xref[i]);
293
0
    fz_write_printf(ctx, out, "trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\nstartxref\n%ld\n%%%%EOF\n", writer->obj_num, t_pos);
294
0
  }
295
0
}
296
297
static void
298
pclm_drop_band_writer(fz_context *ctx, fz_band_writer *writer_)
299
0
{
300
0
  pclm_band_writer *writer = (pclm_band_writer *)writer_;
301
0
  fz_free(ctx, writer->stripbuf);
302
0
  fz_free(ctx, writer->compbuf);
303
0
  fz_free(ctx, writer->page_obj);
304
0
  fz_free(ctx, writer->xref);
305
0
}
306
307
fz_band_writer *fz_new_pclm_band_writer(fz_context *ctx, fz_output *out, const fz_pclm_options *options)
308
0
{
309
0
  pclm_band_writer *writer = fz_new_band_writer(ctx, pclm_band_writer, out);
310
311
0
  writer->super.header = pclm_write_header;
312
0
  writer->super.band = pclm_write_band;
313
0
  writer->super.trailer = pclm_write_trailer;
314
0
  writer->super.close = pclm_close_band_writer;
315
0
  writer->super.drop = pclm_drop_band_writer;
316
317
0
  if (options)
318
0
    writer->options = *options;
319
0
  else
320
0
    memset(&writer->options, 0, sizeof(writer->options));
321
322
0
  if (writer->options.strip_height == 0)
323
0
    writer->options.strip_height = 16;
324
0
  writer->obj_num = 3; /* 1 reserved for catalog, 2 for pages tree. */
325
326
0
  return &writer->super;
327
0
}
328
329
void
330
fz_save_pixmap_as_pclm(fz_context *ctx, fz_pixmap *pixmap, char *filename, int append, const fz_pclm_options *pclm)
331
0
{
332
0
  fz_output *out = fz_new_output_with_path(ctx, filename, append);
333
0
  fz_try(ctx)
334
0
  {
335
0
    fz_write_pixmap_as_pclm(ctx, out, pixmap, pclm);
336
0
    fz_close_output(ctx, out);
337
0
  }
338
0
  fz_always(ctx)
339
0
    fz_drop_output(ctx, out);
340
0
  fz_catch(ctx)
341
0
    fz_rethrow(ctx);
342
0
}
343
344
/* High-level document writer interface */
345
346
typedef struct
347
{
348
  fz_document_writer super;
349
  fz_draw_options draw;
350
  fz_pclm_options pclm;
351
  fz_pixmap *pixmap;
352
  fz_band_writer *bander;
353
  fz_output *out;
354
  int pagenum;
355
} fz_pclm_writer;
356
357
static fz_device *
358
pclm_begin_page(fz_context *ctx, fz_document_writer *wri_, fz_rect mediabox)
359
0
{
360
0
  fz_pclm_writer *wri = (fz_pclm_writer*)wri_;
361
0
  return fz_new_draw_device_with_options(ctx, &wri->draw, mediabox, &wri->pixmap);
362
0
}
363
364
static void
365
pclm_end_page(fz_context *ctx, fz_document_writer *wri_, fz_device *dev)
366
0
{
367
0
  fz_pclm_writer *wri = (fz_pclm_writer*)wri_;
368
0
  fz_pixmap *pix = wri->pixmap;
369
370
0
  fz_try(ctx)
371
0
  {
372
0
    fz_close_device(ctx, dev);
373
0
    fz_write_header(ctx, wri->bander, pix->w, pix->h, pix->n, pix->alpha, pix->xres, pix->yres, wri->pagenum++, pix->colorspace, pix->seps);
374
0
    fz_write_band(ctx, wri->bander, pix->stride, pix->h, pix->samples);
375
0
  }
376
0
  fz_always(ctx)
377
0
  {
378
0
    fz_drop_device(ctx, dev);
379
0
    fz_drop_pixmap(ctx, pix);
380
0
    wri->pixmap = NULL;
381
0
  }
382
0
  fz_catch(ctx)
383
0
    fz_rethrow(ctx);
384
0
}
385
386
static void
387
pclm_close_writer(fz_context *ctx, fz_document_writer *wri_)
388
0
{
389
0
  fz_pclm_writer *wri = (fz_pclm_writer*)wri_;
390
391
0
  fz_close_band_writer(ctx, wri->bander);
392
0
  fz_close_output(ctx, wri->out);
393
0
}
394
395
static void
396
pclm_drop_writer(fz_context *ctx, fz_document_writer *wri_)
397
0
{
398
0
  fz_pclm_writer *wri = (fz_pclm_writer*)wri_;
399
400
0
  fz_drop_pixmap(ctx, wri->pixmap);
401
0
  fz_drop_output(ctx, wri->out);
402
0
  fz_drop_band_writer(ctx, wri->bander);
403
0
}
404
405
fz_document_writer *
406
fz_new_pclm_writer_with_output(fz_context *ctx, fz_output *out, const char *options)
407
0
{
408
0
  fz_pclm_writer *wri = NULL;
409
410
0
  fz_var(wri);
411
412
0
  fz_try(ctx)
413
0
  {
414
0
    wri = fz_new_derived_document_writer(ctx, fz_pclm_writer, pclm_begin_page, pclm_end_page, pclm_close_writer, pclm_drop_writer);
415
0
    fz_parse_draw_options(ctx, &wri->draw, options);
416
0
    fz_parse_pclm_options(ctx, &wri->pclm, options);
417
0
    wri->out = out;
418
0
    wri->bander = fz_new_pclm_band_writer(ctx, wri->out, &wri->pclm);
419
0
  }
420
0
  fz_catch(ctx)
421
0
  {
422
0
    fz_drop_output(ctx, out);
423
0
    fz_free(ctx, wri);
424
0
    fz_rethrow(ctx);
425
0
  }
426
427
0
  return (fz_document_writer*)wri;
428
0
}
429
430
fz_document_writer *
431
fz_new_pclm_writer(fz_context *ctx, const char *path, const char *options)
432
0
{
433
0
  fz_output *out = fz_new_output_with_path(ctx, path ? path : "out.pclm", 0);
434
0
  return fz_new_pclm_writer_with_output(ctx, out, options);
435
0
}