Coverage Report

Created: 2025-11-16 07:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/devices/vector/gdevpdts.c
Line
Count
Source
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
14.4M
{
72
14.4M
    int count = pts->buffer.count_moves;
73
14.4M
    int pos = pts->buffer.count_chars;
74
14.4M
    double rounded;
75
76
14.4M
    if (count > 0 && pts->buffer.moves[count - 1].index == pos) {
77
        /* Merge adjacent moves. */
78
2.22M
        dw += pts->buffer.moves[--count].amount;
79
2.22M
    }
80
    /* Round dw if it's very close to an integer. */
81
14.4M
    rounded = floor(dw + 0.5);
82
14.4M
    if (fabs(dw - rounded) < 0.001)
83
2.97M
        dw = rounded;
84
14.4M
    if (pdev->PDFA == 1 && dw < -MAX_USER_COORD) {
85
        /* PDF/A-1 limit on co-ordinates */
86
0
        return -1;
87
0
    }
88
14.4M
    if (dw != 0) {
89
12.9M
        if (count == MAX_TEXT_BUFFER_MOVES)
90
80.4k
            return -1;
91
12.8M
        pts->buffer.moves[count].index = pos;
92
12.8M
        pts->buffer.moves[count].amount = dw;
93
12.8M
        ++count;
94
12.8M
    }
95
14.3M
    pts->buffer.count_moves = count;
96
14.3M
    return 0;
97
14.4M
}
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
16.6M
{
105
16.6M
    int code;
106
16.6M
    double rounded;
107
108
16.6M
    if (dx > 1e38 || dy > 1e38)
109
6
        code = gs_error_undefinedresult;
110
16.6M
    else
111
16.6M
        code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
112
113
16.6M
    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
987
        pdist->x = pdist->y = 0;
121
16.6M
    } else if (code < 0)
122
0
        return code;
123
    /* If the distance is very close to integers, round it. */
124
16.6M
    if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
125
2.39M
        pdist->x = rounded;
126
16.6M
    if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
127
15.7M
        pdist->y = rounded;
128
16.6M
    return 0;
