xref: /plan9-contrib/sys/src/cmd/gs/src/gdevpdft.c (revision 6a9fc400c33447ef5e1cda7185cb4de2c8e8010e)
1 /* Copyright (C) 1996, 2000 Aladdin Enterprises.  All rights reserved.
2 
3   This file is part of AFPL Ghostscript.
4 
5   AFPL Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author or
6   distributor accepts any responsibility for the consequences of using it, or
7   for whether it serves any particular purpose or works at all, unless he or
8   she says so in writing.  Refer to the Aladdin Free Public License (the
9   "License") for full details.
10 
11   Every copy of AFPL Ghostscript must include a copy of the License, normally
12   in a plain ASCII text file named PUBLIC.  The License grants you the right
13   to copy, modify and redistribute AFPL Ghostscript, but only under certain
14   conditions described in the License.  Among other things, the License
15   requires that the copyright notice and this notice be preserved on all
16   copies.
17 */
18 
19 /*$Id: gdevpdft.c,v 1.36 2001/07/30 15:11:00 lpd Exp $ */
20 /* Text handling for PDF-writing driver. */
21 #include "math_.h"
22 #include "memory_.h"
23 #include "string_.h"
24 #include "gx.h"
25 #include "gserrors.h"
26 #include "gxpath.h"		/* for getting current point */
27 #include "gdevpdfx.h"
28 #include "gdevpdff.h"
29 #include "gdevpdfg.h"
30 #include "scommon.h"
31 
32 /* GC descriptors */
33 private_st_pdf_text_enum();
34 
35 /* Define the auxiliary procedures for text processing. */
36 private int
37 pdf_text_resync(gs_text_enum_t *pte, const gs_text_enum_t *pfrom)
38 {
39     pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
40 
41     if ((pte->text.operation ^ pfrom->text.operation) & ~TEXT_FROM_ANY)
42 	return_error(gs_error_rangecheck);
43     if (penum->pte_default) {
44 	int code = gs_text_resync(penum->pte_default, pfrom);
45 
46 	if (code < 0)
47 	    return code;
48     }
49     pte->text = pfrom->text;
50     gs_text_enum_copy_dynamic(pte, pfrom, false);
51     return 0;
52 }
53 private bool
54 pdf_text_is_width_only(const gs_text_enum_t *pte)
55 {
56     const pdf_text_enum_t *const penum = (const pdf_text_enum_t *)pte;
57 
58     if (penum->pte_default)
59 	return gs_text_is_width_only(penum->pte_default);
60     return false;
61 }
62 private int
63 pdf_text_current_width(const gs_text_enum_t *pte, gs_point *pwidth)
64 {
65     const pdf_text_enum_t *const penum = (const pdf_text_enum_t *)pte;
66 
67     if (penum->pte_default)
68 	return gs_text_current_width(penum->pte_default, pwidth);
69     return_error(gs_error_rangecheck); /* can't happen */
70 }
71 private int
72 pdf_text_set_cache(gs_text_enum_t *pte, const double *pw,
73 		   gs_text_cache_control_t control)
74 {
75     pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
76 
77     if (penum->pte_default)
78 	return gs_text_set_cache(penum->pte_default, pw, control);
79     return_error(gs_error_rangecheck); /* can't happen */
80 }
81 private int
82 pdf_text_retry(gs_text_enum_t *pte)
83 {
84     pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
85 
86     if (penum->pte_default)
87 	return gs_text_retry(penum->pte_default);
88     return_error(gs_error_rangecheck); /* can't happen */
89 }
90 private void
91 pdf_text_release(gs_text_enum_t *pte, client_name_t cname)
92 {
93     pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
94 
95     if (penum->pte_default) {
96 	gs_text_release(penum->pte_default, cname);
97 	penum->pte_default = 0;
98     }
99     gx_default_text_release(pte, cname);
100 }
101 
102 /* Begin processing text. */
103 extern text_enum_proc_process(pdf_text_process); /* in gdevpdfs.c */
104 private const gs_text_enum_procs_t pdf_text_procs = {
105     pdf_text_resync, pdf_text_process,
106     pdf_text_is_width_only, pdf_text_current_width,
107     pdf_text_set_cache, pdf_text_retry,
108     pdf_text_release
109 };
110 int
111 gdev_pdf_text_begin(gx_device * dev, gs_imager_state * pis,
112 		    const gs_text_params_t *text, gs_font * font,
113 		    gx_path * path, const gx_device_color * pdcolor,
114 		    const gx_clip_path * pcpath,
115 		    gs_memory_t * mem, gs_text_enum_t ** ppte)
116 {
117     gx_device_pdf *const pdev = (gx_device_pdf *)dev;
118     pdf_text_enum_t *penum;
119     gs_fixed_point cpt;
120     int code;
121 
122     /* Track the dominant text rotation. */
123     {
124 	gs_matrix tmat;
125 	int i;
126 
127 	gs_matrix_multiply(&font->FontMatrix, &ctm_only(pis), &tmat);
128 	if (is_xxyy(&tmat))
129 	    i = (tmat.xx >= 0 ? 0 : 2);
130 	else if (is_xyyx(&tmat))
131 	    i = (tmat.xy >= 0 ? 1 : 3);
132 	else
133 	    i = 4;
134 	pdf_current_page(pdev)->text_rotation.counts[i] += text->size;
135     }
136 
137     if (!(text->operation & TEXT_DO_DRAW) || path == 0 ||
138 	gx_path_current_point(path, &cpt) < 0
139 	)
140 	return gx_default_text_begin(dev, pis, text, font, path, pdcolor,
141 				     pcpath, mem, ppte);
142 
143     code = pdf_prepare_fill(pdev, pis);
144     if (code < 0)
145 	return code;
146 
147     if (text->operation & TEXT_DO_DRAW) {
148 	/*
149 	 * Set the clipping path and drawing color.  We set both the fill
150 	 * and stroke color, because we don't know whether the fonts will be
151 	 * filled or stroked, and we can't set a color while we are in text
152 	 * mode.  (This is a consequence of the implementation, not a
153 	 * limitation of PDF.)
154 	 */
155 
156 	if (pdf_must_put_clip_path(pdev, pcpath)) {
157 	    int code = pdf_open_page(pdev, PDF_IN_STREAM);
158 
159 	    if (code < 0)
160 		return code;
161 	    pdf_put_clip_path(pdev, pcpath);
162 	}
163 
164 	if ((code =
165 	     pdf_set_drawing_color(pdev, pdcolor, &pdev->stroke_color,
166 				   &psdf_set_stroke_color_commands)) < 0 ||
167 	    (code =
168 	     pdf_set_drawing_color(pdev, pdcolor, &pdev->fill_color,
169 				   &psdf_set_fill_color_commands)) < 0
170 	    )
171 	    return code;
172     }
173 
174     /* Allocate and initialize the enumerator. */
175 
176     rc_alloc_struct_1(penum, pdf_text_enum_t, &st_pdf_text_enum, mem,
177 		      return_error(gs_error_VMerror), "gdev_pdf_text_begin");
178     penum->rc.free = rc_free_text_enum;
179     penum->pte_default = 0;
180     code = gs_text_enum_init((gs_text_enum_t *)penum, &pdf_text_procs,
181 			     dev, pis, text, font, path, pdcolor, pcpath, mem);
182     if (code < 0) {
183 	gs_free_object(mem, penum, "gdev_pdf_text_begin");
184 	return code;
185     }
186 
187     *ppte = (gs_text_enum_t *)penum;
188 
189     return 0;
190 }
191 
192 /* ---------------- Text and font utilities ---------------- */
193 
194 /* Forward declarations */
195 private int assign_char_code(P1(gx_device_pdf * pdev));
196 
197 /*
198  * Set the current font and size, writing a Tf command if needed.
199  */
200 int
201 pdf_set_font_and_size(gx_device_pdf * pdev, pdf_font_t * font, floatp size)
202 {
203     if (font != pdev->text.font || size != pdev->text.size) {
204 	int code = pdf_open_page(pdev, PDF_IN_TEXT);
205 	stream *s = pdev->strm;
206 
207 	if (code < 0)
208 	    return code;
209 	pprints1(s, "/%s ", font->rname);
210 	pprintg1(s, "%g Tf\n", size);
211 	pdev->text.font = font;
212 	pdev->text.size = size;
213     }
214     font->where_used |= pdev->used_mask;
215     return 0;
216 }
217 
218 /*
219  * Set the text matrix for writing text.
220  * The translation component of the matrix is the text origin.
221  * If the non-translation components of the matrix differ from the
222  * current ones, write a Tm command; if there is only a Y translation
223  * and it matches the leading, set use_leading so the next text string
224  * will be written with ' rather than Tj; otherwise, write either a TL
225  * command or a Tj command using space pseudo-characters.
226  */
227 private int
228 set_text_distance(gs_point *pdist, const gs_point *ppt, const gs_matrix *pmat)
229 {
230     double rounded;
231 
232     gs_distance_transform_inverse(pmat->tx - ppt->x, pmat->ty - ppt->y,
233 				  pmat, pdist);
234     /* If the distance is very close to integers, round it. */
235     if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
236 	pdist->x = rounded;
237     if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
238 	pdist->y = rounded;
239     return 0;
240 }
241 int
242 pdf_set_text_matrix(gx_device_pdf * pdev, const gs_matrix * pmat)
243 {
244     stream *s = pdev->strm;
245     double sx = 72.0 / pdev->HWResolution[0],
246 	sy = 72.0 / pdev->HWResolution[1];
247     int code;
248 
249     if (pmat->xx == pdev->text.matrix.xx &&
250 	pmat->xy == pdev->text.matrix.xy &&
251 	pmat->yx == pdev->text.matrix.yx &&
252 	pmat->yy == pdev->text.matrix.yy &&
253     /*
254      * If we aren't already in text context, BT will reset
255      * the text matrix.
256      */
257 	(pdev->context == PDF_IN_TEXT || pdev->context == PDF_IN_STRING)
258 	) {
259 	/* Use leading, Td or a pseudo-character. */
260 	gs_point dist;
261 
262 	set_text_distance(&dist, &pdev->text.current, pmat);
263 	if (dist.y == 0 && dist.x >= X_SPACE_MIN &&
264 	    dist.x <= X_SPACE_MAX &&
265 	    pdev->text.font != 0 &&
266 	    PDF_FONT_IS_SYNTHESIZED(pdev->text.font)
267 	    ) {			/* Use a pseudo-character. */
268 	    int dx = (int)dist.x;
269 	    int dx_i = dx - X_SPACE_MIN;
270 	    byte space_char = pdev->text.font->spaces[dx_i];
271 
272 	    if (space_char == 0) {
273 		if (pdev->text.font != pdev->open_font)
274 		    goto not_spaces;
275 		code = assign_char_code(pdev);
276 		if (code <= 0)
277 		    goto not_spaces;
278 		space_char = pdev->open_font->spaces[dx_i] = (byte)code;
279 		if (pdev->space_char_ids[dx_i] == 0) {
280 		    /* Create the space char_proc now. */
281 		    char spstr[3 + 14 + 1];
282 		    stream *s;
283 
284 		    sprintf(spstr, "%d 0 0 0 0 0 d1\n", dx);
285 		    pdev->space_char_ids[dx_i] = pdf_begin_separate(pdev);
286 		    s = pdev->strm;
287 		    pprintd1(s, "<</Length %d>>\nstream\n", strlen(spstr));
288 		    pprints1(s, "%sendstream\n", spstr);
289 		    pdf_end_separate(pdev);
290 		}
291 	    }
292 	    pdf_append_chars(pdev, &space_char, 1);
293 	    pdev->text.current.x += dx * pmat->xx;
294 	    pdev->text.current.y += dx * pmat->xy;
295 	    /* Don't change use_leading -- it only affects Y placement. */
296 	    return 0;
297 	}
298       not_spaces:
299 	code = pdf_open_page(pdev, PDF_IN_TEXT);
300 	if (code < 0)
301 	    return code;
302 	set_text_distance(&dist, &pdev->text.line_start, pmat);
303 	if (pdev->text.use_leading) {
304 	    /* Leading was deferred: take it into account now. */
305 	    dist.y -= pdev->text.leading;
306 	}
307 	if (dist.x == 0 && dist.y < 0) {
308 	    /* Use TL, if needed, + '. */
309 	    float dist_y = (float)-dist.y;
310 
311 	    if (fabs(pdev->text.leading - dist_y) > 0.0005) {
312 		pprintg1(s, "%g TL\n", dist_y);
313 		pdev->text.leading = dist_y;
314 	    }
315 	    pdev->text.use_leading = true;
316 	} else {
317 	    /* Use Td. */
318 	    pprintg2(s, "%g %g Td\n", dist.x, dist.y);
319 	    pdev->text.use_leading = false;
320 	}
321     } else {			/* Use Tm. */
322 	code = pdf_open_page(pdev, PDF_IN_TEXT);
323 	if (code < 0)
324 	    return code;
325 	/*
326 	 * See stream_to_text in gdevpdf.c for why we need the following
327 	 * matrix adjustments.
328 	 */
329 	pprintg6(pdev->strm, "%g %g %g %g %g %g Tm\n",
330 		 pmat->xx * sx, pmat->xy * sy,
331 		 pmat->yx * sx, pmat->yy * sy,
332 		 pmat->tx * sx, pmat->ty * sy);
333 	pdev->text.matrix = *pmat;
334 	pdev->text.use_leading = false;
335     }
336     pdev->text.line_start.x = pmat->tx;
337     pdev->text.line_start.y = pmat->ty;
338     pdev->text.current.x = pmat->tx;
339     pdev->text.current.y = pmat->ty;
340     return 0;
341 }
342 
343 /*
344  * Append characters to a string being accumulated.
345  */
346 int
347 pdf_append_chars(gx_device_pdf * pdev, const byte * str, uint size)
348 {
349     const byte *p = str;
350     uint left = size;
351 
352     while (left)
353 	if (pdev->text.buffer_count == max_text_buffer) {
354 	    int code = pdf_open_page(pdev, PDF_IN_TEXT);
355 
356 	    if (code < 0)
357 		return code;
358 	} else {
359 	    int code = pdf_open_page(pdev, PDF_IN_STRING);
360 	    uint copy;
361 
362 	    if (code < 0)
363 		return code;
364 	    copy = min(max_text_buffer - pdev->text.buffer_count, left);
365 	    memcpy(pdev->text.buffer + pdev->text.buffer_count, p, copy);
366 	    pdev->text.buffer_count += copy;
367 	    p += copy;
368 	    left -= copy;
369 	}
370     return 0;
371 }
372 
373 /* ---------------- Synthesized fonts ---------------- */
374 
375 /* Assign a code for a char_proc. */
376 private int
377 assign_char_code(gx_device_pdf * pdev)
378 {
379     pdf_font_t *font = pdev->open_font;
380     int c;
381 
382     if (pdev->embedded_encoding_id == 0)
383 	pdev->embedded_encoding_id = pdf_obj_ref(pdev);
384     if (font == 0 || font->num_chars == 256 || !pdev->use_open_font) {
385 	/* Start a new synthesized font. */
386 	int code = pdf_alloc_font(pdev, gs_no_id, &font, NULL, NULL);
387 	char *pc;
388 
389 	if (code < 0)
390 	    return code;
391 	if (pdev->open_font == 0)
392 	    font->rname[0] = 0;
393 	else
394 	    strcpy(font->rname, pdev->open_font->rname);
395 	for (pc = font->rname; *pc == 'Z'; ++pc)
396 	    *pc = '@';
397 	if ((*pc)++ == 0)
398 	    *pc = 'A', pc[1] = 0;
399 	pdev->open_font = font;
400 	pdev->use_open_font = true;
401     }
402     c = font->num_chars++;
403     if (c > pdev->max_embedded_code)
404 	pdev->max_embedded_code = c;
405     return c;
406 }
407 
408 /* Begin a CharProc for a synthesized (bitmap) font. */
409 int
410 pdf_begin_char_proc(gx_device_pdf * pdev, int w, int h, int x_width,
411   int y_offset, gs_id id, pdf_char_proc_t ** ppcp, pdf_stream_position_t * ppos)
412 {
413     pdf_resource_t *pres;
414     pdf_char_proc_t *pcp;
415     int char_code = assign_char_code(pdev);
416     pdf_font_t *font = pdev->open_font;
417     int code;
418 
419     if (char_code < 0)
420 	return char_code;
421     code = pdf_begin_resource(pdev, resourceCharProc, id, &pres);
422     if (code < 0)
423 	return code;
424     pcp = (pdf_char_proc_t *) pres;
425     pcp->font = font;
426     pcp->char_next = font->char_procs;
427     font->char_procs = pcp;
428     pcp->char_code = char_code;
429     pcp->width = w;
430     pcp->height = h;
431     pcp->x_width = x_width;
432     pcp->y_offset = y_offset;
433     font->max_y_offset = max(font->max_y_offset, h + (h >> 2));
434     *ppcp = pcp;
435     {
436 	stream *s = pdev->strm;
437 
438 	/*
439 	 * The resource file is positionable, so rather than use an
440 	 * object reference for the length, we'll go back and fill it in
441 	 * at the end of the definition.  Take 1M as the longest
442 	 * definition we can handle.  (This used to be 10K, but there was
443 	 * a real file that exceeded this limit.)
444 	 */
445 	stream_puts(s, "<</Length       >>stream\n");
446 	ppos->start_pos = stell(s);
447     }
448     return 0;
449 }
450 
451 /* End a CharProc. */
452 int
453 pdf_end_char_proc(gx_device_pdf * pdev, pdf_stream_position_t * ppos)
454 {
455     stream *s = pdev->strm;
456     long start_pos = ppos->start_pos;
457     long end_pos = stell(s);
458     long length = end_pos - start_pos;
459 
460     if (length > 999999)
461 	return_error(gs_error_limitcheck);
462     sseek(s, start_pos - 15);
463     pprintd1(s, "%d", length);
464     sseek(s, end_pos);
465     stream_puts(s, "endstream\n");
466     pdf_end_separate(pdev);
467     return 0;
468 }
469 
470 /* Put out a reference to an image as a character in a synthesized font. */
471 int
472 pdf_do_char_image(gx_device_pdf * pdev, const pdf_char_proc_t * pcp,
473 		  const gs_matrix * pimat)
474 {
475     pdf_set_font_and_size(pdev, pcp->font, 1.0);
476     {
477 	gs_matrix tmat;
478 
479 	tmat = *pimat;
480 	tmat.ty -= pcp->y_offset;
481 	pdf_set_text_matrix(pdev, &tmat);
482     }
483     pdf_append_chars(pdev, &pcp->char_code, 1);
484     pdev->text.current.x += pcp->x_width * pdev->text.matrix.xx;
485     return 0;
486 }
487