1 /* Copyright (C) 2002 Aladdin Enterprises. All rights reserved.
2
3 This software is provided AS-IS with no warranty, either express or
4 implied.
5
6 This software is distributed under license and may not be copied,
7 modified or distributed except as expressly authorized under the terms
8 of the license contained in the file LICENSE in this distribution.
9
10 For more information about licensing, please refer to
11 http://www.ghostscript.com/licensing/. For information on
12 commercial licensing, go to http://www.artifex.com/licensing/ or
13 contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14 San Rafael, CA 94903, U.S.A., +1(415)492-9861.
15 */
16
17 /* $Id: gdevpdts.c,v 1.28 2005/01/24 15:37:48 igor Exp $ */
18 /* Text state management for pdfwrite */
19 #include "math_.h"
20 #include "memory_.h"
21 #include "gx.h"
22 #include "gdevpdfx.h"
23 #include "gdevpdtx.h"
24 #include "gdevpdtf.h" /* for pdfont->FontType */
25 #include "gdevpdts.h"
26
27 /* ================ Types and structures ================ */
28
29 /*
30 * We accumulate text, and possibly horizontal or vertical moves (depending
31 * on the font's writing direction), until forced to emit them. This
32 * happens when changing text state parameters, when the buffer is full, or
33 * when exiting text mode.
34 *
35 * Note that movement distances are measured in unscaled text space.
36 */
37 typedef struct pdf_text_move_s {
38 int index; /* within buffer.chars */
39 float amount;
40 } pdf_text_move_t;
41 #define MAX_TEXT_BUFFER_CHARS 200 /* arbitrary, but overflow costs 5 chars */
42 #define MAX_TEXT_BUFFER_MOVES 50 /* ibid. */
43 typedef struct pdf_text_buffer_s {
44 /*
45 * Invariant:
46 * count_moves <= MAX_TEXT_BUFFER_MOVES
47 * count_chars <= MAX_TEXT_BUFFER_CHARS
48 * 0 < moves[0].index < moves[1].index < ... moves[count_moves-1].index
49 * <= count_chars
50 * moves[*].amount != 0
51 */
52 pdf_text_move_t moves[MAX_TEXT_BUFFER_MOVES + 1];
53 byte chars[MAX_TEXT_BUFFER_CHARS];
54 int count_moves;
55 int count_chars;
56 } pdf_text_buffer_t;
57 #define TEXT_BUFFER_DEFAULT\
58 { { 0, 0 } }, /* moves */\
59 { 0 }, /* chars */\
60 0, /* count_moves */\
61 0 /* count_chars */
62
63 /*
64 * We maintain two sets of text state values (as defined in gdevpdts.h): the
65 * "in" set reflects the current state as seen by the client, while the
66 * "out" set reflects the current state as seen by an interpreter processing
67 * the content stream emitted so far. We emit commands to make "out" the
68 * same as "in" when necessary.
69 */
70 /*typedef struct pdf_text_state_s pdf_text_state_t;*/ /* gdevpdts.h */
71 struct pdf_text_state_s {
72 /* State as seen by client */
73 pdf_text_state_values_t in; /* see above */
74 gs_point start; /* in.txy as of start of buffer */
75 pdf_text_buffer_t buffer;
76 int wmode; /* WMode of in.font */
77 /* State relative to content stream */
78 pdf_text_state_values_t out; /* see above */
79 double leading; /* TL (not settable, only used internally) */
80 bool use_leading; /* if true, use T* or ' */
81 bool continue_line;
82 gs_point line_start;
83 gs_point out_pos; /* output position */
84 };
85 private const pdf_text_state_t ts_default = {
86 /* State as seen by client */
87 { TEXT_STATE_VALUES_DEFAULT }, /* in */
88 { 0, 0 }, /* start */
89 { TEXT_BUFFER_DEFAULT }, /* buffer */
90 0, /* wmode */
91 /* State relative to content stream */
92 { TEXT_STATE_VALUES_DEFAULT }, /* out */
93 0, /* leading */
94 0 /*false*/, /* use_leading */
95 0 /*false*/, /* continue_line */
96 { 0, 0 }, /* line_start */
97 { 0, 0 } /* output position */
98 };
99 /* GC descriptor */
100 gs_private_st_ptrs2(st_pdf_text_state, pdf_text_state_t, "pdf_text_state_t",
101 pdf_text_state_enum_ptrs, pdf_text_state_reloc_ptrs,
102 in.pdfont, out.pdfont);
103
104 /* ================ Procedures ================ */
105
106 /* ---------------- Private ---------------- */
107
108 /*
109 * Append a writing-direction movement to the text being accumulated. If
110 * the buffer is full, or the requested movement is not in writing
111 * direction, return <0 and do nothing. (This is different from
112 * pdf_append_chars.) Requires pts->buffer.count_chars > 0.
113 */
114 private int
append_text_move(pdf_text_state_t * pts,floatp dw)115 append_text_move(pdf_text_state_t *pts, floatp dw)
116 {
117 int count = pts->buffer.count_moves;
118 int pos = pts->buffer.count_chars;
119 double rounded;
120
121 if (count > 0 && pts->buffer.moves[count - 1].index == pos) {
122 /* Merge adjacent moves. */
123 dw += pts->buffer.moves[--count].amount;
124 }
125 /* Round dw if it's very close to an integer. */
126 rounded = floor(dw + 0.5);
127 if (fabs(dw - rounded) < 0.001)
128 dw = rounded;
129 if (dw < -MAX_USER_COORD) {
130 /* Acrobat reader 4.0c, 5.0 can't handle big offsets.
131 Adobe Reader 6 can. */
132 return -1;
133 }
134 if (dw != 0) {
135 if (count == MAX_TEXT_BUFFER_MOVES)
136 return -1;
137 pts->buffer.moves[count].index = pos;
138 pts->buffer.moves[count].amount = dw;
139 ++count;
140 }
141 pts->buffer.count_moves = count;
142 return 0;
143 }
144
145 /*
146 * Set *pdist to the distance (dx,dy), in the space defined by *pmat.
147 */
148 private int
set_text_distance(gs_point * pdist,floatp dx,floatp dy,const gs_matrix * pmat)149 set_text_distance(gs_point *pdist, floatp dx, floatp dy, const gs_matrix *pmat)
150 {
151 int code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
152 double rounded;
153
154 if (code < 0)
155 return code;
156 /* If the distance is very close to integers, round it. */
157 if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
158 pdist->x = rounded;
159 if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
160 pdist->y = rounded;
161 return 0;
162 }
163
164 /*
165 * Test whether the transformation parts of two matrices are compatible.
166 */
167 private bool
matrix_is_compatible(const gs_matrix * pmat1,const gs_matrix * pmat2)168 matrix_is_compatible(const gs_matrix *pmat1, const gs_matrix *pmat2)
169 {
170 return (pmat2->xx == pmat1->xx && pmat2->xy == pmat1->xy &&
171 pmat2->yx == pmat1->yx && pmat2->yy == pmat1->yy);
172 }
173
174 /*
175 * Try to handle a change of text position with TJ or a space
176 * character. If successful, return >=0, if not, return <0.
177 */
178 private int
add_text_delta_move(gx_device_pdf * pdev,const gs_matrix * pmat)179 add_text_delta_move(gx_device_pdf *pdev, const gs_matrix *pmat)
180 {
181 pdf_text_state_t *const pts = pdev->text->text_state;
182
183 if (matrix_is_compatible(pmat, &pts->in.matrix)) {
184 double dx = pmat->tx - pts->in.matrix.tx,
185 dy = pmat->ty - pts->in.matrix.ty;
186 gs_point dist;
187 double dw, dnotw, tdw;
188 int code;
189
190 code = set_text_distance(&dist, dx, dy, pmat);
191 if (code < 0)
192 return code;
193 if (pts->wmode)
194 dw = dist.y, dnotw = dist.x;
195 else
196 dw = dist.x, dnotw = dist.y;
197 if (dnotw == 0 && pts->buffer.count_chars > 0 &&
198 /*
199 * Acrobat Reader limits the magnitude of user-space
200 * coordinates. Also, AR apparently doesn't handle large
201 * positive movement values (negative X displacements), even
202 * though the PDF Reference says this bug was fixed in AR3.
203 *
204 * Old revisions used the upper threshold 1000 for tdw,
205 * but it appears too big when a font sets a too big
206 * character width in setcachedevice. Particularly this happens
207 * with a Type 3 font generated by Aldus Freehand 4.0
208 * to represent a texture - see bug #687051.
209 * The problem is that when the Widths is multiplied
210 * to the font size, the viewer represents the result
211 * with insufficient fraction bits to represent the precise width.
212 * We work around that problem here restricting tdw
213 * with a smaller threshold 990. Our intention is to
214 * disable Tj when the real glyph width appears smaller
215 * than 1% of the width specified in setcachedevice.
216 * A Td instruction will be generated instead.
217 * Note that the value 990 is arbitrary and may need a
218 * further adjustment.
219 */
220 (tdw = dw * -1000.0 / pts->in.size,
221 tdw >= -MAX_USER_COORD && tdw < 990)
222 ) {
223 /* Use TJ. */
224 int code = append_text_move(pts, tdw);
225
226 if (code >= 0)
227 goto finish;
228 }
229 }
230 return -1;
231 finish:
232 pts->in.matrix = *pmat;
233 return 0;
234 }
235
236 /*
237 * Set the text matrix for writing text. The translation component of the
238 * matrix is the text origin. If the non-translation components of the
239 * matrix differ from the current ones, write a Tm command; if there is only
240 * a Y translation, set use_leading so the next text string will be written
241 * with ' rather than Tj; otherwise, write a Td command.
242 */
243 private int
pdf_set_text_matrix(gx_device_pdf * pdev)244 pdf_set_text_matrix(gx_device_pdf * pdev)
245 {
246 pdf_text_state_t *pts = pdev->text->text_state;
247 stream *s = pdev->strm;
248
249 pts->use_leading = false;
250 if (matrix_is_compatible(&pts->out.matrix, &pts->in.matrix)) {
251 gs_point dist;
252 int code;
253
254 code = set_text_distance(&dist, pts->start.x - pts->line_start.x,
255 pts->start.y - pts->line_start.y, &pts->in.matrix);
256 if (code < 0)
257 return code;
258 if (dist.x == 0 && dist.y < 0) {
259 /* Use TL, if needed, and T* or '. */
260 float dist_y = (float)-dist.y;
261
262 if (fabs(pts->leading - dist_y) > 0.0005) {
263 pprintg1(s, "%g TL\n", dist_y);
264 pts->leading = dist_y;
265 }
266 pts->use_leading = true;
267 } else {
268 /* Use Td. */
269 pprintg2(s, "%g %g Td\n", dist.x, dist.y);
270 }
271 } else { /* Use Tm. */
272 /*
273 * See stream_to_text in gdevpdfu.c for why we need the following
274 * matrix adjustments.
275 */
276 double sx = 72.0 / pdev->HWResolution[0],
277 sy = 72.0 / pdev->HWResolution[1];
278
279 pprintg6(s, "%g %g %g %g %g %g Tm\n",
280 pts->in.matrix.xx * sx, pts->in.matrix.xy * sy,
281 pts->in.matrix.yx * sx, pts->in.matrix.yy * sy,
282 pts->start.x * sx, pts->start.y * sy);
283 }
284 pts->line_start.x = pts->start.x;
285 pts->line_start.y = pts->start.y;
286 pts->out.matrix = pts->in.matrix;
287 return 0;
288 }
289
290 /* ---------------- Public ---------------- */
291
292 /*
293 * Allocate and initialize text state bookkeeping.
294 */
295 pdf_text_state_t *
pdf_text_state_alloc(gs_memory_t * mem)296 pdf_text_state_alloc(gs_memory_t *mem)
297 {
298 pdf_text_state_t *pts =
299 gs_alloc_struct(mem, pdf_text_state_t, &st_pdf_text_state,
300 "pdf_text_state_alloc");
301
302 if (pts == 0)
303 return 0;
304 *pts = ts_default;
305 return pts;
306 }
307
308 /*
309 * Set the text state to default values.
310 */
311 void
pdf_set_text_state_default(pdf_text_state_t * pts)312 pdf_set_text_state_default(pdf_text_state_t *pts)
313 {
314 *pts = ts_default;
315 }
316
317 /*
318 * Copy the text state.
319 */
320 void
pdf_text_state_copy(pdf_text_state_t * pts_to,pdf_text_state_t * pts_from)321 pdf_text_state_copy(pdf_text_state_t *pts_to, pdf_text_state_t *pts_from)
322 {
323 *pts_to = *pts_from;
324 }
325
326 /*
327 * Reset the text state to its condition at the beginning of the page.
328 */
329 void
pdf_reset_text_page(pdf_text_data_t * ptd)330 pdf_reset_text_page(pdf_text_data_t *ptd)
331 {
332 pdf_set_text_state_default(ptd->text_state);
333 }
334
335 /*
336 * Reset the text state after a grestore.
337 */
338 void
pdf_reset_text_state(pdf_text_data_t * ptd)339 pdf_reset_text_state(pdf_text_data_t *ptd)
340 {
341 pdf_text_state_t *pts = ptd->text_state;
342
343 pts->out = ts_default.out;
344 pts->leading = 0;
345 }
346
347 /*
348 * Transition from stream context to text context.
349 */
350 int
pdf_from_stream_to_text(gx_device_pdf * pdev)351 pdf_from_stream_to_text(gx_device_pdf *pdev)
352 {
353 pdf_text_state_t *pts = pdev->text->text_state;
354
355 gs_make_identity(&pts->out.matrix);
356 pts->line_start.x = pts->line_start.y = 0;
357 pts->continue_line = false; /* Not sure, probably doesn't matter. */
358 pts->buffer.count_chars = 0;
359 pts->buffer.count_moves = 0;
360 return 0;
361 }
362
363
364 /*
365 * Flush text from buffer.
366 */
367 private int
flush_text_buffer(gx_device_pdf * pdev)368 flush_text_buffer(gx_device_pdf *pdev)
369 {
370 pdf_text_state_t *pts = pdev->text->text_state;
371 stream *s = pdev->strm;
372
373 if (pts->buffer.count_moves > 0) {
374 int i, cur = 0;
375
376 if (pts->use_leading)
377 stream_puts(s, "T*");
378 stream_puts(s, "[");
379 for (i = 0; i < pts->buffer.count_moves; ++i) {
380 int next = pts->buffer.moves[i].index;
381
382 pdf_put_string(pdev, pts->buffer.chars + cur, next - cur);
383 pprintg1(s, "%g", pts->buffer.moves[i].amount);
384 cur = next;
385 }
386 if (pts->buffer.count_chars > cur)
387 pdf_put_string(pdev, pts->buffer.chars + cur,
388 pts->buffer.count_chars - cur);
389 stream_puts(s, "]TJ\n");
390 } else {
391 pdf_put_string(pdev, pts->buffer.chars, pts->buffer.count_chars);
392 stream_puts(s, (pts->use_leading ? "'\n" : "Tj\n"));
393 }
394 pts->buffer.count_chars = 0;
395 pts->buffer.count_moves = 0;
396 pts->use_leading = false;
397 return 0;
398 }
399
400 /*
401 * Transition from string context to text context.
402 */
403 private int
sync_text_state(gx_device_pdf * pdev)404 sync_text_state(gx_device_pdf *pdev)
405 {
406 pdf_text_state_t *pts = pdev->text->text_state;
407 stream *s = pdev->strm;
408 int code;
409
410 if (pts->buffer.count_chars == 0)
411 return 0; /* nothing to output */
412
413 if (pts->continue_line)
414 return flush_text_buffer(pdev);
415
416 /* Bring text state parameters up to date. */
417
418 if (pts->out.character_spacing != pts->in.character_spacing) {
419 pprintg1(s, "%g Tc\n", pts->in.character_spacing);
420 pts->out.character_spacing = pts->in.character_spacing;
421 }
422
423 if (pts->out.pdfont != pts->in.pdfont || pts->out.size != pts->in.size) {
424 pdf_font_resource_t *pdfont = pts->in.pdfont;
425
426 pprints1(s, "/%s ", ((pdf_resource_t *)pts->in.pdfont)->rname);
427 pprintg1(s, "%g Tf\n", pts->in.size);
428 pts->out.pdfont = pdfont;
429 pts->out.size = pts->in.size;
430 /*
431 * In PDF, the only place to specify WMode is in the CMap
432 * (a.k.a. Encoding) of a Type 0 font.
433 */
434 pts->wmode =
435 (pdfont->FontType == ft_composite ?
436 pdfont->u.type0.WMode : 0);
437 code = pdf_used_charproc_resources(pdev, pdfont);
438 if (code < 0)
439 return code;
440 }
441
442 if (memcmp(&pts->in.matrix, &pts->out.matrix, sizeof(pts->in.matrix)) ||
443 ((pts->start.x != pts->out_pos.x || pts->start.y != pts->out_pos.y) &&
444 (pts->buffer.count_chars != 0 || pts->buffer.count_moves != 0))) {
445 /* pdf_set_text_matrix sets out.matrix = in.matrix */
446 code = pdf_set_text_matrix(pdev);
447 if (code < 0)
448 return code;
449 }
450
451 if (pts->out.render_mode != pts->in.render_mode) {
452 pprintg1(s, "%g Tr\n", pts->in.render_mode);
453 pts->out.render_mode = pts->in.render_mode;
454 }
455
456 if (pts->out.word_spacing != pts->in.word_spacing) {
457 if (memchr(pts->buffer.chars, 32, pts->buffer.count_chars)) {
458 pprintg1(s, "%g Tw\n", pts->in.word_spacing);
459 pts->out.word_spacing = pts->in.word_spacing;
460 }
461 }
462
463 return flush_text_buffer(pdev);
464 }
465
466 int
pdf_from_string_to_text(gx_device_pdf * pdev)467 pdf_from_string_to_text(gx_device_pdf *pdev)
468 {
469 return sync_text_state(pdev);
470 }
471
472 /*
473 * Close the text aspect of the current contents part.
474 */
475 void
pdf_close_text_contents(gx_device_pdf * pdev)476 pdf_close_text_contents(gx_device_pdf *pdev)
477 {
478 /*
479 * Clear the font pointer. This is probably left over from old code,
480 * but it is appropriate in case we ever choose in the future to write
481 * out and free font resources before the end of the document.
482 */
483 pdf_text_state_t *pts = pdev->text->text_state;
484
485 pts->in.pdfont = pts->out.pdfont = 0;
486 pts->in.size = pts->out.size = 0;
487 }
488
489 /*
490 * Test whether a change in render_mode requires resetting the stroke
491 * parameters.
492 */
493 bool
pdf_render_mode_uses_stroke(const gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)494 pdf_render_mode_uses_stroke(const gx_device_pdf *pdev,
495 const pdf_text_state_values_t *ptsv)
496 {
497 return (pdev->text->text_state->in.render_mode != ptsv->render_mode &&
498 ptsv->render_mode != 0);
499 }
500
501 /*
502 * Read the stored client view of text state values.
503 */
504 void
pdf_get_text_state_values(gx_device_pdf * pdev,pdf_text_state_values_t * ptsv)505 pdf_get_text_state_values(gx_device_pdf *pdev, pdf_text_state_values_t *ptsv)
506 {
507 *ptsv = pdev->text->text_state->in;
508 }
509
510 /*
511 * Set wmode to text state.
512 */
513 void
pdf_set_text_wmode(gx_device_pdf * pdev,int wmode)514 pdf_set_text_wmode(gx_device_pdf *pdev, int wmode)
515 {
516 pdf_text_state_t *pts = pdev->text->text_state;
517
518 pts->wmode = wmode;
519 }
520
521
522 /*
523 * Set the stored client view of text state values.
524 */
525 int
pdf_set_text_state_values(gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)526 pdf_set_text_state_values(gx_device_pdf *pdev,
527 const pdf_text_state_values_t *ptsv)
528 {
529 pdf_text_state_t *pts = pdev->text->text_state;
530
531 if (pts->buffer.count_chars > 0) {
532 int code;
533
534 if (pts->in.character_spacing == ptsv->character_spacing &&
535 pts->in.pdfont == ptsv->pdfont && pts->in.size == ptsv->size &&
536 pts->in.render_mode == ptsv->render_mode &&
537 pts->in.word_spacing == ptsv->word_spacing
538 ) {
539 if (!memcmp(&pts->in.matrix, &ptsv->matrix,
540 sizeof(pts->in.matrix)))
541 return 0;
542 /* add_text_delta_move sets pts->in.matrix if successful */
543 code = add_text_delta_move(pdev, &ptsv->matrix);
544 if (code >= 0)
545 return 0;
546 }
547 code = sync_text_state(pdev);
548 if (code < 0)
549 return code;
550 }
551
552 pts->in = *ptsv;
553 pts->continue_line = false;
554 return 0;
555 }
556
557 /*
558 * Transform a distance from unscaled text space (text space ignoring the
559 * scaling implied by the font size) to device space.
560 */
561 int
pdf_text_distance_transform(floatp wx,floatp wy,const pdf_text_state_t * pts,gs_point * ppt)562 pdf_text_distance_transform(floatp wx, floatp wy, const pdf_text_state_t *pts,
563 gs_point *ppt)
564 {
565 return gs_distance_transform(wx, wy, &pts->in.matrix, ppt);
566 }
567
568 /*
569 * Return the current (x,y) text position as seen by the client, in
570 * unscaled text space.
571 */
572 void
pdf_text_position(const gx_device_pdf * pdev,gs_point * ppt)573 pdf_text_position(const gx_device_pdf *pdev, gs_point *ppt)
574 {
575 pdf_text_state_t *pts = pdev->text->text_state;
576
577 ppt->x = pts->in.matrix.tx;
578 ppt->y = pts->in.matrix.ty;
579 }
580
581 /*
582 * Append characters to text being accumulated, giving their advance width
583 * in device space.
584 */
585 int
pdf_append_chars(gx_device_pdf * pdev,const byte * str,uint size,floatp wx,floatp wy,bool nobreak)586 pdf_append_chars(gx_device_pdf * pdev, const byte * str, uint size,
587 floatp wx, floatp wy, bool nobreak)
588 {
589 pdf_text_state_t *pts = pdev->text->text_state;
590 const byte *p = str;
591 uint left = size;
592
593 if (pts->buffer.count_chars == 0 && pts->buffer.count_moves == 0) {
594 pts->out_pos.x = pts->start.x = pts->in.matrix.tx;
595 pts->out_pos.y = pts->start.y = pts->in.matrix.ty;
596 }
597 while (left)
598 if (pts->buffer.count_chars == MAX_TEXT_BUFFER_CHARS ||
599 (nobreak && pts->buffer.count_chars + left > MAX_TEXT_BUFFER_CHARS)) {
600 int code = sync_text_state(pdev);
601
602 if (code < 0)
603 return code;
604 /* We'll keep a continuation of this line in the buffer,
605 * but the current input parameters don't correspond to
606 * the current position, because the text was broken in a
607 * middle with unknown current point.
608 * Don't change the output text state parameters
609 * until input parameters are changed.
610 * pdf_set_text_state_values will reset the 'continue_line' flag
611 * at that time.
612 */
613 pts->continue_line = true;
614 } else {
615 int code = pdf_open_page(pdev, PDF_IN_STRING);
616 uint copy;
617
618 if (code < 0)
619 return code;
620 copy = min(MAX_TEXT_BUFFER_CHARS - pts->buffer.count_chars, left);
621 memcpy(pts->buffer.chars + pts->buffer.count_chars, p, copy);
622 pts->buffer.count_chars += copy;
623 p += copy;
624 left -= copy;
625 }
626 pts->in.matrix.tx += wx;
627 pts->in.matrix.ty += wy;
628 pts->out_pos.x += wx;
629 pts->out_pos.y += wy;
630 return 0;
631 }
632