1 /* Copyright (C) 2001-2019 Artifex Software, Inc.
2 All Rights Reserved.
3
4 This software is provided AS-IS with no warranty, either express or
5 implied.
6
7 This software is distributed under license and may not be copied,
8 modified or distributed except as expressly authorized under the terms
9 of the license contained in the file LICENSE in this distribution.
10
11 Refer to licensing information at http://www.artifex.com or contact
12 Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato,
13 CA 94945, U.S.A., +1(415)492-9861, for further information.
14 */
15
16
17 /* Text state management for pdfwrite */
18 #include "math_.h"
19 #include "memory_.h"
20 #include "gx.h"
21 #include "gserrors.h"
22 #include "gdevpdfx.h"
23 #include "gdevpdfg.h"
24 #include "gdevpdtx.h"
25 #include "gdevpdtf.h" /* for pdfont->FontType */
26 #include "gdevpdts.h"
27 #include "gdevpdtt.h"
28 #include "gdevpdti.h"
29
30 /* ================ Types and structures ================ */
31
32 #define TEXT_BUFFER_DEFAULT\
33 { { 0, 0 } }, /* moves */\
34 { 0 }, /* chars */\
35 0, /* count_moves */\
36 0 /* count_chars */
37
38 static const pdf_text_state_t ts_default = {
39 /* State as seen by client */
40 { TEXT_STATE_VALUES_DEFAULT }, /* in */
41 { 0, 0 }, /* start */
42 { TEXT_BUFFER_DEFAULT }, /* buffer */
43 0, /* wmode */
44 /* State relative to content stream */
45 { TEXT_STATE_VALUES_DEFAULT }, /* out */
46 0, /* leading */
47 0 /*false*/, /* use_leading */
48 0 /*false*/, /* continue_line */
49 { 0, 0 }, /* line_start */
50 { 0, 0 }, /* output position */
51 0.0, /* PaintType0Width */
52 1 /* false */ /* can_use_TJ */
53 };
54 /* GC descriptor */
55 gs_private_st_ptrs2(st_pdf_text_state, pdf_text_state_t, "pdf_text_state_t",
56 pdf_text_state_enum_ptrs, pdf_text_state_reloc_ptrs,
57 in.pdfont, out.pdfont);
58
59 /* ================ Procedures ================ */
60
61 /* ---------------- Private ---------------- */
62
63 /*
64 * Append a writing-direction movement to the text being accumulated. If
65 * the buffer is full, or the requested movement is not in writing
66 * direction, return <0 and do nothing. (This is different from
67 * pdf_append_chars.) Requires pts->buffer.count_chars > 0.
68 */
69 static int
append_text_move(pdf_text_state_t * pts,double dw)70 append_text_move(pdf_text_state_t *pts, double dw)
71 {
72 int count = pts->buffer.count_moves;
73 int pos = pts->buffer.count_chars;
74 double rounded;
75
76 if (count > 0 && pts->buffer.moves[count - 1].index == pos) {
77 /* Merge adjacent moves. */
78 dw += pts->buffer.moves[--count].amount;
79 }
80 /* Round dw if it's very close to an integer. */
81 rounded = floor(dw + 0.5);
82 if (fabs(dw - rounded) < 0.001)
83 dw = rounded;
84 if (dw < -MAX_USER_COORD) {
85 /* Acrobat reader 4.0c, 5.0 can't handle big offsets.
86 Adobe Reader 6 can. */
87 return -1;
88 }
89 if (dw != 0) {
90 if (count == MAX_TEXT_BUFFER_MOVES)
91 return -1;
92 pts->buffer.moves[count].index = pos;
93 pts->buffer.moves[count].amount = dw;
94 ++count;
95 }
96 pts->buffer.count_moves = count;
97 return 0;
98 }
99
100 /*
101 * Set *pdist to the distance (dx,dy), in the space defined by *pmat.
102 */
103 static int
set_text_distance(gs_point * pdist,double dx,double dy,const gs_matrix * pmat)104 set_text_distance(gs_point *pdist, double dx, double dy, const gs_matrix *pmat)
105 {
106 int code;
107 double rounded;
108
109 if (dx > 1e38 || dy > 1e38)
110 code = gs_error_undefinedresult;
111 else
112 code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
113
114 if (code == gs_error_undefinedresult) {
115 /* The CTM is degenerate.
116 Can't know the distance in user space.
117 Set zero because we believe it is not important for rendering.
118 We want to copy the text to PDF to make it searchable.
119 Bug 689006.
120 */
121 pdist->x = pdist->y = 0;
122 } else if (code < 0)
123 return code;
124 /* If the distance is very close to integers, round it. */
125 if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
126 pdist->x = rounded;
127 if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
128 pdist->y = rounded;
129 return 0;
130 }
131
132 /*
133 * Test whether the transformation parts of two matrices are compatible.
134 */
135 static bool
matrix_is_compatible(const gs_matrix * pmat1,const gs_matrix * pmat2)136 matrix_is_compatible(const gs_matrix *pmat1, const gs_matrix *pmat2)
137 {
138 return (pmat2->xx == pmat1->xx && pmat2->xy == pmat1->xy &&
139 pmat2->yx == pmat1->yx && pmat2->yy == pmat1->yy);
140 }
141
142 /*
143 * Try to handle a change of text position with TJ or a space
144 * character. If successful, return >=0, if not, return <0.
145 */
146 static int
add_text_delta_move(gx_device_pdf * pdev,const gs_matrix * pmat)147 add_text_delta_move(gx_device_pdf *pdev, const gs_matrix *pmat)
148 {
149 pdf_text_state_t *const pts = pdev->text->text_state;
150
151 if (matrix_is_compatible(pmat, &pts->in.matrix)) {
152 double dx = pmat->tx - pts->in.matrix.tx,
153 dy = pmat->ty - pts->in.matrix.ty;
154 gs_point dist;
155 double dw, dnotw, tdw;
156 int code;
157
158 code = set_text_distance(&dist, dx, dy, pmat);
159 if (code < 0)
160 return code;
161 if (pts->wmode)
162 dw = dist.y, dnotw = dist.x;
163 else
164 dw = dist.x, dnotw = dist.y;
165 tdw = dw * -1000.0 / pts->in.size;
166
167 /* can_use_TJ is normally true, it is false only when we get a
168 * x/y/xyshow, and the width != real_width. In this case we cannot
169 * be certain of exactly how we got there. If its a PDF file with
170 * a /Widths override, and the operation is an x/y/xyshow (which
171 * will happen if the FontMatrix is nither horizontal not vertical)
172 * then we don't want to use a TJ as that will apply the Width once
173 * for the xhow and once for the Width override. Otherwise, we do
174 * want to use TJ as it makes for smaller files.
175 */
176 if (pts->can_use_TJ && dnotw == 0 && pts->buffer.count_chars > 0 &&
177 /*
178 * Acrobat Reader limits the magnitude of user-space
179 * coordinates. Also, AR apparently doesn't handle large
180 * positive movement values (negative X displacements), even
181 * though the PDF Reference says this bug was fixed in AR3.
182 *
183 * Old revisions used the upper threshold 1000 for tdw,
184 * but it appears too big when a font sets a too big
185 * character width in setcachedevice. Particularly this happens
186 * with a Type 3 font generated by Aldus Freehand 4.0
187 * to represent a texture - see bug #687051.
188 * The problem is that when the Widths is multiplied
189 * to the font size, the viewer represents the result
190 * with insufficient fraction bits to represent the precise width.
191 * We work around that problem here restricting tdw
192 * with a smaller threshold 990. Our intention is to
193 * disable Tj when the real glyph width appears smaller
194 * than 1% of the width specified in setcachedevice.
195 * A Td instruction will be generated instead.
196 * Note that the value 990 is arbitrary and may need a
197 * further adjustment.
198 */
199 /* Revised the above. It seems unreasonable to use a fixed
200 * value which is not based on the point size, when the problem is
201 * caused by a large point size being multiplied by the width. The
202 * original fix also caused bitmap fonts (from PCL and other sources)
203 * to fail to use kerning, as these fonts are scaled to 1 point and
204 * therefore use large kerning values. Instead we check the kerned value
205 * multiplied by the point size of the font.
206 */
207 (tdw >= -MAX_USER_COORD && (tdw * pts->in.size) < MAX_USER_COORD)
208 ) {
209 /* Use TJ. */
210 int code;
211
212 if (tdw < MAX_USER_COORD || pdev->CompatibilityLevel > 1.4)
213 code = append_text_move(pts, tdw);
214 else
215 return -1;
216
217 if (code >= 0)
218 goto finish;
219 }
220 }
221 return -1;
222 finish:
223 pts->in.matrix = *pmat;
224 return 0;
225 }
226
227 /*
228 * Set the text matrix for writing text. The translation component of the
229 * matrix is the text origin. If the non-translation components of the
230 * matrix differ from the current ones, write a Tm command; if there is only
231 * a Y translation, set use_leading so the next text string will be written
232 * with ' rather than Tj; otherwise, write a Td command.
233 */
234 static int
pdf_set_text_matrix(gx_device_pdf * pdev)235 pdf_set_text_matrix(gx_device_pdf * pdev)
236 {
237 pdf_text_state_t *pts = pdev->text->text_state;
238 stream *s = pdev->strm;
239
240 pts->use_leading = false;
241 if (matrix_is_compatible(&pts->out.matrix, &pts->in.matrix)) {
242 gs_point dist;
243 int code;
244
245 code = set_text_distance(&dist, pts->start.x - pts->line_start.x,
246 pts->start.y - pts->line_start.y, &pts->in.matrix);
247 if (code < 0)
248 return code;
249 if (dist.x == 0 && dist.y < 0) {
250 /* Use TL, if needed, and T* or '. */
251 float dist_y = (float)-dist.y;
252
253 if (fabs(pts->leading - dist_y) > 0.0005) {
254 pprintg1(s, "%g TL\n", dist_y);
255 pts->leading = dist_y;
256 }
257 pts->use_leading = true;
258 } else {
259 /* Use Td. */
260 pprintg2(s, "%g %g Td\n", dist.x, dist.y);
261 }
262 } else { /* Use Tm. */
263 /*
264 * See stream_to_text in gdevpdfu.c for why we need the following
265 * matrix adjustments.
266 */
267 double sx = 72.0 / pdev->HWResolution[0],
268 sy = 72.0 / pdev->HWResolution[1], ax = sx, bx = sx, ay = sy, by = sy;
269
270 /* We have a precision limit on decimal places with %g, make sure
271 * we don't end up with values which will be truncated to 0
272 */
273 if (pts->in.matrix.xx != 0 && fabs(pts->in.matrix.xx) * ax < 0.00000001)
274 ax = ceil(0.00000001 / pts->in.matrix.xx);
275 if (pts->in.matrix.xy != 0 && fabs(pts->in.matrix.xy) * ay < 0.00000001)
276 ay = ceil(0.00000001 / pts->in.matrix.xy);
277 if (pts->in.matrix.yx != 0 && fabs(pts->in.matrix.yx) * bx < 0.00000001)
278 bx = ceil(0.00000001 / pts->in.matrix.yx);
279 if (pts->in.matrix.yy != 0 && fabs(pts->in.matrix.yy) * by < 0.00000001)
280 by = ceil(0.00000001 / pts->in.matrix.yy);
281 pprintg6(s, "%g %g %g %g %g %g Tm\n",
282 pts->in.matrix.xx * ax, pts->in.matrix.xy * ay,
283 pts->in.matrix.yx * bx, pts->in.matrix.yy * by,
284 pts->start.x * sx, pts->start.y * sy);
285 }
286 pts->line_start.x = pts->start.x;
287 pts->line_start.y = pts->start.y;
288 pts->out.matrix = pts->in.matrix;
289 return 0;
290 }
291
292 /* ---------------- Public ---------------- */
293
294 /*
295 * Allocate and initialize text state bookkeeping.
296 */
297 pdf_text_state_t *
pdf_text_state_alloc(gs_memory_t * mem)298 pdf_text_state_alloc(gs_memory_t *mem)
299 {
300 pdf_text_state_t *pts =
301 gs_alloc_struct(mem, pdf_text_state_t, &st_pdf_text_state,
302 "pdf_text_state_alloc");
303
304 if (pts == 0)
305 return 0;
306 *pts = ts_default;
307 return pts;
308 }
309
310 /*
311 * Set the text state to default values.
312 */
313 void
pdf_set_text_state_default(pdf_text_state_t * pts)314 pdf_set_text_state_default(pdf_text_state_t *pts)
315 {
316 *pts = ts_default;
317 }
318
319 /*
320 * Copy the text state.
321 */
322 void
pdf_text_state_copy(pdf_text_state_t * pts_to,pdf_text_state_t * pts_from)323 pdf_text_state_copy(pdf_text_state_t *pts_to, pdf_text_state_t *pts_from)
324 {
325 *pts_to = *pts_from;
326 }
327
328 /*
329 * Reset the text state to its condition at the beginning of the page.
330 */
331 void
pdf_reset_text_page(pdf_text_data_t * ptd)332 pdf_reset_text_page(pdf_text_data_t *ptd)
333 {
334 pdf_set_text_state_default(ptd->text_state);
335 }
336
337 /*
338 * Reset the text state after a grestore.
339 */
340 void
pdf_reset_text_state(pdf_text_data_t * ptd)341 pdf_reset_text_state(pdf_text_data_t *ptd)
342 {
343 pdf_text_state_t *pts = ptd->text_state;
344
345 pts->out = ts_default.out;
346 pts->leading = 0;
347 }
348
349 /*
350 * Transition from stream context to text context.
351 */
352 int
pdf_from_stream_to_text(gx_device_pdf * pdev)353 pdf_from_stream_to_text(gx_device_pdf *pdev)
354 {
355 pdf_text_state_t *pts = pdev->text->text_state;
356
357 gs_make_identity(&pts->out.matrix);
358 pts->line_start.x = pts->line_start.y = 0;
359 pts->continue_line = false; /* Not sure, probably doesn't matter. */
360 pts->buffer.count_chars = 0;
361 pts->buffer.count_moves = 0;
362 return 0;
363 }
364
365 /*
366 * Flush text from buffer.
367 */
368 static int
flush_text_buffer(gx_device_pdf * pdev)369 flush_text_buffer(gx_device_pdf *pdev)
370 {
371 pdf_text_state_t *pts = pdev->text->text_state;
372 stream *s = pdev->strm;
373
374 if (pts->buffer.count_chars != 0) {
375 pdf_font_resource_t *pdfont = pts->in.pdfont;
376 int code = pdf_assign_font_object_id(pdev, pdfont);
377
378 if (code < 0)
379 return code;
380 code = pdf_add_resource(pdev, pdev->substream_Resources, "/Font", (pdf_resource_t *)pdfont);
381 if (code < 0)
382 return code;
383 }
384 if (pts->buffer.count_moves > 0) {
385 int i, cur = 0;
386
387 if (pts->use_leading)
388 stream_puts(s, "T*");
389 stream_puts(s, "[");
390 for (i = 0; i < pts->buffer.count_moves; ++i) {
391 int next = pts->buffer.moves[i].index;
392
393 pdf_put_string(pdev, pts->buffer.chars + cur, next - cur);
394 pprintg1(s, "%g", pts->buffer.moves[i].amount);
395 cur = next;
396 }
397 if (pts->buffer.count_chars > cur)
398 pdf_put_string(pdev, pts->buffer.chars + cur,
399 pts->buffer.count_chars - cur);
400 stream_puts(s, "]TJ\n");
401 } else {
402 pdf_put_string(pdev, pts->buffer.chars, pts->buffer.count_chars);
403 stream_puts(s, (pts->use_leading ? "'\n" : "Tj\n"));
404 }
405 pts->buffer.count_chars = 0;
406 pts->buffer.count_moves = 0;
407 pts->use_leading = false;
408 return 0;
409 }
410
411 /*
412 * Transition from string context to text context.
413 */
414 int
sync_text_state(gx_device_pdf * pdev)415 sync_text_state(gx_device_pdf *pdev)
416 {
417 pdf_text_state_t *pts = pdev->text->text_state;
418 stream *s = pdev->strm;
419 int code;
420
421 if (pts->buffer.count_chars == 0)
422 return 0; /* nothing to output */
423
424 if (pts->continue_line)
425 return flush_text_buffer(pdev);
426
427 /* Bring text state parameters up to date. */
428
429 if (pts->out.character_spacing != pts->in.character_spacing) {
430 pprintg1(s, "%g Tc\n", pts->in.character_spacing);
431 pts->out.character_spacing = pts->in.character_spacing;
432 }
433
434 if (pts->out.pdfont != pts->in.pdfont || pts->out.size != pts->in.size) {
435 pdf_font_resource_t *pdfont = pts->in.pdfont;
436
437 code = pdf_assign_font_object_id(pdev, pdfont);
438 if (code < 0)
439 return code;
440 pprints1(s, "/%s ", pdfont->rname);
441 pprintg1(s, "%g Tf\n", pts->in.size);
442 pts->out.pdfont = pdfont;
443 pts->out.size = pts->in.size;
444 /*
445 * In PDF, the only place to specify WMode is in the CMap
446 * (a.k.a. Encoding) of a Type 0 font.
447 */
448 pts->wmode =
449 (pdfont->FontType == ft_composite ?
450 pdfont->u.type0.WMode : 0);
451 code = pdf_used_charproc_resources(pdev, pdfont);
452 if (code < 0)
453 return code;
454 }
455
456 if (gs_matrix_compare(&pts->in.matrix, &pts->out.matrix) ||
457 ((pts->start.x != pts->out_pos.x || pts->start.y != pts->out_pos.y) &&
458 (pts->buffer.count_chars != 0 || pts->buffer.count_moves != 0))) {
459 /* pdf_set_text_matrix sets out.matrix = in.matrix */
460 code = pdf_set_text_matrix(pdev);
461 if (code < 0)
462 return code;
463 }
464
465 if (pts->out.render_mode != pts->in.render_mode) {
466 pprintg1(s, "%g Tr\n", pts->in.render_mode);
467 pts->out.render_mode = pts->in.render_mode;
468 }
469
470 if (pts->out.word_spacing != pts->in.word_spacing) {
471 if (memchr(pts->buffer.chars, 32, pts->buffer.count_chars)) {
472 pprintg1(s, "%g Tw\n", pts->in.word_spacing);
473 pts->out.word_spacing = pts->in.word_spacing;
474 }
475 }
476
477 return flush_text_buffer(pdev);
478 }
479
480 int
pdf_from_string_to_text(gx_device_pdf * pdev)481 pdf_from_string_to_text(gx_device_pdf *pdev)
482 {
483 return sync_text_state(pdev);
484 }
485
486 /*
487 * Close the text aspect of the current contents part.
488 */
489 void
pdf_close_text_contents(gx_device_pdf * pdev)490 pdf_close_text_contents(gx_device_pdf *pdev)
491 {
492 /*
493 * Clear the font pointer. This is probably left over from old code,
494 * but it is appropriate in case we ever choose in the future to write
495 * out and free font resources before the end of the document.
496 */
497 pdf_text_state_t *pts = pdev->text->text_state;
498
499 pts->in.pdfont = pts->out.pdfont = 0;
500 pts->in.size = pts->out.size = 0;
501 }
502
503 /*
504 * Test whether a change in render_mode requires resetting the stroke
505 * parameters.
506 */
507 bool
pdf_render_mode_uses_stroke(const gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)508 pdf_render_mode_uses_stroke(const gx_device_pdf *pdev,
509 const pdf_text_state_values_t *ptsv)
510 {
511 return ((ptsv->render_mode == 1 || ptsv->render_mode == 2 ||
512 ptsv->render_mode == 5 || ptsv->render_mode == 6));
513 }
514
515 /*
516 * Read the stored client view of text state values.
517 */
518 void
pdf_get_text_state_values(gx_device_pdf * pdev,pdf_text_state_values_t * ptsv)519 pdf_get_text_state_values(gx_device_pdf *pdev, pdf_text_state_values_t *ptsv)
520 {
521 *ptsv = pdev->text->text_state->in;
522 }
523
524 /*
525 * Set wmode to text state.
526 */
527 void
pdf_set_text_wmode(gx_device_pdf * pdev,int wmode)528 pdf_set_text_wmode(gx_device_pdf *pdev, int wmode)
529 {
530 pdf_text_state_t *pts = pdev->text->text_state;
531
532 pts->wmode = wmode;
533 }
534
535 /*
536 * Set the stored client view of text state values.
537 */
538 int
pdf_set_text_state_values(gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)539 pdf_set_text_state_values(gx_device_pdf *pdev,
540 const pdf_text_state_values_t *ptsv)
541 {
542 pdf_text_state_t *pts = pdev->text->text_state;
543
544 if (pts->buffer.count_chars > 0) {
545 int code;
546
547 if (pts->in.character_spacing == ptsv->character_spacing &&
548 pts->in.pdfont == ptsv->pdfont && pts->in.size == ptsv->size &&
549 pts->in.render_mode == ptsv->render_mode &&
550 pts->in.word_spacing == ptsv->word_spacing
551 ) {
552 if (!gs_matrix_compare(&pts->in.matrix, &ptsv->matrix))
553 return 0;
554 /* add_text_delta_move sets pts->in.matrix if successful */
555 code = add_text_delta_move(pdev, &ptsv->matrix);
556 if (code >= 0)
557 return 0;
558 }
559 code = sync_text_state(pdev);
560 if (code < 0)
561 return code;
562 }
563
564 pts->in = *ptsv;
565 pts->continue_line = false;
566 return 0;
567 }
568
569 /*
570 * Transform a distance from unscaled text space (text space ignoring the
571 * scaling implied by the font size) to device space.
572 */
573 int
pdf_text_distance_transform(double wx,double wy,const pdf_text_state_t * pts,gs_point * ppt)574 pdf_text_distance_transform(double wx, double wy, const pdf_text_state_t *pts,
575 gs_point *ppt)
576 {
577 return gs_distance_transform(wx, wy, &pts->in.matrix, ppt);
578 }
579
580 /*
581 * Return the current (x,y) text position as seen by the client, in
582 * unscaled text space.
583 */
584 void
pdf_text_position(const gx_device_pdf * pdev,gs_point * ppt)585 pdf_text_position(const gx_device_pdf *pdev, gs_point *ppt)
586 {
587 pdf_text_state_t *pts = pdev->text->text_state;
588
589 ppt->x = pts->in.matrix.tx;
590 ppt->y = pts->in.matrix.ty;
591 }
592
pdf_bitmap_char_update_bbox(gx_device_pdf * pdev,int x_offset,int y_offset,double x,double y)593 int pdf_bitmap_char_update_bbox(gx_device_pdf * pdev,int x_offset, int y_offset, double x, double y)
594 {
595 pdf_text_state_t *pts = pdev->text->text_state;
596 gs_rect bbox;
597
598 bbox.p.x = (pts->in.matrix.tx + x_offset) / (pdev->HWResolution[0] / 72);
599 bbox.p.y = (pts->in.matrix.ty + y_offset) / (pdev->HWResolution[1] / 72);
600 bbox.q.x = bbox.p.x + (x / (pdev->HWResolution[0] / 72));
601 bbox.q.y = bbox.p.y + (y / (pdev->HWResolution[0] / 72));
602
603 if (bbox.p.x < pdev->BBox.p.x)
604 pdev->BBox.p.x = bbox.p.x;
605 if (bbox.p.y < pdev->BBox.p.y)
606 pdev->BBox.p.y = bbox.p.y;
607 if (bbox.q.x > pdev->BBox.q.x)
608 pdev->BBox.q.x = bbox.q.x;
609 if (bbox.q.y > pdev->BBox.q.y)
610 pdev->BBox.q.y = bbox.q.y;
611
612 return 0;
613 }
614 /*
615 * Append characters to text being accumulated, giving their advance width
616 * in device space.
617 */
618 int
pdf_append_chars(gx_device_pdf * pdev,const byte * str,uint size,double wx,double wy,bool nobreak)619 pdf_append_chars(gx_device_pdf * pdev, const byte * str, uint size,
620 double wx, double wy, bool nobreak)
621 {
622 pdf_text_state_t *pts = pdev->text->text_state;
623 const byte *p = str;
624 uint left = size;
625
626 if (pts->buffer.count_chars == 0 && pts->buffer.count_moves == 0) {
627 pts->out_pos.x = pts->start.x = pts->in.matrix.tx;
628 pts->out_pos.y = pts->start.y = pts->in.matrix.ty;
629 }
630 while (left)
631 if (pts->buffer.count_chars == MAX_TEXT_BUFFER_CHARS ||
632 (nobreak && pts->buffer.count_chars + left > MAX_TEXT_BUFFER_CHARS)) {
633 int code = sync_text_state(pdev);
634
635 if (code < 0)
636 return code;
637 /* We'll keep a continuation of this line in the buffer,
638 * but the current input parameters don't correspond to
639 * the current position, because the text was broken in a
640 * middle with unknown current point.
641 * Don't change the output text state parameters
642 * until input parameters are changed.
643 * pdf_set_text_state_values will reset the 'continue_line' flag
644 * at that time.
645 */
646 pts->continue_line = true;
647 } else {
648 int code = pdf_open_page(pdev, PDF_IN_STRING);
649 uint copy;
650
651 if (code < 0)
652 return code;
653 copy = min(MAX_TEXT_BUFFER_CHARS - pts->buffer.count_chars, left);
654 memcpy(pts->buffer.chars + pts->buffer.count_chars, p, copy);
655 pts->buffer.count_chars += copy;
656 p += copy;
657 left -= copy;
658 }
659 pts->in.matrix.tx += wx;
660 pts->in.matrix.ty += wy;
661 pts->out_pos.x += wx;
662 pts->out_pos.y += wy;
663 return 0;
664 }
665
666 /* Check a new piece of charpath text to see if its safe to combine
667 * with a previous text operation using text rendering modes.
668 */
pdf_compare_text_state_for_charpath(pdf_text_state_t * pts,gx_device_pdf * pdev,gs_gstate * pgs,gs_font * font,const gs_text_params_t * text)669 bool pdf_compare_text_state_for_charpath(pdf_text_state_t *pts, gx_device_pdf *pdev,
670 gs_gstate *pgs, gs_font *font,
671 const gs_text_params_t *text)
672 {
673 int code;
674 float size;
675 gs_matrix smat, tmat;
676 struct pdf_font_resource_s *pdfont;
677
678 /* check to ensure the new text has the same length as the saved text */
679 if(text->size != pts->buffer.count_chars)
680 return(false);
681
682 if(font->FontType == ft_user_defined ||
683 font->FontType == ft_PDF_user_defined ||
684 font->FontType == ft_PCL_user_defined ||
685 font->FontType == ft_MicroType ||
686 font->FontType == ft_GL2_stick_user_defined ||
687 font->FontType == ft_GL2_531)
688 return(false);
689
690 /* check to ensure the new text has the same data as the saved text */
691 if(memcmp(text->data.bytes, &pts->buffer.chars, text->size))
692 return(false);
693
694 /* See if the same font is in use by checking the attahced pdfont resource for
695 * the currrent font and comparing with the saved text state
696 */
697 code = pdf_attached_font_resource(pdev, font, &pdfont, NULL, NULL, NULL, NULL);
698 if(code < 0)
699 return(false);
700
701 if(!pdfont || pdfont != pts->in.pdfont)
702 return(false);
703
704 /* Check to see the new text starts at the same point as the saved text.
705 * NB! only check 2 decimal places, allow some slack in the match. This
706 * still may prove to be too tight a requirement.
707 */
708 if(fabs(pts->start.x - pgs->current_point.x) > 0.01 ||
709 fabs(pts->start.y - pgs->current_point.y) > 0.01)
710 return(false);
711
712 size = pdf_calculate_text_size(pgs, pdfont, &font->FontMatrix, &smat, &tmat, font, pdev);
713
714 /* Finally, check the calculated size against the size stored in
715 * the text state.
716 */
717 if(size != pts->in.size)
718 return(false);
719
720 return(true);
721 }
722
pdf_get_text_render_mode(pdf_text_state_t * pts)723 int pdf_get_text_render_mode(pdf_text_state_t *pts)
724 {
725 return(pts->in.render_mode);
726 }
727
pdf_set_text_render_mode(pdf_text_state_t * pts,int mode)728 void pdf_set_text_render_mode(pdf_text_state_t *pts, int mode)
729 {
730 pts->in.render_mode = mode;
731 }
732
733 /* Add a render mode to the rendering mode of the current text.
734 * mode 0 = fill
735 * mode 1 = stroke
736 * mode 2 = clip
737 * If the modes are not compatible returns 0. NB currently only
738 * a stroke rendering mode is supported.
739 */
pdf_modify_text_render_mode(pdf_text_state_t * pts,int render_mode)740 int pdf_modify_text_render_mode(pdf_text_state_t *pts, int render_mode)
741 {
742 switch (pts->in.render_mode) {
743 case 0:
744 if (render_mode == 1) {
745 pts->in.render_mode = 2;
746 return(1);
747 }
748 break;
749 case 1:
750 if (render_mode == 1)
751 return(1);
752 break;
753 case 2:
754 if (render_mode == 1)
755 return(1);
756 break;
757 case 3:
758 if (render_mode == 1) {
759 pts->in.render_mode = 1;
760 return(1);
761 }
762 break;
763 case 4:
764 if (render_mode == 1) {
765 pts->in.render_mode = 6;
766 return(1);
767 }
768 break;
769 case 5:
770 if (render_mode == 1)
771 return(1);
772 break;
773 case 6:
774 if (render_mode == 1)
775 return(1);
776 break;
777 case 7:
778 if (render_mode == 1) {
779 pts->in.render_mode = 5;
780 return(1);
781 }
782 break;
783 default:
784 break;
785 }
786 return(0);
787 }
788
pdf_set_PaintType0_params(gx_device_pdf * pdev,gs_gstate * pgs,float size,double scaled_width,const pdf_text_state_values_t * ptsv)789 int pdf_set_PaintType0_params (gx_device_pdf *pdev, gs_gstate *pgs, float size,
790 double scaled_width, const pdf_text_state_values_t *ptsv)
791 {
792 pdf_text_state_t *pts = pdev->text->text_state;
793 double saved_width = pgs->line_params.half_width;
794 int code;
795
796 /* This routine is used to check if we have accumulated glyphs waiting for output
797 * if we do, and we are using a PaintType 0 font (stroke), which is the only way we
798 * can get here, then we check to see if the stroke width has changed. If so we want to
799 * flush the buffer, and set the new stroke width. This produces:
800 * <width> w
801 * (text) Tj
802 * <new width> w
803 * (new text) Tj
804 *
805 * instead of :
806 * <width> w
807 * <new width> w
808 * (text) Tj
809 * (new text) Tj
810 */
811 if (pts->buffer.count_chars > 0) {
812 if (pts->PaintType0Width != scaled_width) {
813 pgs->line_params.half_width = scaled_width / 2;
814 code = pdf_set_text_state_values(pdev, ptsv);
815 if (code < 0)
816 return code;
817 if (pdev->text->text_state->in.render_mode == ptsv->render_mode){
818 code = pdf_prepare_stroke(pdev, pgs, false);
819 if (code >= 0)
820 code = gdev_vector_prepare_stroke((gx_device_vector *)pdev,
821 pgs, NULL, NULL, 1);
822 }
823 if (code < 0)
824 return code;
825 pgs->line_params.half_width = saved_width;
826 pts->PaintType0Width = scaled_width;
827 }
828 }
829 return 0;
830 }
831