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