129
16.6M
}
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
16.9M
{
137
16.9M
    return (pmat2->xx == pmat1->xx && pmat2->xy == pmat1->xy &&
138
16.6M
            pmat2->yx == pmat1->yx && pmat2->yy == pmat1->yy);
139
16.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
15.3M
{
148
15.3M
    pdf_text_state_t *const pts = pdev->text->text_state;
149
150
15.3M
    if (matrix_is_compatible(pmat, &pts->in.matrix)) {
151
15.3M
        double dx = pmat->tx - pts->in.matrix.tx,
152
15.3M
            dy = pmat->ty - pts->in.matrix.ty;
153
15.3M
        gs_point dist;
154
15.3M
        double dw, dnotw, tdw;
155
15.3M
        int code;
156
157
15.3M
        code = set_text_distance(&dist, dx, dy, pmat);
158
15.3M
        if (code < 0)
159
0
            return code;
160
15.3M
        if (pts->wmode)
161
3.88k
            dw = dist.y, dnotw = dist.x;
162
15.3M
        else
163
15.3M
            dw = dist.x, dnotw = dist.y;
164
15.3M
        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
15.3M
        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
14.4M
            (((tdw >= -MAX_USER_COORD && (tdw * pts->in.size) < MAX_USER_COORD) || pdev->PDFA != 1))
207
15.3M
            ) {
208
            /* Use TJ. */
209
14.4M
            int code;
210
211
14.4M
            if (tdw < MAX_USER_COORD || pdev->PDFA != 1)
212
14.4M
                code = append_text_move(pdev, pts, tdw);
213
0
            else
214
0
                return -1;
215
216
14.4M
            if (code >= 0)
217
14.3M
                goto finish;
218
14.4M
        }
219
15.3M
    }
220
969k
    return -1;
221
14.3M
 finish:
222
14.3M
    pts->in.matrix = *pmat;
223
14.3M
    return 0;
224
15.3M
}
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.65M
{
236
1.65M
    pdf_text_state_t *pts = pdev->text->text_state;
237
1.65M
    stream *s = pdev->strm;
238
239
1.65M
    pts->use_leading = false;
240
1.65M
    if (matrix_is_compatible(&pts->out.matrix, &pts->in.matrix)) {
241
1.29M
        gs_point dist;
242
1.29M
        int code;
243
244
1.29M
        code = set_text_distance(&dist, pts->start.x - pts->line_start.x,
245
1.29M
                          pts->start.y - pts->line_start.y, &pts->in.matrix);
246
1.29M
        if (code < 0)
247
0
            return code;
248
1.29M
        if (dist.x == 0 && dist.y < 0) {
249
            /* Use TL, if needed, and T* or '. */
250
198k
            float dist_y = (float)-dist.y;
251
252
198k
            if (fabs(pts->leading - dist_y) > 0.0005) {
253
148k
                pprintg1(s, "%g TL\n", dist_y);
254
148k
                pts->leading = dist_y;
255
148k
            }
256
198k
            pts->use_leading = true;
257
1.09M
        } else {
258
            /* Use Td. */
259
1.09M
            pprintg2(s, "%g %g Td\n", dist.x, dist.y);
260
1.09M
        }
261
1.29M
    } else {     /* Use Tm. */
262
        /*
263
         * See stream_to_text in gdevpdfu.c for why we need the following
264
         * matrix adjustments.
265
         */
266
360k
        double sx = 72.0 / pdev->HWResolution[0],
267
360k
            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
360k
        if (pts->in.matrix.xx != 0 && fabs(pts->in.matrix.xx) * ax < 0.00000001)
273
50
            ax = ceil(0.00000001 / pts->in.matrix.xx);
274
360k
        if (pts->in.matrix.xy != 0 && fabs(pts->in.matrix.xy) * ay < 0.00000001)
275
113
            ay = ceil(0.00000001 / pts->in.matrix.xy);
276
360k
        if (pts->in.matrix.yx != 0 && fabs(pts->in.matrix.yx) * bx < 0.00000001)
277
142
            bx = ceil(0.00000001 / pts->in.matrix.yx);
278
360k
        if (pts->in.matrix.yy != 0 && fabs(pts->in.matrix.yy) * by < 0.00000001)
279
43
            by = ceil(0.00000001 / pts->in.matrix.yy);
280
360k
        pprintg6(s, "%g %g %g %g %g %g Tm\n",
281
360k
                 pts->in.matrix.xx * ax, pts->in.matrix.xy * ay,
282
360k
                 pts->in.matrix.yx * bx, pts->in.matrix.yy * by,
283
360k
                 pts->start.x * sx, pts->start.y * sy);
284
360k
    }
285
1.65M
    pts->line_start.x = pts->start.x;
286
1.65M
    pts->line_start.y = pts->start.y;
287
1.65M
    pts->out.matrix = pts->in.matrix;
288
1.65M
    return 0;
289
1.65M
}
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
228k
{
299
228k
    pdf_text_state_t *pts =
300
228k
        gs_alloc_struct(mem, pdf_text_state_t, &st_pdf_text_state,
301
228k
                        "pdf_text_state_alloc");
302
303
228k
    if (pts == 0)
304
0
        return 0;
305
228k
    *pts = ts_default;
306
228k
    return pts;
307
228k
}
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
279k
{
315
279k
    *pts = ts_default;
316
279k
}
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
377k
{
324
377k
    *pts_to = *pts_from;
325
377k
}
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
90.6k
{
333
90.6k
    pdf_set_text_state_default(ptd->text_state);
334
90.6k
}
335
336
/*
337
 * Reset the text state after a grestore.
338
 */
339
void
340
pdf_reset_text_state(pdf_text_data_t *ptd)
341
641k
{
342
641k
    pdf_text_state_t *pts = ptd->text_state;
343
344
641k
    pts->out = ts_default.out;
345
641k
    pts->leading = 0;
346
641k
}
347
348
/*
349
 * Transition from stream context to text context.
350
 */
