1/***********************************************************************
2 *                                                                     *
3 * $Id: hpgsdevices.c 373 2007-01-24 13:43:04Z softadm $
4 *                                                                     *
5 * hpgs - HPGl Script, a hpgl/2 interpreter, which uses a Postscript   *
6 *        API for rendering a scene and thus renders to a variety of   *
7 *        devices and fileformats.                                     *
8 *                                                                     *
9 * (C) 2004-2006 ev-i Informationstechnologie GmbH  http://www.ev-i.at *
10 *                                                                     *
11 * Author: Wolfgang Glas                                               *
12 *                                                                     *
13 *  hpgs is free software; you can redistribute it and/or              *
14 * modify it under the terms of the GNU Lesser General Public          *
15 * License as published by the Free Software Foundation; either        *
16 * version 2.1 of the License, or (at your option) any later version.  *
17 *                                                                     *
18 * hpgs is distributed in the hope that it will be useful,             *
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of      *
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   *
21 * Lesser General Public License for more details.                     *
22 *                                                                     *
23 * You should have received a copy of the GNU Lesser General Public    *
24 * License along with this library; if not, write to the               *
25 * Free Software  Foundation, Inc., 59 Temple Place, Suite 330,        *
26 * Boston, MA  02111-1307  USA                                         *
27 *                                                                     *
28 ***********************************************************************
29 *                                                                     *
30 * The implementations of the simple plotsize and eps devices.         *
31 *                                                                     *
32 ***********************************************************************/
33
34#include <hpgsdevices.h>
35#include <math.h>
36#include <string.h>
37#include <errno.h>
38#ifdef WIN32
39#include<windows.h>
40#else
41#include <dlfcn.h>
42#endif
43#if defined ( __MINGW32__ ) || defined ( _MSC_VER )
44#include<malloc.h>
45#include<io.h>
46#else
47#include<alloca.h>
48#include<unistd.h>
49#endif
50
51//#define HPGS_EPS_DEBUG_ROP3
52
53/*! \defgroup device Basic vector graphics devices.
54
55   This group contains the definitions for the abstract vector graphics device
56   \c hpgs_device as well as the implementations of the two very basic
57   vector devices \c hpgs_plotsize_device ans \c hpgs_eps_device.
58 */
59
60/*!
61   Returns the name of the device for use with RTTI, runtime type information.
62 */
63const char *hpgs_device_rtti(hpgs_device *_this)
64{
65  return _this->vtable->rtti;
66}
67
68/*! Sets the raster operation for the given device.
69    Raster operations and source/pattern transparency are described in
70
71    PCL 5 Comparison Guide, Edition 2, 6/2003, Hewlett Packard
72    (May be downloaded as bpl13206.pdf from http://www.hp.com)
73
74    The function returns -1, if an invalid raster operation is specified.
75    If the device is not capable of raster operations, the function succeeds
76    anyways.
77*/
78int hpgs_setrop3 (hpgs_device *_this, int rop,
79                  hpgs_bool src_transparency, hpgs_bool pattern_transparency)
80{
81  if (!_this->vtable->setrop3) return 0;
82
83  return _this->vtable->setrop3(_this,rop,
84                                src_transparency,pattern_transparency);
85}
86
87/*! Sets the patter ncolor applied using the raster operation specified
88    in \c hpgs_setrop3.
89*/
90int hpgs_setpatcol (hpgs_device *_this, const hpgs_color *rgb)
91{
92  if (!_this->vtable->setpatcol) return 0;
93
94  return _this->vtable->setpatcol(_this,rgb);
95}
96
97/*! Draw an image to the device.
98    The arguments \c ll, \c lr and \c ur are the
99    lower left, lower right and upper right corner of the drawn image
100    in world coordinates.
101
102    The function returns 0 on success.
103    -1 is returned upon failure.
104*/
105int hpgs_drawimage(hpgs_device *_this, const hpgs_image *img,
106                   const hpgs_point *ll, const hpgs_point *lr,
107                   const hpgs_point *ur)
108{
109  if (!_this->vtable->drawimage) return 0;
110
111  return _this->vtable->drawimage(_this,img,ll,lr,ur);
112}
113
114/*! Report a HPGL PS command to the device. If the function returns 2,
115    the interpretation of the file is interrupted immediately without error.
116
117    For devices with the \c HPGS_DEVICE_CAP_MULTISIZE capability, this function
118    may be called immediately after a showpage command.
119*/
120int hpgs_setplotsize (hpgs_device *_this, const hpgs_bbox *bb)
121{
122  if (!_this->vtable->setplotsize)
123    return 0;
124
125  return _this->vtable->setplotsize(_this,bb);
126}
127
128/*! Gets the plotsize of the given page number \c i or the overall bounding box,
129    if \c i is zero.
130
131    The function returns 0 on success.
132
133    If the function returns 1, the overall bounding box is returned,
134    because the plotsize of page \c i is not known.
135
136    -1 is returned, if the funtion is unimplemented for the given device.
137*/
138int hpgs_getplotsize (hpgs_device *_this, int i, hpgs_bbox *bb)
139{
140  if (!_this->vtable->getplotsize)
141    return -1;
142
143  return _this->vtable->getplotsize(_this,i,bb);
144}
145
146/*! Finishes the output of a page to the device. If the function returns 2,
147    the interpretation of the file is interrupted immediately without error.
148    This is the case for device, which are not capable of displaying multiple
149    pages.
150
151    The integer argument is the number of the page begin finished.
152    This argument is intended as a hint for devices, which write a file
153    for each page.
154
155    If this argument is less than or equal to 0, this is the only page
156    written to the device. In this case, devices which write a file for
157    each page may omit a page counter from the filename of the written
158    file.
159*/
160int hpgs_showpage (hpgs_device *_this, int i)
161{
162  if (!_this->vtable->showpage)
163    return 0;
164
165  return _this->vtable->showpage(_this,i);
166}
167
168/*! Finishes the output of a document to the device.
169    Implementations of device should discard all output,
170    which has been undertaken since the past showpage call.
171*/
172int hpgs_device_finish (hpgs_device *_this)
173{
174  if (!_this->vtable->finish)
175    return 0;
176
177  return _this->vtable->finish(_this);
178}
179
180
181/*! Destroys the given device and frees all allocated resources by this device.
182*/
183void hpgs_device_destroy (hpgs_device *_this)
184{
185  _this->vtable->destroy(_this);
186  free(_this);
187}
188
189static int eps_expand_media_sizes(hpgs_eps_device *eps)
190{
191  int n = eps->media_sizes_alloc_size*2;
192  hpgs_ps_media_size *nnms = realloc(eps->media_sizes,sizeof(hpgs_bbox)*n);
193
194  if (!nnms)
195    return hpgs_set_error(hpgs_i18n("Out of memory expanding page size stack."));
196
197  eps->media_sizes=nnms;
198  eps->media_sizes_alloc_size=n;
199  return 0;
200}
201
202#define HPGS_MEDIA_SIZE_HASH(w,h) (size_t)((419430343*((size_t)(w)))^((size_t)(h)))
203
204static int eps_get_papersize (hpgs_eps_device *eps,
205                              char *name, size_t name_len,
206                              double w, double h)
207{
208  int paper_width = (int)ceil(w);
209  int paper_height = (int)ceil(h);
210  size_t hash = HPGS_MEDIA_SIZE_HASH(paper_width,paper_height);
211  int i0 = 0;
212  int i1 = eps->n_media_sizes;
213
214  // binary search for hash;
215  while (i1>i0)
216    {
217      int i = i0+(i1-i0)/2;
218
219      if (eps->media_sizes[i].hash < hash)
220        i0 = i+1;
221      else
222        i1 = i;
223    }
224
225  i1 = 0;
226
227  // search all media sizes with equal hash
228  while (i0 < eps->n_media_sizes &&
229         eps->media_sizes[i0].hash == hash)
230    {
231      if (eps->media_sizes[i0].width == paper_width &&
232          eps->media_sizes[i0].height == paper_height  )
233        { i1 = 1; break; }
234
235      ++i0;
236    }
237
238  if (i1)
239    {
240      ++eps->media_sizes[i0].usage;
241    }
242  else
243    {
244      // no page size found -> make a new one
245      if (eps->n_media_sizes>=eps->media_sizes_alloc_size &&
246          eps_expand_media_sizes(eps))
247        return -1;
248
249      if (i0 < eps->n_media_sizes)
250        memmove (eps->media_sizes+i0+1,eps->media_sizes+i0,
251                 sizeof(hpgs_ps_media_size) * (eps->n_media_sizes-i0));
252
253      eps->media_sizes[i0].width = paper_width;
254      eps->media_sizes[i0].height = paper_height;
255      eps->media_sizes[i0].name  = 0;
256      eps->media_sizes[i0].usage = 1;
257      eps->media_sizes[i0].hash  = hash;
258    }
259
260  // fill in media size.
261  if (eps->media_sizes[i0].name)
262    {
263      strcpy(name,eps->media_sizes[i0].name);
264    }
265  else
266    {
267#ifdef WIN32
268      _snprintf(name,name_len,"Custom%dx%d",
269                paper_width,paper_height);
270#else
271      snprintf(name,name_len,"Custom%dx%d",
272               paper_width,paper_height);
273#endif
274    }
275  return 0;
276}
277
278
279static int eps_startpage (hpgs_eps_device *eps)
280{
281  if (eps->n_pages < 0)
282    {
283      // EPS page file start
284      eps->out = hpgs_new_file_ostream (eps->filename);
285
286      if (!eps->out)
287        return hpgs_set_error(hpgs_i18n("Error opening file %s: %s"),
288                              eps->filename,strerror(errno));
289
290      if (hpgs_ostream_printf (eps->out,
291                               "%%!PS-Adobe-3.0 EPSF-3.0\n"
292                               "%%%%Creator: hpgs-%s\n"
293                               "%%%%Title: %s\n"
294                               "%%%%BoundingBox: %d %d %d %d\n"
295                               "%%%%HiResBoundingBox: %g %g %g %g\n"
296                               "/hpgsdict 20 dict def\n"
297                               "hpgsdict begin\n"
298                               "/B{bind def} bind def\n"
299                               "/r{setrgbcolor}B/w{setlinewidth}B/j{setlinejoin}B/J{setlinecap}B/M{setmiterlimit}B\n"
300                               "/d{setdash}B/n{newpath}B/h{closepath}B/m{moveto}B/l{lineto}B/c{curveto}B\n"
301                               "/s{stroke}B/f{fill}B/g{eofill}B/x{clip}B/y{eoclip}B/cs{gsave}B\n"
302                               "/cr{currentrgbcolor currentlinewidth currentlinecap currentlinejoin currentmiterlimit currentdash grestore setdash setmiterlimit setlinejoin setlinecap setlinewidth setrgbcolor}B\n"
303                               "end\n"
304                               "%%EndComments\n"
305                               "%%EndProlog\n"
306                               "hpgsdict begin\n"
307                               "gsave\n",
308                               HPGS_VERSION,
309                               eps->filename ? eps->filename : "(stdout)",
310                               (int)floor(eps->page_bb.llx),(int)floor(eps->page_bb.lly),
311                               (int)ceil(eps->page_bb.urx),(int)ceil(eps->page_bb.ury),
312                               eps->page_bb.llx,eps->page_bb.lly,
313                               eps->page_bb.urx,eps->page_bb.ury ) < 0)
314        {
315          hpgs_ostream_close(eps->out);
316          eps->out = 0;
317          return hpgs_set_error(hpgs_i18n("Error writing header of file %s: %s"),
318                                eps->filename?eps->filename:"(stdout)",
319                                strerror(errno));
320        }
321      eps->page_setup = HPGS_TRUE;
322    }
323  else
324    {
325      hpgs_bbox bb;
326      char paper_name[32];
327      double paper_width = eps->page_bb.urx;
328      double paper_height = eps->page_bb.ury;
329
330      hpgs_bool landscape = paper_width > paper_height;
331
332      if (eps_get_papersize(eps,paper_name,sizeof(paper_name),
333                            landscape ? paper_height : paper_width,
334                            landscape ? paper_width  : paper_height))
335        return -1;
336
337      if (landscape)
338        {
339          if (hpgs_ostream_printf (eps->out,
340                                   "%%%%Page: %d %d\n"
341                                   "%%%%PageMedia: %s\n"
342                                   "%%%%PageOrientation: Landscape\n"
343                                   "hpgsdict begin\n"
344                                   "gsave\n"
345                                   "%.8f %.8f translate\n"
346                                   "90 rotate\n",
347                                   eps->n_pages+1,eps->n_pages+1,paper_name,
348                                   paper_height,0.0 ) < 0)
349            return hpgs_set_error(hpgs_i18n("Error writing header of file %s: %s"),
350                                  eps->filename?eps->filename:"(stdout)",
351                                  strerror(errno));
352
353
354          // caclulate the bounding box on the page.
355          bb.llx = 0.0;
356          bb.lly = 0.0;
357          bb.urx = paper_height;
358          bb.ury = paper_width;
359        }
360      else
361        {
362          if (hpgs_ostream_printf (eps->out,
363                                   "%%%%Page: %d %d\n"
364                                   "%%%%PageMedia: %s\n"
365                                   "%%%%PageOrientation: Portrait\n"
366                                   "gsave\n",
367                                   eps->n_pages+1,eps->n_pages+1,paper_name ) < 0)
368            return hpgs_set_error(hpgs_i18n("Error writing header of file %s: %s"),
369                                  eps->filename?eps->filename:"(stdout)",
370                                  strerror(errno));
371
372          // calculate the bounding box on the page.
373          bb = eps->page_bb;
374        }
375
376      // expand document bounding box.
377      if (eps->n_pages)
378        hpgs_bbox_expand(&eps->doc_bb,&bb);
379      else
380        eps->doc_bb = bb;
381
382      ++eps->n_pages;
383      eps->page_setup = HPGS_TRUE;
384    }
385
386  return 0;
387}
388
389
390static  int eps_moveto (hpgs_device *_this, const hpgs_point *p)
391{
392  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
393
394  if (!eps->page_setup && eps_startpage(eps)) return -1;
395
396  if (hpgs_ostream_printf(eps->out,"%g %g m\n",p->x,p->y) < 0)
397    return hpgs_set_error("moveto: %s",strerror(errno));
398  return 0;
399}
400
401static  int eps_lineto (hpgs_device *_this, const hpgs_point *p)
402{
403  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
404
405  if (!eps->page_setup && eps_startpage(eps)) return -1;
406
407  if (hpgs_ostream_printf(eps->out,"%g %g l\n",p->x,p->y) < 0)
408    return hpgs_set_error("lineto: %s",strerror(errno));
409  return 0;
410}
411
412static  int eps_curveto (hpgs_device *_this, const hpgs_point *p1,
413			 const hpgs_point *p2, const hpgs_point *p3 )
414{
415  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
416
417  if (!eps->page_setup && eps_startpage(eps)) return -1;
418
419  if (hpgs_ostream_printf(eps->out,"%g %g %g %g %g %g c\n",
420		 p1->x,p1->y,p2->x,p2->y,p3->x,p3->y) < 0)
421    return hpgs_set_error("curveto: %s",strerror(errno));
422  return 0;
423}
424
425static  int eps_newpath     (hpgs_device *_this)
426{
427  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
428
429  if (!eps->page_setup && eps_startpage(eps)) return -1;
430
431  if (hpgs_ostream_printf(eps->out,"n\n") < 0)
432    return hpgs_set_error("newpath: %s",strerror(errno));
433  return 0;
434}
435
436static  int eps_closepath   (hpgs_device *_this)
437{
438  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
439
440  if (!eps->page_setup && eps_startpage(eps)) return -1;
441
442  if (hpgs_ostream_printf(eps->out,"h\n") < 0)
443    return hpgs_set_error("closepath: %s",strerror(errno));
444  return 0;
445}
446
447static  int eps_stroke      (hpgs_device *_this)
448{
449  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
450
451  if (!eps->page_setup && eps_startpage(eps)) return -1;
452
453  if (hpgs_ostream_printf(eps->out,"s\n") < 0)
454    return hpgs_set_error("stroke: %s",strerror(errno));
455  return 0;
456}
457
458static  int eps_fill        (hpgs_device *_this, hpgs_bool winding)
459{
460  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
461
462  if (!eps->page_setup && eps_startpage(eps)) return -1;
463
464  if (hpgs_ostream_printf(eps->out,winding ? "f\n" : "g\n") < 0)
465    return hpgs_set_error("fill: %s",strerror(errno));
466  return 0;
467}
468
469static  int eps_clip        (hpgs_device *_this, hpgs_bool winding)
470{
471  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
472
473  if (!eps->page_setup && eps_startpage(eps)) return -1;
474
475  if (hpgs_ostream_printf(eps->out,winding ? "x\n" : "y\n") < 0)
476    return hpgs_set_error("clip: %s",strerror(errno));
477  return 0;
478}
479
480static  int eps_clipsave       (hpgs_device *_this)
481{
482  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
483
484  if (!eps->page_setup && eps_startpage(eps)) return -1;
485
486  if (hpgs_ostream_printf(eps->out,"cs\n") < 0)
487    return hpgs_set_error("clipsave: %s",strerror(errno));
488  return 0;
489}
490
491static  int eps_cliprestore    (hpgs_device *_this)
492{
493  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
494
495  if (!eps->page_setup && eps_startpage(eps)) return -1;
496
497  if (hpgs_ostream_printf(eps->out,"cr\n") < 0)
498    return hpgs_set_error("cliprestore: %s",strerror(errno));
499  return 0;
500}
501
502static  int eps_setrgbcolor (hpgs_device *_this, const hpgs_color *rgb)
503{
504  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
505
506  if (!eps->page_setup && eps_startpage(eps)) return -1;
507
508  if (hpgs_ostream_printf(eps->out,"%g %g %g r\n",rgb->r,rgb->g,rgb->b) < 0)
509    return hpgs_set_error("setrgbcolor: %s",strerror(errno));
510
511  eps->color = *rgb;
512  return 0;
513}
514
515static  int eps_setdash (hpgs_device *_this, const float *segs,
516			 unsigned n, double d)
517{
518  unsigned i;
519  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
520
521  if (!eps->page_setup && eps_startpage(eps)) return -1;
522
523  if (hpgs_ostream_printf(eps->out,"[ ")<0) return 1;
524
525  for (i=0;i<n;++i)
526    if (hpgs_ostream_printf(eps->out,"%f ",(double)segs[i])<0)
527      return hpgs_set_error("setdash: %s",strerror(errno));
528
529  if (hpgs_ostream_printf(eps->out,"] %g d\n",d)<0)
530    return hpgs_set_error("setdash: %s",strerror(errno));
531  return 0;
532}
533
534static  int eps_setlinewidth(hpgs_device *_this, double lw)
535{
536  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
537
538  if (!eps->page_setup && eps_startpage(eps)) return -1;
539
540  if (hpgs_ostream_printf(eps->out,"%g w\n",lw) < 0)
541    return hpgs_set_error("setlinewidth: %s",strerror(errno));
542  return 0;
543}
544
545static  int eps_setlinecap  (hpgs_device *_this, hpgs_line_cap lc)
546{
547  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
548
549  if (!eps->page_setup && eps_startpage(eps)) return -1;
550
551  if (hpgs_ostream_printf(eps->out,"%d J\n",(int)lc) < 0)
552    return hpgs_set_error("setlinecap: %s",strerror(errno));
553  return 0;
554}
555
556static  int eps_setlinejoin (hpgs_device *_this, hpgs_line_join lj)
557{
558  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
559
560  if (!eps->page_setup && eps_startpage(eps)) return -1;
561
562  if (hpgs_ostream_printf(eps->out,"%d j\n",(int)lj) < 0)
563    return hpgs_set_error("setlinejoin: %s",strerror(errno));
564  return 0;
565}
566
567static  int eps_setmiterlimit (hpgs_device *_this, double l)
568{
569  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
570
571  if (!eps->page_setup && eps_startpage(eps)) return -1;
572
573  if (hpgs_ostream_printf(eps->out,"%g M\n",l) < 0)
574    return hpgs_set_error("setmiterlimit: %s",strerror(errno));
575  return 0;
576}
577
578static  int eps_setrop3 (hpgs_device *_this, int rop,
579                         hpgs_bool src_transparency, hpgs_bool pattern_transparency)
580{
581  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
582  hpgs_xrop3_func_t rop3;
583
584  if (!eps->rop3) return 0;
585
586#ifdef HPGS_EPS_DEBUG_ROP3
587  hpgs_log("setrop3: rop,src_trans,pat_trans=%d,%d,%d.\n",
588          rop,src_transparency,pattern_transparency);
589#endif
590  rop3 = hpgs_xrop3_func(rop,src_transparency,pattern_transparency);
591
592  if (!rop3)
593    return hpgs_set_error("setrop3: Invalid ROP3 %d specified",rop);
594
595  eps->rop3 = rop3;
596  return 0;
597}
598
599static  int eps_setpatcol(hpgs_device *_this, const hpgs_color *rgb)
600{
601  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
602
603  eps->pattern_color.r = (unsigned char)(rgb->r * 255.0);
604  eps->pattern_color.g = (unsigned char)(rgb->g * 255.0);
605  eps->pattern_color.b = (unsigned char)(rgb->b * 255.0);
606
607#ifdef HPGS_EPS_DEBUG_ROP3
608  hpgs_log("setpatcol: patcol=%g,%g,%g.\n",rgb->r,rgb->g,rgb->b);
609#endif
610
611  return 0;
612}
613
614static int eps_drawimage(hpgs_device *_this, const hpgs_image *img,
615                         const hpgs_point *ll, const hpgs_point *lr,
616                         const hpgs_point *ur)
617{
618  static const char *hex_4_bit = "0123456789abcdef";
619  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
620  int i,j;
621  hpgs_point ul;
622  int ret = -1;
623
624  if (!img)
625    return hpgs_set_error("drawimage: Null image specified.");
626
627  if (!eps->page_setup && eps_startpage(eps)) return -1;
628
629  ul.x = ll->x + (ur->x - lr->x);
630  ul.y = ll->y + (ur->y - lr->y);
631
632  hpgs_palette_color *data = (hpgs_palette_color *)
633    malloc(img->width*img->height*sizeof(hpgs_palette_color));
634
635  int have_clip = 0;
636
637  // data aqusition.
638  if (eps->rop3)
639    {
640      hpgs_color rgb = eps->color;
641
642      switch (hpgs_image_rop3_clip(_this,data,img,
643                                   ll,lr,ur,
644                                   &eps->pattern_color,eps->rop3))
645        {
646        case 0:
647          // invisible.
648          ret = 0;
649          goto cleanup;
650        case 1:
651          // clipped.
652          have_clip = 1;
653        case 2:
654          // totally visible.
655          break;
656        case 3:
657          // optimized to a single color.
658          eps->color = rgb;
659          if (hpgs_ostream_printf(eps->out,"%g %g %g r\n",
660                                  eps->color.r,eps->color.g,eps->color.b) >= 0)
661            ret = 0;
662          goto cleanup;
663        default:
664          goto cleanup;
665        }
666    }
667  else
668    {
669      hpgs_palette_color *d = data;
670
671      for (j=0; j<img->height;++j)
672        {
673          hpgs_paint_color c;
674
675          for (i=0; i<img->width;++i,++d)
676            {
677              hpgs_image_get_pixel(img,i,j,&c,(double *)0/*alpha*/);
678              d->r = c.r;
679              d->g = c.g;
680              d->b = c.b;
681            }
682        }
683    }
684
685  if (!have_clip)
686    hpgs_ostream_printf(eps->out,
687                        "gsave\n");
688
689  if (hpgs_ostream_printf(eps->out,
690              "[ %g %g %g %g %g %g ] concat\n"
691              "%d %d 8\n"
692              "[ %d 0 0 %d 0 0 ]\n"
693              "{ currentfile %d string readhexstring pop }\n"
694              "dup dup true 3 colorimage\n",
695              lr->x-ll->x,lr->y-ll->y,lr->x-ur->x,lr->y-ur->y,ul.x,ul.y,
696              img->width,img->height,
697              img->width,img->height,img->width) < 0)
698    { hpgs_set_error("drawimage: %s",strerror(errno)); goto cleanup; }
699
700  // write data
701  for (j=0; j<img->height; ++j)
702    {
703      hpgs_palette_color *scanline = data + j * img->width;
704
705      for (i=0; i<img->width; i++)
706        {
707          hpgs_ostream_putc(hex_4_bit[scanline[i].r>>4],eps->out);
708          hpgs_ostream_putc(hex_4_bit[scanline[i].r&0xf],eps->out);
709        }
710      for (i=0; i<img->width; i++)
711        {
712          hpgs_ostream_putc(hex_4_bit[scanline[i].g>>4],eps->out);
713          hpgs_ostream_putc(hex_4_bit[scanline[i].g&0xf],eps->out);
714        }
715      for (i=0; i<img->width; i++)
716        {
717          hpgs_ostream_putc(hex_4_bit[scanline[i].b>>4],eps->out);
718          hpgs_ostream_putc(hex_4_bit[scanline[i].b&0xf],eps->out);
719        }
720    }
721
722  if (hpgs_ostream_printf(eps->out,"\ngrestore\n")<0)
723    { hpgs_set_error("drawimage: %s",strerror(errno)); goto cleanup; }
724
725  ret = 0;
726
727 cleanup:
728  free(data);
729
730  return ret;
731}
732
733static  int eps_showpage    (hpgs_device *_this, int i)
734{
735  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
736
737  if (!eps->out) return 0;
738
739  if (hpgs_ostream_printf(eps->out,"grestore\nshowpage\nend\n") < 0)
740    return hpgs_set_error("showpage: %s",strerror(errno));
741
742  // write an eps file, if we are not in multipage mode.
743  if (eps->n_pages<0)
744    {
745      if (hpgs_ostream_printf(eps->out,"%%%%EOF\n")<0 ||
746          hpgs_ostream_close(eps->out) < 0)
747        return hpgs_set_error("showpage: %s",strerror(errno));
748
749      eps->out = 0;
750
751      if (i > 0 && eps->filename)
752        {
753          int l = strlen(eps->filename);
754          char *fn = hpgs_alloca(l+20);
755          char *dot  = strrchr(eps->filename,'.');
756          int pos = dot ? dot-eps->filename : l;
757
758#ifdef WIN32
759          _snprintf(fn,l+20,"%.*s%4.4d%s",
760                    pos,eps->filename,i,eps->filename+pos);
761#else
762          snprintf(fn,l+20,"%.*s%4.4d%s",
763                   pos,eps->filename,i,eps->filename+pos);
764#endif
765          l = rename(eps->filename,fn);
766
767          if (l<0)
768            return hpgs_set_error(hpgs_i18n("Error moving file %s to %.*s%4.4d%s.eps: %s"),
769                                  eps->filename,
770                                  pos,eps->filename,i,eps->filename+pos,
771                                  strerror(errno));
772        }
773    }
774
775  eps->page_setup = HPGS_FALSE;
776
777  return 0;
778}
779
780static int eps_setplotsize (hpgs_device *_this, const hpgs_bbox *bb)
781{
782  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
783
784  eps->page_bb = *bb;
785
786  return 0;
787}
788
789static  int eps_capabilities (hpgs_device *_this)
790{
791  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
792
793  int ret =
794    HPGS_DEVICE_CAP_VECTOR |
795    HPGS_DEVICE_CAP_MULTIPAGE |
796    HPGS_DEVICE_CAP_MULTISIZE |
797    HPGS_DEVICE_CAP_DRAWIMAGE;
798
799  if (eps->n_pages >= 0)
800    ret |= HPGS_DEVICE_CAP_PAGECOLLATION;
801
802  if (eps->rop3)
803    ret |= HPGS_DEVICE_CAP_ROP3;
804
805  return ret;
806}
807
808static  int eps_finish (hpgs_device *_this)
809{
810  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
811
812  if (!eps->out) return 0;
813
814  if (eps->n_pages < 0)
815    {
816      // discard trailing output in EPS mode.
817      if (hpgs_ostream_close(eps->out) < 0)
818        {
819          eps->out = 0;
820          return hpgs_set_error("finish: %s",strerror(errno));
821        }
822
823      eps->out = 0;
824
825      if (eps->filename && unlink(eps->filename)<0)
826        return hpgs_set_error(hpgs_i18n("Error removing file %s: %s"),
827                              eps->filename,strerror(errno));
828    }
829  else
830    {
831      // now really write the postscript file
832      int i,i_media=0;
833      hpgs_istream *buffer;
834
835      hpgs_ostream *out = hpgs_new_file_ostream (eps->filename);
836
837      if (!out)
838        return hpgs_set_error(hpgs_i18n("Error opening file %s: %s"),
839                              eps->filename,strerror(errno));
840
841      if (hpgs_ostream_flush(eps->out) <0 ||
842          (buffer =hpgs_ostream_getbuf(eps->out)) == 0)
843        return hpgs_set_error(hpgs_i18n("Error getting page stream data"));
844
845      hpgs_ostream_printf (out,
846                           "%%!PS-Adobe-3.0\n"
847                           "%%%%Creator: hpgs-%s\n"
848                           "%%%%Title: %s\n"
849                           "%%%%Pages: %d\n"
850                           "%%%%BoundingBox: %d %d %d %d\n"
851                           "%%%%HiResBoundingBox: %.3f %.3f %.3f %.3f\n"
852                           "%%%%DocumentMedia:",
853                           HPGS_VERSION,
854                           eps->filename ? eps->filename : "(stdout)",
855                           eps->n_pages,
856                           (int)floor(eps->doc_bb.llx),(int)floor(eps->doc_bb.lly),
857                           (int)ceil(eps->doc_bb.urx),(int)ceil(eps->doc_bb.ury),
858                           eps->doc_bb.llx,eps->doc_bb.lly,
859                           eps->doc_bb.urx,eps->doc_bb.ury);
860
861      // write paper sizes.
862      for (i=0;i<eps->n_media_sizes;++i)
863        if (eps->media_sizes[i].usage)
864          {
865            if (eps->media_sizes[i].name)
866              hpgs_ostream_printf (out,"%s %s %d %d 75 white()\n",
867                                   i_media ? "%%+" : "",
868                                   eps->media_sizes[i].name,
869                                   eps->media_sizes[i].width,
870                                   eps->media_sizes[i].height );
871            else
872              hpgs_ostream_printf (out,"%s Custom%dx%d %d %d 75 white()\n",
873                                   i_media ? "%%+" : "",
874                                   eps->media_sizes[i].width,
875                                   eps->media_sizes[i].height,
876                                   eps->media_sizes[i].width,
877                                   eps->media_sizes[i].height );
878
879            ++i_media;
880          }
881
882      hpgs_ostream_printf (out,
883                           "%%%%EndComments\n"
884                           "/hpgsdict 20 dict def\n"
885                           "hpgsdict begin\n"
886                           "/B{bind def} bind def\n"
887                           "/r{setrgbcolor}B/w{setlinewidth}B/j{setlinejoin}B/J{setlinecap}B/M{setmiterlimit}B\n"
888                           "/d{setdash}B/n{newpath}B/h{closepath}B/m{moveto}B/l{lineto}B/c{curveto}B\n"
889                           "/s{stroke}B/f{fill}B/g{eofill}B/x{clip}B/y{eoclip}B/cs{gsave}B\n"
890                           "/cr{currentrgbcolor currentlinewidth currentlinecap currentlinejoin currentmiterlimit currentdash grestore setdash setmiterlimit setlinejoin setlinecap setlinewidth setrgbcolor}B\n"
891                           "end\n"
892                           "%%%%EndProlog\n");
893
894      hpgs_copy_streams (out,buffer);
895      hpgs_istream_close(buffer);
896
897      hpgs_ostream_printf (out,"%%%%EOF\n");
898
899      if (hpgs_ostream_iserror(out))
900        {
901          if (eps->filename)
902            hpgs_ostream_close(out);
903          return hpgs_set_error(hpgs_i18n("Error writing file %s: %s"),
904                                eps->filename?eps->filename:"(stdout)",
905                                strerror(errno));
906        }
907
908      hpgs_ostream_close(out);
909    }
910
911  return 0;
912}
913
914static  void eps_destroy     (hpgs_device *_this)
915{
916  hpgs_eps_device *eps = (hpgs_eps_device *)_this;
917  if (eps->out)
918    hpgs_ostream_close(eps->out);
919
920  if (eps->filename)
921    free(eps->filename);
922}
923
924static hpgs_device_vtable eps_vtable =
925  {
926    "hpgs_eps_device",
927    eps_moveto,
928    eps_lineto,
929    eps_curveto,
930    eps_newpath,
931    eps_closepath,
932    eps_stroke,
933    eps_fill,
934    eps_clip,
935    eps_clipsave,
936    eps_cliprestore,
937    eps_setrgbcolor,
938    eps_setdash,
939    eps_setlinewidth,
940    eps_setlinecap,
941    eps_setlinejoin,
942    eps_setmiterlimit,
943    eps_setrop3,
944    eps_setpatcol,
945    eps_drawimage,
946    eps_setplotsize,
947    0 /* eps_getplotsize */,
948    eps_showpage,
949    eps_finish,
950    eps_capabilities,
951    eps_destroy
952  };
953
954/*! Retrieves the pointer to a new \c hpgs_eps_device on the heap,
955    which writes to the file with the gieven \c filename.
956
957    The bounding box in the eps files is passed to this functions.
958
959    If the file cannot be opened or the system is out of memory,
960    a null pointer is returned.
961*/
962hpgs_eps_device *hpgs_new_eps_device(const char *filename,
963				     const hpgs_bbox *bb,
964                                     hpgs_bool do_rop3)
965{
966  hpgs_eps_device *ret = (hpgs_eps_device *)malloc(sizeof(hpgs_eps_device));
967
968  if (!ret)
969    return 0;
970
971  ret->out = 0;
972  ret->n_pages = -1;
973  ret->page_setup = HPGS_FALSE;
974
975  if (filename)
976    {
977      ret->filename = strdup(filename);
978
979      if (!ret->filename)
980        {
981          free(ret);
982          return 0;
983        }
984    }
985  else
986    ret->filename = 0;
987
988  ret->pattern_color.r = 0;
989  ret->pattern_color.g = 0;
990  ret->pattern_color.b = 0;
991
992  if (do_rop3)
993    ret->rop3 = hpgs_xrop3_func(252,HPGS_TRUE,HPGS_TRUE);
994  else
995    ret->rop3 = 0;
996
997  ret->doc_bb = *bb;
998  ret->page_bb = *bb;
999
1000  ret->media_sizes_alloc_size = 0;
1001  ret->n_media_sizes = 0;
1002  ret->media_sizes = 0;
1003
1004  ret->color.r = 0.0;
1005  ret->color.g = 0.0;
1006  ret->color.b = 0.0;
1007
1008  ret->inherited.vtable=&eps_vtable;
1009
1010  return ret;
1011}
1012
1013#define HPGS_STD_MEDIA_SIZE(w,h,n) {w,h,n,0,HPGS_MEDIA_SIZE_HASH(w,h)}
1014
1015static hpgs_ps_media_size ps_std_media_sizes[]=
1016  {
1017    HPGS_STD_MEDIA_SIZE(596 ,843 ,"A4"),
1018    HPGS_STD_MEDIA_SIZE(843 ,1192,"A3"),
1019    HPGS_STD_MEDIA_SIZE(1192,1686,"A2"),
1020    HPGS_STD_MEDIA_SIZE(1686,2384,"A1"),
1021    HPGS_STD_MEDIA_SIZE(2384,3371,"A0")
1022  };
1023
1024/* A helper for qsort */
1025static int compare_media_hashes(const void *a, const void *b)
1026{
1027  const hpgs_ps_media_size *m1 = (const hpgs_ps_media_size *)a;
1028  const hpgs_ps_media_size *m2 = (const hpgs_ps_media_size *)b;
1029
1030  if (m1->hash < m2->hash) return -1;
1031  if (m1->hash > m2->hash) return 1;
1032  return 0;
1033}
1034
1035
1036/*! Retrieves the pointer to a new \c hpgs_eps_device on the heap,
1037    which writes to a multipage PostScript file with the given \c filename.
1038
1039    The overall document bounding box for the PostScript file is
1040    passed as \c bb.
1041
1042    If \c paper_width and \c paper_height are greater than zero,
1043    the content of each page is scaled to this fixed paper format.
1044    Otherwise, the paper size of each page adpats to the page
1045    bounding box.
1046
1047    The given \c border is used in order to place the contents on the
1048    page.
1049
1050    If the file cannot be opened or the system is out of memory,
1051    a null pointer is returned.
1052*/
1053hpgs_eps_device *hpgs_new_ps_device(const char *filename,
1054                                    const hpgs_bbox *bb,
1055                                    hpgs_bool do_rop3)
1056{
1057  hpgs_eps_device *ret = (hpgs_eps_device *)malloc(sizeof(hpgs_eps_device));
1058
1059  if (!ret)
1060    return 0;
1061
1062  ret->out = hpgs_new_mem_ostream(1024*1024);
1063
1064  if (!ret->out)
1065    {
1066      free(ret);
1067      return 0;
1068    }
1069
1070  ret->n_pages = 0;
1071  ret->page_setup = HPGS_FALSE;
1072
1073  if (filename)
1074    {
1075      ret->filename = strdup(filename);
1076
1077      if (!ret->filename)
1078        {
1079          hpgs_ostream_close(ret->out);
1080          free(ret);
1081          return 0;
1082        }
1083    }
1084  else
1085    ret->filename = 0;
1086
1087  ret->pattern_color.r = 0;
1088  ret->pattern_color.g = 0;
1089  ret->pattern_color.b = 0;
1090
1091  if (do_rop3)
1092    ret->rop3 = hpgs_xrop3_func(252,HPGS_TRUE,HPGS_TRUE);
1093  else
1094    ret->rop3 = 0;
1095
1096  ret->doc_bb = *bb;
1097  ret->page_bb = *bb;
1098
1099  ret->media_sizes_alloc_size = 32;
1100  ret->media_sizes =
1101    (hpgs_ps_media_size*)malloc(sizeof(hpgs_ps_media_size)*ret->media_sizes_alloc_size);
1102
1103  if (!ret->media_sizes)
1104    {
1105      hpgs_ostream_close(ret->out);
1106      free(ret->filename);
1107      free(ret);
1108      return 0;
1109    }
1110
1111  // fill in std media sizes.
1112  ret->n_media_sizes = sizeof(ps_std_media_sizes)/sizeof(hpgs_ps_media_size);
1113  memcpy(ret->media_sizes,ps_std_media_sizes,sizeof(ps_std_media_sizes));
1114
1115  qsort(ret->media_sizes,ret->n_media_sizes,
1116        sizeof(hpgs_ps_media_size),compare_media_hashes);
1117
1118  ret->color.r = 0.0;
1119  ret->color.g = 0.0;
1120  ret->color.b = 0.0;
1121
1122  ret->inherited.vtable=&eps_vtable;
1123
1124  return ret;
1125}
1126
1127/*!
1128  Plotsize device.
1129*/
1130static int pls_expand_pages(hpgs_plotsize_device *pls)
1131{
1132  int i,n = pls->page_bbs_alloc_size*2;
1133  hpgs_bbox *nbbs;
1134
1135  if (pls->n_page_bbs > n) n=pls->n_page_bbs;
1136
1137  nbbs = realloc(pls->page_bbs,sizeof(hpgs_bbox)*n);
1138
1139  if (!nbbs)
1140    return hpgs_set_error(hpgs_i18n("Out of memory expanding page size stack."));
1141
1142  pls->page_bbs=nbbs;
1143
1144  for (i=pls->page_bbs_alloc_size;i<n;++i)
1145    hpgs_bbox_null(pls->page_bbs+i);
1146
1147  pls->page_bbs_alloc_size=n;
1148  return 0;
1149}
1150
1151static  int pls_moveto (hpgs_device *_this, const hpgs_point *p)
1152{
1153  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1154
1155  pls->moveto = *p;
1156  pls->deferred_moveto = 1;
1157
1158  return 0;
1159}
1160
1161static  int pls_lineto (hpgs_device *_this, const hpgs_point *p)
1162{
1163  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1164
1165  if (pls->deferred_moveto)
1166    {
1167      hpgs_bbox_add(&pls->path_bb,&pls->moveto);
1168      pls->deferred_moveto = 0;
1169    }
1170
1171  hpgs_bbox_add(&pls->path_bb,p);
1172  pls->moveto = *p;
1173
1174  return 0;
1175}
1176
1177static void add_bezier(hpgs_plotsize_device *pls,
1178                       const hpgs_point *p0, const hpgs_point *p1,
1179                       const hpgs_point *p2, const hpgs_point *p3,
1180                       int depth )
1181{
1182  hpgs_point ll =  { HPGS_MIN(p0->x,p3->x),HPGS_MIN(p0->y,p3->y) };
1183  hpgs_point ur =  { HPGS_MAX(p0->x,p3->x),HPGS_MAX(p0->y,p3->y) };
1184
1185  // p1 and p2 inside bbox of p0 and p3.
1186  if (depth > 2 ||
1187      p1->x < ll.x || p2->x < ll.x ||
1188      p1->y < ll.y || p2->y < ll.y ||
1189      p1->x > ur.x || p2->x > ur.x ||
1190      p1->y > ur.y || p2->y > ur.y   )
1191    {
1192      hpgs_bbox_add(&pls->path_bb,p3);
1193    }
1194  else
1195    {
1196      // split spline
1197      hpgs_point p1l = { 0.5 * (p0->x + p1->x), 0.5 * (p0->y + p1->y) };
1198      hpgs_point p2l = { 0.25 * (p0->x + p2->x) + 0.5 * p1->x, 0.25 * (p0->y + p2->y) + 0.5 * p1->y };
1199      hpgs_point pm = { (p0->x + p3->x) * 0.125 + 0.375 * (p1->x + p2->x), (p0->y + p3->y) * 0.125 + 0.375 * (p1->y + p2->y)  };
1200      hpgs_point p1u = { 0.25 * (p1->x + p3->x) + 0.5 * p2->x, 0.25 * (p1->y + p3->y) + 0.5 * p2->y };
1201      hpgs_point p2u = { 0.5 * (p2->x + p3->x), 0.5 * (p2->y + p3->y) };
1202
1203      add_bezier(pls,p0,&p1l,&p2l,&pm,depth+1);
1204      add_bezier(pls,&pm,&p1u,&p2u,p3,depth+1);
1205    }
1206}
1207
1208static  int pls_curveto (hpgs_device *_this, const hpgs_point *p1,
1209			 const hpgs_point *p2, const hpgs_point *p3 )
1210{
1211  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1212
1213  if (pls->deferred_moveto)
1214    {
1215      hpgs_bbox_add(&pls->path_bb,&pls->moveto);
1216      pls->deferred_moveto = 0;
1217    }
1218
1219  add_bezier(pls,&pls->moveto,p1,p2,p3,0);
1220
1221  pls->moveto = *p3;
1222
1223  return 0;
1224}
1225
1226static  int pls_addpath (hpgs_device *_this, hpgs_bool do_linewidth)
1227{
1228  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1229
1230  if (do_linewidth)
1231    {
1232      hpgs_bbox_addborder(&pls->path_bb,0.5 * pls->linewidth);
1233    }
1234
1235  if (pls->clip_depth >= 0)
1236    {
1237      hpgs_bbox_intersect(&pls->path_bb,pls->clip_bbs+pls->clip_depth);
1238
1239      // check for null intersection
1240      if (hpgs_bbox_isnull(&pls->path_bb))
1241        goto get_out;
1242    }
1243
1244  hpgs_bbox_expand(&pls->page_bb,&pls->path_bb);
1245  hpgs_bbox_expand(&pls->global_bb,&pls->path_bb);
1246
1247 get_out:
1248  hpgs_bbox_null(&pls->path_bb);
1249
1250  pls->moveto.x = 0.0;
1251  pls->moveto.y = 0.0;
1252  pls->deferred_moveto = 0;
1253
1254  return 0;
1255}
1256
1257static  int pls_stroke (hpgs_device *_this)
1258{
1259  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1260
1261  return pls_addpath (_this,pls->do_linewidth);
1262}
1263
1264static  int pls_fill (hpgs_device *_this, hpgs_bool winding)
1265{
1266  return pls_addpath (_this,HPGS_FALSE);
1267}
1268
1269static  int pls_closepath   (hpgs_device *_this)
1270{
1271  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1272
1273  pls->deferred_moveto = 1;
1274
1275  return 0;
1276}
1277
1278static  int pls_newpath   (hpgs_device *_this)
1279{
1280  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1281
1282  hpgs_bbox_null(&pls->path_bb);
1283
1284  pls->moveto.x = 0.0;
1285  pls->moveto.y = 0.0;
1286  pls->deferred_moveto = 0;
1287
1288  return 0;
1289}
1290
1291static  int pls_clip        (hpgs_device *_this, hpgs_bool winding)
1292{
1293  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1294
1295  hpgs_bbox_intersect(pls->clip_bbs+pls->clip_depth,&pls->path_bb);
1296
1297  return 0;
1298}
1299
1300static  int pls_clipsave       (hpgs_device *_this)
1301{
1302  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1303
1304  if (pls->clip_depth+1 >= HPGS_PLOTSIZE_MAX_CLIP_DEPTH)
1305    return hpgs_set_error(hpgs_i18n("Maximum clip depth %d exceeded."),
1306                          HPGS_PLOTSIZE_MAX_CLIP_DEPTH);
1307
1308  pls->clip_bbs[pls->clip_depth+1] = pls->clip_bbs[pls->clip_depth] ;
1309
1310  ++pls->clip_depth;
1311
1312  return 0;
1313}
1314
1315static  int pls_cliprestore    (hpgs_device *_this)
1316{
1317  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1318  --pls->clip_depth;
1319
1320  if (pls->clip_depth < 0)
1321    return hpgs_set_error("cliprestore: clip stack underflow.");
1322
1323  return 0;
1324}
1325
1326static int pls_setlinewidth(hpgs_device *_this, double lw)
1327{
1328  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1329
1330  pls->linewidth = lw;
1331  return 0;
1332}
1333
1334static int pls_drawimage(hpgs_device *_this, const hpgs_image *img,
1335                         const hpgs_point *ll, const hpgs_point *lr,
1336                         const hpgs_point *ur)
1337{
1338  hpgs_point ul;
1339
1340  ul.x = ll->x + (ur->x - lr->x);
1341  ul.y = ll->y + (ur->y - lr->y);
1342
1343  pls_newpath(_this);
1344  pls_moveto(_this,ll);
1345  pls_lineto(_this,lr);
1346  pls_lineto(_this,ur);
1347  pls_lineto(_this,&ul);
1348  pls_addpath(_this,HPGS_FALSE);
1349
1350  return 0;
1351}
1352
1353static  int pls_showpage (hpgs_device *_this, int i)
1354{
1355  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1356
1357  if (i>0)
1358    {
1359      if (pls->page_bb.urx < pls->page_bb.llx)
1360        pls->page_bb.urx = pls->page_bb.llx = 0.0;
1361
1362      if (pls->page_bb.ury < pls->page_bb.lly)
1363        pls->page_bb.ury = pls->page_bb.lly = 0.0;
1364
1365      if (i > pls->n_page_bbs) pls->n_page_bbs=i;
1366
1367      if (pls->n_page_bbs >  pls->page_bbs_alloc_size &&
1368          pls_expand_pages(pls))
1369        return -1;
1370
1371      pls->page_bbs[i-1] = pls->page_bb;
1372    }
1373
1374  hpgs_bbox_null(&pls->page_bb);
1375
1376  return 0;
1377}
1378
1379static  int pls_finish (hpgs_device *_this)
1380{
1381  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1382
1383  if (pls->global_bb.urx < pls->global_bb.llx)
1384    pls->global_bb.urx = pls->global_bb.llx = 0.0;
1385
1386  if (pls->global_bb.ury < pls->global_bb.lly)
1387    pls->global_bb.ury = pls->global_bb.lly = 0.0;
1388
1389  return 0;
1390}
1391
1392static  int pls_capabilities (hpgs_device *_this)
1393{
1394  return
1395    HPGS_DEVICE_CAP_PLOTSIZE |
1396    HPGS_DEVICE_CAP_MULTIPAGE |
1397    HPGS_DEVICE_CAP_MULTISIZE |
1398    HPGS_DEVICE_CAP_NULLIMAGE;
1399}
1400
1401static  void pls_destroy     (hpgs_device *_this)
1402{
1403  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1404
1405  if (pls->page_bbs)
1406    free (pls->page_bbs);
1407}
1408
1409static  int pls_setplotsize (hpgs_device *_this, const hpgs_bbox *bb)
1410{
1411  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1412
1413  if (pls->ignore_ps)
1414    return 0;
1415
1416  pls->global_bb = *bb;
1417  pls->page_bb = *bb;
1418
1419  return 2;
1420}
1421
1422static  int pls_getplotsize (hpgs_device *_this, int i, hpgs_bbox *bb)
1423{
1424  hpgs_plotsize_device *pls = (hpgs_plotsize_device *)_this;
1425
1426  int ret = 0;
1427
1428  if (i<=0 || i > pls->n_page_bbs)
1429    {
1430      *bb = pls->global_bb;
1431      if (i>0) ret = 1;
1432    }
1433  else
1434    *bb = pls->page_bbs[i-1];
1435
1436  return ret;
1437}
1438
1439static hpgs_device_vtable pls_vtable =
1440  {
1441    "hpgs_plotsize_device",
1442    pls_moveto,
1443    pls_lineto,
1444    pls_curveto,
1445    pls_newpath,
1446    pls_closepath,
1447    pls_stroke,
1448    pls_fill,
1449    pls_clip,
1450    pls_clipsave,
1451    pls_cliprestore,
1452    0 /* pls_setrgbcolor */,
1453    0 /* pls_setdash */,
1454    pls_setlinewidth,
1455    0 /* pls_setlinecap */,
1456    0 /* pls_setlinejoin */,
1457    0 /* pls_setmiterlimit */,
1458    0 /* pls_setrop3 */,
1459    0 /* pls_setpatcol */,
1460    pls_drawimage,
1461    pls_setplotsize,
1462    pls_getplotsize,
1463    pls_showpage,
1464    pls_finish,
1465    pls_capabilities,
1466    pls_destroy
1467  };
1468
1469/*! Retrieves the pointer to a new \c hpgs_plotsize_device on the heap.
1470
1471    If \c ignore_ps is \c HPGS_TRUE, a HPGL PS statement is ignored an the
1472    plotsize is calculated from the vector graphics contents.
1473
1474    If \c do_linewidth is \c HPGS_TRUE, the current linewidth is
1475    taken into account in the plotsize calculation.
1476
1477    If the system is out of memory, a null pointer is returned.
1478*/
1479hpgs_plotsize_device *hpgs_new_plotsize_device(hpgs_bool ignore_ps,
1480                                               hpgs_bool do_linewidth)
1481{
1482  hpgs_plotsize_device *ret =
1483    (hpgs_plotsize_device *)malloc(sizeof(hpgs_plotsize_device));
1484
1485  if (ret)
1486    {
1487      ret->n_page_bbs = 0;
1488      ret->page_bbs_alloc_size = 32;
1489      ret->page_bbs = malloc(sizeof(hpgs_bbox)*ret->page_bbs_alloc_size);
1490
1491      if (!ret->page_bbs)
1492        {
1493          free(ret);
1494          return 0;
1495        }
1496
1497      hpgs_bbox_null(&ret->path_bb);
1498      hpgs_bbox_null(&ret->page_bb);
1499      hpgs_bbox_null(&ret->global_bb);
1500      ret->moveto.x = 0.0;
1501      ret->moveto.y = 0.0;
1502      ret->deferred_moveto = 0;
1503      ret->clip_bbs[0].llx = -1.0e20;
1504      ret->clip_bbs[0].lly = -1.0e20;
1505      ret->clip_bbs[0].urx = 1.0e20;
1506      ret->clip_bbs[0].ury = 1.0e20;
1507      ret->clip_depth = 0;
1508      ret->linewidth = 1.0;
1509      ret->ignore_ps = ignore_ps;
1510      ret->do_linewidth = do_linewidth;
1511      ret->inherited.vtable=&pls_vtable;
1512    }
1513
1514  return ret;
1515}
1516
1517/*
1518  Custom device from plugin.
1519*/
1520typedef int (*hpgs_new_device_func_t)(hpgs_device **device,
1521                                      void **page_asset_ctxt,
1522                                      hpgs_reader_asset_func_t *page_asset_func,
1523                                      void **frame_asset_ctxt,
1524                                      hpgs_reader_asset_func_t *frame_asset_func,
1525                                      const char *dev_name,
1526                                      const char *filename,
1527                                      const hpgs_bbox *bb,
1528                                      double xres, double yres,
1529                                      hpgs_bool do_rop3,
1530                                      int argc, const char *argv[]);
1531
1532typedef void (*hpgs_plugin_version_func_t)(int *major, int *minor);
1533
1534typedef void (*hpgs_plugin_init_func_t)();
1535typedef void (*hpgs_plugin_cleanup_func_t)();
1536
1537typedef struct hpgs_plugin_ref_st hpgs_plugin_ref;
1538
1539#define HPGS_PLUGIN_NAME_LEN 8
1540
1541struct hpgs_plugin_ref_st
1542{
1543  char name[HPGS_PLUGIN_NAME_LEN];
1544#ifdef WIN32
1545  HMODULE handle;
1546#else
1547  void *handle;
1548#endif
1549  hpgs_new_device_func_t new_dev_func;
1550  hpgs_plugin_version_func_t version_func;
1551  hpgs_plugin_init_func_t init_func;
1552  hpgs_plugin_init_func_t cleanup_func;
1553};
1554
1555static hpgs_plugin_ref plugins[4] =
1556  { { "",0,0,0}, { "",0,0,0}, { "",0,0,0}, { "",0,0,0} };
1557
1558static void hpgs_cleanup_plugin(hpgs_plugin_ref *plugin)
1559{
1560  if (strlen(plugin->name) == 0)
1561    return;
1562
1563  if (plugin->cleanup_func) plugin->cleanup_func();
1564
1565#ifdef WIN32
1566  if (!FreeLibrary(plugin->handle))
1567    hpgs_log("hpgs_cleanup_plugin_devices: unable to close plugin %s.\n",
1568             plugin->name);
1569#else
1570  if (dlclose(plugin->handle))
1571    hpgs_log("hpgs_cleanup_plugin_devices: unable to close plugin %s: %s.\n",
1572             plugin->name,dlerror());
1573#endif
1574
1575  plugin->name[0]      = '\0';
1576  plugin->handle       = 0;
1577  plugin->new_dev_func = 0;
1578  plugin->version_func = 0;
1579  plugin->init_func    = 0;
1580  plugin->cleanup_func = 0;
1581}
1582
1583void hpgs_cleanup_plugin_devices()
1584{
1585  int i;
1586
1587  for (i=0;i<sizeof(plugins)/sizeof(hpgs_plugin_ref);++i)
1588    {
1589      hpgs_cleanup_plugin(&plugins[i]);
1590    }
1591}
1592
1593int hpgs_new_plugin_device( hpgs_device **device,
1594                            void **page_asset_ctxt,
1595                            hpgs_reader_asset_func_t *page_asset_func,
1596                            void **frame_asset_ctxt,
1597                            hpgs_reader_asset_func_t *frame_asset_func,
1598                            const char *dev_name,
1599                            const char *filename,
1600                            const hpgs_bbox *bb,
1601                            double xres, double yres,
1602                            hpgs_bool do_rop3,
1603                            int argc, const char *argv[])
1604{
1605  char *underscore=strchr(dev_name,'_');
1606  int i,l = underscore ? underscore-dev_name : strlen(dev_name);
1607  hpgs_plugin_ref *plugin=0;
1608
1609  if (l >= HPGS_PLUGIN_NAME_LEN) l = HPGS_PLUGIN_NAME_LEN-1;
1610
1611  for (i=0;i<sizeof(plugins)/sizeof(hpgs_plugin_ref);++i)
1612    {
1613      if (strlen(plugins[i].name) == 0)
1614        break;
1615
1616      if (strlen(plugins[i].name) == l &&
1617          strncmp(plugins[i].name,dev_name,l) == 0)
1618        { plugin = plugins+i; break; }
1619    }
1620
1621  if (i>=sizeof(plugins)/sizeof(hpgs_plugin_ref))
1622    {
1623      hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: too much plugins registered (max %u).\n"),
1624                     (unsigned)(sizeof(plugins)/sizeof(hpgs_plugin_ref)));
1625      return 0;
1626    }
1627
1628  if (!plugin)
1629    {
1630      char plugin_name[1024];
1631      plugin = plugins+i;
1632
1633#ifdef WIN32
1634      _snprintf(plugin_name,sizeof(plugin_name),"%s\\lib\\hpgs\\hpgs%.*splugin.%d.%d.dll",
1635                hpgs_get_prefix(),
1636                l,dev_name,
1637                HPGS_MAJOR_VERSION,HPGS_MINOR_VERSION);
1638      plugin->handle =LoadLibraryA(plugin_name);
1639
1640      if (!plugin->handle)
1641	{
1642	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to open plugin %s.\n"),
1643                         plugin_name);
1644	  return 0;
1645	}
1646#else
1647
1648#ifndef HPGS_LIBSFX
1649#define HPGS_LIBSFX "lib"
1650#endif
1651      snprintf(plugin_name,sizeof(plugin_name),"%s/" HPGS_LIBSFX "/hpgs/hpgs%.*splugin.so.%d.%d",
1652               hpgs_get_prefix(),
1653               l,dev_name,
1654               HPGS_MAJOR_VERSION,HPGS_MINOR_VERSION);
1655
1656      plugin->handle = dlopen(plugin_name,RTLD_LAZY);
1657
1658      if (!plugin->handle)
1659	{
1660	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to open plugin %s: %s.\n"),
1661                         plugin_name,dlerror());
1662	  return 0;
1663	}
1664#endif
1665      memcpy(plugin->name,dev_name,l);
1666      plugin->name[l]='\0';
1667
1668      plugin->version_func=0;
1669      plugin->init_func=0;
1670      plugin->cleanup_func=0;
1671      plugin->new_dev_func=0;
1672    }
1673
1674
1675  if (! plugin->version_func)
1676    {
1677      int minor = 0;
1678      int major = 0;
1679#ifdef WIN32
1680      plugin->version_func =
1681	(hpgs_plugin_version_func_t)GetProcAddress(plugin->handle,
1682                                                   "hpgs_plugin_version");
1683      if (!plugin->version_func)
1684	{
1685          hpgs_cleanup_plugin(plugin);
1686	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to resolve function hpgs_plugin_version.\n"));
1687	  return 0;
1688	}
1689#else
1690      plugin->version_func =
1691	(hpgs_plugin_version_func_t)dlsym(plugin->handle,
1692					  "hpgs_plugin_version");
1693      if (!plugin->version_func)
1694	{
1695          hpgs_cleanup_plugin(plugin);
1696	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to resolve function hpgs_plugin_version: %s.\n"),
1697                         dlerror());
1698	  return 0;
1699	}
1700#endif
1701
1702      plugin->version_func(&major,&minor);
1703
1704      if (major != HPGS_MAJOR_VERSION ||
1705	  minor != HPGS_MINOR_VERSION   )
1706	{
1707          hpgs_cleanup_plugin(plugin);
1708	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: Plugin version %d.%d does not match application version %d.%d.\n"),
1709                         major,minor,HPGS_MAJOR_VERSION,HPGS_MINOR_VERSION);
1710
1711	  return 0;
1712	}
1713
1714#ifdef WIN32
1715      plugin->init_func =
1716        (hpgs_plugin_init_func_t)GetProcAddress(plugin->handle,
1717                                                "hpgs_plugin_init");
1718      plugin->cleanup_func =
1719        (hpgs_plugin_cleanup_func_t)GetProcAddress(plugin->handle,
1720                                                "hpgs_plugin_cleanup");
1721#else
1722      plugin->init_func =
1723        (hpgs_plugin_init_func_t)dlsym(plugin->handle,
1724                                       "hpgs_plugin_init");
1725      plugin->cleanup_func =
1726        (hpgs_plugin_cleanup_func_t)dlsym(plugin->handle,
1727                                          "hpgs_plugin_cleanup");
1728#endif
1729
1730      if (plugin->init_func) plugin->init_func();
1731    }
1732
1733
1734  if (!plugin->new_dev_func)
1735    {
1736#ifdef WIN32
1737      plugin->new_dev_func =
1738	(hpgs_new_device_func_t)GetProcAddress(plugin->handle,
1739                                               "hpgs_plugin_new_device");
1740      if (!plugin->new_dev_func)
1741	{
1742          hpgs_cleanup_plugin(plugin);
1743	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to resolve function hpgs_plugin_new_device.\n"));
1744	  return 0;
1745	}
1746#else
1747      plugin->new_dev_func =
1748	(hpgs_new_device_func_t)dlsym(plugin->handle,
1749				      "hpgs_plugin_new_device");
1750
1751      if (!plugin->new_dev_func)
1752	{
1753          hpgs_cleanup_plugin(plugin);
1754	  hpgs_set_error(hpgs_i18n("hpgs_new_plugin_device: unable to resolve function hpgs_plugin_new_device: %s.\n"),
1755                         dlerror());
1756	  return 0;
1757	}
1758#endif
1759    }
1760
1761  return plugin->new_dev_func(device,
1762                              page_asset_ctxt,page_asset_func,
1763                              frame_asset_ctxt,frame_asset_func,
1764                              dev_name,filename,bb,xres,yres,do_rop3,argc,argv);
1765}
1766