1 /******************************************************************************
2   File:     $Id: gdeveprn.c,v 1.25 2001/04/30 05:15:51 Martin Rel $
3   Contents: Implementation of the abstract ghostscript device 'eprn':
4             general functions and page layout
5   Author:   Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig,
6             Germany. E-mail: Martin.Lottermoser@t-online.de.
7 
8 *******************************************************************************
9 *									      *
10 *	Copyright (C) 2000, 2001 by Martin Lottermoser			      *
11 *	All rights reserved						      *
12 *									      *
13 *******************************************************************************
14 
15   Preprocessor variables:
16 
17     EPRN_NO_PAGECOUNTFILE
18 	Define this if you do not want to use eprn's pagecount-file feature.
19 	You very likely must define this on Microsoft Windows.
20 
21     EPRN_TRACE
22 	Define this to enable tracing. Only useful for development.
23 
24     EPRN_USE_GSTATE (integer)
25 	Define this to be non-zero if the graphics state should be accessed
26 	directly instead of via the interpreter context state. Newer ghostscript
27 	versions require the latter path. The default is zero unless
28 	GS_REVISION is defined and less than 600.
29 
30     GS_REVISION (integer)
31 	If defined, this must be the ghostscript version number, e.g., 601 for
32 	ghostscript 6.01.
33 
34 ******************************************************************************/
35 
36 /* Configuration management identification */
37 #ifndef lint
38 static const char
39   cm_id[] = "@(#)$Id: gdeveprn.c,v 1.25 2001/04/30 05:15:51 Martin Rel $";
40 #endif
41 
42 /*****************************************************************************/
43 
44 #ifndef _XOPEN_SOURCE
45 #define _XOPEN_SOURCE	500
46 #endif
47 
48 /* Preprocessor symbol with version-dependent default */
49 #ifndef EPRN_USE_GSTATE
50 #if !defined(GS_REVISION) || GS_REVISION >= 600
51 #define EPRN_USE_GSTATE	0
52 #else
53 #define EPRN_USE_GSTATE	1
54 #endif
55 #endif	/* !EPRN_USE_GSTATE */
56 
57 /*****************************************************************************/
58 
59 /* Special Aladdin header, must be included before <sys/types.h> on some
60    platforms (e.g., FreeBSD). */
61 #include "std.h"
62 
63 /* Standard headers */
64 #include <assert.h>
65 #include <math.h>
66 #include <string.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #ifdef EPRN_TRACE
70 #include <time.h>
71 #endif	/* EPRN_TRACE */
72 
73 /*  Ghostscript headers. With the exception of gdebug.h, these files are only
74     needed to compile eprn_forget_defaultmatrix() which needs the prototypes
75     for gs_setdefaultmatrix() (in gscoord.h) and gs_main_instance_default()
76     (in imain.h). Unfortunately and in disregard of good SE practice,
77     ghostscript's header files are not self-contained. Therefore, if this file
78     does not compile because of undefined symbols, just add include directives
79     until it does.  */
80 #include "iref.h"	/* needed by icstate.h */
81 #include "gsmemraw.h"	/* needed by icstate.h */
82 #include "gsmemory.h"	/* needed by icstate.h */
83 #include "gstypes.h"	/* needed by gsstate.h */
84 #include "gsstate.h"	/* needed by icstate.h */
85 #include "icstate.h"	/* for struct gs_context_state_s */
86 #if !defined(GS_REVISION) || GS_REVISION >= 700
87 #include "iapi.h"	/* needed by iminst.h */
88 #endif	/* GS_REVISION */
89 #include "iminst.h"	/* for struct gs_main_instance_s */
90 #include "imain.h"	/* for gs_main_instance_default() */
91 #include "gscoord.h"	/* for gs_setdefaultmatrix() */
92 #if EPRN_USE_GSTATE
93 #include "igstate.h"
94 #endif	/* EPRN_USE_GSTATE */
95 #ifdef EPRN_TRACE
96 #include "gdebug.h"
97 #endif	/* EPRN_TRACE */
98 #include "gxstdio.h"
99 
100 /* Special headers for this device */
101 #ifndef EPRN_NO_PAGECOUNTFILE
102 #include "pagecount.h"
103 #endif	/* EPRN_NO_PAGECOUNTFILE */
104 #include "gdeveprn.h"
105 
106 /*****************************************************************************/
107 
108 /* Prefix for error messages */
109 #define ERRPREF "? eprn: "
110 
111 /******************************************************************************
112 
113   Function: eprn_get_initial_matrix
114 
115   This function returns the initial matrix for the device.
116 
117   The result is based on the following parameters:
118   - eprn: default_orientation, down_shift, right_shift, soft_tumble
119   - HWResolution, MediaSize, ShowpageCount
120 
121 ******************************************************************************/
122 
eprn_get_initial_matrix(gx_device * device,gs_matrix * mptr)123 void eprn_get_initial_matrix(gx_device *device, gs_matrix *mptr)
124 {
125   eprn_Device *dev = (eprn_Device *)device;
126   float
127     /*  The following two arrays are oriented w.r.t. pixmap device space, i.e.,
128 	the index 0 refers to the x coordinate (horizontal) and the index 1 to
129 	the y coordinate (vertical) in pixmap device space. */
130     extension[2],	/* media extension in pixels */
131     pixels_per_bp[2];	/* resolution */
132   int
133     j,
134     quarters;
135 
136 #ifdef EPRN_TRACE
137   if_debug0(EPRN_TRACE_CHAR, "! eprn_get_initial_matrix()...\n");
138 #endif
139 
140   /* We need 'default_orientation' and also the margins. */
141   if (dev->eprn.code == ms_none) {
142 #ifdef EPRN_TRACE
143     if_debug0(EPRN_TRACE_CHAR,
144       "! eprn_get_initial_matrix(): code is still ms_none.\n");
145 #endif
146     if (eprn_set_page_layout(dev) != 0)
147       eprintf("  Processing can't be stopped at this point although this error "
148 	"occurred.\n");
149       /* The current function has a signature without the ability to signal
150 	 an error condition. */
151   }
152 
153   quarters = dev->eprn.default_orientation +
154     (dev->MediaSize[0] <= dev->MediaSize[1]? 0: 1);
155      /* Number of quarter-circle rotations by +90 degrees necessary to obtain
156 	default user space starting with the y axis upwards in pixmap device
157 	space.
158 	It's not documented, but 'MediaSize' is the requested "PageSize" page
159 	device parameter value and hence is to be interpreted in default (not
160 	default default!) user space. The condition above therefore tests
161 	whether landscape orientation has been requested.
162       */
163 
164   /* Soft tumble option: rotate default user space by 180 degrees on every
165      second page */
166   if (dev->eprn.soft_tumble && dev->ShowpageCount % 2 != 0) quarters += 2;
167 
168   /* Prepare auxiliary data */
169   for (j = 0; j < 2; j++) pixels_per_bp[j] = dev->HWResolution[j]/BP_PER_IN;
170   /*  'HWResolution[]' contains the standard PostScript page device parameter
171       'HWResolution' which is defined in pixels per inch with respect to
172        device space. */
173   if (quarters % 2 == 0) {
174     /* Default user space and pixmap device space agree in what is "horizontal"
175        and what is "vertical". */
176     extension[0] = dev->MediaSize[0];
177     extension[1] = dev->MediaSize[1];
178   }
179   else {
180     extension[0] = dev->MediaSize[1];
181     extension[1] = dev->MediaSize[0];
182   }
183   /* Convert from bp to pixels: */
184   for (j = 0; j < 2; j++) extension[j] *= pixels_per_bp[j];
185    /* Note that we are using the user-specified extension of the sheet, not the
186       "official" one we could obtain in most cases from 'size'. */
187 
188   switch (quarters % 4) {
189   case 0:
190     /*  The y axis of default user space points upwards in pixmap device space.
191 	The CTM is uniquely characterized by the following mappings from
192 	default user space to pixmap device space:
193 	  (0, 0)		-> (0, height in pixels)
194 	  (width in bp, 0)	-> (width in pixels, height in pixels)
195 	  (0, height in bp)	-> (0, 0)
196 	'width' and 'height' refer to the sheet's extension as seen from pixmap
197 	device space, i.e., width in pixels == extension[0] and
198 	height in pixels == extension[1].
199 
200 	From the PLR we find that the CTM is a PostScript matrix
201 	[a b c d tx ty] used for mapping user space coordinates (x, y) to
202 	device space coordinates (x', y') as follows:
203 	  x' = a*x + c*y + tx
204 	  y' = b*x + d*y + ty
205 	Ghostscript's matrix type 'gs_matrix' writes its structure components
206 	'xx' etc. in storage layout order into a PostScript matrix (see
207 	write_matrix() in iutil.c), hence we obtain by comparison with
208 	gsmatrix.h the PostScript matrix [ xx xy yx yy tx ty ].
209 	The correspondence can also be seen by comparison of the equations
210 	above with the code in gs_point_transform() in gsmatrix.c.
211 	It would, however, still be reassuring to have a corresponding
212 	statement in ghostscript's documentation.
213     */
214     gx_default_get_initial_matrix(device, mptr);
215     /*  Of course, I could also set this directly:
216 	  mptr->xx = pixels_per_bp[0];
217 	  mptr->xy = 0;
218 	  mptr->yx = 0;
219 	  mptr->yy = -pixels_per_bp[1];
220 	  mptr->tx = 0;
221 	  mptr->ty = extension[1];
222         Doing it in this way is, however, more stable against dramatic changes
223         in ghostscript.
224     */
225     break;
226   case 1:
227     /*  The y axis of default user space points to the left in pixmap device
228 	space. The CTM is uniquely characterized by the following mappings from
229 	default user space to pixmap device space:
230 	  (0, 0)		-> (width in pixels, height in pixels)
231 	  (height in bp, 0)	-> (width in pixels, 0)
232 	  (0, width in bp)	-> (0, height in pixels)
233     */
234     mptr->xx = 0;
235     mptr->xy = -pixels_per_bp[1];
236     mptr->yx = -pixels_per_bp[0];
237     mptr->yy = 0;
238     mptr->tx = extension[0];
239     mptr->ty = extension[1];
240     break;
241   case 2:
242     /*  The y axis of default user space points downwards in pixmap device
243 	space. The CTM is uniquely characterized by the following mappings from
244 	default user space to pixmap device space:
245 	  (0, 0)		-> (width in pixels, 0)
246 	  (width in bp, 0)	-> (0, 0)
247 	  (0, height in bp)	-> (width in pixels, height in pixels)
248     */
249     mptr->xx = -pixels_per_bp[0];
250     mptr->xy = 0;
251     mptr->yx = 0;
252     mptr->yy = pixels_per_bp[1];
253     mptr->tx = extension[0];
254     mptr->ty = 0;
255     break;
256   case 3:
257     /*  The y axis of default user space points to the right in pixmap device
258 	space. The CTM is uniquely characterized by the following mappings from
259 	default user space to pixmap device space:
260 	  (0, 0)		-> (0, 0)
261 	  (height in bp, 0)	-> (0, height in pixels)
262 	  (0, width in bp)	-> (width in pixels, 0)
263     */
264     mptr->xx = 0;
265     mptr->xy = pixels_per_bp[1];
266     mptr->yx = pixels_per_bp[0];
267     mptr->yy = 0;
268     mptr->tx = 0;
269     mptr->ty = 0;
270     break;
271   }
272 
273   /*  Finally, shift the device space origin to the top-left corner of the
274       printable area. I am deliberately not using the corresponding shift
275       feature in gx_device_set_margins() because it achieves its effect by
276       using the 'Margins' array which should remain at the user's disposal for
277       correcting misadjustments. In addition, gx_device_set_margins() will not
278       work correctly for quarters % 4 != 0 anyway.
279   */
280   {
281     gs_matrix translation;
282 
283     /*	Translation of pixmap device space origin by top and left margins in
284 	pixmap device space */
285     gs_make_translation(
286       -dev->eprn.right_shift*pixels_per_bp[0],
287       -dev->eprn.down_shift *pixels_per_bp[1],
288       &translation);
289 
290     /* Multiply the initial matrix from the right with the translation matrix,
291        i.e., in going from user to device space the translation will be applied
292        last. */
293     gs_matrix_multiply(mptr, &translation, mptr);
294   }
295 
296 #ifdef EPRN_TRACE
297   if_debug6(EPRN_TRACE_CHAR, "  Returning [%g %g %g %g %g %g].\n",
298     mptr->xx, mptr->xy, mptr->yx, mptr->yy, mptr->tx, mptr->ty);
299 #endif
300   return;
301 }
302 
303 /******************************************************************************
304 
305   Function: print_flags
306 
307   Print a textual description of 'flags' to the error stream.
308 
309 ******************************************************************************/
310 
print_flags(ms_MediaCode flags,const ms_Flag * user_flags)311 static void print_flags(ms_MediaCode flags, const ms_Flag *user_flags)
312 {
313   /* Non-standard flags first */
314   if (user_flags != NULL) {
315     while (user_flags->code != ms_none) {
316       if (user_flags->code & flags) {
317 	errprintf("%s", user_flags->name);
318 	flags &= ~user_flags->code;
319       }
320       user_flags++;
321     }
322   }
323 
324   /* Standard substrings */
325   if (flags & MS_SMALL_FLAG) eprintf(MS_SMALL_STRING);
326   if (flags & MS_BIG_FLAG  ) eprintf(MS_BIG_STRING);
327   if (flags & MS_EXTRA_FLAG) eprintf(MS_EXTRA_STRING);
328   flags &= ~(MS_SMALL_FLAG | MS_BIG_FLAG | MS_EXTRA_FLAG);
329 
330   /* Completeness check */
331   if (flags & ~MS_TRANSVERSE_FLAG)
332     eprintf1("0x%04X", (unsigned int)(flags & ~MS_TRANSVERSE_FLAG));
333 
334   /* Standard qualifier */
335   if (flags & MS_TRANSVERSE_FLAG) eprintf("." MS_TRANSVERSE_STRING);
336 
337   return;
338 }
339 
340 /******************************************************************************
341 
342   Function: eprn_flag_mismatch
343 
344   This routine is called if the media size can be supported for some
345   combination of flags but not for that combination which has been requested.
346   The parameter 'no_match' indicates whether these flags are supported at all
347   for any of the supported sizes or not.
348 
349   If the derived device has set a flag mismatch error reporting function, the
350   call will be passed to that function. Otherwise a general error message is
351   written through the graphics library's eprintf().
352 
353 ******************************************************************************/
354 
eprn_flag_mismatch(const struct s_eprn_Device * eprn,bool no_match)355 static void eprn_flag_mismatch(const struct s_eprn_Device *eprn,
356   bool no_match)
357 {
358   if (eprn->fmr != NULL) (*eprn->fmr)(eprn, no_match);
359   else {
360     const char *epref = eprn->CUPS_messages? CUPS_ERRPREF: "";
361 
362     eprintf2("%s" ERRPREF "The %s does not support ",
363       epref, eprn->cap->name);
364     if (eprn->desired_flags == 0) eprintf("an empty set of media flags");
365     else {
366       eprintf("the \"");
367       print_flags(eprn->desired_flags, eprn->flag_desc);
368       eprintf("\" flag(s)");
369     }
370     eprintf1("\n%s  (ignoring presence or absence of \"", epref);
371     {
372       ms_MediaCode optional = MS_TRANSVERSE_FLAG;
373       if (eprn->optional_flags != NULL) {
374 	const ms_MediaCode *of = eprn->optional_flags;
375 	while (*of != ms_none) optional |= *of++;
376       }
377       print_flags(optional, eprn->flag_desc);
378     }
379     eprintf("\") for ");
380     if (no_match) eprintf("any"); else eprintf("this");
381     eprintf(" page size.\n");
382   }
383 
384   return;
385 }
386 
387 /******************************************************************************
388 
389   Function: better_flag_match
390 
391   This function returns true iff the flags in 'new_code' match the requested
392   flags (strictly) better than those in 'old_code'.
393 
394 ******************************************************************************/
395 
better_flag_match(ms_MediaCode desired,const ms_MediaCode * optional,ms_MediaCode old_code,ms_MediaCode new_code)396 static bool better_flag_match(ms_MediaCode desired,
397   const ms_MediaCode *optional, ms_MediaCode old_code, ms_MediaCode new_code)
398 {
399   ms_MediaCode
400     old_diff,	/* difference between old flags and desired flags */
401     new_diff;	/* difference between new flags and desired flags */
402 
403   /* Ignore the size information */
404   old_code = ms_flags(old_code);
405   new_code = ms_flags(new_code);
406 
407   /* Determine differences to desired flags */
408   old_diff = old_code ^ desired;
409   new_diff = new_code ^ desired;
410 
411   /* Check for exact matches */
412   if (old_diff == 0) return false;
413   if (new_diff == 0) return true;
414 
415   /* Is the difference at most MS_TRANSVERSE_FLAG? */
416   old_diff = old_diff & ~MS_TRANSVERSE_FLAG;
417   new_diff = new_diff & ~MS_TRANSVERSE_FLAG;
418   if (old_diff == 0) return false;
419   if (new_diff == 0) return true;
420 
421   /* Loop over the remaining optional flags */
422   if (optional != NULL) {
423     const ms_MediaCode *opt = optional;
424 
425     while (*opt != ms_none) {
426       old_diff = old_diff & ~*opt;
427       new_diff = new_diff & ~*opt;
428       if (old_diff == 0) {
429 	if (new_diff != 0) return false;
430 	/* At this point both are matches at the same level of optional flags.
431 	   Now look for the last preceding flag in which they differ. */
432 	{
433 	  ms_MediaCode diff = ms_flags(old_code ^ new_code);
434 	  while (optional < opt && (diff & *opt) == 0) opt--;
435 	  if ((diff & *opt) == 0) {
436 	    if ((diff & MS_TRANSVERSE_FLAG) == 0) return false;
437 	    /* old and new differ in MS_TRANSVERSE_FLAG */
438 	    return (new_code & MS_TRANSVERSE_FLAG) ==
439 	      (desired & MS_TRANSVERSE_FLAG);
440 	  }
441 	  return (new_code & *opt) == (desired & *opt);
442 	}
443       }
444       if (new_diff == 0) return true;
445       opt++;
446     }
447   }
448 
449   return false;	/* Both codes are mismatches at this point */
450 }
451 
452 /******************************************************************************
453 
454   Function: flag_match
455 
456   This function returns true iff 'code' is an acceptable match for the flag
457   request.
458 
459 ******************************************************************************/
460 
flag_match(ms_MediaCode desired,const ms_MediaCode * optional,ms_MediaCode code)461 static bool flag_match(ms_MediaCode desired,
462   const ms_MediaCode *optional, ms_MediaCode code)
463 {
464   code = (ms_flags(code) ^ desired) & ~MS_TRANSVERSE_FLAG;
465   if (code == 0) return true;
466 
467   if (optional == NULL) return false;
468 
469   while (*optional != ms_none && code != 0) {
470     code = code & ~*optional;
471     optional++;
472   }
473 
474   return code == 0;
475 }
476 
477 /******************************************************************************
478 
479   Function: eprn_set_page_layout
480 
481   This function determines media size, sheet orientation in pixmap device space,
482   the orientation of default user space, and the imageable area. It should be
483   called whenever the page device parameters "PageSize" and "LeadingEdge",
484   the media flags, or the page descriptions have been changed.
485 
486   The function returns zero on success and a non-zero value otherwise.
487   In the latter case, an error message has been issued. This can only
488   occur if the media size is not supported with the flags requested.
489 
490   On success, the following variables in the device structure are consistent:
491   width, height, MediaSize[], HWMargins[], eprn.code, eprn.default_orientation,
492   eprn.right_shift, eprn.down_shift.
493 
494 ******************************************************************************/
495 
eprn_set_page_layout(eprn_Device * dev)496 int eprn_set_page_layout(eprn_Device *dev)
497 {
498   bool
499     no_match = true,
500      /* Are the requested flags supported for some size? */
501     landscape = dev->MediaSize[0] > dev->MediaSize[1];
502      /* It's not documented, but 'MediaSize' is the requested "PageSize" page
503 	device parameter value and hence is to be interpreted in default (not
504 	default default!) user space. */
505   const char *epref = dev->eprn.CUPS_messages? CUPS_ERRPREF: "";
506   const eprn_CustomPageDescription
507     *best_cmatch = NULL;	/* best custom page size match */
508   eprn_Eprn
509     *eprn = &dev->eprn;
510   const eprn_PageDescription
511     *best_cdmatch = NULL,     /* best custom page size match in discrete list*/
512     *best_dmatch = NULL,	/* best discrete match */
513     *pd;			/* loop variable */
514   float
515     /* Page width and height in bp with w <= h (in a moment): */
516     w = dev->MediaSize[0],
517     h = dev->MediaSize[1],
518     /* pixmap device space margins in bp (canonical order): */
519     margins[4];
520   int
521     quarters;
522   ms_MediaCode
523     desired = eprn->desired_flags;
524 
525 #ifdef EPRN_TRACE
526   if_debug3(EPRN_TRACE_CHAR,
527     "! eprn_set_page_layout(): PageSize = [%.0f %.0f], "
528       "desired_flags = 0x%04X.\n",
529     dev->MediaSize[0], dev->MediaSize[1], (unsigned int)desired);
530 #endif
531 
532   /* Ensure w <= h */
533   if (w > h) {
534     float temp;
535     temp = w; w = h; h = temp;
536     /* This has effectively split 'MediaSize[]' into 'w', 'h' and 'landscape'.
537      */
538   }
539 
540   /* Initialization of primary return value */
541   eprn->code = ms_none;
542 
543   /* Put the LeadingEdge value into the desired flag pattern if it's set */
544   if (eprn->leading_edge_set) {
545     if (eprn->default_orientation % 2 == 0)	/* true on short edge first */
546       desired &= ~MS_TRANSVERSE_FLAG;
547     else
548       desired |= MS_TRANSVERSE_FLAG;
549   }
550 
551   /* Find best match in discrete sizes */
552   if (eprn->media_overrides == NULL) pd = eprn->cap->sizes;
553   else pd = eprn->media_overrides;
554   while (pd->code != ms_none) {
555     const ms_SizeDescription *ms = ms_find_size_from_code(pd->code);
556     if (ms->dimen[0] > 0.0 /* ignore variable sizes */ &&
557 	fabs(w - ms->dimen[0])  <= 5.0 &&
558 	fabs(h - ms->dimen[1]) <= 5.0) {
559        /* The size does match at 5 bp tolerance. This value has been chosen
560 	  arbitrarily to be equal to PostScript's PageSize matching tolerance
561 	  during media selection. The tolerance should really be that at which
562 	  the printer in question distinguishes between sizes or smaller than
563 	  that in order to at least prevent printing on unsupported sizes.
564         */
565       if (best_dmatch == NULL ||
566 	  better_flag_match(desired, eprn->optional_flags, best_dmatch->code,
567 	    pd->code))
568 	best_dmatch = pd;
569       if (flag_match(desired, eprn->optional_flags, pd->code))
570 	no_match = false;
571     }
572     pd++;
573   }
574 
575   /* Next find the best match among the custom size descriptions */
576   if (eprn->cap->custom != NULL) {
577     const eprn_CustomPageDescription *cp = eprn->cap->custom;
578 
579     /* First check whether the size is in the supported range */
580     while (cp->width_max > 0.0) {
581       if (cp->width_min  <= w && w <= cp->width_max &&
582 	  cp->height_min <= h && h <= cp->height_max) {
583 	/* The size does match. */
584 	if (best_cmatch == NULL ||
585 	    better_flag_match(desired, eprn->optional_flags, best_cmatch->code,
586 	      cp->code))
587 	  best_cmatch = cp;
588 	if (eprn->media_overrides == NULL &&
589 	    flag_match(desired, eprn->optional_flags, cp->code))
590 	  no_match = false;
591       }
592       cp++;
593     }
594 
595     /* If we have read a media configuration file, the flags to be matched
596        must be sought in 'media_overrides'. */
597     if (best_cmatch != NULL && eprn->media_overrides != NULL) {
598       for (pd = eprn->media_overrides; pd->code != ms_none; pd++) {
599 	if (ms_without_flags(pd->code) == ms_CustomPageSize) {
600 	  if (best_cdmatch == NULL ||
601 	      better_flag_match(desired, eprn->optional_flags,
602 		best_cdmatch->code, pd->code))
603 	    best_cdmatch = pd;
604 	  if (flag_match(desired, eprn->optional_flags, pd->code))
605 	    no_match = false;
606 	}
607       }
608     }
609   }
610 
611   /*  Now the 'best_*match' variables indicate for each of the categories of
612       page descriptions to which extent the size is supported at all (non-NULL
613       value) and what the best flag match in the category is. Here we now check
614       for NULL values, i.e., size matches. */
615   if (best_dmatch == NULL) {
616     /* No discrete match */
617     if (best_cmatch == NULL) {
618       /* No match at all. */
619       eprintf3("%s" ERRPREF
620 	"This document requests a page size of %.0f x %.0f bp.\n",
621 	   epref, dev->MediaSize[0], dev->MediaSize[1]);
622       if (eprn->cap->custom == NULL) {
623 	/* The printer does not support custom page sizes */
624 	if (eprn->media_overrides != NULL)
625 	  eprintf1(
626 	    "%s  The media configuration file does not contain an entry for "
627 	      " this size.\n", epref);
628 	else
629 	  eprintf2("%s  This size is not supported by the %s.\n",
630 	    epref, eprn->cap->name);
631       }
632       else
633 	eprintf3(
634 	  "%s  This size is not supported as a discrete size and it exceeds "
635 	    "the\n"
636 	  "%s  custom page size limits for the %s.\n",
637 	  epref, epref, eprn->cap->name);
638       return -1;
639     }
640     if (eprn->media_overrides != NULL && best_cdmatch == NULL) {
641       eprintf6("%s" ERRPREF
642 	"This document requests a page size of %.0f x %.0f bp\n"
643 	"%s  but there is no entry for this size in the "
644 	  "media configuration file\n"
645 	"%s  %s.\n",
646 	epref, dev->MediaSize[0], dev->MediaSize[1], epref, epref,
647 	eprn->media_file);
648       return -1;
649     }
650   }
651   /* Now we have: best_dmatch != NULL || best_cmatch != NULL &&
652      (eprn->media_overrides == NULL || best_cdmatch != NULL). */
653 
654   /* Find a flag match among the size matches found so far */
655   {
656     ms_MediaCode custom_code = ms_none;
657       /* best custom page size match (either from cmatch or dcmatch) */
658     if (best_cmatch != NULL &&
659 	(eprn->media_overrides == NULL || best_cdmatch != NULL))
660       custom_code = (eprn->media_overrides == NULL?
661 	best_cmatch->code: best_cdmatch->code);
662 
663     if (best_dmatch == NULL ||
664 	best_cmatch != NULL &&
665 	  better_flag_match(desired, eprn->optional_flags, best_dmatch->code,
666 	    custom_code)) {
667       if (flag_match(desired, eprn->optional_flags, custom_code)) {
668 	if (eprn->media_overrides == NULL) {
669 	  eprn->code = best_cmatch->code;
670 	  margins[0] = best_cmatch->left;
671 	  margins[1] = best_cmatch->bottom;
672 	  margins[2] = best_cmatch->right;
673 	  margins[3] = best_cmatch->top;
674 	}
675 	else {
676 	  eprn->code = best_cdmatch->code;
677 	  margins[0] = best_cdmatch->left;
678 	  margins[1] = best_cdmatch->bottom;
679 	  margins[2] = best_cdmatch->right;
680 	  margins[3] = best_cdmatch->top;
681 	}
682       }
683     }
684     else {
685       if (flag_match(desired, eprn->optional_flags, best_dmatch->code)) {
686 	eprn->code = best_dmatch->code;
687 	margins[0] = best_dmatch->left;
688 	margins[1] = best_dmatch->bottom;
689 	margins[2] = best_dmatch->right;
690 	margins[3] = best_dmatch->top;
691       }
692     }
693   }
694   /* If we've found a match, 'code' is no longer 'ms_none'. */
695   if (eprn->code == ms_none) {
696     eprn_flag_mismatch(eprn, no_match);
697     return -1;
698   }
699 
700   /* Adapt the orientation of default default user space if not prescribed */
701   if (!eprn->leading_edge_set) {
702     if (eprn->code & MS_TRANSVERSE_FLAG) eprn->default_orientation = 3;
703      /* This leads to 0 if landscape orientation is requested. */
704     else eprn->default_orientation = 0;
705   }
706 
707   /*
708     Now 'eprn->default_orientation % 2' describes the sheet's orientation in
709     pixmap device space. If this does not agree with the width and height
710     values in the device instance, we'll have to adapt them.
711     This is only necessary if there is a significant difference between width
712     and height.
713    */
714   if (fabs(w - h) > 1 /* arbitrary */ &&
715     (eprn->default_orientation % 2 == 0) !=
716 	(dev->width/dev->HWResolution[0] <= dev->height/dev->HWResolution[1])) {
717     bool reallocate = false;
718 
719 #ifdef EPRN_TRACE
720     if_debug0(EPRN_TRACE_CHAR,
721       "! eprn_set_page_layout(): width-height change is necessary.\n");
722 #endif
723 
724     /* Free old storage if the device is open */
725     if (dev->is_open) {
726 #ifdef EPRN_TRACE
727       if_debug0(EPRN_TRACE_CHAR, "! eprn_set_page_layout(): Device is open.\n");
728 #endif
729       reallocate = true;
730        /* One could try and call the allocation/reallocation routines of the
731 	  prn device directly, but they are not available in older ghostscript
732 	  versions and this method is safer anyway because it relies on a
733 	  documented API. */
734       gdev_prn_close((gx_device *)dev);		/* ignore the result */
735     }
736 
737     /*  Now set width and height via gx_device_set_media_size(). This function
738 	sets 'MediaSize[]', 'width', and 'height' based on the assumption that
739 	default user space has a y axis which is vertical in pixmap device
740 	space. This may be wrong and we have to fix it. Because fixing
741 	'MediaSize[]' is simpler, gx_device_set_media_size() is called such
742 	that it gives the correct values for 'width' and 'height'. */
743     if (eprn->default_orientation % 2 == 0) {
744       /* portrait orientation of the sheet in pixmap device space */
745       gx_device_set_media_size((gx_device *)dev, w, h);
746       if (landscape) {
747 	dev->MediaSize[0] = h;
748 	dev->MediaSize[1] = w;
749       }
750     }
751     else {
752       /* landscape orientation in pixmap device space (transverse) */
753       gx_device_set_media_size((gx_device *)dev, h, w);
754       if (!landscape) {
755 	dev->MediaSize[0] = w;
756 	dev->MediaSize[1] = h;
757       }
758     }
759 
760     /* If the device is/was open, reallocate storage */
761     if (reallocate) {
762       int rc;
763 
764       rc = gdev_prn_open((gx_device *)dev);
765       if (rc < 0) {
766 	eprintf2("%s" ERRPREF
767 	  "Failure of gdev_prn_open(), code is %d.\n",
768 	  epref, rc);
769 	return rc;
770       }
771     }
772   }
773 
774   /* Increase the bottom margin for coloured modes except if it is exactly
775      zero */
776   if (eprn->colour_model != eprn_DeviceGray && margins[1] != 0.0)
777     margins[1] += eprn->cap->bottom_increment;
778 
779   /* Number of +90-degree rotations needed for default user space: */
780   quarters = eprn->default_orientation;
781   if (landscape) quarters = (quarters + 1)%4;
782 
783   /* Store the top and left margins in the device structure for use by
784      eprn_get_initial_matrix() and set the margins of the printable area if
785      we may.
786      gx_device_set_margins() (see gsdevice.c) copies the margins[] array to
787      HWMargins[] which is presumably to be interpreted in default user space
788      (see gs_initclip() in gspath.c), and if its second argument is true it
789      also modifies the offset variable Margins[]. The first property means
790      that gx_device_set_margins() can only be used if default user space and
791      pixmap device space have the same "up" direction, and the second
792      appropriates a parameter which is intended for the user.
793   */
794   if (eprn->keep_margins) {
795     eprn->down_shift  = dev->HWMargins[3 - quarters];
796     eprn->right_shift = dev->HWMargins[(4 - quarters)%4];
797   }
798   else {
799     int j;
800 
801     eprn->down_shift  = margins[3];
802     eprn->right_shift = margins[0];
803 
804     if (quarters != 0) {
805        /* The "canonical margin order" for ghostscript is left, bottom, right,
806 	  top. Hence for, e.g., a +90-degree rotation ('quarters' is 1) of
807 	  default user space with respect to pixmap device space the left
808 	  margin (index 0) in default user space is actually the bottom margin
809 	  (index 1) in pixmap device space, the bottom margin is the right one,
810 	  etc.
811         */
812       for (j = 0; j < 4; j++) dev->HWMargins[j] = margins[(j+quarters)%4];
813       /* 'HWMargins[]' is in bp (see gxdevcli.h) */
814     }
815     else {
816       /* Convert to inches */
817       for (j = 0; j < 4; j++) margins[j] /= BP_PER_IN;
818 
819       gx_device_set_margins((gx_device *)dev, margins, false);
820        /* Of course, I could set HWMargins[] directly also in this case. This
821 	  way is however less prone to break on possible future incompatible
822 	  changes to ghostscript and it covers the most frequent case (portrait
823 	  and short edge first). */
824     }
825   }
826 
827   return 0;
828 }
829 
830 /******************************************************************************
831 
832   Function: eprn_init_device
833 
834   This function sets 'cap' to 'desc' and all device parameters which are
835   modified through the put_params routines to default values. The resolution is
836   left at its old value (and don't ask me why or I'll start to whimper). If the
837   device is open when this function is called the device will be closed
838   afterwards.
839 
840   'desc' may not be NULL.
841 
842 ******************************************************************************/
843 
eprn_init_device(eprn_Device * dev,const eprn_PrinterDescription * desc)844 void eprn_init_device(eprn_Device *dev, const eprn_PrinterDescription *desc)
845 {
846   eprn_Eprn *eprn = &dev->eprn;
847   int j;
848   float hres, vres;
849 
850   if (dev->is_open) gs_closedevice((gx_device *)dev);
851 
852   assert(desc != NULL);
853   eprn->cap = desc;
854   eprn_set_media_data(dev, NULL, 0);
855 
856   /* The media flags are retained because they have not been prescribed by the
857      user directly in contact with eprn but are completely under the control
858      of the derived device. */
859 
860   eprn->code = ms_none;
861   eprn->leading_edge_set = false;
862   eprn->right_shift = 0;
863   eprn->down_shift = 0;
864   eprn->keep_margins = false;
865   eprn->soft_tumble = false;
866   for (j = 0; j < 4; j++) dev->HWMargins[j] = 0;
867 
868   /* Set to default colour state, ignoring request failures */
869   eprn->colour_model = eprn_DeviceGray;
870   eprn->black_levels = 2;
871   eprn->non_black_levels = 0;
872   eprn->intensity_rendering = eprn_IR_halftones;
873   hres = dev->HWResolution[0];
874   vres = dev->HWResolution[1];
875   eprn_check_colour_info(desc->colour_info, &eprn->colour_model,
876       &hres, &vres, &eprn->black_levels, &eprn->non_black_levels);
877 
878   if (eprn->pagecount_file != NULL) {
879     gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->pagecount_file, strlen(eprn->pagecount_file) + 1,
880       sizeof(char), "eprn_init_device");
881     eprn->pagecount_file = NULL;
882   }
883 
884   eprn->media_position_set = false;
885 
886   return;
887 }
888 
889 /******************************************************************************
890 
891   Function: eprn_set_media_flags
892 
893 ******************************************************************************/
894 
eprn_set_media_flags(eprn_Device * dev,ms_MediaCode desired,const ms_MediaCode * optional)895 void eprn_set_media_flags(eprn_Device *dev, ms_MediaCode desired,
896   const ms_MediaCode *optional)
897 {
898   dev->eprn.code = ms_none;
899 
900   dev->eprn.desired_flags = desired;
901   dev->eprn.optional_flags = optional;
902 
903   return;
904 }
905 
906 /******************************************************************************
907 
908   Function: eprn_open_device
909 
910   This function "opens" the device. According to Drivers.htm, the 'open_device'
911   functions are called before any output is sent to the device, and they must
912   ensure that the device instance is valid, possibly by doing suitable
913   initialization.
914 
915   This particular implementation also checks whether the requested page size
916   is supported by the printer. This discovery must, unfortunately, be
917   delayed until the moment this function is called. Note that this also implies
918   that various eprn parameters depending on the page size (e.g., 'eprn.code')
919   can be relied upon to have valid values only after the device has been
920   successfully opened. The same applies to rendering parameters.
921 
922   This function also opens the parts defined by base classes.
923 
924   The function returns zero on success and a ghostscript error value otherwise.
925 
926 ******************************************************************************/
927 
eprn_open_device(gx_device * device)928 int eprn_open_device(gx_device *device)
929 {
930   eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;
931   const char *epref = eprn->CUPS_messages? CUPS_ERRPREF: "";
932   int rc;
933 
934 #ifdef EPRN_TRACE
935   if_debug0(EPRN_TRACE_CHAR, "! eprn_open_device()...\n");
936 #endif
937 
938   /* Checks on page size and determination of derived values */
939   if (eprn_set_page_layout((eprn_Device *)device) != 0)
940     return_error(gs_error_rangecheck);
941 
942   /* Check the rendering parameters */
943   if (eprn_check_colour_info(eprn->cap->colour_info, &eprn->colour_model,
944       &device->HWResolution[0], &device->HWResolution[1],
945       &eprn->black_levels, &eprn->non_black_levels) != 0) {
946     gs_param_string str;
947 
948     eprintf1("%s" ERRPREF "The requested combination of colour model (",
949       epref);
950     str.size = 0;
951     if (eprn_get_string(eprn->colour_model, eprn_colour_model_list, &str) != 0)
952       assert(0); /* Bug. No harm on NDEBUG because I've just set the size. */
953     errwrite((const char *)str.data, str.size * sizeof(str.data[0]));
954     eprintf7("),\n"
955       "%s  resolution (%gx%g ppi) and intensity levels (%d, %d) is\n"
956       "%s  not supported by the %s.\n",
957       epref, device->HWResolution[0], device->HWResolution[1],
958       eprn->black_levels, eprn->non_black_levels, epref, eprn->cap->name);
959     return_error(gs_error_rangecheck);
960   }
961 
962   /* Initialization for colour rendering */
963   if (device->color_info.num_components == 4) {
964     /* Native colour space is 'DeviceCMYK' */
965     set_dev_proc(device, map_rgb_color, NULL);
966 
967     if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
968       set_dev_proc(device, map_cmyk_color, &eprn_map_cmyk_color_max);
969     else if (device->color_info.max_gray > 1 || device->color_info.max_color > 1)
970       set_dev_proc(device, map_cmyk_color, &eprn_map_cmyk_color_flex);
971     else
972       set_dev_proc(device, map_cmyk_color, &eprn_map_cmyk_color);
973 
974     if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
975       set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K_max);
976     else if (device->color_info.max_gray > 1 || device->color_info.max_color > 1)
977       set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K_flex);
978     else
979       set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K);
980 
981   }
982   else {
983     set_dev_proc(device, map_cmyk_color, NULL);
984 
985     if (eprn->colour_model == eprn_DeviceRGB) {
986       if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
987 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_RGB_max);
988       else if (device->color_info.max_color > 1)
989 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_RGB_flex);
990       else
991 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_RGB);
992     } else {
993       if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
994 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K_max);
995       else if (device->color_info.max_gray > 1 || device->color_info.max_color > 1)
996 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K_flex);
997       else
998 	set_dev_proc(device, map_rgb_color, &eprn_map_rgb_color_for_CMY_or_K);
999     }
1000   }
1001   eprn->output_planes = eprn_bits_for_levels(eprn->black_levels) +
1002     3 * eprn_bits_for_levels(eprn->non_black_levels);
1003 
1004 #if !defined(GS_REVISION) || GS_REVISION >= 600
1005   /*  According to my understanding, the following call should be superfluous
1006       (because the colour mapping functions may not be called while the device
1007       is closed) and I am also not aware of any situation where it does make a
1008       difference. It shouldn't do any harm, though, and I feel safer with it :-)
1009   */
1010   gx_device_decache_colors(device);
1011 #endif
1012 
1013 #ifndef EPRN_NO_PAGECOUNTFILE
1014   /* Read the page count value */
1015   if (eprn->pagecount_file != NULL) {
1016     unsigned long count;
1017     if (pcf_getcount(eprn->pagecount_file, &count) == 0)
1018       device->PageCount = count;
1019        /* unsigned to signed. The C standard permits
1020 	  an implementation to generate an overflow indication if the value is
1021 	  too large. I consider this to mean that the type of 'PageCount' is
1022 	  inappropriate :-). Note that eprn does not use 'PageCount' for
1023 	  updating the file. */
1024     else {
1025       /* pcf_getcount() has issued an error message. */
1026       eprintf(
1027         "  No further attempts will be made to access the page count file.\n");
1028       gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->pagecount_file, strlen(eprn->pagecount_file) + 1,
1029 	sizeof(char), "eprn_open_device");
1030       eprn->pagecount_file = NULL;
1031     }
1032   }
1033 #endif	/* !EPRN_NO_PAGECOUNTFILE */
1034 
1035   /* Open the "prn" device part */
1036   if ((rc = gdev_prn_open(device)) != 0) return rc;
1037 
1038   /* Just in case a previous open call failed in a derived device (note that
1039      'octets_per_line' is still the same as then): */
1040   if (eprn->scan_line.str != NULL)
1041     gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->scan_line.str, eprn->octets_per_line, sizeof(eprn_Octet),
1042       "eprn_open_device");
1043   if (eprn->next_scan_line.str != NULL) {
1044     gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->next_scan_line.str, eprn->octets_per_line, sizeof(eprn_Octet),
1045       "eprn_open_device");
1046     eprn->next_scan_line.str = NULL;
1047   }
1048 
1049   /* Calls which might depend on prn having been initialized */
1050   eprn->octets_per_line = gdev_prn_raster((gx_device_printer *)device);
1051   eprn->scan_line.str = (eprn_Octet *) gs_malloc(gs_lib_ctx_get_non_gc_memory_t(), eprn->octets_per_line,
1052     sizeof(eprn_Octet), "eprn_open_device");
1053   if (eprn->intensity_rendering == eprn_IR_FloydSteinberg) {
1054     eprn->next_scan_line.str = (eprn_Octet *) gs_malloc(gs_lib_ctx_get_non_gc_memory_t(), eprn->octets_per_line,
1055       sizeof(eprn_Octet), "eprn_open_device");
1056     if (eprn->next_scan_line.str == NULL && eprn->scan_line.str != NULL) {
1057       gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->scan_line.str, eprn->octets_per_line, sizeof(eprn_Octet),
1058 	"eprn_open_device");
1059       eprn->scan_line.str = NULL;
1060     }
1061   }
1062   if (eprn->scan_line.str == NULL) {
1063     eprintf1("%s" ERRPREF
1064       "Memory allocation failure from gs_malloc() in eprn_open_device().\n",
1065       epref);
1066     return_error(gs_error_VMerror);
1067   }
1068 
1069   return rc;
1070 }
1071 
1072 /******************************************************************************
1073 
1074   Function: eprn_close_device
1075 
1076 ******************************************************************************/
1077 
eprn_close_device(gx_device * device)1078 int eprn_close_device(gx_device *device)
1079 {
1080   eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;
1081 
1082 #ifdef EPRN_TRACE
1083   if_debug0(EPRN_TRACE_CHAR, "! eprn_close_device()...\n");
1084 #endif
1085 
1086   if (eprn->scan_line.str != NULL) {
1087     gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->scan_line.str, eprn->octets_per_line, sizeof(eprn_Octet),
1088       "eprn_close_device");
1089     eprn->scan_line.str = NULL;
1090   }
1091   if (eprn->next_scan_line.str != NULL) {
1092     gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->next_scan_line.str, eprn->octets_per_line, sizeof(eprn_Octet),
1093       "eprn_close_device");
1094     eprn->next_scan_line.str = NULL;
1095   }
1096 
1097   return gdev_prn_close(device);
1098 }
1099 
1100 /******************************************************************************
1101 
1102   Function: eprn_forget_defaultmatrix
1103 
1104   This function tells the ghostscript kernel to forget the default matrix,
1105   i.e., to consult the get_initial_matrix device procedure the next time the
1106   default CTM is needed.
1107 
1108 ******************************************************************************/
1109 
eprn_forget_defaultmatrix(void)1110 static void eprn_forget_defaultmatrix(void)
1111 {
1112 #if EPRN_USE_GSTATE
1113   /* Old ghostscript versions */
1114   gs_setdefaultmatrix(igs, NULL);
1115 #else
1116   gs_setdefaultmatrix(get_minst_from_memory(gs_lib_ctx_get_non_gc_memory_t())->i_ctx_p->pgs, NULL);
1117 #endif
1118 
1119   return;
1120 }
1121 
1122 /******************************************************************************
1123 
1124   Function: eprn_output_page
1125 
1126   This function is a wrapper for gdev_prn_output_page() in order to catch the
1127   number of pages printed and to initialize the eprn_get_planes() API.
1128 
1129 ******************************************************************************/
1130 
eprn_output_page(gx_device * dev,int num_copies,int flush)1131 int eprn_output_page(gx_device *dev, int num_copies, int flush)
1132 {
1133   eprn_Eprn *eprn = &((eprn_Device *)dev)->eprn;
1134   int rc;
1135 
1136 #ifdef EPRN_TRACE
1137   clock_t start_time = clock();
1138   if_debug0(EPRN_TRACE_CHAR, "! eprn_output_page()...\n");
1139 #endif
1140 
1141   /* Initialize eprn_get_planes() data */
1142   eprn->next_y = 0;
1143   if (eprn->intensity_rendering == eprn_IR_FloydSteinberg) {
1144     /* Fetch the first line and store it in 'next_scan_line'. */
1145     if (eprn_fetch_scan_line((eprn_Device *)dev, &eprn->next_scan_line) == 0)
1146       eprn->next_y++;
1147   }
1148 
1149   /* Ship out */
1150   rc = gdev_prn_output_page(dev, num_copies, flush);
1151 
1152   /*  CUPS page accounting message. The CUPS documentation is not perfectly
1153       clear on whether one should generate this message before printing a page
1154       or after printing has been successful. The rasterto* filters generate it
1155       before sending the page, but as the scheduler uses these messages for
1156       accounting, this seems unfair.
1157   */
1158   if (rc == 0 && eprn->CUPS_accounting)
1159     eprintf2("PAGE: %ld %d\n", dev->ShowpageCount, num_copies);
1160     /* The arguments are the number of the page, starting at 1, and the number
1161        of copies of that page. */
1162 
1163 #ifndef EPRN_NO_PAGECOUNTFILE
1164   /* On success, record the number of pages printed */
1165   if (rc == 0 && eprn->pagecount_file != NULL) {
1166     assert(num_copies > 0);	/* because of signed/unsigned */
1167     if (pcf_inccount(eprn->pagecount_file, num_copies) != 0) {
1168       /* pcf_inccount() has issued an error message. */
1169       eprintf(
1170 	"  No further attempts will be made to access the page count file.\n");
1171       gs_free(gs_lib_ctx_get_non_gc_memory_t(), eprn->pagecount_file, strlen(eprn->pagecount_file) + 1,
1172 	sizeof(char), "eprn_output_page");
1173       eprn->pagecount_file = NULL;
1174     }
1175   }
1176 #endif	/* !EPRN_NO_PAGECOUNTFILE */
1177 
1178   /* If soft tumble has been demanded, ensure the get_initial_matrix procedure
1179      is consulted for the next page */
1180   if (eprn->soft_tumble) eprn_forget_defaultmatrix();
1181 
1182 #ifdef EPRN_TRACE
1183   if_debug1(EPRN_TRACE_CHAR, "! eprn_output_page() terminates after %f s.\n",
1184     ((float)(clock() - start_time))/CLOCKS_PER_SEC);
1185 #endif
1186 
1187   return rc;
1188 }
1189