351
int
352
pdf_from_stream_to_text(gx_device_pdf *pdev)
353
303k
{
354
303k
    pdf_text_state_t *pts = pdev->text->text_state;
355
356
303k
    gs_make_identity(&pts->out.matrix);
357
303k
    pts->line_start.x = pts->line_start.y = 0;
358
303k
    pts->continue_line = false; /* Not sure, probably doesn't matter. */
359
303k
    pts->buffer.count_chars = 0;
360
303k
    pts->buffer.count_moves = 0;
361
303k
    return 0;
362
303k
}
363
364
/*
365
 *  Flush text from buffer.
366
 */
367
static int
368
flush_text_buffer(gx_device_pdf *pdev)
369
1.65M
{
370
1.65M
    pdf_text_state_t *pts = pdev->text->text_state;
371
1.65M
    stream *s = pdev->strm;
372
373
1.65M
    if (pts->buffer.count_chars != 0) {
374
1.65M
        pdf_font_resource_t *pdfont = pts->in.pdfont;
375
1.65M
        int code = pdf_assign_font_object_id(pdev, pdfont);
376
377
1.65M
        if (code < 0)
378
0
            return code;
379
1.65M
        code = pdf_add_resource(pdev, pdev->substream_Resources, "/Font", (pdf_resource_t *)pdfont);
380
1.65M
        if (code < 0)
381
0
            return code;
382
1.65M
    }
383
1.65M
    if (pts->buffer.count_moves > 0) {
384
949k
        int i, cur = 0;
385
386
949k
        if (pts->use_leading)
387
173k
            stream_puts(s, "T*");
388
949k
        stream_puts(s, "[");
389
11.6M
        for (i = 0; i < pts->buffer.count_moves; ++i) {
390
10.6M
            int next = pts->buffer.moves[i].index;
391
392
10.6M
            pdf_put_string(pdev, pts->buffer.chars + cur, next - cur);
393
10.6M
            pprintg1(s, "%g", pts->buffer.moves[i].amount);
394
10.6M
            cur = next;
395
10.6M
        }
396
949k
        if (pts->buffer.count_chars > cur)
397
648k
            pdf_put_string(pdev, pts->buffer.chars + cur,
398
648k
                           pts->buffer.count_chars - cur);
399
949k
        stream_puts(s, "]TJ\n");
400
949k
    } else {
401
707k
        pdf_put_string(pdev, pts->buffer.chars, pts->buffer.count_chars);
402
707k
        stream_puts(s, (pts->use_leading ? "'\n" : "Tj\n"));
403
707k
    }
404
1.65M
    pts->buffer.count_chars = 0;
405
1.65M
    pts->buffer.count_moves = 0;
406
1.65M
    pts->use_leading = false;
407
1.65M
    return 0;
408
1.65M
}
409
410
/*
411
 * Transition from string context to text context.
412
 */
