xref: /plan9/sys/src/cmd/gs/src/gdevpdts.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 2002 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: gdevpdts.c,v 1.28 2005/01/24 15:37:48 igor Exp $ */
18 /* Text state management for pdfwrite */
19 #include "math_.h"
20 #include "memory_.h"
21 #include "gx.h"
22 #include "gdevpdfx.h"
23 #include "gdevpdtx.h"
24 #include "gdevpdtf.h"		/* for pdfont->FontType */
25 #include "gdevpdts.h"
26 
27 /* ================ Types and structures ================ */
28 
29 /*
30  * We accumulate text, and possibly horizontal or vertical moves (depending
31  * on the font's writing direction), until forced to emit them.  This
32  * happens when changing text state parameters, when the buffer is full, or
33  * when exiting text mode.
34  *
35  * Note that movement distances are measured in unscaled text space.
36  */
37 typedef struct pdf_text_move_s {
38     int index;			/* within buffer.chars */
39     float amount;
40 } pdf_text_move_t;
41 #define MAX_TEXT_BUFFER_CHARS 200 /* arbitrary, but overflow costs 5 chars */
42 #define MAX_TEXT_BUFFER_MOVES 50 /* ibid. */
43 typedef struct pdf_text_buffer_s {
44     /*
45      * Invariant:
46      *   count_moves <= MAX_TEXT_BUFFER_MOVES
47      *   count_chars <= MAX_TEXT_BUFFER_CHARS
48      *   0 < moves[0].index < moves[1].index < ... moves[count_moves-1].index
49      *	   <= count_chars
50      *   moves[*].amount != 0
51      */
52     pdf_text_move_t moves[MAX_TEXT_BUFFER_MOVES + 1];
53     byte chars[MAX_TEXT_BUFFER_CHARS];
54     int count_moves;
55     int count_chars;
56 } pdf_text_buffer_t;
57 #define TEXT_BUFFER_DEFAULT\
58     { { 0, 0 } },		/* moves */\
59     { 0 },			/* chars */\
60     0,				/* count_moves */\
61     0				/* count_chars */
62 
63 /*
64  * We maintain two sets of text state values (as defined in gdevpdts.h): the
65  * "in" set reflects the current state as seen by the client, while the
66  * "out" set reflects the current state as seen by an interpreter processing
67  * the content stream emitted so far.  We emit commands to make "out" the
68  * same as "in" when necessary.
69  */
70 /*typedef struct pdf_text_state_s pdf_text_state_t;*/  /* gdevpdts.h */
71 struct pdf_text_state_s {
72     /* State as seen by client */
73     pdf_text_state_values_t in; /* see above */
74     gs_point start;		/* in.txy as of start of buffer */
75     pdf_text_buffer_t buffer;
76     int wmode;			/* WMode of in.font */
77     /* State relative to content stream */
78     pdf_text_state_values_t out; /* see above */
79     double leading;		/* TL (not settable, only used internally) */
80     bool use_leading;		/* if true, use T* or ' */
81     bool continue_line;
82     gs_point line_start;
83     gs_point out_pos;		/* output position */
84 };
85 private const pdf_text_state_t ts_default = {
86     /* State as seen by client */
87     { TEXT_STATE_VALUES_DEFAULT },	/* in */
88     { 0, 0 },			/* start */
89     { TEXT_BUFFER_DEFAULT },	/* buffer */
90     0,				/* wmode */
91     /* State relative to content stream */
92     { TEXT_STATE_VALUES_DEFAULT },	/* out */
93     0,				/* leading */
94     0 /*false*/,		/* use_leading */
95     0 /*false*/,		/* continue_line */
96     { 0, 0 },			/* line_start */
97     { 0, 0 }			/* output position */
98 };
99 /* GC descriptor */
100 gs_private_st_ptrs2(st_pdf_text_state, pdf_text_state_t,  "pdf_text_state_t",
101 		    pdf_text_state_enum_ptrs, pdf_text_state_reloc_ptrs,
102 		    in.pdfont, out.pdfont);
103 
104 /* ================ Procedures ================ */
105 
106 /* ---------------- Private ---------------- */
107 
108 /*
109  * Append a writing-direction movement to the text being accumulated.  If
110  * the buffer is full, or the requested movement is not in writing
111  * direction, return <0 and do nothing.  (This is different from
112  * pdf_append_chars.)  Requires pts->buffer.count_chars > 0.
113  */
114 private int
append_text_move(pdf_text_state_t * pts,floatp dw)115 append_text_move(pdf_text_state_t *pts, floatp dw)
116 {
117     int count = pts->buffer.count_moves;
118     int pos = pts->buffer.count_chars;
119     double rounded;
120 
121     if (count > 0 && pts->buffer.moves[count - 1].index == pos) {
122 	/* Merge adjacent moves. */
123 	dw += pts->buffer.moves[--count].amount;
124     }
125     /* Round dw if it's very close to an integer. */
126     rounded = floor(dw + 0.5);
127     if (fabs(dw - rounded) < 0.001)
128 	dw = rounded;
129     if (dw < -MAX_USER_COORD) {
130 	/* Acrobat reader 4.0c, 5.0 can't handle big offsets.
131 	   Adobe Reader 6 can. */
132 	return -1;
133     }
134     if (dw != 0) {
135 	if (count == MAX_TEXT_BUFFER_MOVES)
136 	    return -1;
137 	pts->buffer.moves[count].index = pos;
138 	pts->buffer.moves[count].amount = dw;
139 	++count;
140     }
141     pts->buffer.count_moves = count;
142     return 0;
143 }
144 
145 /*
146  * Set *pdist to the distance (dx,dy), in the space defined by *pmat.
147  */
148 private int
set_text_distance(gs_point * pdist,floatp dx,floatp dy,const gs_matrix * pmat)149 set_text_distance(gs_point *pdist, floatp dx, floatp dy, const gs_matrix *pmat)
150 {
151     int code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
152     double rounded;
153 
154     if (code < 0)
155 	return code;
156     /* If the distance is very close to integers, round it. */
157     if (fabs(pdist->x - (rounded = floor(pdist->x + 0.5))) < 0.0005)
158 	pdist->x = rounded;
159     if (fabs(pdist->y - (rounded = floor(pdist->y + 0.5))) < 0.0005)
160 	pdist->y = rounded;
161     return 0;
162 }
163 
164 /*
165  * Test whether the transformation parts of two matrices are compatible.
166  */
167 private bool
matrix_is_compatible(const gs_matrix * pmat1,const gs_matrix * pmat2)168 matrix_is_compatible(const gs_matrix *pmat1, const gs_matrix *pmat2)
169 {
170     return (pmat2->xx == pmat1->xx && pmat2->xy == pmat1->xy &&
171 	    pmat2->yx == pmat1->yx && pmat2->yy == pmat1->yy);
172 }
173 
174 /*
175  * Try to handle a change of text position with TJ or a space
176  * character.  If successful, return >=0, if not, return <0.
177  */
178 private int
add_text_delta_move(gx_device_pdf * pdev,const gs_matrix * pmat)179 add_text_delta_move(gx_device_pdf *pdev, const gs_matrix *pmat)
180 {
181     pdf_text_state_t *const pts = pdev->text->text_state;
182 
183     if (matrix_is_compatible(pmat, &pts->in.matrix)) {
184 	double dx = pmat->tx - pts->in.matrix.tx,
185 	    dy = pmat->ty - pts->in.matrix.ty;
186 	gs_point dist;
187 	double dw, dnotw, tdw;
188 	int code;
189 
190 	code = set_text_distance(&dist, dx, dy, pmat);
191 	if (code < 0)
192 	    return code;
193 	if (pts->wmode)
194 	    dw = dist.y, dnotw = dist.x;
195 	else
196 	    dw = dist.x, dnotw = dist.y;
197 	if (dnotw == 0 && pts->buffer.count_chars > 0 &&
198 	    /*
199 	     * Acrobat Reader limits the magnitude of user-space
200 	     * coordinates.  Also, AR apparently doesn't handle large
201 	     * positive movement values (negative X displacements), even
202 	     * though the PDF Reference says this bug was fixed in AR3.
203 	     *
204 	     * Old revisions used the upper threshold 1000 for tdw,
205 	     * but it appears too big when a font sets a too big
206 	     * character width in setcachedevice. Particularly this happens
207 	     * with a Type 3 font generated by Aldus Freehand 4.0
208 	     * to represent a texture - see bug #687051.
209 	     * The problem is that when the Widths is multiplied
210 	     * to the font size, the viewer represents the result
211 	     * with insufficient fraction bits to represent the precise width.
212 	     * We work around that problem here restricting tdw
213 	     * with a smaller threshold 990. Our intention is to
214 	     * disable Tj when the real glyph width appears smaller
215 	     * than 1% of the width specified in setcachedevice.
216 	     * A Td instruction will be generated instead.
217 	     * Note that the value 990 is arbitrary and may need a
218 	     * further adjustment.
219 	     */
220 	    (tdw = dw * -1000.0 / pts->in.size,
221 	     tdw >= -MAX_USER_COORD && tdw < 990)
222 	    ) {
223 	    /* Use TJ. */
224 	    int code = append_text_move(pts, tdw);
225 
226 	    if (code >= 0)
227 		goto finish;
228 	}
229     }
230     return -1;
231  finish:
232     pts->in.matrix = *pmat;
233     return 0;
234 }
235 
236 /*
237  * Set the text matrix for writing text.  The translation component of the
238  * matrix is the text origin.  If the non-translation components of the
239  * matrix differ from the current ones, write a Tm command; if there is only
240  * a Y translation, set use_leading so the next text string will be written
241  * with ' rather than Tj; otherwise, write a Td command.
242  */
243 private int
pdf_set_text_matrix(gx_device_pdf * pdev)244 pdf_set_text_matrix(gx_device_pdf * pdev)
245 {
246     pdf_text_state_t *pts = pdev->text->text_state;
247     stream *s = pdev->strm;
248 
249     pts->use_leading = false;
250     if (matrix_is_compatible(&pts->out.matrix, &pts->in.matrix)) {
251 	gs_point dist;
252 	int code;
253 
254 	code = set_text_distance(&dist, pts->start.x - pts->line_start.x,
255 			  pts->start.y - pts->line_start.y, &pts->in.matrix);
256 	if (code < 0)
257 	    return code;
258 	if (dist.x == 0 && dist.y < 0) {
259 	    /* Use TL, if needed, and T* or '. */
260 	    float dist_y = (float)-dist.y;
261 
262 	    if (fabs(pts->leading - dist_y) > 0.0005) {
263 		pprintg1(s, "%g TL\n", dist_y);
264 		pts->leading = dist_y;
265 	    }
266 	    pts->use_leading = true;
267 	} else {
268 	    /* Use Td. */
269 	    pprintg2(s, "%g %g Td\n", dist.x, dist.y);
270 	}
271     } else {			/* Use Tm. */
272 	/*
273 	 * See stream_to_text in gdevpdfu.c for why we need the following
274 	 * matrix adjustments.
275 	 */
276 	double sx = 72.0 / pdev->HWResolution[0],
277 	    sy = 72.0 / pdev->HWResolution[1];
278 
279 	pprintg6(s, "%g %g %g %g %g %g Tm\n",
280 		 pts->in.matrix.xx * sx, pts->in.matrix.xy * sy,
281 		 pts->in.matrix.yx * sx, pts->in.matrix.yy * sy,
282 		 pts->start.x * sx, pts->start.y * sy);
283     }
284     pts->line_start.x = pts->start.x;
285     pts->line_start.y = pts->start.y;
286     pts->out.matrix = pts->in.matrix;
287     return 0;
288 }
289 
290 /* ---------------- Public ---------------- */
291 
292 /*
293  * Allocate and initialize text state bookkeeping.
294  */
295 pdf_text_state_t *
pdf_text_state_alloc(gs_memory_t * mem)296 pdf_text_state_alloc(gs_memory_t *mem)
297 {
298     pdf_text_state_t *pts =
299 	gs_alloc_struct(mem, pdf_text_state_t, &st_pdf_text_state,
300 			"pdf_text_state_alloc");
301 
302     if (pts == 0)
303 	return 0;
304     *pts = ts_default;
305     return pts;
306 }
307 
308 /*
309  * Set the text state to default values.
310  */
311 void
pdf_set_text_state_default(pdf_text_state_t * pts)312 pdf_set_text_state_default(pdf_text_state_t *pts)
313 {
314     *pts = ts_default;
315 }
316 
317 /*
318  * Copy the text state.
319  */
320 void
pdf_text_state_copy(pdf_text_state_t * pts_to,pdf_text_state_t * pts_from)321 pdf_text_state_copy(pdf_text_state_t *pts_to, pdf_text_state_t *pts_from)
322 {
323     *pts_to = *pts_from;
324 }
325 
326 /*
327  * Reset the text state to its condition at the beginning of the page.
328  */
329 void
pdf_reset_text_page(pdf_text_data_t * ptd)330 pdf_reset_text_page(pdf_text_data_t *ptd)
331 {
332     pdf_set_text_state_default(ptd->text_state);
333 }
334 
335 /*
336  * Reset the text state after a grestore.
337  */
338 void
pdf_reset_text_state(pdf_text_data_t * ptd)339 pdf_reset_text_state(pdf_text_data_t *ptd)
340 {
341     pdf_text_state_t *pts = ptd->text_state;
342 
343     pts->out = ts_default.out;
344     pts->leading = 0;
345 }
346 
347 /*
348  * Transition from stream context to text context.
349  */
350 int
pdf_from_stream_to_text(gx_device_pdf * pdev)351 pdf_from_stream_to_text(gx_device_pdf *pdev)
352 {
353     pdf_text_state_t *pts = pdev->text->text_state;
354 
355     gs_make_identity(&pts->out.matrix);
356     pts->line_start.x = pts->line_start.y = 0;
357     pts->continue_line = false; /* Not sure, probably doesn't matter. */
358     pts->buffer.count_chars = 0;
359     pts->buffer.count_moves = 0;
360     return 0;
361 }
362 
363 
364 /*
365  *  Flush text from buffer.
366  */
367 private int
flush_text_buffer(gx_device_pdf * pdev)368 flush_text_buffer(gx_device_pdf *pdev)
369 {
370     pdf_text_state_t *pts = pdev->text->text_state;
371     stream *s = pdev->strm;
372 
373     if (pts->buffer.count_moves > 0) {
374 	int i, cur = 0;
375 
376 	if (pts->use_leading)
377 	    stream_puts(s, "T*");
378 	stream_puts(s, "[");
379 	for (i = 0; i < pts->buffer.count_moves; ++i) {
380 	    int next = pts->buffer.moves[i].index;
381 
382 	    pdf_put_string(pdev, pts->buffer.chars + cur, next - cur);
383 	    pprintg1(s, "%g", pts->buffer.moves[i].amount);
384 	    cur = next;
385 	}
386 	if (pts->buffer.count_chars > cur)
387 	    pdf_put_string(pdev, pts->buffer.chars + cur,
388 			   pts->buffer.count_chars - cur);
389 	stream_puts(s, "]TJ\n");
390     } else {
391 	pdf_put_string(pdev, pts->buffer.chars, pts->buffer.count_chars);
392 	stream_puts(s, (pts->use_leading ? "'\n" : "Tj\n"));
393     }
394     pts->buffer.count_chars = 0;
395     pts->buffer.count_moves = 0;
396     pts->use_leading = false;
397     return 0;
398 }
399 
400 /*
401  * Transition from string context to text context.
402  */
403 private int
sync_text_state(gx_device_pdf * pdev)404 sync_text_state(gx_device_pdf *pdev)
405 {
406     pdf_text_state_t *pts = pdev->text->text_state;
407     stream *s = pdev->strm;
408     int code;
409 
410     if (pts->buffer.count_chars == 0)
411 	return 0;		/* nothing to output */
412 
413     if (pts->continue_line)
414 	return flush_text_buffer(pdev);
415 
416     /* Bring text state parameters up to date. */
417 
418     if (pts->out.character_spacing != pts->in.character_spacing) {
419 	pprintg1(s, "%g Tc\n", pts->in.character_spacing);
420 	pts->out.character_spacing = pts->in.character_spacing;
421     }
422 
423     if (pts->out.pdfont != pts->in.pdfont || pts->out.size != pts->in.size) {
424 	pdf_font_resource_t *pdfont = pts->in.pdfont;
425 
426 	pprints1(s, "/%s ", ((pdf_resource_t *)pts->in.pdfont)->rname);
427 	pprintg1(s, "%g Tf\n", pts->in.size);
428 	pts->out.pdfont = pdfont;
429 	pts->out.size = pts->in.size;
430 	/*
431 	 * In PDF, the only place to specify WMode is in the CMap
432 	 * (a.k.a. Encoding) of a Type 0 font.
433 	 */
434 	pts->wmode =
435 	    (pdfont->FontType == ft_composite ?
436 	     pdfont->u.type0.WMode : 0);
437 	code = pdf_used_charproc_resources(pdev, pdfont);
438 	if (code < 0)
439 	    return code;
440     }
441 
442     if (memcmp(&pts->in.matrix, &pts->out.matrix, sizeof(pts->in.matrix)) ||
443 	 ((pts->start.x != pts->out_pos.x || pts->start.y != pts->out_pos.y) &&
444 	  (pts->buffer.count_chars != 0 || pts->buffer.count_moves != 0))) {
445 	/* pdf_set_text_matrix sets out.matrix = in.matrix */
446 	code = pdf_set_text_matrix(pdev);
447 	if (code < 0)
448 	    return code;
449     }
450 
451     if (pts->out.render_mode != pts->in.render_mode) {
452 	pprintg1(s, "%g Tr\n", pts->in.render_mode);
453 	pts->out.render_mode = pts->in.render_mode;
454     }
455 
456     if (pts->out.word_spacing != pts->in.word_spacing) {
457 	if (memchr(pts->buffer.chars, 32, pts->buffer.count_chars)) {
458 	    pprintg1(s, "%g Tw\n", pts->in.word_spacing);
459 	    pts->out.word_spacing = pts->in.word_spacing;
460 	}
461     }
462 
463     return flush_text_buffer(pdev);
464 }
465 
466 int
pdf_from_string_to_text(gx_device_pdf * pdev)467 pdf_from_string_to_text(gx_device_pdf *pdev)
468 {
469     return sync_text_state(pdev);
470 }
471 
472 /*
473  * Close the text aspect of the current contents part.
474  */
475 void
pdf_close_text_contents(gx_device_pdf * pdev)476 pdf_close_text_contents(gx_device_pdf *pdev)
477 {
478     /*
479      * Clear the font pointer.  This is probably left over from old code,
480      * but it is appropriate in case we ever choose in the future to write
481      * out and free font resources before the end of the document.
482      */
483     pdf_text_state_t *pts = pdev->text->text_state;
484 
485     pts->in.pdfont = pts->out.pdfont = 0;
486     pts->in.size = pts->out.size = 0;
487 }
488 
489 /*
490  * Test whether a change in render_mode requires resetting the stroke
491  * parameters.
492  */
493 bool
pdf_render_mode_uses_stroke(const gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)494 pdf_render_mode_uses_stroke(const gx_device_pdf *pdev,
495 			    const pdf_text_state_values_t *ptsv)
496 {
497     return (pdev->text->text_state->in.render_mode != ptsv->render_mode &&
498 	    ptsv->render_mode != 0);
499 }
500 
501 /*
502  * Read the stored client view of text state values.
503  */
504 void
pdf_get_text_state_values(gx_device_pdf * pdev,pdf_text_state_values_t * ptsv)505 pdf_get_text_state_values(gx_device_pdf *pdev, pdf_text_state_values_t *ptsv)
506 {
507     *ptsv = pdev->text->text_state->in;
508 }
509 
510 /*
511  * Set wmode to text state.
512  */
513 void
pdf_set_text_wmode(gx_device_pdf * pdev,int wmode)514 pdf_set_text_wmode(gx_device_pdf *pdev, int wmode)
515 {
516     pdf_text_state_t *pts = pdev->text->text_state;
517 
518     pts->wmode = wmode;
519 }
520 
521 
522 /*
523  * Set the stored client view of text state values.
524  */
525 int
pdf_set_text_state_values(gx_device_pdf * pdev,const pdf_text_state_values_t * ptsv)526 pdf_set_text_state_values(gx_device_pdf *pdev,
527 			  const pdf_text_state_values_t *ptsv)
528 {
529     pdf_text_state_t *pts = pdev->text->text_state;
530 
531     if (pts->buffer.count_chars > 0) {
532 	int code;
533 
534 	if (pts->in.character_spacing == ptsv->character_spacing &&
535 	    pts->in.pdfont == ptsv->pdfont && pts->in.size == ptsv->size &&
536 	    pts->in.render_mode == ptsv->render_mode &&
537 	    pts->in.word_spacing == ptsv->word_spacing
538 	    ) {
539 	    if (!memcmp(&pts->in.matrix, &ptsv->matrix,
540 			sizeof(pts->in.matrix)))
541 		return 0;
542 	    /* add_text_delta_move sets pts->in.matrix if successful */
543 	    code = add_text_delta_move(pdev, &ptsv->matrix);
544 	    if (code >= 0)
545 		return 0;
546 	}
547 	code = sync_text_state(pdev);
548 	if (code < 0)
549 	    return code;
550     }
551 
552     pts->in = *ptsv;
553     pts->continue_line = false;
554     return 0;
555 }
556 
557 /*
558  * Transform a distance from unscaled text space (text space ignoring the
559  * scaling implied by the font size) to device space.
560  */
561 int
pdf_text_distance_transform(floatp wx,floatp wy,const pdf_text_state_t * pts,gs_point * ppt)562 pdf_text_distance_transform(floatp wx, floatp wy, const pdf_text_state_t *pts,
563 			    gs_point *ppt)
564 {
565     return gs_distance_transform(wx, wy, &pts->in.matrix, ppt);
566 }
567 
568 /*
569  * Return the current (x,y) text position as seen by the client, in
570  * unscaled text space.
571  */
572 void
pdf_text_position(const gx_device_pdf * pdev,gs_point * ppt)573 pdf_text_position(const gx_device_pdf *pdev, gs_point *ppt)
574 {
575     pdf_text_state_t *pts = pdev->text->text_state;
576 
577     ppt->x = pts->in.matrix.tx;
578     ppt->y = pts->in.matrix.ty;
579 }
580 
581 /*
582  * Append characters to text being accumulated, giving their advance width
583  * in device space.
584  */
585 int
pdf_append_chars(gx_device_pdf * pdev,const byte * str,uint size,floatp wx,floatp wy,bool nobreak)586 pdf_append_chars(gx_device_pdf * pdev, const byte * str, uint size,
587 		 floatp wx, floatp wy, bool nobreak)
588 {
589     pdf_text_state_t *pts = pdev->text->text_state;
590     const byte *p = str;
591     uint left = size;
592 
593     if (pts->buffer.count_chars == 0 && pts->buffer.count_moves == 0) {
594 	pts->out_pos.x = pts->start.x = pts->in.matrix.tx;
595 	pts->out_pos.y = pts->start.y = pts->in.matrix.ty;
596     }
597     while (left)
598 	if (pts->buffer.count_chars == MAX_TEXT_BUFFER_CHARS ||
599 	    (nobreak && pts->buffer.count_chars + left > MAX_TEXT_BUFFER_CHARS)) {
600 	    int code = sync_text_state(pdev);
601 
602 	    if (code < 0)
603 		return code;
604 	    /* We'll keep a continuation of this line in the buffer,
605 	     * but the current input parameters don't correspond to
606 	     * the current position, because the text was broken in a
607 	     * middle with unknown current point.
608 	     * Don't change the output text state parameters
609 	     * until input parameters are changed.
610 	     * pdf_set_text_state_values will reset the 'continue_line' flag
611 	     * at that time.
612 	     */
613 	    pts->continue_line = true;
614 	} else {
615 	    int code = pdf_open_page(pdev, PDF_IN_STRING);
616 	    uint copy;
617 
618 	    if (code < 0)
619 		return code;
620 	    copy = min(MAX_TEXT_BUFFER_CHARS - pts->buffer.count_chars, left);
621 	    memcpy(pts->buffer.chars + pts->buffer.count_chars, p, copy);
622 	    pts->buffer.count_chars += copy;
623 	    p += copy;
624 	    left -= copy;
625 	}
626     pts->in.matrix.tx += wx;
627     pts->in.matrix.ty += wy;
628     pts->out_pos.x += wx;
629     pts->out_pos.y += wy;
630     return 0;
631 }
632