1
/* Copyright (C) 2001-2006 Artifex Software, Inc.
4
This software is provided AS-IS with no warranty, either express or
7
This software is distributed under license and may not be copied, modified
8
or distributed except as expressly authorized under the terms of that
9
license. Refer to licensing information at http://www.artifex.com/
10
or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
11
San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
14
/* $Id: gdevpdts.c 8518 2008-02-07 09:33:22Z ken $ */
15
/* Text state management for pdfwrite */
22
#include "gdevpdtf.h" /* for pdfont->FontType */
26
/* ================ Types and structures ================ */
29
* We accumulate text, and possibly horizontal or vertical moves (depending
30
* on the font's writing direction), until forced to emit them. This
31
* happens when changing text state parameters, when the buffer is full, or
32
* when exiting text mode.
34
* Note that movement distances are measured in unscaled text space.
36
typedef struct pdf_text_move_s {
37
int index; /* within buffer.chars */
40
#define MAX_TEXT_BUFFER_CHARS 200 /* arbitrary, but overflow costs 5 chars */
41
#define MAX_TEXT_BUFFER_MOVES 50 /* ibid. */
42
typedef struct pdf_text_buffer_s {
45
* count_moves <= MAX_TEXT_BUFFER_MOVES
46
* count_chars <= MAX_TEXT_BUFFER_CHARS
47
* 0 < moves[0].index < moves[1].index < ... moves[count_moves-1].index
49
* moves[*].amount != 0
51
pdf_text_move_t moves[MAX_TEXT_BUFFER_MOVES + 1];
52
byte chars[MAX_TEXT_BUFFER_CHARS];
56
#define TEXT_BUFFER_DEFAULT\
57
{ { 0, 0 } }, /* moves */\
63
* We maintain two sets of text state values (as defined in gdevpdts.h): the
64
* "in" set reflects the current state as seen by the client, while the
65
* "out" set reflects the current state as seen by an interpreter processing
66
* the content stream emitted so far. We emit commands to make "out" the
67
* same as "in" when necessary.
69
/*typedef struct pdf_text_state_s pdf_text_state_t;*/ /* gdevpdts.h */
70
struct pdf_text_state_s {
71
/* State as seen by client */
72
pdf_text_state_values_t in; /* see above */
73
gs_point start; /* in.txy as of start of buffer */
74
pdf_text_buffer_t buffer;
75
int wmode; /* WMode of in.font */
76
/* State relative to content stream */
77
pdf_text_state_values_t out; /* see above */
78
double leading; /* TL (not settable, only used internally) */
79
bool use_leading; /* if true, use T* or ' */
82
gs_point out_pos; /* output position */
84
static const pdf_text_state_t ts_default = {
85
/* State as seen by client */
86
{ TEXT_STATE_VALUES_DEFAULT }, /* in */
88
{ TEXT_BUFFER_DEFAULT }, /* buffer */
90
/* State relative to content stream */
91
{ TEXT_STATE_VALUES_DEFAULT }, /* out */
93
0 /*false*/, /* use_leading */
94
0 /*false*/, /* continue_line */
95
{ 0, 0 }, /* line_start */
96
{ 0, 0 } /* output position */
99
gs_private_st_ptrs2(st_pdf_text_state, pdf_text_state_t, "pdf_text_state_t",
100
pdf_text_state_enum_ptrs, pdf_text_state_reloc_ptrs,
101
in.pdfont, out.pdfont);
103
/* ================ Procedures ================ */
105
/* ---------------- Private ---------------- */
108
* Append a writing-direction movement to the text being accumulated. If
109
* the buffer is full, or the requested movement is not in writing
110
* direction, return <0 and do nothing. (This is different from
111
* pdf_append_chars.) Requires pts->buffer.count_chars > 0.
114
append_text_move(pdf_text_state_t *pts, floatp dw)
116
int count = pts->buffer.count_moves;
117
int pos = pts->buffer.count_chars;
120
if (count > 0 && pts->buffer.moves[count - 1].index == pos) {
121
/* Merge adjacent moves. */
122
dw += pts->buffer.moves[--count].amount;
124
/* Round dw if it's very close to an integer. */
125
rounded = floor(dw + 0.5);
126
if (fabs(dw - rounded) < 0.001)
128
if (dw < -MAX_USER_COORD) {
129
/* Acrobat reader 4.0c, 5.0 can't handle big offsets.
130
Adobe Reader 6 can. */
134
if (count == MAX_TEXT_BUFFER_MOVES)
136
pts->buffer.moves[count].index = pos;
137
pts->buffer.moves[count].amount = dw;
140
pts->buffer.count_moves = count;
145
* Set *pdist to the distance (dx,dy), in the space defined by *pmat.
148
set_text_distance(gs_point *pdist, floatp dx, floatp dy, const gs_matrix *pmat)
150
int code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
153
if (code == gs_error_undefinedresult) {
154
/* The CTM is degenerate.
155
Can't know the distance in user space.
156
Set zero because we believe it is not important for rendering.
157
We want to copy the text to PDF to make it searchable.
160
pdist->x = pdist->y = 0;
163
/* If the distance is very close to integers, round it. */
164
if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
166
if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
172
* Test whether the transformation parts of two matrices are compatible.
175
matrix_is_compatible(const gs_matrix *pmat1, const gs_matrix *pmat2)
177
return (pmat2->xx == pmat1->xx && pmat2->xy == pmat1->xy &&
178
pmat2->yx == pmat1->yx && pmat2->yy == pmat1->yy);
182
* Try to handle a change of text position with TJ or a space
183
* character. If successful, return >=0, if not, return <0.
186
add_text_delta_move(gx_device_pdf *pdev, const gs_matrix *pmat)
188
pdf_text_state_t *const pts = pdev->text->text_state;
190
if (matrix_is_compatible(pmat, &pts->in.matrix)) {
191
double dx = pmat->tx - pts->in.matrix.tx,
192
dy = pmat->ty - pts->in.matrix.ty;
194
double dw, dnotw, tdw;
197
code = set_text_distance(&dist, dx, dy, pmat);
201
dw = dist.y, dnotw = dist.x;
203
dw = dist.x, dnotw = dist.y;
204
if (dnotw == 0 && pts->buffer.count_chars > 0 &&
206
* Acrobat Reader limits the magnitude of user-space
207
* coordinates. Also, AR apparently doesn't handle large
208
* positive movement values (negative X displacements), even
209
* though the PDF Reference says this bug was fixed in AR3.
211
* Old revisions used the upper threshold 1000 for tdw,
212
* but it appears too big when a font sets a too big
213
* character width in setcachedevice. Particularly this happens
214
* with a Type 3 font generated by Aldus Freehand 4.0
215
* to represent a texture - see bug #687051.
216
* The problem is that when the Widths is multiplied
217
* to the font size, the viewer represents the result
218
* with insufficient fraction bits to represent the precise width.
219
* We work around that problem here restricting tdw
220
* with a smaller threshold 990. Our intention is to
221
* disable Tj when the real glyph width appears smaller
222
* than 1% of the width specified in setcachedevice.
223
* A Td instruction will be generated instead.
224
* Note that the value 990 is arbitrary and may need a
225
* further adjustment.
227
(tdw = dw * -1000.0 / pts->in.size,
228
tdw >= -MAX_USER_COORD && tdw < 990)
231
int code = append_text_move(pts, tdw);
239
pts->in.matrix = *pmat;
244
* Set the text matrix for writing text. The translation component of the
245
* matrix is the text origin. If the non-translation components of the
246
* matrix differ from the current ones, write a Tm command; if there is only
247
* a Y translation, set use_leading so the next text string will be written
248
* with ' rather than Tj; otherwise, write a Td command.
251
pdf_set_text_matrix(gx_device_pdf * pdev)
253
pdf_text_state_t *pts = pdev->text->text_state;
254
stream *s = pdev->strm;
256
pts->use_leading = false;
257
if (matrix_is_compatible(&pts->out.matrix, &pts->in.matrix)) {
261
code = set_text_distance(&dist, pts->start.x - pts->line_start.x,
262
pts->start.y - pts->line_start.y, &pts->in.matrix);
265
if (dist.x == 0 && dist.y < 0) {
266
/* Use TL, if needed, and T* or '. */
267
float dist_y = (float)-dist.y;
269
if (fabs(pts->leading - dist_y) > 0.0005) {
270
pprintg1(s, "%g TL\n", dist_y);
271
pts->leading = dist_y;
273
pts->use_leading = true;
276
pprintg2(s, "%g %g Td\n", dist.x, dist.y);
278
} else { /* Use Tm. */
280
* See stream_to_text in gdevpdfu.c for why we need the following
281
* matrix adjustments.
283
double sx = 72.0 / pdev->HWResolution[0],
284
sy = 72.0 / pdev->HWResolution[1];
286
pprintg6(s, "%g %g %g %g %g %g Tm\n",
287
pts->in.matrix.xx * sx, pts->in.matrix.xy * sy,
288
pts->in.matrix.yx * sx, pts->in.matrix.yy * sy,
289
pts->start.x * sx, pts->start.y * sy);
291
pts->line_start.x = pts->start.x;
292
pts->line_start.y = pts->start.y;
293
pts->out.matrix = pts->in.matrix;
297
/* ---------------- Public ---------------- */
300
* Allocate and initialize text state bookkeeping.
303
pdf_text_state_alloc(gs_memory_t *mem)
305
pdf_text_state_t *pts =
306
gs_alloc_struct(mem, pdf_text_state_t, &st_pdf_text_state,
307
"pdf_text_state_alloc");
316
* Set the text state to default values.
319
pdf_set_text_state_default(pdf_text_state_t *pts)
325
* Copy the text state.
328
pdf_text_state_copy(pdf_text_state_t *pts_to, pdf_text_state_t *pts_from)
334
* Reset the text state to its condition at the beginning of the page.
337
pdf_reset_text_page(pdf_text_data_t *ptd)
339
pdf_set_text_state_default(ptd->text_state);
343
* Reset the text state after a grestore.
346
pdf_reset_text_state(pdf_text_data_t *ptd)
348
pdf_text_state_t *pts = ptd->text_state;
350
pts->out = ts_default.out;
355
* Transition from stream context to text context.
358
pdf_from_stream_to_text(gx_device_pdf *pdev)
360
pdf_text_state_t *pts = pdev->text->text_state;
362
gs_make_identity(&pts->out.matrix);
363
pts->line_start.x = pts->line_start.y = 0;
364
pts->continue_line = false; /* Not sure, probably doesn't matter. */
365
pts->buffer.count_chars = 0;
366
pts->buffer.count_moves = 0;
371
pdf_get_stoted_text_size(pdf_text_state_t *state)
373
return state->buffer.count_chars;
377
* Flush text from buffer.
380
flush_text_buffer(gx_device_pdf *pdev)
382
pdf_text_state_t *pts = pdev->text->text_state;
383
stream *s = pdev->strm;
385
if (pts->buffer.count_chars != 0) {
386
pdf_font_resource_t *pdfont = pts->in.pdfont;
387
int code = pdf_assign_font_object_id(pdev, pdfont);
391
code = pdf_add_resource(pdev, pdev->substream_Resources, "/Font", (pdf_resource_t *)pdfont);
395
if (pts->buffer.count_moves > 0) {
398
if (pts->use_leading)
399
stream_puts(s, "T*");
401
for (i = 0; i < pts->buffer.count_moves; ++i) {
402
int next = pts->buffer.moves[i].index;
404
pdf_put_string(pdev, pts->buffer.chars + cur, next - cur);
405
pprintg1(s, "%g", pts->buffer.moves[i].amount);
408
if (pts->buffer.count_chars > cur)
409
pdf_put_string(pdev, pts->buffer.chars + cur,
410
pts->buffer.count_chars - cur);
411
stream_puts(s, "]TJ\n");
413
pdf_put_string(pdev, pts->buffer.chars, pts->buffer.count_chars);
414
stream_puts(s, (pts->use_leading ? "'\n" : "Tj\n"));
416
pts->buffer.count_chars = 0;
417
pts->buffer.count_moves = 0;
418
pts->use_leading = false;
423
* Transition from string context to text context.
426
sync_text_state(gx_device_pdf *pdev)
428
pdf_text_state_t *pts = pdev->text->text_state;
429
stream *s = pdev->strm;
432
if (pts->buffer.count_chars == 0)
433
return 0; /* nothing to output */
435
if (pts->continue_line)
436
return flush_text_buffer(pdev);
438
/* Bring text state parameters up to date. */
440
if (pts->out.character_spacing != pts->in.character_spacing) {
441
pprintg1(s, "%g Tc\n", pts->in.character_spacing);
442
pts->out.character_spacing = pts->in.character_spacing;
445
if (pts->out.pdfont != pts->in.pdfont || pts->out.size != pts->in.size) {
446
pdf_font_resource_t *pdfont = pts->in.pdfont;
448
code = pdf_assign_font_object_id(pdev, pdfont);
451
pprints1(s, "/%s ", pdfont->rname);
452
pprintg1(s, "%g Tf\n", pts->in.size);
453
pts->out.pdfont = pdfont;
454
pts->out.size = pts->in.size;
456
* In PDF, the only place to specify WMode is in the CMap
457
* (a.k.a. Encoding) of a Type 0 font.
460
(pdfont->FontType == ft_composite ?
461
pdfont->u.type0.WMode : 0);
462
code = pdf_used_charproc_resources(pdev, pdfont);
467
if (memcmp(&pts->in.matrix, &pts->out.matrix, sizeof(pts->in.matrix)) ||
468
((pts->start.x != pts->out_pos.x || pts->start.y != pts->out_pos.y) &&
469
(pts->buffer.count_chars != 0 || pts->buffer.count_moves != 0))) {
470
/* pdf_set_text_matrix sets out.matrix = in.matrix */
471
code = pdf_set_text_matrix(pdev);
476
if (pts->out.render_mode != pts->in.render_mode) {
477
pprintg1(s, "%g Tr\n", pts->in.render_mode);
478
pts->out.render_mode = pts->in.render_mode;
481
if (pts->out.word_spacing != pts->in.word_spacing) {
482
if (memchr(pts->buffer.chars, 32, pts->buffer.count_chars)) {
483
pprintg1(s, "%g Tw\n", pts->in.word_spacing);
484
pts->out.word_spacing = pts->in.word_spacing;
488
return flush_text_buffer(pdev);
492
pdf_from_string_to_text(gx_device_pdf *pdev)
494
return sync_text_state(pdev);
498
* Close the text aspect of the current contents part.
501
pdf_close_text_contents(gx_device_pdf *pdev)
504
* Clear the font pointer. This is probably left over from old code,
505
* but it is appropriate in case we ever choose in the future to write
506
* out and free font resources before the end of the document.
508
pdf_text_state_t *pts = pdev->text->text_state;
510
pts->in.pdfont = pts->out.pdfont = 0;
511
pts->in.size = pts->out.size = 0;
515
* Test whether a change in render_mode requires resetting the stroke
519
pdf_render_mode_uses_stroke(const gx_device_pdf *pdev,
520
const pdf_text_state_values_t *ptsv)
522
return (pdev->text->text_state->in.render_mode != ptsv->render_mode &&
523
ptsv->render_mode == 1 || ptsv->render_mode == 2 ||
524
ptsv->render_mode == 5 || ptsv->render_mode == 6);
528
* Read the stored client view of text state values.
531
pdf_get_text_state_values(gx_device_pdf *pdev, pdf_text_state_values_t *ptsv)
533
*ptsv = pdev->text->text_state->in;
537
* Set wmode to text state.
540
pdf_set_text_wmode(gx_device_pdf *pdev, int wmode)
542
pdf_text_state_t *pts = pdev->text->text_state;
549
* Set the stored client view of text state values.
552
pdf_set_text_state_values(gx_device_pdf *pdev,
553
const pdf_text_state_values_t *ptsv)
555
pdf_text_state_t *pts = pdev->text->text_state;
557
if (pts->buffer.count_chars > 0) {
560
if (pts->in.character_spacing == ptsv->character_spacing &&
561
pts->in.pdfont == ptsv->pdfont && pts->in.size == ptsv->size &&
562
pts->in.render_mode == ptsv->render_mode &&
563
pts->in.word_spacing == ptsv->word_spacing
565
if (!memcmp(&pts->in.matrix, &ptsv->matrix,
566
sizeof(pts->in.matrix)))
568
/* add_text_delta_move sets pts->in.matrix if successful */
569
code = add_text_delta_move(pdev, &ptsv->matrix);
573
code = sync_text_state(pdev);
579
pts->continue_line = false;
584
* Transform a distance from unscaled text space (text space ignoring the
585
* scaling implied by the font size) to device space.
588
pdf_text_distance_transform(floatp wx, floatp wy, const pdf_text_state_t *pts,
591
return gs_distance_transform(wx, wy, &pts->in.matrix, ppt);
595
* Return the current (x,y) text position as seen by the client, in
596
* unscaled text space.
599
pdf_text_position(const gx_device_pdf *pdev, gs_point *ppt)
601
pdf_text_state_t *pts = pdev->text->text_state;
603
ppt->x = pts->in.matrix.tx;
604
ppt->y = pts->in.matrix.ty;
608
* Append characters to text being accumulated, giving their advance width
612
pdf_append_chars(gx_device_pdf * pdev, const byte * str, uint size,
613
floatp wx, floatp wy, bool nobreak)
615
pdf_text_state_t *pts = pdev->text->text_state;
619
if (pts->buffer.count_chars == 0 && pts->buffer.count_moves == 0) {
620
pts->out_pos.x = pts->start.x = pts->in.matrix.tx;
621
pts->out_pos.y = pts->start.y = pts->in.matrix.ty;
624
if (pts->buffer.count_chars == MAX_TEXT_BUFFER_CHARS ||
625
(nobreak && pts->buffer.count_chars + left > MAX_TEXT_BUFFER_CHARS)) {
626
int code = sync_text_state(pdev);
630
/* We'll keep a continuation of this line in the buffer,
631
* but the current input parameters don't correspond to
632
* the current position, because the text was broken in a
633
* middle with unknown current point.
634
* Don't change the output text state parameters
635
* until input parameters are changed.
636
* pdf_set_text_state_values will reset the 'continue_line' flag
639
pts->continue_line = true;
641
int code = pdf_open_page(pdev, PDF_IN_STRING);
646
copy = min(MAX_TEXT_BUFFER_CHARS - pts->buffer.count_chars, left);
647
memcpy(pts->buffer.chars + pts->buffer.count_chars, p, copy);
648
pts->buffer.count_chars += copy;
652
pts->in.matrix.tx += wx;
653
pts->in.matrix.ty += wy;
654
pts->out_pos.x += wx;
655
pts->out_pos.y += wy;
659
/* Check a new piece of charpath text to see if its safe to combine
660
* with a previous text operation using text rendering modes.
662
bool pdf_compare_text_state_for_charpath(pdf_text_state_t *pts, gx_device_pdf *pdev,
663
gs_imager_state *pis, gs_font *font,
664
const gs_text_params_t *text)
668
gs_matrix smat, tmat;
669
struct pdf_font_resource_s *pdfont;
671
/* check to ensure the new text has the same length as the saved text */
672
if(text->size != pts->buffer.count_chars)
675
if(font->FontType == ft_user_defined)
678
/* check to ensure the new text has the same data as the saved text */
679
if(memcmp(text->data.bytes, &pts->buffer.chars, text->size))
682
/* See if the same font is in use by checking the attahced pdfont resource for
683
* the currrent font and comparing with the saved text state
685
code = pdf_attached_font_resource(pdev, font, &pdfont, NULL, NULL, NULL, NULL);
689
if(!pdfont || pdfont != pts->in.pdfont)
692
/* Check to see the new text starts at the same point as the saved text.
693
* NB! only check 2 decimal places, allow some slack in the match. This
694
* still may prove to be too tight a requirement.
696
if((int)(pts->start.x * 100) != (int)(pis->current_point.x * 100) ||
697
(int)(pts->start.y * 100) != (int)(pis->current_point.y * 100))
700
size = pdf_calculate_text_size(pis, pdfont, &font->FontMatrix, &smat, &tmat, font, pdev);
702
/* Finally, check the calculated size against the size stored in
705
if(size != pts->in.size)
711
/* Add a render mode to the rendering mode of the current text.
715
* If the modes are not compatible returns 0. NB currently only
716
* a stroke rendering mode is supported.
718
int pdf_modify_text_render_mode(pdf_text_state_t *pts, int render_mode)
720
switch (pts->in.render_mode) {
722
if (render_mode == 1) {
723
pts->in.render_mode = 2;
728
if (render_mode == 1)
732
if (render_mode == 1)
736
if (render_mode == 1) {
737
pts->in.render_mode = 1;
742
if (render_mode == 1) {
743
pts->in.render_mode = 6;
748
if (render_mode == 1)
752
if (render_mode == 1)
756
if (render_mode == 1) {
757
pts->in.render_mode = 5;