413
int
414
sync_text_state(gx_device_pdf *pdev)
415
2.30M
{
416
2.30M
    pdf_text_state_t *pts = pdev->text->text_state;
417
2.30M
    stream *s = pdev->strm;
418
2.30M
    int code;
419
420
2.30M
    if (pts->buffer.count_chars == 0)
421
643k
        return 0;    /* nothing to output */
422
423
1.65M
    if (pts->continue_line)
424
5.61k
        return flush_text_buffer(pdev);
425
426
    /* Bring text state parameters up to date. */
427
428
1.65M
    if (pts->out.character_spacing != pts->in.character_spacing) {
429
83.6k
        pprintg1(s, "%g Tc\n", pts->in.character_spacing);
430
83.6k
        pts->out.character_spacing = pts->in.character_spacing;
431
83.6k
    }
432
433
1.65M
    if (pts->out.pdfont != pts->in.pdfont || pts->out.size != pts->in.size) {
434
621k
        pdf_font_resource_t *pdfont = pts->in.pdfont;
435
436
621k
        code = pdf_assign_font_object_id(pdev, pdfont);
437
621k
        if (code < 0)
438
0
            return code;
439
621k
        pprints1(s, "/%s ", pdfont->rname);
440
621k
        pprintg1(s, "%g Tf\n", pts->in.size);
441
621k
        pts->out.pdfont = pdfont;
442
621k
        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
621k
        pts->wmode =
448
621k
            (pdfont->FontType == ft_composite ?
449
600k
             pdfont->u.type0.WMode : 0);
450
621k
        code = pdf_used_charproc_resources(pdev, pdfont);
451
621k
        if (code < 0)
452
0
            return code;
453
621k
    }
454
455
1.65M
    if (gs_matrix_compare(&pts->in.matrix, &pts->out.matrix) ||
456
1.28k
         ((pts->start.x != pts->out_pos.x || pts->start.y != pts->out_pos.y) &&
457
1.65M
          (pts->buffer.count_chars != 0 || pts->buffer.count_moves != 0))) {
458
        /* pdf_set_text_matrix sets out.matrix = in.matrix */
459
1.65M
        code = pdf_set_text_matrix(pdev);
460
1.65M
        if (code < 0)
461
0
            return code;
462
1.65M
    }
463
464
1.65M
    if (pts->out.render_mode != pts->in.render_mode) {
465
46.2k
        pprintg1(s, "%g Tr\n", pts->in.render_mode);
466
46.2k
        pts->out.render_mode = pts->in.render_mode;
467
46.2k
    }
468
469
1.65M
    if (pts->out.word_spacing != pts->in.word_spacing) {
470
105k
        if (memchr(pts->buffer.chars, 32, pts->buffer.count_chars)) {
471
33.2k
            pprintg1(s, "%g Tw\n", pts->in.word_spacing);
472
33.2k
            pts->out.word_spacing = pts->in.word_spacing;
473
33.2k
        }
474
105k
    }
475
476
1.65M
    return flush_text_buffer(pdev);
477
1.65M
}
478
479
int
480
pdf_from_string_to_text(gx_device_pdf *pdev)
481
303k
{
482
303k
    return sync_text_state(pdev);
483
303k
}
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
51.2k
{
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
51.2k
    pdf_text_state_t *pts = pdev->text->text_state;
497
498
51.2k
    pts->in.pdfont = pts->out.pdfont = 0;
499
51.2k
    pts->in.size = pts->out.size = 0;
500
51.2k
}
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
6.07M
{
510
6.07M
    return ((ptsv->render_mode == 1 || ptsv->render_mode == 2 ||
511
6.07M
            ptsv->render_mode == 5 || ptsv->render_mode == 6));
512
6.07M
}
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
166k
{
529
166k
    pdf_text_state_t *pts = pdev->text->text_state;
530
531
166k
    pts->wmode = wmode;
532
166k
}
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
21.8M
{
541
21.8M
    pdf_text_state_t *pts = pdev->text->text_state;
542
543
21.8M
    if (pts->buffer.count_chars > 0) {
544
20.6M
        int code;
545
546
20.6M
        if (pts->in.character_spacing == ptsv->character_spacing &&
547
20.6M
            pts->in.pdfont == ptsv->pdfont && pts->in.size == ptsv->size &&
548
20.3M
            pts->in.render_mode == ptsv->render_mode &&
549
20.3M
            pts->in.word_spacing == ptsv->word_spacing
550
20.6M
            ) {
551
20.2M
            if (!gs_matrix_compare(&pts->in.matrix, &ptsv->matrix))
552
4.95M
                return 0;
553
            /* add_text_delta_move sets pts->in.matrix if successful */
554
15.3M
            code = add_text_delta_move(pdev, &ptsv->matrix);
555
15.3M
            if (code >= 0)
556
14.3M
                return 0;
557
15.3M
        }
558
1.35M
        code = sync_text_state(pdev);
559
1.35M
        if (code < 0)
560
0
            return code;
561
1.35M
    }
562
563
2.57M
    pts->in = *ptsv;
564
2.57M
    pts->continue_line = false;
565
2.57M
    return 0;
566
21.8M
}
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.67M
{
594
1.67M
    pdf_text_state_t *pts = pdev->text->text_state;
595
1.67M
    gs_rect bbox;
596
597
1.67M
    bbox.p.x = (pts->in.matrix.tx + x_offset) / (pdev->HWResolution[0] / 72);
598
1.67M
    bbox.p.y = (pts->in.matrix.ty + y_offset) / (pdev->HWResolution[1] / 72);
599
1.67M
    bbox.q.x = bbox.p.x + (x / (pdev->HWResolution[0] / 72));
600
1.67M
    bbox.q.y = bbox.p.y + (y / (pdev->HWResolution[0] / 72));
601
602
1.67M
    if (bbox.p.x < pdev->BBox.p.x)
603
1.75k
        pdev->BBox.p.x = bbox.p.x;
604
1.67M
    if (bbox.p.y < pdev->BBox.p.y)
605
11.0k
        pdev->BBox.p.y = bbox.p.y;
606
1.67M
    if (bbox.q.x > pdev->BBox.q.x)
607
21.0k
        pdev->BBox.q.x = bbox.q.x;
608
1.67M
    if (bbox.q.y > pdev->BBox.q.y)
609
2.01k
        pdev->BBox.q.y = bbox.q.y;
610
611
1.67M
    return 0;
612
1.67M
}
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
17.2M
{
621
17.2M
    pdf_text_state_t *pts = pdev->text->text_state;
622
17.2M
    const byte *p = str;
623
17.2M
    uint left = size;
624
625
17.2M
    if (pts->buffer.count_chars == 0 && pts->buffer.count_moves == 0) {
626
1.65M
        pts->out_pos.x = pts->start.x = pts->in.matrix.tx;
627
1.65M
        pts->out_pos.y = pts->start.y = pts->in.matrix.ty;
628
1.65M
    }
629
34.4M
    while (left)
630
17.2M
        if (pts->buffer.count_chars == MAX_TEXT_BUFFER_CHARS ||
631
17.2M
            (nobreak && pts->buffer.count_chars + left > MAX_TEXT_BUFFER_CHARS)) {
632
5.61k
            int code = sync_text_state(pdev);
633
634
5.61k
            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
5.61k
            pts->continue_line = true;
646
17.2M
        } else {
647
17.2M
            int code = pdf_open_page(pdev, PDF_IN_STRING);
648
17.2M
            uint copy;
649
650
17.2M
            if (code < 0)
651
0
                return code;
652
17.2M
            copy = min(MAX_TEXT_BUFFER_CHARS - pts->buffer.count_chars, left);
653
17.2M
            memcpy(pts->buffer.chars + pts->buffer.count_chars, p, copy);
654
17.2M
            pts->buffer.count_chars += copy;
655
17.2M
            p += copy;
656
17.2M
            left -= copy;
657
17.2M
        }
658
17.2M
    pts->in.matrix.tx += wx;
659
17.2M
    pts->in.matrix.ty += wy;
660
17.2M
    pts->out_pos.x += wx;
661
17.2M
    pts->out_pos.y += wy;
662
17.2M
    return 0;
663
17.2M
}
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
1.92k
{
672
1.92k
    int code;
673
1.92k
    float size;
674
1.92k
    gs_matrix smat, tmat;
675
1.92k
    struct pdf_font_resource_s *pdfont;
676
677
    /* check to ensure the new text has the same length as the saved text */
678
1.92k
    if(text->size != pts->buffer.count_chars)
679
1.89k
        return(false);
680
681
23
    if(font->FontType == ft_user_defined ||
682
23
        font->FontType == ft_PDF_user_defined ||
683
23
        font->FontType == ft_PCL_user_defined ||
684
23
        font->FontType == ft_MicroType ||
685
23
        font->FontType == ft_GL2_stick_user_defined ||
686
23
        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
23
    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
15
    code = pdf_attached_font_resource(pdev, font, &pdfont, NULL, NULL, NULL, NULL);
697
15
    if(code < 0)
698
0
        return(false);
699
700
15
    if(!pdfont || pdfont != pts->in.pdfont)
701
3
        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
12
    if(fabs(pts->start.x - pgs->current_point.x) > 0.01 ||
708
12
       fabs(pts->start.y - pgs->current_point.y) > 0.01)
709
0
        return(false);
710
711
12
    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
12
    if(size != pts->in.size)
717
0
        return(false);
718
719
12
    return(true);
720
12
}
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
}