xps-path.c 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. // Copyright (C) 2004-2024 Artifex Software, Inc.
  2. //
  3. // This file is part of MuPDF.
  4. //
  5. // MuPDF is free software: you can redistribute it and/or modify it under the
  6. // terms of the GNU Affero General Public License as published by the Free
  7. // Software Foundation, either version 3 of the License, or (at your option)
  8. // any later version.
  9. //
  10. // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
  11. // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. // details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
  17. //
  18. // Alternative licensing terms are available from the licensor.
  19. // For commercial licensing, see <https://www.artifex.com/> or contact
  20. // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
  21. // CA 94129, USA, for further information.
  22. #include "mupdf/fitz.h"
  23. #include "xps-imp.h"
  24. #include <math.h>
  25. #include <string.h>
  26. #include <stdlib.h>
  27. static char *
  28. xps_parse_float_array(fz_context *ctx, xps_document *doc, char *s, int num, int *obtained, float *x)
  29. {
  30. int k = 0;
  31. if (s == NULL || *s == 0)
  32. {
  33. if (obtained)
  34. *obtained = k;
  35. return NULL;
  36. }
  37. while (*s)
  38. {
  39. while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
  40. s++;
  41. x[k] = fz_strtof(s, &s);
  42. while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
  43. s++;
  44. if (*s == ',')
  45. s++;
  46. if (++k == num)
  47. break;
  48. }
  49. if (obtained)
  50. *obtained = k;
  51. return s;
  52. }
  53. char *
  54. xps_parse_point(fz_context *ctx, xps_document *doc, char *s_in, float *x, float *y)
  55. {
  56. char *s_out = s_in;
  57. float xy[2];
  58. int obtained = 0;
  59. s_out = xps_parse_float_array(ctx, doc, s_out, 2, &obtained, &xy[0]);
  60. if (obtained >= 2)
  61. {
  62. *x = xy[0];
  63. *y = xy[1];
  64. }
  65. return s_out;
  66. }
  67. /* Draw an arc segment transformed by the matrix, we approximate with straight
  68. * line segments. We cannot use the fz_arc function because they only draw
  69. * circular arcs, we need to transform the line to make them elliptical but
  70. * without transforming the line width.
  71. *
  72. * We are guaranteed that on entry the point is at the point that would be
  73. * calculated by th0, and on exit, a point is generated for us at th0.
  74. */
  75. static void
  76. xps_draw_arc_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
  77. {
  78. float t, d;
  79. fz_point p;
  80. while (th1 < th0)
  81. th1 += FZ_PI * 2;
  82. d = FZ_PI / 180; /* 1-degree precision */
  83. if (iscw)
  84. {
  85. for (t = th0 + d; t < th1 - d/2; t += d)
  86. {
  87. p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
  88. fz_lineto(ctx, path, p.x, p.y);
  89. }
  90. }
  91. else
  92. {
  93. th0 += FZ_PI * 2;
  94. for (t = th0 - d; t > th1 + d/2; t -= d)
  95. {
  96. p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
  97. fz_lineto(ctx, path, p.x, p.y);
  98. }
  99. }
  100. }
  101. /* Given two vectors find the angle between them. */
  102. static float
  103. angle_between(fz_point u, fz_point v)
  104. {
  105. float det = u.x * v.y - u.y * v.x;
  106. float sign = (det < 0 ? -1 : 1);
  107. float magu = u.x * u.x + u.y * u.y;
  108. float magv = v.x * v.x + v.y * v.y;
  109. float udotv = u.x * v.x + u.y * v.y;
  110. float t = udotv / (magu * magv);
  111. /* guard against rounding errors when near |1| (where acos will return NaN) */
  112. if (t < -1) t = -1;
  113. if (t > 1) t = 1;
  114. return sign * acosf(t);
  115. }
  116. /*
  117. Some explanation of the parameters here is warranted. See:
  118. http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  119. Add an arc segment to path, that describes a section of an elliptical
  120. arc from the current point of path to (point_x,point_y), such that:
  121. The arc segment is taken from an elliptical arc of semi major radius
  122. size_x, semi minor radius size_y, where the semi major axis of the
  123. ellipse is rotated by rotation_angle.
  124. If is_large_arc, then the arc segment is selected to be > 180 degrees.
  125. If is_clockwise, then the arc sweeps clockwise.
  126. */
  127. static void
  128. xps_draw_arc(fz_context *ctx, xps_document *doc, fz_path *path,
  129. float size_x, float size_y, float rotation_angle,
  130. int is_large_arc, int is_clockwise,
  131. float point_x, float point_y)
  132. {
  133. fz_matrix rotmat, revmat;
  134. fz_matrix mtx;
  135. fz_point pt;
  136. float rx, ry;
  137. float x1, y1, x2, y2;
  138. float x1t, y1t;
  139. float cxt, cyt, cx, cy;
  140. float t1, t2, t3;
  141. float sign;
  142. float th1, dth;
  143. pt = fz_currentpoint(ctx, path);
  144. x1 = pt.x;
  145. y1 = pt.y;
  146. x2 = point_x;
  147. y2 = point_y;
  148. rx = size_x;
  149. ry = size_y;
  150. if (is_clockwise != is_large_arc)
  151. sign = 1;
  152. else
  153. sign = -1;
  154. rotmat = fz_rotate(rotation_angle);
  155. revmat = fz_rotate(-rotation_angle);
  156. /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
  157. /* Conversion from endpoint to center parameterization */
  158. /* F.6.6.1 -- ensure radii are positive and non-zero */
  159. rx = fabsf(rx);
  160. ry = fabsf(ry);
  161. if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
  162. {
  163. fz_lineto(ctx, path, x2, y2);
  164. return;
  165. }
  166. /* F.6.5.1 */
  167. pt.x = (x1 - x2) / 2;
  168. pt.y = (y1 - y2) / 2;
  169. pt = fz_transform_vector(pt, revmat);
  170. x1t = pt.x;
  171. y1t = pt.y;
  172. /* F.6.6.2 -- ensure radii are large enough */
  173. t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
  174. if (t1 > 1)
  175. {
  176. rx = rx * sqrtf(t1);
  177. ry = ry * sqrtf(t1);
  178. }
  179. /* F.6.5.2 */
  180. t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
  181. t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
  182. t3 = t1 / t2;
  183. /* guard against rounding errors; sqrt of negative numbers is bad for your health */
  184. if (t3 < 0) t3 = 0;
  185. t3 = sqrtf(t3);
  186. cxt = sign * t3 * (rx * y1t) / ry;
  187. cyt = sign * t3 * -(ry * x1t) / rx;
  188. /* F.6.5.3 */
  189. pt.x = cxt;
  190. pt.y = cyt;
  191. pt = fz_transform_vector(pt, rotmat);
  192. cx = pt.x + (x1 + x2) / 2;
  193. cy = pt.y + (y1 + y2) / 2;
  194. /* F.6.5.4 */
  195. {
  196. fz_point coord1, coord2, coord3, coord4;
  197. coord1.x = 1;
  198. coord1.y = 0;
  199. coord2.x = (x1t - cxt) / rx;
  200. coord2.y = (y1t - cyt) / ry;
  201. coord3.x = (x1t - cxt) / rx;
  202. coord3.y = (y1t - cyt) / ry;
  203. coord4.x = (-x1t - cxt) / rx;
  204. coord4.y = (-y1t - cyt) / ry;
  205. th1 = angle_between(coord1, coord2);
  206. dth = angle_between(coord3, coord4);
  207. if (dth < 0 && !is_clockwise)
  208. dth += ((FZ_PI / 180) * 360);
  209. if (dth > 0 && is_clockwise)
  210. dth -= ((FZ_PI / 180) * 360);
  211. }
  212. mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
  213. xps_draw_arc_segment(ctx, doc, path, mtx, th1, th1 + dth, is_clockwise);
  214. fz_lineto(ctx, path, point_x, point_y);
  215. }
  216. fz_path *
  217. xps_parse_abbreviated_geometry(fz_context *ctx, xps_document *doc, char *geom, int *fill_rule)
  218. {
  219. fz_path *path;
  220. char **args = NULL;
  221. char **pargs;
  222. char *s = geom;
  223. fz_point pt;
  224. int i, n;
  225. int cmd, old;
  226. float x1, y1, x2, y2, x3, y3;
  227. float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */
  228. int reset_smooth;
  229. fz_var(args);
  230. path = fz_new_path(ctx);
  231. fz_try(ctx)
  232. {
  233. args = fz_malloc_array(ctx, strlen(geom) + 1, char*);
  234. pargs = args;
  235. while (*s)
  236. {
  237. if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
  238. {
  239. *pargs++ = s++;
  240. }
  241. else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
  242. {
  243. *pargs++ = s;
  244. while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
  245. s ++;
  246. }
  247. else
  248. {
  249. s++;
  250. }
  251. }
  252. *pargs = s;
  253. n = pargs - args;
  254. i = 0;
  255. old = 0;
  256. reset_smooth = 1;
  257. smooth_x = 0;
  258. smooth_y = 0;
  259. while (i < n)
  260. {
  261. cmd = args[i][0];
  262. if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9'))
  263. cmd = old; /* it's a number, repeat old command */
  264. else
  265. i ++;
  266. if (reset_smooth)
  267. {
  268. smooth_x = 0;
  269. smooth_y = 0;
  270. }
  271. reset_smooth = 1;
  272. switch (cmd)
  273. {
  274. case 'F':
  275. if (i >= n) break;
  276. *fill_rule = atoi(args[i]);
  277. i ++;
  278. break;
  279. case 'M':
  280. if (i + 1 >= n) break;
  281. fz_moveto(ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
  282. i += 2;
  283. break;
  284. case 'm':
  285. if (i + 1 >= n) break;
  286. pt = fz_currentpoint(ctx, path);
  287. fz_moveto(ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
  288. i += 2;
  289. break;
  290. case 'L':
  291. if (i + 1 >= n) break;
  292. fz_lineto(ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
  293. i += 2;
  294. break;
  295. case 'l':
  296. if (i + 1 >= n) break;
  297. pt = fz_currentpoint(ctx, path);
  298. fz_lineto(ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
  299. i += 2;
  300. break;
  301. case 'H':
  302. if (i >= n) break;
  303. pt = fz_currentpoint(ctx, path);
  304. fz_lineto(ctx, path, fz_atof(args[i]), pt.y);
  305. i += 1;
  306. break;
  307. case 'h':
  308. if (i >= n) break;
  309. pt = fz_currentpoint(ctx, path);
  310. fz_lineto(ctx, path, pt.x + fz_atof(args[i]), pt.y);
  311. i += 1;
  312. break;
  313. case 'V':
  314. if (i >= n) break;
  315. pt = fz_currentpoint(ctx, path);
  316. fz_lineto(ctx, path, pt.x, fz_atof(args[i]));
  317. i += 1;
  318. break;
  319. case 'v':
  320. if (i >= n) break;
  321. pt = fz_currentpoint(ctx, path);
  322. fz_lineto(ctx, path, pt.x, pt.y + fz_atof(args[i]));
  323. i += 1;
  324. break;
  325. case 'C':
  326. if (i + 5 >= n) break;
  327. x1 = fz_atof(args[i+0]);
  328. y1 = fz_atof(args[i+1]);
  329. x2 = fz_atof(args[i+2]);
  330. y2 = fz_atof(args[i+3]);
  331. x3 = fz_atof(args[i+4]);
  332. y3 = fz_atof(args[i+5]);
  333. fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
  334. i += 6;
  335. reset_smooth = 0;
  336. smooth_x = x3 - x2;
  337. smooth_y = y3 - y2;
  338. break;
  339. case 'c':
  340. if (i + 5 >= n) break;
  341. pt = fz_currentpoint(ctx, path);
  342. x1 = fz_atof(args[i+0]) + pt.x;
  343. y1 = fz_atof(args[i+1]) + pt.y;
  344. x2 = fz_atof(args[i+2]) + pt.x;
  345. y2 = fz_atof(args[i+3]) + pt.y;
  346. x3 = fz_atof(args[i+4]) + pt.x;
  347. y3 = fz_atof(args[i+5]) + pt.y;
  348. fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
  349. i += 6;
  350. reset_smooth = 0;
  351. smooth_x = x3 - x2;
  352. smooth_y = y3 - y2;
  353. break;
  354. case 'S':
  355. if (i + 3 >= n) break;
  356. pt = fz_currentpoint(ctx, path);
  357. x1 = fz_atof(args[i+0]);
  358. y1 = fz_atof(args[i+1]);
  359. x2 = fz_atof(args[i+2]);
  360. y2 = fz_atof(args[i+3]);
  361. fz_curveto(ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
  362. i += 4;
  363. reset_smooth = 0;
  364. smooth_x = x2 - x1;
  365. smooth_y = y2 - y1;
  366. break;
  367. case 's':
  368. if (i + 3 >= n) break;
  369. pt = fz_currentpoint(ctx, path);
  370. x1 = fz_atof(args[i+0]) + pt.x;
  371. y1 = fz_atof(args[i+1]) + pt.y;
  372. x2 = fz_atof(args[i+2]) + pt.x;
  373. y2 = fz_atof(args[i+3]) + pt.y;
  374. fz_curveto(ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
  375. i += 4;
  376. reset_smooth = 0;
  377. smooth_x = x2 - x1;
  378. smooth_y = y2 - y1;
  379. break;
  380. case 'Q':
  381. if (i + 3 >= n) break;
  382. x1 = fz_atof(args[i+0]);
  383. y1 = fz_atof(args[i+1]);
  384. x2 = fz_atof(args[i+2]);
  385. y2 = fz_atof(args[i+3]);
  386. fz_quadto(ctx, path, x1, y1, x2, y2);
  387. i += 4;
  388. break;
  389. case 'q':
  390. if (i + 3 >= n) break;
  391. pt = fz_currentpoint(ctx, path);
  392. x1 = fz_atof(args[i+0]) + pt.x;
  393. y1 = fz_atof(args[i+1]) + pt.y;
  394. x2 = fz_atof(args[i+2]) + pt.x;
  395. y2 = fz_atof(args[i+3]) + pt.y;
  396. fz_quadto(ctx, path, x1, y1, x2, y2);
  397. i += 4;
  398. break;
  399. case 'A':
  400. if (i + 6 >= n) break;
  401. xps_draw_arc(ctx, doc, path,
  402. fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
  403. atoi(args[i+3]), atoi(args[i+4]),
  404. fz_atof(args[i+5]), fz_atof(args[i+6]));
  405. i += 7;
  406. break;
  407. case 'a':
  408. if (i + 6 >= n) break;
  409. pt = fz_currentpoint(ctx, path);
  410. xps_draw_arc(ctx, doc, path,
  411. fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
  412. atoi(args[i+3]), atoi(args[i+4]),
  413. fz_atof(args[i+5]) + pt.x, fz_atof(args[i+6]) + pt.y);
  414. i += 7;
  415. break;
  416. case 'Z':
  417. case 'z':
  418. fz_closepath(ctx, path);
  419. break;
  420. default:
  421. fz_warn(ctx, "ignoring invalid command '%c'", cmd);
  422. if (old == cmd) /* avoid infinite loop */
  423. i++;
  424. break;
  425. }
  426. old = cmd;
  427. }
  428. }
  429. fz_always(ctx)
  430. fz_free(ctx, args);
  431. fz_catch(ctx)
  432. {
  433. fz_drop_path(ctx, path);
  434. fz_rethrow(ctx);
  435. }
  436. return path;
  437. }
  438. static void
  439. xps_parse_arc_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
  440. {
  441. /* ArcSegment pretty much follows the SVG algorithm for converting an
  442. * arc in endpoint representation to an arc in centerpoint
  443. * representation. Once in centerpoint it can be given to the
  444. * graphics library in the form of a postscript arc. */
  445. float rotation_angle;
  446. int is_large_arc, is_clockwise;
  447. float point_x, point_y;
  448. float size_x, size_y;
  449. int is_stroked;
  450. char *point_att = fz_xml_att(root, "Point");
  451. char *size_att = fz_xml_att(root, "Size");
  452. char *rotation_angle_att = fz_xml_att(root, "RotationAngle");
  453. char *is_large_arc_att = fz_xml_att(root, "IsLargeArc");
  454. char *sweep_direction_att = fz_xml_att(root, "SweepDirection");
  455. char *is_stroked_att = fz_xml_att(root, "IsStroked");
  456. if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
  457. {
  458. fz_warn(ctx, "ArcSegment element is missing attributes");
  459. return;
  460. }
  461. is_stroked = 1;
  462. if (is_stroked_att && !strcmp(is_stroked_att, "false"))
  463. is_stroked = 0;
  464. if (!is_stroked)
  465. *skipped_stroke = 1;
  466. point_x = point_y = 0;
  467. size_x = size_y = 0;
  468. xps_parse_point(ctx, doc, point_att, &point_x, &point_y);
  469. xps_parse_point(ctx, doc, size_att, &size_x, &size_y);
  470. rotation_angle = fz_atof(rotation_angle_att);
  471. is_large_arc = !strcmp(is_large_arc_att, "true");
  472. is_clockwise = !strcmp(sweep_direction_att, "Clockwise");
  473. if (stroking && !is_stroked)
  474. {
  475. fz_moveto(ctx, path, point_x, point_y);
  476. return;
  477. }
  478. xps_draw_arc(ctx, doc, path, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y);
  479. }
  480. static void
  481. xps_parse_poly_quadratic_bezier_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
  482. {
  483. char *points_att = fz_xml_att(root, "Points");
  484. char *is_stroked_att = fz_xml_att(root, "IsStroked");
  485. float x[2], y[2];
  486. int is_stroked;
  487. fz_point pt;
  488. char *s;
  489. int n;
  490. if (!points_att)
  491. {
  492. fz_warn(ctx, "PolyQuadraticBezierSegment element has no points");
  493. return;
  494. }
  495. is_stroked = 1;
  496. if (is_stroked_att && !strcmp(is_stroked_att, "false"))
  497. is_stroked = 0;
  498. if (!is_stroked)
  499. *skipped_stroke = 1;
  500. s = points_att;
  501. n = 0;
  502. while (*s != 0)
  503. {
  504. while (*s == ' ') s++;
  505. x[n] = y[n] = 0;
  506. s = xps_parse_point(ctx, doc, s, &x[n], &y[n]);
  507. n ++;
  508. if (n == 2)
  509. {
  510. if (stroking && !is_stroked)
  511. {
  512. fz_moveto(ctx, path, x[1], y[1]);
  513. }
  514. else
  515. {
  516. pt = fz_currentpoint(ctx, path);
  517. fz_curveto(ctx, path,
  518. (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3,
  519. (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3,
  520. x[1], y[1]);
  521. }
  522. n = 0;
  523. }
  524. }
  525. }
  526. static void
  527. xps_parse_poly_bezier_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
  528. {
  529. char *points_att = fz_xml_att(root, "Points");
  530. char *is_stroked_att = fz_xml_att(root, "IsStroked");
  531. float x[3], y[3];
  532. int is_stroked;
  533. char *s;
  534. int n;
  535. if (!points_att)
  536. {
  537. fz_warn(ctx, "PolyBezierSegment element has no points");
  538. return;
  539. }
  540. is_stroked = 1;
  541. if (is_stroked_att && !strcmp(is_stroked_att, "false"))
  542. is_stroked = 0;
  543. if (!is_stroked)
  544. *skipped_stroke = 1;
  545. s = points_att;
  546. n = 0;
  547. while (*s != 0)
  548. {
  549. while (*s == ' ') s++;
  550. x[n] = y[n] = 0;
  551. s = xps_parse_point(ctx, doc, s, &x[n], &y[n]);
  552. n ++;
  553. if (n == 3)
  554. {
  555. if (stroking && !is_stroked)
  556. fz_moveto(ctx, path, x[2], y[2]);
  557. else
  558. fz_curveto(ctx, path, x[0], y[0], x[1], y[1], x[2], y[2]);
  559. n = 0;
  560. }
  561. }
  562. }
  563. static void
  564. xps_parse_poly_line_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
  565. {
  566. char *points_att = fz_xml_att(root, "Points");
  567. char *is_stroked_att = fz_xml_att(root, "IsStroked");
  568. int is_stroked;
  569. float x, y;
  570. char *s;
  571. if (!points_att)
  572. {
  573. fz_warn(ctx, "PolyLineSegment element has no points");
  574. return;
  575. }
  576. is_stroked = 1;
  577. if (is_stroked_att && !strcmp(is_stroked_att, "false"))
  578. is_stroked = 0;
  579. if (!is_stroked)
  580. *skipped_stroke = 1;
  581. s = points_att;
  582. while (*s != 0)
  583. {
  584. while (*s == ' ') s++;
  585. x = y = 0;
  586. s = xps_parse_point(ctx, doc, s, &x, &y);
  587. if (stroking && !is_stroked)
  588. fz_moveto(ctx, path, x, y);
  589. else
  590. fz_lineto(ctx, path, x, y);
  591. }
  592. }
  593. static void
  594. xps_parse_path_figure(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking)
  595. {
  596. fz_xml *node;
  597. char *is_closed_att;
  598. char *start_point_att;
  599. char *is_filled_att;
  600. int is_closed = 0;
  601. int is_filled = 1;
  602. float start_x = 0;
  603. float start_y = 0;
  604. int skipped_stroke = 0;
  605. is_closed_att = fz_xml_att(root, "IsClosed");
  606. start_point_att = fz_xml_att(root, "StartPoint");
  607. is_filled_att = fz_xml_att(root, "IsFilled");
  608. if (is_closed_att)
  609. is_closed = !strcmp(is_closed_att, "true");
  610. if (is_filled_att)
  611. is_filled = !strcmp(is_filled_att, "true");
  612. if (start_point_att)
  613. xps_parse_point(ctx, doc, start_point_att, &start_x, &start_y);
  614. if (!stroking && !is_filled) /* not filled, when filling */
  615. return;
  616. fz_moveto(ctx, path, start_x, start_y);
  617. for (node = fz_xml_down(root); node; node = fz_xml_next(node))
  618. {
  619. if (fz_xml_is_tag(node, "ArcSegment"))
  620. xps_parse_arc_segment(ctx, doc, path, node, stroking, &skipped_stroke);
  621. if (fz_xml_is_tag(node, "PolyBezierSegment"))
  622. xps_parse_poly_bezier_segment(ctx, doc, path, node, stroking, &skipped_stroke);
  623. if (fz_xml_is_tag(node, "PolyLineSegment"))
  624. xps_parse_poly_line_segment(ctx, doc, path, node, stroking, &skipped_stroke);
  625. if (fz_xml_is_tag(node, "PolyQuadraticBezierSegment"))
  626. xps_parse_poly_quadratic_bezier_segment(ctx, doc, path, node, stroking, &skipped_stroke);
  627. }
  628. if (is_closed)
  629. {
  630. if (stroking && skipped_stroke)
  631. fz_lineto(ctx, path, start_x, start_y); /* we've skipped using fz_moveto... */
  632. else
  633. fz_closepath(ctx, path); /* no skipped segments, safe to closepath properly */
  634. }
  635. }
  636. fz_path *
  637. xps_parse_path_geometry(fz_context *ctx, xps_document *doc, xps_resource *dict, fz_xml *root, int stroking, int *fill_rule)
  638. {
  639. fz_xml *node;
  640. char *figures_att;
  641. char *fill_rule_att;
  642. char *transform_att;
  643. fz_xml *transform_tag = NULL;
  644. fz_xml *figures_tag = NULL; /* only used by resource */
  645. fz_matrix transform;
  646. fz_path *path;
  647. figures_att = fz_xml_att(root, "Figures");
  648. fill_rule_att = fz_xml_att(root, "FillRule");
  649. transform_att = fz_xml_att(root, "Transform");
  650. for (node = fz_xml_down(root); node; node = fz_xml_next(node))
  651. {
  652. if (fz_xml_is_tag(node, "PathGeometry.Transform"))
  653. transform_tag = fz_xml_down(node);
  654. }
  655. xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
  656. xps_resolve_resource_reference(ctx, doc, dict, &figures_att, &figures_tag, NULL);
  657. if (fill_rule_att)
  658. {
  659. if (!strcmp(fill_rule_att, "NonZero"))
  660. *fill_rule = 1;
  661. if (!strcmp(fill_rule_att, "EvenOdd"))
  662. *fill_rule = 0;
  663. }
  664. transform = xps_parse_transform(ctx, doc, transform_att, transform_tag, fz_identity);
  665. if (figures_att)
  666. path = xps_parse_abbreviated_geometry(ctx, doc, figures_att, fill_rule);
  667. else
  668. path = fz_new_path(ctx);
  669. fz_try(ctx)
  670. {
  671. if (figures_tag)
  672. xps_parse_path_figure(ctx, doc, path, figures_tag, stroking);
  673. for (node = fz_xml_down(root); node; node = fz_xml_next(node))
  674. {
  675. if (fz_xml_is_tag(node, "PathFigure"))
  676. xps_parse_path_figure(ctx, doc, path, node, stroking);
  677. }
  678. if (transform_att || transform_tag)
  679. fz_transform_path(ctx, path, transform);
  680. }
  681. fz_catch(ctx)
  682. {
  683. fz_drop_path(ctx, path);
  684. fz_rethrow(ctx);
  685. }
  686. return path;
  687. }
  688. static int
  689. xps_parse_line_cap(char *attr)
  690. {
  691. if (attr)
  692. {
  693. if (!strcmp(attr, "Flat")) return 0;
  694. if (!strcmp(attr, "Round")) return 1;
  695. if (!strcmp(attr, "Square")) return 2;
  696. if (!strcmp(attr, "Triangle")) return 3;
  697. }
  698. return 0;
  699. }
  700. void
  701. xps_clip(fz_context *ctx, xps_document *doc, fz_matrix ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
  702. {
  703. fz_device *dev = doc->dev;
  704. fz_path *path;
  705. int fill_rule = 0;
  706. if (clip_att)
  707. path = xps_parse_abbreviated_geometry(ctx, doc, clip_att, &fill_rule);
  708. else if (clip_tag)
  709. path = xps_parse_path_geometry(ctx, doc, dict, clip_tag, 0, &fill_rule);
  710. else
  711. path = fz_new_path(ctx);
  712. fz_try(ctx)
  713. fz_clip_path(ctx, dev, path, fill_rule == 0, ctm, fz_infinite_rect);
  714. fz_always(ctx)
  715. fz_drop_path(ctx, path);
  716. fz_catch(ctx)
  717. fz_rethrow(ctx);
  718. }
  719. void
  720. xps_parse_path(fz_context *ctx, xps_document *doc, fz_matrix ctm, char *base_uri, xps_resource *dict, fz_xml *root)
  721. {
  722. fz_device *dev = doc->dev;
  723. fz_xml *node;
  724. char *fill_uri;
  725. char *stroke_uri;
  726. char *opacity_mask_uri;
  727. char *transform_att;
  728. char *clip_att;
  729. char *data_att;
  730. char *fill_att;
  731. char *stroke_att;
  732. char *opacity_att;
  733. char *opacity_mask_att;
  734. fz_xml *transform_tag = NULL;
  735. fz_xml *clip_tag = NULL;
  736. fz_xml *data_tag = NULL;
  737. fz_xml *fill_tag = NULL;
  738. fz_xml *stroke_tag = NULL;
  739. fz_xml *opacity_mask_tag = NULL;
  740. char *fill_opacity_att = NULL;
  741. char *stroke_opacity_att = NULL;
  742. char *stroke_dash_array_att;
  743. char *stroke_dash_cap_att;
  744. char *stroke_dash_offset_att;
  745. char *stroke_end_line_cap_att;
  746. char *stroke_start_line_cap_att;
  747. char *stroke_line_join_att;
  748. char *stroke_miter_limit_att;
  749. char *stroke_thickness_att;
  750. fz_stroke_state *stroke = NULL;
  751. float samples[FZ_MAX_COLORS];
  752. fz_colorspace *colorspace;
  753. fz_path *path = NULL;
  754. fz_path *stroke_path = NULL;
  755. fz_rect area;
  756. int fill_rule;
  757. int dash_len = 0;
  758. /*
  759. * Extract attributes and extended attributes.
  760. */
  761. transform_att = fz_xml_att(root, "RenderTransform");
  762. clip_att = fz_xml_att(root, "Clip");
  763. data_att = fz_xml_att(root, "Data");
  764. fill_att = fz_xml_att(root, "Fill");
  765. stroke_att = fz_xml_att(root, "Stroke");
  766. opacity_att = fz_xml_att(root, "Opacity");
  767. opacity_mask_att = fz_xml_att(root, "OpacityMask");
  768. stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray");
  769. stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap");
  770. stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset");
  771. stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap");
  772. stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap");
  773. stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin");
  774. stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit");
  775. stroke_thickness_att = fz_xml_att(root, "StrokeThickness");
  776. for (node = fz_xml_down(root); node; node = fz_xml_next(node))
  777. {
  778. if (fz_xml_is_tag(node, "Path.RenderTransform"))
  779. transform_tag = fz_xml_down(node);
  780. if (fz_xml_is_tag(node, "Path.OpacityMask"))
  781. opacity_mask_tag = fz_xml_down(node);
  782. if (fz_xml_is_tag(node, "Path.Clip"))
  783. clip_tag = fz_xml_down(node);
  784. if (fz_xml_is_tag(node, "Path.Fill"))
  785. fill_tag = fz_xml_down(node);
  786. if (fz_xml_is_tag(node, "Path.Stroke"))
  787. stroke_tag = fz_xml_down(node);
  788. if (fz_xml_is_tag(node, "Path.Data"))
  789. data_tag = fz_xml_down(node);
  790. }
  791. fill_uri = base_uri;
  792. stroke_uri = base_uri;
  793. opacity_mask_uri = base_uri;
  794. xps_resolve_resource_reference(ctx, doc, dict, &data_att, &data_tag, NULL);
  795. xps_resolve_resource_reference(ctx, doc, dict, &clip_att, &clip_tag, NULL);
  796. xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
  797. xps_resolve_resource_reference(ctx, doc, dict, &fill_att, &fill_tag, &fill_uri);
  798. xps_resolve_resource_reference(ctx, doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
  799. xps_resolve_resource_reference(ctx, doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
  800. /*
  801. * Act on the information we have gathered:
  802. */
  803. if (!data_att && !data_tag)
  804. return;
  805. if (fz_xml_is_tag(fill_tag, "SolidColorBrush"))
  806. {
  807. fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
  808. fill_att = fz_xml_att(fill_tag, "Color");
  809. fill_tag = NULL;
  810. }
  811. if (fz_xml_is_tag(stroke_tag, "SolidColorBrush"))
  812. {
  813. stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity");
  814. stroke_att = fz_xml_att(stroke_tag, "Color");
  815. stroke_tag = NULL;
  816. }
  817. if (stroke_att || stroke_tag)
  818. {
  819. if (stroke_dash_array_att)
  820. {
  821. char *s = stroke_dash_array_att;
  822. while (*s)
  823. {
  824. while (*s == ' ')
  825. s++;
  826. if (*s) /* needed in case of a space before the last quote */
  827. dash_len++;
  828. while (*s && *s != ' ')
  829. s++;
  830. }
  831. }
  832. stroke = fz_new_stroke_state_with_dash_len(ctx, dash_len);
  833. stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
  834. stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
  835. stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att);
  836. stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
  837. if (stroke_line_join_att)
  838. {
  839. if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
  840. if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND;
  841. if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL;
  842. }
  843. stroke->miterlimit = 10;
  844. if (stroke_miter_limit_att)
  845. stroke->miterlimit = fz_atof(stroke_miter_limit_att);
  846. stroke->linewidth = 1;
  847. if (stroke_thickness_att)
  848. stroke->linewidth = fz_atof(stroke_thickness_att);
  849. stroke->dash_phase = 0;
  850. stroke->dash_len = 0;
  851. if (stroke_dash_array_att)
  852. {
  853. char *s = stroke_dash_array_att;
  854. if (stroke_dash_offset_att)
  855. stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth;
  856. while (*s)
  857. {
  858. while (*s == ' ')
  859. s++;
  860. if (*s) /* needed in case of a space before the last quote */
  861. stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth;
  862. while (*s && *s != ' ')
  863. s++;
  864. }
  865. if (dash_len > 0)
  866. {
  867. /* fz_stroke_path doesn't draw non-empty paths with phase length zero */
  868. float phase_len = 0;
  869. int i;
  870. for (i = 0; i < dash_len; i++)
  871. phase_len += stroke->dash_list[i];
  872. if (phase_len == 0)
  873. dash_len = 0;
  874. }
  875. stroke->dash_len = dash_len;
  876. }
  877. }
  878. ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm);
  879. if (clip_att || clip_tag)
  880. xps_clip(ctx, doc, ctm, dict, clip_att, clip_tag);
  881. fz_try(ctx)
  882. {
  883. fill_rule = 0;
  884. if (data_att)
  885. path = xps_parse_abbreviated_geometry(ctx, doc, data_att, &fill_rule);
  886. else if (data_tag)
  887. {
  888. path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 0, &fill_rule);
  889. // /home/sebras/src/jxr/fts_06xx.xps
  890. if (stroke_att || stroke_tag)
  891. stroke_path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 1, &fill_rule);
  892. }
  893. if (!stroke_path)
  894. stroke_path = path;
  895. if (stroke_att || stroke_tag)
  896. {
  897. area = fz_bound_path(ctx, stroke_path, stroke, ctm);
  898. if (stroke_path != path && (fill_att || fill_tag)) {
  899. fz_rect bounds = fz_bound_path(ctx, path, NULL, ctm);
  900. area = fz_union_rect(area, bounds);
  901. }
  902. }
  903. else
  904. area = fz_bound_path(ctx, path, NULL, ctm);
  905. xps_begin_opacity(ctx, doc, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
  906. if (fill_att)
  907. {
  908. xps_parse_color(ctx, doc, base_uri, fill_att, &colorspace, samples);
  909. if (fill_opacity_att)
  910. samples[0] *= fz_atof(fill_opacity_att);
  911. xps_set_color(ctx, doc, colorspace, samples);
  912. fz_fill_path(ctx, dev, path, fill_rule == 0, ctm,
  913. doc->colorspace, doc->color, doc->alpha, fz_default_color_params);
  914. }
  915. if (fill_tag)
  916. {
  917. fz_clip_path(ctx, dev, path, fill_rule == 0, ctm, area);
  918. xps_parse_brush(ctx, doc, ctm, area, fill_uri, dict, fill_tag);
  919. fz_pop_clip(ctx, dev);
  920. }
  921. if (stroke_att)
  922. {
  923. xps_parse_color(ctx, doc, base_uri, stroke_att, &colorspace, samples);
  924. if (stroke_opacity_att)
  925. samples[0] *= fz_atof(stroke_opacity_att);
  926. xps_set_color(ctx, doc, colorspace, samples);
  927. fz_stroke_path(ctx, dev, stroke_path, stroke, ctm,
  928. doc->colorspace, doc->color, doc->alpha, fz_default_color_params);
  929. }
  930. if (stroke_tag)
  931. {
  932. fz_clip_stroke_path(ctx, dev, stroke_path, stroke, ctm, area);
  933. xps_parse_brush(ctx, doc, ctm, area, stroke_uri, dict, stroke_tag);
  934. fz_pop_clip(ctx, dev);
  935. }
  936. xps_end_opacity(ctx, doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
  937. }
  938. fz_always(ctx)
  939. {
  940. if (stroke_path != path)
  941. fz_drop_path(ctx, stroke_path);
  942. fz_drop_path(ctx, path);
  943. fz_drop_stroke_state(ctx, stroke);
  944. }
  945. fz_catch(ctx)
  946. fz_rethrow(ctx);
  947. if (clip_att || clip_tag)
  948. fz_pop_clip(ctx, dev);
  949. }