1 /* Copyright (C) 1997, 2000 artofcode LLC.  All rights reserved.
2 
3   This program is free software; you can redistribute it and/or modify it
4   under the terms of the GNU General Public License as published by the
5   Free Software Foundation; either version 2 of the License, or (at your
6   option) any later version.
7 
8   This program is distributed in the hope that it will be useful, but
9   WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   General Public License for more details.
12 
13   You should have received a copy of the GNU General Public License along
14   with this program; if not, write to the Free Software Foundation, Inc.,
15   59 Temple Place, Suite 330, Boston, MA, 02111-1307.
16 
17 */
18 
19 /*$Id: gdevpdfd.c,v 1.16.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
20 /* Path drawing procedures for pdfwrite driver */
21 #include "math_.h"
22 #include "gx.h"
23 #include "gxdevice.h"
24 #include "gxfixed.h"
25 #include "gxistate.h"
26 #include "gxpaint.h"
27 #include "gzpath.h"
28 #include "gzcpath.h"
29 #include "gdevpdfx.h"
30 #include "gdevpdfg.h"
31 
32 /* ---------------- Drawing ---------------- */
33 
34 /* Fill a rectangle. */
35 int
gdev_pdf_fill_rectangle(gx_device * dev,int x,int y,int w,int h,gx_color_index color)36 gdev_pdf_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
37 			gx_color_index color)
38 {
39     gx_device_pdf *pdev = (gx_device_pdf *) dev;
40     int code;
41 
42     /* Make a special check for the initial fill with white, */
43     /* which shouldn't cause the page to be opened. */
44     if (color == pdev->white && !is_in_page(pdev))
45 	return 0;
46     code = pdf_open_page(pdev, PDF_IN_STREAM);
47     if (code < 0)
48 	return code;
49     /* Make sure we aren't being clipped. */
50     pdf_put_clip_path(pdev, NULL);
51     pdf_set_pure_color(pdev, color, &pdev->fill_color,
52 		       &psdf_set_fill_color_commands);
53     pprintd4(pdev->strm, "%d %d %d %d re f\n", x, y, w, h);
54     return 0;
55 }
56 
57 /* ---------------- Path drawing ---------------- */
58 
59 /* ------ Vector device implementation ------ */
60 
61 private int
pdf_setlinewidth(gx_device_vector * vdev,floatp width)62 pdf_setlinewidth(gx_device_vector * vdev, floatp width)
63 {
64     /* Acrobat Reader doesn't accept negative line widths. */
65     return psdf_setlinewidth(vdev, fabs(width));
66 }
67 
68 private int
pdf_setfillcolor(gx_device_vector * vdev,const gx_drawing_color * pdc)69 pdf_setfillcolor(gx_device_vector * vdev, const gx_drawing_color * pdc)
70 {
71     gx_device_pdf *const pdev = (gx_device_pdf *)vdev;
72 
73     return pdf_set_drawing_color(pdev, pdc, &pdev->fill_color,
74 				 &psdf_set_fill_color_commands);
75 }
76 
77 private int
pdf_setstrokecolor(gx_device_vector * vdev,const gx_drawing_color * pdc)78 pdf_setstrokecolor(gx_device_vector * vdev, const gx_drawing_color * pdc)
79 {
80     gx_device_pdf *const pdev = (gx_device_pdf *)vdev;
81 
82     return pdf_set_drawing_color(pdev, pdc, &pdev->stroke_color,
83 				 &psdf_set_stroke_color_commands);
84 }
85 
86 private int
pdf_dorect(gx_device_vector * vdev,fixed x0,fixed y0,fixed x1,fixed y1,gx_path_type_t type)87 pdf_dorect(gx_device_vector * vdev, fixed x0, fixed y0, fixed x1, fixed y1,
88 	   gx_path_type_t type)
89 {
90     fixed xmax = int2fixed(vdev->width), ymax = int2fixed(vdev->height);
91     fixed xmin = 0, ymin = 0;
92 
93     /*
94      * If we're doing a stroke operation, expand the checking box by the
95      * stroke width.
96      */
97     if (type & gx_path_type_stroke) {
98 	double w = vdev->state.line_params.half_width;
99 	double xw = w * (fabs(vdev->state.ctm.xx) + fabs(vdev->state.ctm.yx));
100 	double yw = w * (fabs(vdev->state.ctm.xy) + fabs(vdev->state.ctm.yy));
101 
102 	xmin = -(float2fixed(xw) + fixed_1);
103 	xmax -= xmin;
104 	ymin = -(float2fixed(yw) + fixed_1);
105 	ymax -= ymin;
106     }
107     if (!(type & gx_path_type_clip) &&
108 	(x0 > xmax || x1 < xmin || y0 > ymax || y1 < ymin ||
109 	 x0 > x1 || y0 > y1)
110 	)
111 	return 0;		/* nothing to fill or stroke */
112     /*
113      * Clamp coordinates to avoid tripping over Acrobat Reader's limit
114      * of 32K on user coordinate values.
115      */
116     if (x0 < xmin)
117 	x0 = xmin;
118     if (x1 > xmax)
119 	x1 = xmax;
120     if (y0 < ymin)
121 	y0 = ymin;
122     if (y1 > ymax)
123 	y1 = ymax;
124     return psdf_dorect(vdev, x0, y0, x1, y1, type);
125 }
126 
127 private int
pdf_endpath(gx_device_vector * vdev,gx_path_type_t type)128 pdf_endpath(gx_device_vector * vdev, gx_path_type_t type)
129 {
130     return 0;			/* always handled by caller */
131 }
132 
133 const gx_device_vector_procs pdf_vector_procs = {
134 	/* Page management */
135     NULL,
136 	/* Imager state */
137     pdf_setlinewidth,
138     psdf_setlinecap,
139     psdf_setlinejoin,
140     psdf_setmiterlimit,
141     psdf_setdash,
142     psdf_setflat,
143     psdf_setlogop,
144 	/* Other state */
145     pdf_setfillcolor,
146     pdf_setstrokecolor,
147 	/* Paths */
148     psdf_dopath,
149     pdf_dorect,
150     psdf_beginpath,
151     psdf_moveto,
152     psdf_lineto,
153     psdf_curveto,
154     psdf_closepath,
155     pdf_endpath
156 };
157 
158 /* ------ Utilities ------ */
159 
160 /* Test whether we will need to put the clipping path. */
161 bool
pdf_must_put_clip_path(gx_device_pdf * pdev,const gx_clip_path * pcpath)162 pdf_must_put_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath)
163 {
164     if (pcpath == NULL)
165 	return pdev->clip_path_id != pdev->no_clip_path_id;
166     if (pdev->clip_path_id == pcpath->id)
167 	return false;
168     if (gx_cpath_includes_rectangle(pcpath, fixed_0, fixed_0,
169 				    int2fixed(pdev->width),
170 				    int2fixed(pdev->height))
171 	)
172 	return pdev->clip_path_id != pdev->no_clip_path_id;
173     return true;
174 }
175 
176 /* Put a clipping path on the output file. */
177 int
pdf_put_clip_path(gx_device_pdf * pdev,const gx_clip_path * pcpath)178 pdf_put_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath)
179 {
180     stream *s = pdev->strm;
181 
182     if (pcpath == NULL) {
183 	if (pdev->clip_path_id == pdev->no_clip_path_id)
184 	    return 0;
185 	stream_puts(s, "Q\nq\n");
186 	pdev->clip_path_id = pdev->no_clip_path_id;
187     } else {
188 	if (pdev->clip_path_id == pcpath->id)
189 	    return 0;
190 	if (gx_cpath_includes_rectangle(pcpath, fixed_0, fixed_0,
191 					int2fixed(pdev->width),
192 					int2fixed(pdev->height))
193 	    ) {
194 	    if (pdev->clip_path_id == pdev->no_clip_path_id)
195 		return 0;
196 	    stream_puts(s, "Q\nq\n");
197 	    pdev->clip_path_id = pdev->no_clip_path_id;
198 	} else {
199 	    gdev_vector_dopath_state_t state;
200 	    gs_cpath_enum cenum;
201 	    gs_fixed_point vs[3];
202 	    int pe_op;
203 
204 	    stream_puts(s, "Q\nq\n");
205 	    gdev_vector_dopath_init(&state, (gx_device_vector *)pdev,
206 				    gx_path_type_fill, NULL);
207 	    /*
208 	     * We have to break 'const' here because the clip path
209 	     * enumeration logic uses some internal mark bits.
210 	     * This is very unfortunate, but until we can come up with
211 	     * a better algorithm, it's necessary.
212 	     */
213 	    gx_cpath_enum_init(&cenum, (gx_clip_path *) pcpath);
214 	    while ((pe_op = gx_cpath_enum_next(&cenum, vs)) > 0)
215 		gdev_vector_dopath_segment(&state, pe_op, vs);
216 	    pprints1(s, "%s n\n", (pcpath->rule <= 0 ? "W" : "W*"));
217 	    if (pe_op < 0)
218 		return pe_op;
219 	    pdev->clip_path_id = pcpath->id;
220 	}
221     }
222     pdev->text.font = 0;
223     if (pdev->context == PDF_IN_TEXT)
224 	pdev->context = PDF_IN_STREAM;
225     pdf_reset_graphics(pdev);
226     return 0;
227 }
228 
229 /*
230  * Compute the scaling to ensure that user coordinates for a path are within
231  * Acrobat's range.  Return true if scaling was needed.  In this case, the
232  * CTM will be multiplied by *pscale, and all coordinates will be divided by
233  * *pscale.
234  */
235 private bool
make_path_scaling(const gx_device_pdf * pdev,gx_path * ppath,floatp prescale,double * pscale)236 make_path_scaling(const gx_device_pdf *pdev, gx_path *ppath,
237 		  floatp prescale, double *pscale)
238 {
239     gs_fixed_rect bbox;
240     double bmin, bmax;
241 
242     gx_path_bbox(ppath, &bbox);
243     bmin = min(bbox.p.x / pdev->scale.x, bbox.p.y / pdev->scale.y) * prescale;
244     bmax = max(bbox.q.x / pdev->scale.x, bbox.q.y / pdev->scale.y) * prescale;
245     if (bmin <= int2fixed(-MAX_USER_COORD) ||
246 	bmax > int2fixed(MAX_USER_COORD)
247 	) {
248 	/* Rescale the path. */
249 	*pscale = max(bmin / int2fixed(-MAX_USER_COORD),
250 		      bmax / int2fixed(MAX_USER_COORD));
251 	return true;
252     } else {
253 	*pscale = 1;
254 	return false;
255     }
256 #undef MAX_USER_COORD
257 }
258 
259 /* ------ Driver procedures ------ */
260 
261 /* Fill a path. */
262 int
gdev_pdf_fill_path(gx_device * dev,const gs_imager_state * pis,gx_path * ppath,const gx_fill_params * params,const gx_drawing_color * pdcolor,const gx_clip_path * pcpath)263 gdev_pdf_fill_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath,
264 		   const gx_fill_params * params,
265 	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath)
266 {
267     gx_device_pdf *pdev = (gx_device_pdf *) dev;
268     int code;
269     /*
270      * HACK: we fill an empty path in order to set the clipping path
271      * and the color for writing text.  If it weren't for this, we
272      * could detect and skip empty paths before putting out the clip
273      * path or the color.  We also clip with an empty path in order
274      * to advance currentpoint for show operations without actually
275      * drawing anything.
276      */
277     bool have_path;
278 
279     /*
280      * Check for an empty clipping path.
281      */
282     if (pcpath) {
283 	gs_fixed_rect box;
284 
285 	gx_cpath_outer_box(pcpath, &box);
286 	if (box.p.x >= box.q.x || box.p.y >= box.q.y)
287 	    return 0;		/* empty clipping path */
288     }
289     code = pdf_prepare_fill(pdev, pis);
290     if (code < 0)
291 	return code;
292     if (gx_dc_is_pure(pdcolor)) {
293 	/*
294 	 * Make a special check for the initial fill with white,
295 	 * which shouldn't cause the page to be opened.
296 	 */
297 	if (gx_dc_pure_color(pdcolor) == pdev->white && !is_in_page(pdev))
298 	    return 0;
299     }
300     have_path = !gx_path_is_void(ppath);
301     if (have_path || pdev->context == PDF_IN_NONE ||
302 	pdf_must_put_clip_path(pdev, pcpath)
303 	) {
304 	code = pdf_open_page(pdev, PDF_IN_STREAM);
305 	if (code < 0)
306 	    return code;
307     }
308     pdf_put_clip_path(pdev, pcpath);
309     if (pdf_setfillcolor((gx_device_vector *)pdev, pdcolor) < 0)
310 	return gx_default_fill_path(dev, pis, ppath, params, pdcolor,
311 				    pcpath);
312     if (have_path) {
313 	stream *s = pdev->strm;
314 	double scale;
315 	gs_matrix smat;
316 	gs_matrix *psmat = NULL;
317 
318 	if (params->flatness != pdev->state.flatness) {
319 	    pprintg1(s, "%g i\n", params->flatness);
320 	    pdev->state.flatness = params->flatness;
321 	}
322 	if (make_path_scaling(pdev, ppath, 1.0, &scale)) {
323 	    gs_make_scaling(pdev->scale.x * scale, pdev->scale.y * scale,
324 			    &smat);
325             pdf_put_matrix(pdev, "q ", &smat, "cm\n");
326 	    psmat = &smat;
327 	}
328 	gdev_vector_dopath((gx_device_vector *)pdev, ppath,
329 			   gx_path_type_fill | gx_path_type_optimize,
330 			   psmat);
331 	stream_puts(s, (params->rule < 0 ? "f\n" : "f*\n"));
332 	if (psmat)
333 	    stream_puts(s, "Q\n");
334     }
335     return 0;
336 }
337 
338 /* Stroke a path. */
339 int
gdev_pdf_stroke_path(gx_device * dev,const gs_imager_state * pis,gx_path * ppath,const gx_stroke_params * params,const gx_drawing_color * pdcolor,const gx_clip_path * pcpath)340 gdev_pdf_stroke_path(gx_device * dev, const gs_imager_state * pis,
341 		     gx_path * ppath, const gx_stroke_params * params,
342 	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath)
343 {
344     gx_device_pdf *pdev = (gx_device_pdf *) dev;
345     stream *s;
346     int code;
347     double scale, path_scale;
348     bool set_ctm;
349     gs_matrix mat;
350     double prescale = 1;
351 
352     if (gx_path_is_void(ppath))
353 	return 0;		/* won't mark the page */
354     code = pdf_prepare_stroke(pdev, pis);
355     if (code < 0)
356 	return code;
357     code = pdf_open_page(pdev, PDF_IN_STREAM);
358     if (code < 0)
359 	return code;
360     /*
361      * If the CTM is not uniform, stroke width depends on angle.
362      * We'd like to avoid resetting the CTM, so we check for uniform
363      * CTMs explicitly.  Note that in PDF, unlike PostScript, it is
364      * the CTM at the time of the stroke operation, not the CTM at
365      * the time the path was constructed, that is used for transforming
366      * the points of the path; so if we have to reset the CTM, we must
367      * do it before constructing the path, and inverse-transform all
368      * the coordinates.
369      */
370     set_ctm = (bool)gdev_vector_stroke_scaling((gx_device_vector *)pdev,
371 					       pis, &scale, &mat);
372     if (set_ctm) {
373 	/*
374 	 * We want a scaling factor that will bring the largest reasonable
375 	 * user coordinate within bounds.  We choose a factor based on the
376 	 * minor axis of the transformation.  Thanks to Raph Levien for
377 	 * the following formula.
378 	 */
379 	double a = mat.xx, b = mat.xy, c = mat.yx, d = mat.yy;
380 	double u = fabs(a * d - b * c);
381 	double v = a * a + b * b + c * c + d * d;
382 	double minor = (sqrt(v + 2 * u) - sqrt(v - 2 * u)) * 0.5;
383 
384 	prescale = (minor == 0 || minor > 1 ? 1 : 1 / minor);
385     }
386     if (make_path_scaling(pdev, ppath, prescale, &path_scale)) {
387 	scale /= path_scale;
388 	if (set_ctm)
389 	    gs_matrix_scale(&mat, path_scale, path_scale, &mat);
390 	else {
391 	    gs_make_scaling(path_scale, path_scale, &mat);
392 	    set_ctm = true;
393 	}
394     }
395     pdf_put_clip_path(pdev, pcpath);
396     code = gdev_vector_prepare_stroke((gx_device_vector *)pdev, pis, params,
397 				      pdcolor, scale);
398     if (code < 0)
399 	return gx_default_stroke_path(dev, pis, ppath, params, pdcolor,
400 				      pcpath);
401 
402     if (set_ctm)
403   	pdf_put_matrix(pdev, "q ", &mat, "cm\n");
404     code = gdev_vector_dopath((gx_device_vector *)pdev, ppath,
405 			      gx_path_type_stroke | gx_path_type_optimize,
406 			      (set_ctm ? &mat : (const gs_matrix *)0));
407     if (code < 0)
408 	return code;
409     s = pdev->strm;
410     stream_puts(s, (code ? "s" : "S"));
411     stream_puts(s, (set_ctm ? " Q\n" : "\n"));
412     return 0;
413 }
414