gdevpdts.c 18 KB

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