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