Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/source/svg/svg-run.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 // Copyright (C) 2004-2025 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 | |
| 23 #include "mupdf/fitz.h" | |
| 24 #include "svg-imp.h" | |
| 25 | |
| 26 #include <string.h> | |
| 27 #include <math.h> | |
| 28 | |
| 29 /* default page size */ | |
| 30 #define DEF_WIDTH 612 | |
| 31 #define DEF_HEIGHT 792 | |
| 32 #define DEF_FONTSIZE 12 | |
| 33 | |
| 34 #define MAX_USE_DEPTH 100 | |
| 35 | |
| 36 typedef struct svg_state | |
| 37 { | |
| 38 fz_matrix transform; | |
| 39 fz_stroke_state *stroke; | |
| 40 int use_depth; | |
| 41 | |
| 42 float viewport_w, viewport_h; | |
| 43 float viewbox_w, viewbox_h, viewbox_size; | |
| 44 float fontsize; | |
| 45 | |
| 46 float opacity; | |
| 47 | |
| 48 int fill_rule; | |
| 49 int fill_is_set; | |
| 50 float fill_color[3]; | |
| 51 float fill_opacity; | |
| 52 | |
| 53 int stroke_is_set; | |
| 54 float stroke_color[3]; | |
| 55 float stroke_opacity; | |
| 56 | |
| 57 const char *font_family; | |
| 58 int is_bold; | |
| 59 int is_italic; | |
| 60 int text_anchor; | |
| 61 } svg_state; | |
| 62 | |
| 63 static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state); | |
| 64 static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state); | |
| 65 | |
| 66 void svg_begin_state(fz_context *ctx, svg_state *child, const svg_state *parent) | |
| 67 { | |
| 68 memcpy(child, parent, sizeof(svg_state)); | |
| 69 child->stroke = fz_clone_stroke_state(ctx, parent->stroke); | |
| 70 } | |
| 71 | |
| 72 void svg_end_state(fz_context *ctx, svg_state *child) | |
| 73 { | |
| 74 fz_drop_stroke_state(ctx, child->stroke); | |
| 75 } | |
| 76 | |
| 77 static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state) | |
| 78 { | |
| 79 float opacity = state->opacity * state->fill_opacity; | |
| 80 if (path) | |
| 81 fz_fill_path(ctx, dev, path, state->fill_rule, state->transform, fz_device_rgb(ctx), state->fill_color, opacity, fz_default_color_params); | |
| 82 } | |
| 83 | |
| 84 static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state) | |
| 85 { | |
| 86 float opacity = state->opacity * state->stroke_opacity; | |
| 87 if (path) | |
| 88 fz_stroke_path(ctx, dev, path, state->stroke, state->transform, fz_device_rgb(ctx), state->stroke_color, opacity, fz_default_color_params); | |
| 89 } | |
| 90 | |
| 91 static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state) | |
| 92 { | |
| 93 if (state->fill_is_set) | |
| 94 svg_fill(ctx, dev, doc, path, state); | |
| 95 if (state->stroke_is_set) | |
| 96 svg_stroke(ctx, dev, doc, path, state); | |
| 97 } | |
| 98 | |
| 99 /* | |
| 100 We use the MAGIC number 0.551915 as a bezier subdivision to approximate | |
| 101 a quarter circle arc. The reasons for this can be found here: | |
| 102 http://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf | |
| 103 */ | |
| 104 static const float MAGIC_CIRCLE = 0.551915f; | |
| 105 | |
| 106 static void approx_circle(fz_context *ctx, fz_path *path, float cx, float cy, float rx, float ry) | |
| 107 { | |
| 108 float mx = rx * MAGIC_CIRCLE; | |
| 109 float my = ry * MAGIC_CIRCLE; | |
| 110 fz_moveto(ctx, path, cx, cy+ry); | |
| 111 fz_curveto(ctx, path, cx + mx, cy + ry, cx + rx, cy + my, cx + rx, cy); | |
| 112 fz_curveto(ctx, path, cx + rx, cy - my, cx + mx, cy - ry, cx, cy - ry); | |
| 113 fz_curveto(ctx, path, cx - mx, cy - ry, cx - rx, cy - my, cx - rx, cy); | |
| 114 fz_curveto(ctx, path, cx - rx, cy + my, cx - mx, cy + ry, cx, cy + ry); | |
| 115 fz_closepath(ctx, path); | |
| 116 } | |
| 117 | |
| 118 static void | |
| 119 svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 120 { | |
| 121 svg_state local_state; | |
| 122 | |
| 123 char *x_att = fz_xml_att(node, "x"); | |
| 124 char *y_att = fz_xml_att(node, "y"); | |
| 125 char *w_att = fz_xml_att(node, "width"); | |
| 126 char *h_att = fz_xml_att(node, "height"); | |
| 127 char *rx_att = fz_xml_att(node, "rx"); | |
| 128 char *ry_att = fz_xml_att(node, "ry"); | |
| 129 | |
| 130 float x = 0; | |
| 131 float y = 0; | |
| 132 float w = 0; | |
| 133 float h = 0; | |
| 134 float rx = 0; | |
| 135 float ry = 0; | |
| 136 | |
| 137 fz_path *path = NULL; | |
| 138 | |
| 139 fz_var(path); | |
| 140 | |
| 141 fz_try(ctx) | |
| 142 { | |
| 143 svg_begin_state(ctx, &local_state, inherit_state); | |
| 144 svg_parse_common(ctx, doc, node, &local_state); | |
| 145 | |
| 146 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize); | |
| 147 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize); | |
| 148 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize); | |
| 149 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize); | |
| 150 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize); | |
| 151 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize); | |
| 152 | |
| 153 if (rx_att && !ry_att) | |
| 154 ry = rx; | |
| 155 if (ry_att && !rx_att) | |
| 156 rx = ry; | |
| 157 if (rx > w * 0.5f) | |
| 158 rx = w * 0.5f; | |
| 159 if (ry > h * 0.5f) | |
| 160 ry = h * 0.5f; | |
| 161 | |
| 162 if (w <= 0 || h <= 0) | |
| 163 return; | |
| 164 | |
| 165 path = fz_new_path(ctx); | |
| 166 if (rx == 0 || ry == 0) | |
| 167 { | |
| 168 fz_moveto(ctx, path, x, y); | |
| 169 fz_lineto(ctx, path, x + w, y); | |
| 170 fz_lineto(ctx, path, x + w, y + h); | |
| 171 fz_lineto(ctx, path, x, y + h); | |
| 172 } | |
| 173 else | |
| 174 { | |
| 175 float rxs = rx * MAGIC_CIRCLE; | |
| 176 float rys = rx * MAGIC_CIRCLE; | |
| 177 fz_moveto(ctx, path, x + w - rx, y); | |
| 178 fz_curveto(ctx, path, x + w - rxs, y, x + w, y + rys, x + w, y + ry); | |
| 179 fz_lineto(ctx, path, x + w, y + h - ry); | |
| 180 fz_curveto(ctx, path, x + w, y + h - rys, x + w - rxs, y + h, x + w - rx, y + h); | |
| 181 fz_lineto(ctx, path, x + rx, y + h); | |
| 182 fz_curveto(ctx, path, x + rxs, y + h, x, y + h - rys, x, y + h - rx); | |
| 183 fz_lineto(ctx, path, x, y + rx); | |
| 184 fz_curveto(ctx, path, x, y + rxs, x + rxs, y, x + rx, y); | |
| 185 } | |
| 186 fz_closepath(ctx, path); | |
| 187 | |
| 188 svg_draw_path(ctx, dev, doc, path, &local_state); | |
| 189 } | |
| 190 fz_always(ctx) | |
| 191 { | |
| 192 fz_drop_path(ctx, path); | |
| 193 svg_end_state(ctx, &local_state); | |
| 194 } | |
| 195 fz_catch(ctx) | |
| 196 fz_rethrow(ctx); | |
| 197 | |
| 198 } | |
| 199 | |
| 200 static void | |
| 201 svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 202 { | |
| 203 svg_state local_state; | |
| 204 | |
| 205 char *cx_att = fz_xml_att(node, "cx"); | |
| 206 char *cy_att = fz_xml_att(node, "cy"); | |
| 207 char *r_att = fz_xml_att(node, "r"); | |
| 208 | |
| 209 float cx = 0; | |
| 210 float cy = 0; | |
| 211 float r = 0; | |
| 212 fz_path *path = NULL; | |
| 213 | |
| 214 fz_var(path); | |
| 215 | |
| 216 fz_try(ctx) | |
| 217 { | |
| 218 svg_begin_state(ctx, &local_state, inherit_state); | |
| 219 svg_parse_common(ctx, doc, node, &local_state); | |
| 220 | |
| 221 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize); | |
| 222 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize); | |
| 223 if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12); | |
| 224 | |
| 225 if (r > 0) | |
| 226 { | |
| 227 path = fz_new_path(ctx); | |
| 228 approx_circle(ctx, path, cx, cy, r, r); | |
| 229 svg_draw_path(ctx, dev, doc, path, &local_state); | |
| 230 } | |
| 231 } | |
| 232 fz_always(ctx) | |
| 233 { | |
| 234 fz_drop_path(ctx, path); | |
| 235 svg_end_state(ctx, &local_state); | |
| 236 } | |
| 237 fz_catch(ctx) | |
| 238 fz_rethrow(ctx); | |
| 239 | |
| 240 } | |
| 241 | |
| 242 static void | |
| 243 svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 244 { | |
| 245 svg_state local_state; | |
| 246 | |
| 247 char *cx_att = fz_xml_att(node, "cx"); | |
| 248 char *cy_att = fz_xml_att(node, "cy"); | |
| 249 char *rx_att = fz_xml_att(node, "rx"); | |
| 250 char *ry_att = fz_xml_att(node, "ry"); | |
| 251 | |
| 252 float cx = 0; | |
| 253 float cy = 0; | |
| 254 float rx = 0; | |
| 255 float ry = 0; | |
| 256 | |
| 257 fz_path *path = NULL; | |
| 258 | |
| 259 fz_var(path); | |
| 260 | |
| 261 fz_try(ctx) | |
| 262 { | |
| 263 svg_begin_state(ctx, &local_state, inherit_state); | |
| 264 svg_parse_common(ctx, doc, node, &local_state); | |
| 265 | |
| 266 if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize); | |
| 267 if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize); | |
| 268 if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize); | |
| 269 if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize); | |
| 270 | |
| 271 if (rx > 0 && ry > 0) | |
| 272 { | |
| 273 path = fz_new_path(ctx); | |
| 274 approx_circle(ctx, path, cx, cy, rx, ry); | |
| 275 svg_draw_path(ctx, dev, doc, path, &local_state); | |
| 276 } | |
| 277 } | |
| 278 fz_always(ctx) | |
| 279 { | |
| 280 fz_drop_path(ctx, path); | |
| 281 svg_end_state(ctx, &local_state); | |
| 282 } | |
| 283 fz_catch(ctx) | |
| 284 fz_rethrow(ctx); | |
| 285 } | |
| 286 | |
| 287 static void | |
| 288 svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 289 { | |
| 290 svg_state local_state; | |
| 291 fz_path *path = NULL; | |
| 292 | |
| 293 char *x1_att = fz_xml_att(node, "x1"); | |
| 294 char *y1_att = fz_xml_att(node, "y1"); | |
| 295 char *x2_att = fz_xml_att(node, "x2"); | |
| 296 char *y2_att = fz_xml_att(node, "y2"); | |
| 297 | |
| 298 float x1 = 0; | |
| 299 float y1 = 0; | |
| 300 float x2 = 0; | |
| 301 float y2 = 0; | |
| 302 | |
| 303 fz_var(path); | |
| 304 | |
| 305 fz_try(ctx) | |
| 306 { | |
| 307 svg_begin_state(ctx, &local_state, inherit_state); | |
| 308 svg_parse_common(ctx, doc, node, &local_state); | |
| 309 | |
| 310 if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize); | |
| 311 if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize); | |
| 312 if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize); | |
| 313 if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize); | |
| 314 | |
| 315 if (local_state.stroke_is_set) | |
| 316 { | |
| 317 path = fz_new_path(ctx); | |
| 318 fz_moveto(ctx, path, x1, y1); | |
| 319 fz_lineto(ctx, path, x2, y2); | |
| 320 svg_stroke(ctx, dev, doc, path, &local_state); | |
| 321 } | |
| 322 } | |
| 323 fz_always(ctx) | |
| 324 { | |
| 325 fz_drop_path(ctx, path); | |
| 326 svg_end_state(ctx, &local_state); | |
| 327 } | |
| 328 fz_catch(ctx) | |
| 329 fz_rethrow(ctx); | |
| 330 } | |
| 331 | |
| 332 static fz_path * | |
| 333 svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose) | |
| 334 { | |
| 335 fz_path *path; | |
| 336 | |
| 337 const char *str = fz_xml_att(node, "points"); | |
| 338 float number; | |
| 339 float args[2]; | |
| 340 int nargs; | |
| 341 int isfirst; | |
| 342 | |
| 343 if (!str) | |
| 344 return NULL; | |
| 345 | |
| 346 isfirst = 1; | |
| 347 nargs = 0; | |
| 348 | |
| 349 path = fz_new_path(ctx); | |
| 350 fz_try(ctx) | |
| 351 { | |
| 352 while (*str) | |
| 353 { | |
| 354 while (svg_is_whitespace_or_comma(*str)) | |
| 355 str ++; | |
| 356 | |
| 357 if (svg_is_digit(*str)) | |
| 358 { | |
| 359 str = svg_lex_number(&number, str); | |
| 360 args[nargs++] = number; | |
| 361 } | |
| 362 | |
| 363 if (nargs == 2) | |
| 364 { | |
| 365 if (isfirst) | |
| 366 { | |
| 367 fz_moveto(ctx, path, args[0], args[1]); | |
| 368 isfirst = 0; | |
| 369 } | |
| 370 else | |
| 371 { | |
| 372 fz_lineto(ctx, path, args[0], args[1]); | |
| 373 } | |
| 374 nargs = 0; | |
| 375 } | |
| 376 } | |
| 377 } | |
| 378 fz_catch(ctx) | |
| 379 { | |
| 380 fz_drop_path(ctx, path); | |
| 381 fz_rethrow(ctx); | |
| 382 } | |
| 383 | |
| 384 return path; | |
| 385 } | |
| 386 | |
| 387 static void | |
| 388 svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 389 { | |
| 390 svg_state local_state; | |
| 391 fz_path *path = NULL; | |
| 392 | |
| 393 fz_var(path); | |
| 394 | |
| 395 fz_try(ctx) | |
| 396 { | |
| 397 svg_begin_state(ctx, &local_state, inherit_state); | |
| 398 svg_parse_common(ctx, doc, node, &local_state); | |
| 399 | |
| 400 if (local_state.stroke_is_set) | |
| 401 { | |
| 402 path = svg_parse_polygon_imp(ctx, doc, node, 0); | |
| 403 svg_stroke(ctx, dev, doc, path, &local_state); | |
| 404 } | |
| 405 } | |
| 406 fz_always(ctx) | |
| 407 { | |
| 408 fz_drop_path(ctx, path); | |
| 409 svg_end_state(ctx, &local_state); | |
| 410 } | |
| 411 fz_catch(ctx) | |
| 412 fz_rethrow(ctx); | |
| 413 } | |
| 414 | |
| 415 static void | |
| 416 svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 417 { | |
| 418 svg_state local_state; | |
| 419 fz_path *path = NULL; | |
| 420 | |
| 421 fz_var(path); | |
| 422 | |
| 423 fz_try(ctx) | |
| 424 { | |
| 425 svg_begin_state(ctx, &local_state, inherit_state); | |
| 426 svg_parse_common(ctx, doc, node, &local_state); | |
| 427 | |
| 428 path = svg_parse_polygon_imp(ctx, doc, node, 1); | |
| 429 svg_draw_path(ctx, dev, doc, path, &local_state); | |
| 430 } | |
| 431 fz_always(ctx) | |
| 432 { | |
| 433 fz_drop_path(ctx, path); | |
| 434 svg_end_state(ctx, &local_state); | |
| 435 } | |
| 436 fz_catch(ctx) | |
| 437 fz_rethrow(ctx); | |
| 438 } | |
| 439 | |
| 440 static void | |
| 441 svg_add_arc_segment(fz_context *ctx, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw) | |
| 442 { | |
| 443 float t, d; | |
| 444 fz_point p; | |
| 445 | |
| 446 while (th1 < th0) | |
| 447 th1 += FZ_PI * 2; | |
| 448 | |
| 449 d = FZ_PI / 180; /* 1-degree precision */ | |
| 450 | |
| 451 if (iscw) | |
| 452 { | |
| 453 for (t = th0 + d; t < th1 - d/2; t += d) | |
| 454 { | |
| 455 p = fz_transform_point_xy(cosf(t), sinf(t), mtx); | |
| 456 fz_lineto(ctx, path, p.x, p.y); | |
| 457 } | |
| 458 } | |
| 459 else | |
| 460 { | |
| 461 th0 += FZ_PI * 2; | |
| 462 for (t = th0 - d; t > th1 + d/2; t -= d) | |
| 463 { | |
| 464 p = fz_transform_point_xy(cosf(t), sinf(t), mtx); | |
| 465 fz_lineto(ctx, path, p.x, p.y); | |
| 466 } | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 static float | |
| 471 angle_between(const fz_point u, const fz_point v) | |
| 472 { | |
| 473 float det = u.x * v.y - u.y * v.x; | |
| 474 float sign = (det < 0 ? -1 : 1); | |
| 475 float magu = u.x * u.x + u.y * u.y; | |
| 476 float magv = v.x * v.x + v.y * v.y; | |
| 477 float udotv = u.x * v.x + u.y * v.y; | |
| 478 float t = udotv / (magu * magv); | |
| 479 /* guard against rounding errors when near |1| (where acos will return NaN) */ | |
| 480 if (t < -1) t = -1; | |
| 481 if (t > 1) t = 1; | |
| 482 return sign * acosf(t); | |
| 483 } | |
| 484 | |
| 485 static void | |
| 486 svg_add_arc(fz_context *ctx, fz_path *path, | |
| 487 float size_x, float size_y, float rotation_angle, | |
| 488 int is_large_arc, int is_clockwise, | |
| 489 float point_x, float point_y) | |
| 490 { | |
| 491 fz_matrix rotmat, revmat; | |
| 492 fz_matrix mtx; | |
| 493 fz_point pt; | |
| 494 float rx, ry; | |
| 495 float x1, y1, x2, y2; | |
| 496 float x1t, y1t; | |
| 497 float cxt, cyt, cx, cy; | |
| 498 float t1, t2, t3; | |
| 499 float sign; | |
| 500 float th1, dth; | |
| 501 | |
| 502 pt = fz_currentpoint(ctx, path); | |
| 503 x1 = pt.x; | |
| 504 y1 = pt.y; | |
| 505 x2 = point_x; | |
| 506 y2 = point_y; | |
| 507 rx = size_x; | |
| 508 ry = size_y; | |
| 509 | |
| 510 if (is_clockwise != is_large_arc) | |
| 511 sign = 1; | |
| 512 else | |
| 513 sign = -1; | |
| 514 | |
| 515 rotmat = fz_rotate(rotation_angle); | |
| 516 revmat = fz_rotate(-rotation_angle); | |
| 517 | |
| 518 /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */ | |
| 519 /* Conversion from endpoint to center parameterization */ | |
| 520 | |
| 521 /* F.6.6.1 -- ensure radii are positive and non-zero */ | |
| 522 rx = fabsf(rx); | |
| 523 ry = fabsf(ry); | |
| 524 if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2)) | |
| 525 { | |
| 526 fz_lineto(ctx, path, x2, y2); | |
| 527 return; | |
| 528 } | |
| 529 | |
| 530 /* F.6.5.1 */ | |
| 531 pt.x = (x1 - x2) / 2; | |
| 532 pt.y = (y1 - y2) / 2; | |
| 533 pt = fz_transform_vector(pt, revmat); | |
| 534 x1t = pt.x; | |
| 535 y1t = pt.y; | |
| 536 | |
| 537 /* F.6.6.2 -- ensure radii are large enough */ | |
| 538 t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry); | |
| 539 if (t1 > 1) | |
| 540 { | |
| 541 rx = rx * sqrtf(t1); | |
| 542 ry = ry * sqrtf(t1); | |
| 543 } | |
| 544 | |
| 545 /* F.6.5.2 */ | |
| 546 t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t); | |
| 547 t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t); | |
| 548 t3 = t1 / t2; | |
| 549 /* guard against rounding errors; sqrt of negative numbers is bad for your health */ | |
| 550 if (t3 < 0) t3 = 0; | |
| 551 t3 = sqrtf(t3); | |
| 552 | |
| 553 cxt = sign * t3 * (rx * y1t) / ry; | |
| 554 cyt = sign * t3 * -(ry * x1t) / rx; | |
| 555 | |
| 556 /* F.6.5.3 */ | |
| 557 pt.x = cxt; | |
| 558 pt.y = cyt; | |
| 559 pt = fz_transform_vector(pt, rotmat); | |
| 560 cx = pt.x + (x1 + x2) / 2; | |
| 561 cy = pt.y + (y1 + y2) / 2; | |
| 562 | |
| 563 /* F.6.5.4 */ | |
| 564 { | |
| 565 fz_point coord1, coord2, coord3, coord4; | |
| 566 coord1.x = 1; | |
| 567 coord1.y = 0; | |
| 568 coord2.x = (x1t - cxt) / rx; | |
| 569 coord2.y = (y1t - cyt) / ry; | |
| 570 coord3.x = (x1t - cxt) / rx; | |
| 571 coord3.y = (y1t - cyt) / ry; | |
| 572 coord4.x = (-x1t - cxt) / rx; | |
| 573 coord4.y = (-y1t - cyt) / ry; | |
| 574 th1 = angle_between(coord1, coord2); | |
| 575 dth = angle_between(coord3, coord4); | |
| 576 if (dth < 0 && !is_clockwise) | |
| 577 dth += ((FZ_PI / 180) * 360); | |
| 578 if (dth > 0 && is_clockwise) | |
| 579 dth -= ((FZ_PI / 180) * 360); | |
| 580 } | |
| 581 | |
| 582 mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry); | |
| 583 svg_add_arc_segment(ctx, path, mtx, th1, th1 + dth, is_clockwise); | |
| 584 | |
| 585 fz_lineto(ctx, path, point_x, point_y); | |
| 586 } | |
| 587 | |
| 588 static void | |
| 589 svg_parse_path_data(fz_context *ctx, fz_path *path, const char *str) | |
| 590 { | |
| 591 fz_point p; | |
| 592 float x1, y1, x2, y2; | |
| 593 | |
| 594 int cmd; | |
| 595 float number; | |
| 596 float args[7]; | |
| 597 int nargs; | |
| 598 | |
| 599 /* saved control point for smooth curves */ | |
| 600 int reset_smooth = 1; | |
| 601 float smooth_x = 0.0f; | |
| 602 float smooth_y = 0.0f; | |
| 603 | |
| 604 cmd = 0; | |
| 605 nargs = 0; | |
| 606 | |
| 607 fz_moveto(ctx, path, 0.0f, 0.0f); /* for the case of opening 'm' */ | |
| 608 | |
| 609 while (*str) | |
| 610 { | |
| 611 while (svg_is_whitespace_or_comma(*str)) | |
| 612 str ++; | |
| 613 | |
| 614 /* arcto flag arguments are 1-character 0 or 1 */ | |
| 615 if ((cmd == 'a' || cmd == 'A') && (nargs == 3 || nargs == 4) && (*str == '0' || *str == '1')) | |
| 616 { | |
| 617 args[nargs++] = *str++ - '0'; | |
| 618 } | |
| 619 else if (svg_is_digit(*str)) | |
| 620 { | |
| 621 str = svg_lex_number(&number, str); | |
| 622 if (nargs == nelem(args)) | |
| 623 { | |
| 624 fz_warn(ctx, "stack overflow in path data"); | |
| 625 return; | |
| 626 } | |
| 627 args[nargs++] = number; | |
| 628 } | |
| 629 else if (svg_is_alpha(*str)) | |
| 630 { | |
| 631 if (nargs != 0) | |
| 632 { | |
| 633 fz_warn(ctx, "syntax error in path data (wrong number of parameters to '%c')", cmd); | |
| 634 return; | |
| 635 } | |
| 636 cmd = *str++; | |
| 637 } | |
| 638 else if (*str == 0) | |
| 639 { | |
| 640 return; | |
| 641 } | |
| 642 else | |
| 643 { | |
| 644 fz_warn(ctx, "syntax error in path data: '%c'", *str); | |
| 645 return; | |
| 646 } | |
| 647 | |
| 648 if (reset_smooth) | |
| 649 { | |
| 650 smooth_x = 0.0f; | |
| 651 smooth_y = 0.0f; | |
| 652 } | |
| 653 | |
| 654 reset_smooth = 1; | |
| 655 | |
| 656 switch (cmd) | |
| 657 { | |
| 658 case 'M': | |
| 659 if (nargs == 2) | |
| 660 { | |
| 661 fz_moveto(ctx, path, args[0], args[1]); | |
| 662 nargs = 0; | |
| 663 cmd = 'L'; /* implicit lineto after */ | |
| 664 } | |
| 665 break; | |
| 666 | |
| 667 case 'm': | |
| 668 if (nargs == 2) | |
| 669 { | |
| 670 p = fz_currentpoint(ctx, path); | |
| 671 fz_moveto(ctx, path, p.x + args[0], p.y + args[1]); | |
| 672 nargs = 0; | |
| 673 cmd = 'l'; /* implicit lineto after */ | |
| 674 } | |
| 675 break; | |
| 676 | |
| 677 case 'Z': | |
| 678 case 'z': | |
| 679 if (nargs == 0) | |
| 680 { | |
| 681 fz_closepath(ctx, path); | |
| 682 } | |
| 683 break; | |
| 684 | |
| 685 case 'L': | |
| 686 if (nargs == 2) | |
| 687 { | |
| 688 fz_lineto(ctx, path, args[0], args[1]); | |
| 689 nargs = 0; | |
| 690 } | |
| 691 break; | |
| 692 | |
| 693 case 'l': | |
| 694 if (nargs == 2) | |
| 695 { | |
| 696 p = fz_currentpoint(ctx, path); | |
| 697 fz_lineto(ctx, path, p.x + args[0], p.y + args[1]); | |
| 698 nargs = 0; | |
| 699 } | |
| 700 break; | |
| 701 | |
| 702 case 'H': | |
| 703 if (nargs == 1) | |
| 704 { | |
| 705 p = fz_currentpoint(ctx, path); | |
| 706 fz_lineto(ctx, path, args[0], p.y); | |
| 707 nargs = 0; | |
| 708 } | |
| 709 break; | |
| 710 | |
| 711 case 'h': | |
| 712 if (nargs == 1) | |
| 713 { | |
| 714 p = fz_currentpoint(ctx, path); | |
| 715 fz_lineto(ctx, path, p.x + args[0], p.y); | |
| 716 nargs = 0; | |
| 717 } | |
| 718 break; | |
| 719 | |
| 720 case 'V': | |
| 721 if (nargs == 1) | |
| 722 { | |
| 723 p = fz_currentpoint(ctx, path); | |
| 724 fz_lineto(ctx, path, p.x, args[0]); | |
| 725 nargs = 0; | |
| 726 } | |
| 727 break; | |
| 728 | |
| 729 case 'v': | |
| 730 if (nargs == 1) | |
| 731 { | |
| 732 p = fz_currentpoint(ctx, path); | |
| 733 fz_lineto(ctx, path, p.x, p.y + args[0]); | |
| 734 nargs = 0; | |
| 735 } | |
| 736 break; | |
| 737 | |
| 738 case 'C': | |
| 739 reset_smooth = 0; | |
| 740 if (nargs == 6) | |
| 741 { | |
| 742 fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]); | |
| 743 smooth_x = args[4] - args[2]; | |
| 744 smooth_y = args[5] - args[3]; | |
| 745 nargs = 0; | |
| 746 } | |
| 747 break; | |
| 748 | |
| 749 case 'c': | |
| 750 reset_smooth = 0; | |
| 751 if (nargs == 6) | |
| 752 { | |
| 753 p = fz_currentpoint(ctx, path); | |
| 754 fz_curveto(ctx, path, | |
| 755 p.x + args[0], p.y + args[1], | |
| 756 p.x + args[2], p.y + args[3], | |
| 757 p.x + args[4], p.y + args[5]); | |
| 758 smooth_x = args[4] - args[2]; | |
| 759 smooth_y = args[5] - args[3]; | |
| 760 nargs = 0; | |
| 761 } | |
| 762 break; | |
| 763 | |
| 764 case 'S': | |
| 765 reset_smooth = 0; | |
| 766 if (nargs == 4) | |
| 767 { | |
| 768 p = fz_currentpoint(ctx, path); | |
| 769 fz_curveto(ctx, path, | |
| 770 p.x + smooth_x, p.y + smooth_y, | |
| 771 args[0], args[1], | |
| 772 args[2], args[3]); | |
| 773 smooth_x = args[2] - args[0]; | |
| 774 smooth_y = args[3] - args[1]; | |
| 775 nargs = 0; | |
| 776 } | |
| 777 break; | |
| 778 | |
| 779 case 's': | |
| 780 reset_smooth = 0; | |
| 781 if (nargs == 4) | |
| 782 { | |
| 783 p = fz_currentpoint(ctx, path); | |
| 784 fz_curveto(ctx, path, | |
| 785 p.x + smooth_x, p.y + smooth_y, | |
| 786 p.x + args[0], p.y + args[1], | |
| 787 p.x + args[2], p.y + args[3]); | |
| 788 smooth_x = args[2] - args[0]; | |
| 789 smooth_y = args[3] - args[1]; | |
| 790 nargs = 0; | |
| 791 } | |
| 792 break; | |
| 793 | |
| 794 case 'Q': | |
| 795 reset_smooth = 0; | |
| 796 if (nargs == 4) | |
| 797 { | |
| 798 p = fz_currentpoint(ctx, path); | |
| 799 x1 = args[0]; | |
| 800 y1 = args[1]; | |
| 801 x2 = args[2]; | |
| 802 y2 = args[3]; | |
| 803 fz_curveto(ctx, path, | |
| 804 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3, | |
| 805 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, | |
| 806 x2, y2); | |
| 807 smooth_x = x2 - x1; | |
| 808 smooth_y = y2 - y1; | |
| 809 nargs = 0; | |
| 810 } | |
| 811 break; | |
| 812 | |
| 813 case 'q': | |
| 814 reset_smooth = 0; | |
| 815 if (nargs == 4) | |
| 816 { | |
| 817 p = fz_currentpoint(ctx, path); | |
| 818 x1 = args[0] + p.x; | |
| 819 y1 = args[1] + p.y; | |
| 820 x2 = args[2] + p.x; | |
| 821 y2 = args[3] + p.y; | |
| 822 fz_curveto(ctx, path, | |
| 823 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3, | |
| 824 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, | |
| 825 x2, y2); | |
| 826 smooth_x = x2 - x1; | |
| 827 smooth_y = y2 - y1; | |
| 828 nargs = 0; | |
| 829 } | |
| 830 break; | |
| 831 | |
| 832 case 'T': | |
| 833 reset_smooth = 0; | |
| 834 if (nargs == 2) | |
| 835 { | |
| 836 p = fz_currentpoint(ctx, path); | |
| 837 x1 = p.x + smooth_x; | |
| 838 y1 = p.y + smooth_y; | |
| 839 x2 = args[0]; | |
| 840 y2 = args[1]; | |
| 841 fz_curveto(ctx, path, | |
| 842 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3, | |
| 843 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, | |
| 844 x2, y2); | |
| 845 smooth_x = x2 - x1; | |
| 846 smooth_y = y2 - y1; | |
| 847 nargs = 0; | |
| 848 } | |
| 849 break; | |
| 850 | |
| 851 case 't': | |
| 852 reset_smooth = 0; | |
| 853 if (nargs == 2) | |
| 854 { | |
| 855 p = fz_currentpoint(ctx, path); | |
| 856 x1 = p.x + smooth_x; | |
| 857 y1 = p.y + smooth_y; | |
| 858 x2 = args[0] + p.x; | |
| 859 y2 = args[1] + p.y; | |
| 860 fz_curveto(ctx, path, | |
| 861 (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3, | |
| 862 (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, | |
| 863 x2, y2); | |
| 864 smooth_x = x2 - x1; | |
| 865 smooth_y = y2 - y1; | |
| 866 nargs = 0; | |
| 867 } | |
| 868 break; | |
| 869 | |
| 870 case 'A': | |
| 871 if (nargs == 7) | |
| 872 { | |
| 873 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); | |
| 874 nargs = 0; | |
| 875 } | |
| 876 break; | |
| 877 case 'a': | |
| 878 if (nargs == 7) | |
| 879 { | |
| 880 p = fz_currentpoint(ctx, path); | |
| 881 svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y); | |
| 882 nargs = 0; | |
| 883 } | |
| 884 break; | |
| 885 | |
| 886 case 0: | |
| 887 if (nargs != 0) | |
| 888 { | |
| 889 fz_warn(ctx, "path data must begin with a command"); | |
| 890 return; | |
| 891 } | |
| 892 break; | |
| 893 | |
| 894 default: | |
| 895 fz_warn(ctx, "unrecognized command in path data: '%c'", cmd); | |
| 896 return; | |
| 897 } | |
| 898 } | |
| 899 } | |
| 900 | |
| 901 static void | |
| 902 svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state) | |
| 903 { | |
| 904 svg_state local_state; | |
| 905 fz_path *path = NULL; | |
| 906 | |
| 907 const char *d_att = fz_xml_att(node, "d"); | |
| 908 /* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */ | |
| 909 | |
| 910 fz_var(path); | |
| 911 | |
| 912 fz_try(ctx) | |
| 913 { | |
| 914 svg_begin_state(ctx, &local_state, inherit_state); | |
| 915 svg_parse_common(ctx, doc, node, &local_state); | |
| 916 | |
| 917 if (d_att) | |
| 918 { | |
| 919 path = fz_new_path(ctx); | |
| 920 svg_parse_path_data(ctx, path, d_att); | |
| 921 svg_draw_path(ctx, dev, doc, path, &local_state); | |
| 922 } | |
| 923 } | |
| 924 fz_always(ctx) | |
| 925 { | |
| 926 fz_drop_path(ctx, path); | |
| 927 svg_end_state(ctx, &local_state); | |
| 928 } | |
| 929 fz_catch(ctx) | |
| 930 fz_rethrow(ctx); | |
| 931 } | |
| 932 | |
| 933 /* svg, symbol, image, foreignObject establish new viewports */ | |
| 934 static void | |
| 935 svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state) | |
| 936 { | |
| 937 char *w_att = fz_xml_att(node, "width"); | |
| 938 char *h_att = fz_xml_att(node, "height"); | |
| 939 | |
| 940 if (w_att) | |
| 941 state->viewport_w = svg_parse_length(w_att, state->viewbox_w, state->fontsize); | |
| 942 if (h_att) | |
| 943 state->viewport_h = svg_parse_length(h_att, state->viewbox_h, state->fontsize); | |
| 944 | |
| 945 } | |
| 946 | |
| 947 static void | |
| 948 svg_lex_viewbox(const char *s, float *x, float *y, float *w, float *h) | |
| 949 { | |
| 950 *x = *y = *w = *h = 0; | |
| 951 while (svg_is_whitespace_or_comma(*s)) ++s; | |
| 952 if (svg_is_digit(*s)) s = svg_lex_number(x, s); | |
| 953 while (svg_is_whitespace_or_comma(*s)) ++s; | |
| 954 if (svg_is_digit(*s)) s = svg_lex_number(y, s); | |
| 955 while (svg_is_whitespace_or_comma(*s)) ++s; | |
| 956 if (svg_is_digit(*s)) s = svg_lex_number(w, s); | |
| 957 while (svg_is_whitespace_or_comma(*s)) ++s; | |
| 958 if (svg_is_digit(*s)) s = svg_lex_number(h, s); | |
| 959 } | |
| 960 | |
| 961 static int | |
| 962 svg_parse_preserve_aspect_ratio(const char *att, int *x, int *y) | |
| 963 { | |
| 964 *x = *y = 1; | |
| 965 if (strstr(att, "none")) return 0; | |
| 966 if (strstr(att, "xMin")) *x = 0; | |
| 967 if (strstr(att, "xMid")) *x = 1; | |
| 968 if (strstr(att, "xMax")) *x = 2; | |
| 969 if (strstr(att, "YMin")) *y = 0; | |
| 970 if (strstr(att, "YMid")) *y = 1; | |
| 971 if (strstr(att, "YMax")) *y = 2; | |
| 972 return 1; | |
| 973 } | |
| 974 | |
| 975 /* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */ | |
| 976 static void | |
| 977 svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state) | |
| 978 { | |
| 979 char *viewbox_att = fz_xml_att(node, "viewBox"); | |
| 980 char *preserve_att = fz_xml_att(node, "preserveAspectRatio"); | |
| 981 if (viewbox_att) | |
| 982 { | |
| 983 /* scale and translate to fit [minx miny minx+w miny+h] to [0 0 viewport.w viewport.h] */ | |
| 984 float min_x, min_y, box_w, box_h, sx, sy; | |
| 985 int align_x=1, align_y=1, preserve=1; | |
| 986 float pad_x=0, pad_y=0; | |
| 987 | |
| 988 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h); | |
| 989 sx = state->viewport_w / box_w; | |
| 990 sy = state->viewport_h / box_h; | |
| 991 | |
| 992 if (preserve_att) | |
| 993 preserve = svg_parse_preserve_aspect_ratio(preserve_att, &align_x, &align_y); | |
| 994 if (preserve) | |
| 995 { | |
| 996 sx = sy = fz_min(sx, sy); | |
| 997 if (align_x == 1) pad_x = (box_w * sx - state->viewport_w) / 2; | |
| 998 if (align_x == 2) pad_x = (box_w * sx - state->viewport_w); | |
| 999 if (align_y == 1) pad_y = (box_h * sy - state->viewport_h) / 2; | |
| 1000 if (align_y == 2) pad_y = (box_h * sy - state->viewport_h); | |
| 1001 state->transform = fz_concat(fz_translate(-pad_x, -pad_y), state->transform); | |
| 1002 } | |
| 1003 state->transform = fz_concat(fz_scale(sx, sy), state->transform); | |
| 1004 state->transform = fz_concat(fz_translate(-min_x, -min_y), state->transform); | |
| 1005 state->viewbox_w = box_w; | |
| 1006 state->viewbox_h = box_h; | |
| 1007 state->viewbox_size = sqrtf(box_w*box_w + box_h*box_h) / sqrtf(2); | |
| 1008 } | |
| 1009 } | |
| 1010 | |
| 1011 static const char *linecap_table[] = { "butt", "round", "square" }; | |
| 1012 static const char *linejoin_table[] = { "miter", "round", "bevel" }; | |
| 1013 | |
| 1014 /* parse transform and presentation attributes */ | |
| 1015 static void | |
| 1016 svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state) | |
| 1017 { | |
| 1018 fz_stroke_state *stroke = state->stroke; | |
| 1019 | |
| 1020 char *transform_att = fz_xml_att(node, "transform"); | |
| 1021 | |
| 1022 char *font_size_att = fz_xml_att(node, "font-size"); | |
| 1023 | |
| 1024 char *style_att = fz_xml_att(node, "style"); | |
| 1025 | |
| 1026 // TODO: clip, clip-path, clip-rule | |
| 1027 | |
| 1028 char *opacity_att = fz_xml_att(node, "opacity"); | |
| 1029 | |
| 1030 char *fill_att = fz_xml_att(node, "fill"); | |
| 1031 char *fill_rule_att = fz_xml_att(node, "fill-rule"); | |
| 1032 char *fill_opacity_att = fz_xml_att(node, "fill-opacity"); | |
| 1033 | |
| 1034 char *stroke_att = fz_xml_att(node, "stroke"); | |
| 1035 char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity"); | |
| 1036 char *stroke_width_att = fz_xml_att(node, "stroke-width"); | |
| 1037 char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap"); | |
| 1038 char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin"); | |
| 1039 char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit"); | |
| 1040 // TODO: stroke-dasharray, stroke-dashoffset | |
| 1041 | |
| 1042 // TODO: marker, marker-start, marker-mid, marker-end | |
| 1043 | |
| 1044 // TODO: overflow | |
| 1045 // TODO: mask | |
| 1046 | |
| 1047 /* Dirty hack scans of CSS style */ | |
| 1048 if (style_att) | |
| 1049 { | |
| 1050 svg_parse_color_from_style(ctx, doc, style_att, | |
| 1051 &state->fill_is_set, state->fill_color, | |
| 1052 &state->stroke_is_set, state->stroke_color); | |
| 1053 } | |
| 1054 | |
| 1055 if (transform_att) | |
| 1056 { | |
| 1057 state->transform = svg_parse_transform(ctx, doc, transform_att, state->transform); | |
| 1058 } | |
| 1059 | |
| 1060 if (font_size_att) | |
| 1061 { | |
| 1062 state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize); | |
| 1063 } | |
| 1064 else | |
| 1065 { | |
| 1066 state->fontsize = svg_parse_number_from_style(ctx, doc, style_att, "font-size", state->fontsize); | |
| 1067 } | |
| 1068 | |
| 1069 if (opacity_att) | |
| 1070 { | |
| 1071 state->opacity = svg_parse_number(opacity_att, 0, 1, state->opacity); | |
| 1072 } | |
| 1073 | |
| 1074 if (fill_att) | |
| 1075 { | |
| 1076 if (!strcmp(fill_att, "none")) | |
| 1077 { | |
| 1078 state->fill_is_set = 0; | |
| 1079 } | |
| 1080 else | |
| 1081 { | |
| 1082 state->fill_is_set = 1; | |
| 1083 svg_parse_color(ctx, doc, fill_att, state->fill_color); | |
| 1084 } | |
| 1085 } | |
| 1086 | |
| 1087 if (fill_opacity_att) | |
| 1088 state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity); | |
| 1089 | |
| 1090 if (fill_rule_att) | |
| 1091 { | |
| 1092 if (!strcmp(fill_rule_att, "nonzero")) | |
| 1093 state->fill_rule = 0; | |
| 1094 if (!strcmp(fill_rule_att, "evenodd")) | |
| 1095 state->fill_rule = 1; | |
| 1096 } | |
| 1097 | |
| 1098 if (stroke_att) | |
| 1099 { | |
| 1100 if (!strcmp(stroke_att, "none")) | |
| 1101 { | |
| 1102 state->stroke_is_set = 0; | |
| 1103 } | |
| 1104 else | |
| 1105 { | |
| 1106 state->stroke_is_set = 1; | |
| 1107 svg_parse_color(ctx, doc, stroke_att, state->stroke_color); | |
| 1108 } | |
| 1109 } | |
| 1110 | |
| 1111 if (stroke_opacity_att) | |
| 1112 state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity); | |
| 1113 | |
| 1114 if (stroke_width_att) | |
| 1115 { | |
| 1116 if (!strcmp(stroke_width_att, "inherit")) | |
| 1117 ; | |
| 1118 else | |
| 1119 stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize); | |
| 1120 } | |
| 1121 else | |
| 1122 { | |
| 1123 stroke->linewidth = svg_parse_number_from_style(ctx, doc, style_att, "stroke-width", state->stroke->linewidth); | |
| 1124 } | |
| 1125 | |
| 1126 if (stroke_linecap_att) | |
| 1127 { | |
| 1128 if (!strcmp(stroke_linecap_att, "butt")) | |
| 1129 stroke->start_cap = FZ_LINECAP_BUTT; | |
| 1130 if (!strcmp(stroke_linecap_att, "round")) | |
| 1131 stroke->start_cap = FZ_LINECAP_ROUND; | |
| 1132 if (!strcmp(stroke_linecap_att, "square")) | |
| 1133 stroke->start_cap = FZ_LINECAP_SQUARE; | |
| 1134 } | |
| 1135 else | |
| 1136 { | |
| 1137 stroke->start_cap = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linecap", | |
| 1138 nelem(linecap_table), linecap_table, FZ_LINECAP_BUTT); | |
| 1139 } | |
| 1140 | |
| 1141 stroke->dash_cap = stroke->start_cap; | |
| 1142 stroke->end_cap = stroke->start_cap; | |
| 1143 | |
| 1144 if (stroke_linejoin_att) | |
| 1145 { | |
| 1146 if (!strcmp(stroke_linejoin_att, "miter")) | |
| 1147 stroke->linejoin = FZ_LINEJOIN_MITER; | |
| 1148 if (!strcmp(stroke_linejoin_att, "round")) | |
| 1149 stroke->linejoin = FZ_LINEJOIN_ROUND; | |
| 1150 if (!strcmp(stroke_linejoin_att, "bevel")) | |
| 1151 stroke->linejoin = FZ_LINEJOIN_BEVEL; | |
| 1152 } | |
| 1153 else | |
| 1154 { | |
| 1155 stroke->linejoin = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linejoin", | |
| 1156 nelem(linejoin_table), linejoin_table, FZ_LINEJOIN_MITER); | |
| 1157 } | |
| 1158 | |
| 1159 if (stroke_miterlimit_att) | |
| 1160 { | |
| 1161 if (!strcmp(stroke_miterlimit_att, "inherit")) | |
| 1162 ; | |
| 1163 else | |
| 1164 stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize); | |
| 1165 } | |
| 1166 else | |
| 1167 { | |
| 1168 stroke->miterlimit = svg_parse_number_from_style(ctx, doc, style_att, "stroke-miterlimit", state->stroke->miterlimit); | |
| 1169 } | |
| 1170 } | |
| 1171 | |
| 1172 static void | |
| 1173 svg_parse_font_attributes(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state, char *buf, int buf_size) | |
| 1174 { | |
| 1175 char *style_att = fz_xml_att(node, "style"); | |
| 1176 char *font_family_att = fz_xml_att(node, "font-family"); | |
| 1177 char *font_weight_att = fz_xml_att(node, "font-weight"); | |
| 1178 char *font_style_att = fz_xml_att(node, "font-style"); | |
| 1179 char *text_anchor_att = fz_xml_att(node, "text-anchor"); | |
| 1180 | |
| 1181 if (font_family_att) | |
| 1182 fz_strlcpy(buf, font_family_att, buf_size); | |
| 1183 else | |
| 1184 svg_parse_string_from_style(ctx, doc, style_att, "font-family", buf, buf_size, state->font_family); | |
| 1185 state->font_family = buf; | |
| 1186 | |
| 1187 if (font_weight_att) | |
| 1188 { | |
| 1189 state->is_bold = atoi(font_weight_att) > 400; | |
| 1190 if (!strcmp(font_weight_att, "bold")) state->is_bold = 1; | |
| 1191 if (!strcmp(font_weight_att, "bolder")) state->is_bold = 1; | |
| 1192 } | |
| 1193 else | |
| 1194 { | |
| 1195 static const char *is_bold_table[] = { | |
| 1196 "normal", "100", "200", "300", "400", "bold", "bolder", "500", "600", "700", "800", "900" | |
| 1197 }; | |
| 1198 state->is_bold = svg_parse_enum_from_style(ctx, doc, style_att, "font-weight", | |
| 1199 nelem(is_bold_table), is_bold_table, state->is_bold ? 5 : 0) >= 5; | |
| 1200 } | |
| 1201 | |
| 1202 if (font_style_att) | |
| 1203 { | |
| 1204 state->is_italic = 0; | |
| 1205 if (!strcmp(font_style_att, "italic")) state->is_italic = 1; | |
| 1206 if (!strcmp(font_style_att, "oblique")) state->is_italic = 1; | |
| 1207 } | |
| 1208 else | |
| 1209 { | |
| 1210 static const char *is_italic_table[] = { | |
| 1211 "normal", "italic", "oblique" | |
| 1212 }; | |
| 1213 state->is_italic = svg_parse_enum_from_style(ctx, doc, style_att, "font-style", | |
| 1214 nelem(is_italic_table), is_italic_table, state->is_italic) >= 1; | |
| 1215 } | |
| 1216 | |
| 1217 if (text_anchor_att) | |
| 1218 { | |
| 1219 state->text_anchor = 0; | |
| 1220 if (!strcmp(text_anchor_att, "middle")) state->text_anchor = 1; | |
| 1221 if (!strcmp(text_anchor_att, "end")) state->text_anchor = 2; | |
| 1222 } | |
| 1223 else | |
| 1224 { | |
| 1225 static const char *text_anchor_table[] = { | |
| 1226 "start", "middle", "end" | |
| 1227 }; | |
| 1228 state->text_anchor = svg_parse_enum_from_style(ctx, doc, style_att, "text-anchor", | |
| 1229 nelem(text_anchor_table), text_anchor_table, state->text_anchor); | |
| 1230 } | |
| 1231 } | |
| 1232 | |
| 1233 static void | |
| 1234 svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state) | |
| 1235 { | |
| 1236 svg_state local_state; | |
| 1237 fz_xml *node; | |
| 1238 | |
| 1239 char *w_att = fz_xml_att(root, "width"); | |
| 1240 char *h_att = fz_xml_att(root, "height"); | |
| 1241 char *viewbox_att = fz_xml_att(root, "viewBox"); | |
| 1242 | |
| 1243 fz_try(ctx) | |
| 1244 { | |
| 1245 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1246 | |
| 1247 /* get default viewport from viewBox if width and/or height is missing */ | |
| 1248 if (viewbox_att && (!w_att || !h_att)) | |
| 1249 { | |
| 1250 float x, y; | |
| 1251 svg_lex_viewbox(viewbox_att, &x, &y, &local_state.viewbox_w, &local_state.viewbox_h); | |
| 1252 if (!w_att) local_state.viewport_w = local_state.viewbox_w; | |
| 1253 if (!h_att) local_state.viewport_h = local_state.viewbox_h; | |
| 1254 } | |
| 1255 | |
| 1256 svg_parse_viewport(ctx, doc, root, &local_state); | |
| 1257 svg_parse_viewbox(ctx, doc, root, &local_state); | |
| 1258 svg_parse_common(ctx, doc, root, &local_state); | |
| 1259 | |
| 1260 for (node = fz_xml_down(root); node; node = fz_xml_next(node)) | |
| 1261 svg_run_element(ctx, dev, doc, node, &local_state); | |
| 1262 } | |
| 1263 fz_always(ctx) | |
| 1264 svg_end_state(ctx, &local_state); | |
| 1265 fz_catch(ctx) | |
| 1266 fz_rethrow(ctx); | |
| 1267 } | |
| 1268 | |
| 1269 static void | |
| 1270 svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state) | |
| 1271 { | |
| 1272 svg_state local_state; | |
| 1273 fz_xml *node; | |
| 1274 | |
| 1275 fz_try(ctx) | |
| 1276 { | |
| 1277 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1278 svg_parse_common(ctx, doc, root, &local_state); | |
| 1279 | |
| 1280 for (node = fz_xml_down(root); node; node = fz_xml_next(node)) | |
| 1281 svg_run_element(ctx, dev, doc, node, &local_state); | |
| 1282 } | |
| 1283 fz_always(ctx) | |
| 1284 svg_end_state(ctx, &local_state); | |
| 1285 fz_catch(ctx) | |
| 1286 fz_rethrow(ctx); | |
| 1287 } | |
| 1288 | |
| 1289 static void | |
| 1290 svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state) | |
| 1291 { | |
| 1292 svg_state local_state; | |
| 1293 fz_xml *node; | |
| 1294 | |
| 1295 fz_try(ctx) | |
| 1296 { | |
| 1297 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1298 | |
| 1299 svg_parse_viewport(ctx, doc, use, &local_state); | |
| 1300 svg_parse_viewbox(ctx, doc, use, &local_state); | |
| 1301 | |
| 1302 for (node = fz_xml_down(symbol); node; node = fz_xml_next(node)) | |
| 1303 svg_run_element(ctx, dev, doc, node, &local_state); | |
| 1304 } | |
| 1305 fz_always(ctx) | |
| 1306 svg_end_state(ctx, &local_state); | |
| 1307 fz_catch(ctx) | |
| 1308 fz_rethrow(ctx); | |
| 1309 } | |
| 1310 | |
| 1311 static int | |
| 1312 is_use_cycle(fz_xml *use, fz_xml *symbol) | |
| 1313 { | |
| 1314 /* If "use" is a direct child of "symbol", we have a recursive symbol/use definition! */ | |
| 1315 while (use) | |
| 1316 { | |
| 1317 if (use == symbol) | |
| 1318 return 1; | |
| 1319 use = fz_xml_up(use); | |
| 1320 } | |
| 1321 return 0; | |
| 1322 } | |
| 1323 | |
| 1324 static void | |
| 1325 svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state) | |
| 1326 { | |
| 1327 svg_state local_state; | |
| 1328 | |
| 1329 char *href_att = fz_xml_att_alt(root, "xlink:href", "href"); | |
| 1330 char *x_att = fz_xml_att(root, "x"); | |
| 1331 char *y_att = fz_xml_att(root, "y"); | |
| 1332 fz_xml *linked = NULL; | |
| 1333 | |
| 1334 float x = 0; | |
| 1335 float y = 0; | |
| 1336 | |
| 1337 fz_try(ctx) | |
| 1338 { | |
| 1339 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1340 | |
| 1341 if (++local_state.use_depth > MAX_USE_DEPTH) | |
| 1342 { | |
| 1343 fz_warn(ctx, "svg: too much recursion"); | |
| 1344 break; | |
| 1345 } | |
| 1346 | |
| 1347 svg_parse_common(ctx, doc, root, &local_state); | |
| 1348 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize); | |
| 1349 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize); | |
| 1350 | |
| 1351 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform); | |
| 1352 | |
| 1353 if (href_att && href_att[0] == '#') | |
| 1354 { | |
| 1355 linked = fz_tree_lookup(ctx, doc->idmap, href_att + 1); | |
| 1356 if (linked) | |
| 1357 { | |
| 1358 if (is_use_cycle(root, linked)) | |
| 1359 fz_warn(ctx, "svg: cyclic <use> reference"); | |
| 1360 | |
| 1361 if (fz_xml_is_tag(linked, "symbol")) | |
| 1362 svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state); | |
| 1363 else | |
| 1364 svg_run_element(ctx, dev, doc, linked, &local_state); | |
| 1365 } | |
| 1366 else | |
| 1367 { | |
| 1368 fz_warn(ctx, "svg: cannot find linked symbol"); | |
| 1369 } | |
| 1370 } | |
| 1371 else | |
| 1372 { | |
| 1373 fz_warn(ctx, "svg: cannot find linked symbol"); | |
| 1374 } | |
| 1375 | |
| 1376 } | |
| 1377 fz_always(ctx) | |
| 1378 svg_end_state(ctx, &local_state); | |
| 1379 fz_catch(ctx) | |
| 1380 fz_rethrow(ctx); | |
| 1381 } | |
| 1382 | |
| 1383 static void | |
| 1384 svg_run_image(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state) | |
| 1385 { | |
| 1386 svg_state local_state; | |
| 1387 float x=0, y=0, w=0, h=0; | |
| 1388 const char *data; | |
| 1389 | |
| 1390 static const char *jpeg_uri = "data:image/jpeg;base64,"; | |
| 1391 static const char *png_uri = "data:image/png;base64,"; | |
| 1392 | |
| 1393 char *href_att = fz_xml_att_alt(root, "xlink:href", "href"); | |
| 1394 char *x_att = fz_xml_att(root, "x"); | |
| 1395 char *y_att = fz_xml_att(root, "y"); | |
| 1396 char *w_att = fz_xml_att(root, "width"); | |
| 1397 char *h_att = fz_xml_att(root, "height"); | |
| 1398 | |
| 1399 fz_try(ctx) | |
| 1400 { | |
| 1401 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1402 | |
| 1403 svg_parse_common(ctx, doc, root, &local_state); | |
| 1404 if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize); | |
| 1405 if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize); | |
| 1406 if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize); | |
| 1407 if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize); | |
| 1408 | |
| 1409 if (w <= 0 || h <= 0) | |
| 1410 break; // out of try-catch | |
| 1411 | |
| 1412 if (!href_att) | |
| 1413 break; // out of try-catch | |
| 1414 | |
| 1415 local_state.transform = fz_concat(fz_translate(x, y), local_state.transform); | |
| 1416 local_state.transform = fz_concat(fz_scale(w, h), local_state.transform); | |
| 1417 | |
| 1418 if (!strncmp(href_att, jpeg_uri, strlen(jpeg_uri))) | |
| 1419 data = href_att + strlen(jpeg_uri); | |
| 1420 else if (!strncmp(href_att, png_uri, strlen(png_uri))) | |
| 1421 data = href_att + strlen(png_uri); | |
| 1422 else | |
| 1423 data = NULL; | |
| 1424 if (data) | |
| 1425 { | |
| 1426 fz_image *img = NULL; | |
| 1427 fz_buffer *buf; | |
| 1428 | |
| 1429 fz_var(img); | |
| 1430 | |
| 1431 buf = fz_new_buffer_from_base64(ctx, data, 0); | |
| 1432 fz_try(ctx) | |
| 1433 { | |
| 1434 fz_matrix orient; | |
| 1435 img = fz_new_image_from_buffer(ctx, buf); | |
| 1436 orient = fz_image_orientation_matrix(ctx, img); | |
| 1437 local_state.transform = fz_concat(orient, local_state.transform); | |
| 1438 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params); | |
| 1439 } | |
| 1440 fz_always(ctx) | |
| 1441 { | |
| 1442 fz_drop_buffer(ctx, buf); | |
| 1443 fz_drop_image(ctx, img); | |
| 1444 } | |
| 1445 fz_catch(ctx) | |
| 1446 { | |
| 1447 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); | |
| 1448 fz_report_error(ctx); | |
| 1449 fz_warn(ctx, "svg: ignoring embedded image '%s'", href_att); | |
| 1450 } | |
| 1451 } | |
| 1452 else if (doc->zip) | |
| 1453 { | |
| 1454 char path[2048]; | |
| 1455 fz_buffer *buf = NULL; | |
| 1456 fz_image *img = NULL; | |
| 1457 | |
| 1458 fz_var(buf); | |
| 1459 fz_var(img); | |
| 1460 | |
| 1461 fz_strlcpy(path, doc->base_uri, sizeof path); | |
| 1462 fz_strlcat(path, "/", sizeof path); | |
| 1463 fz_strlcat(path, href_att, sizeof path); | |
| 1464 fz_urldecode(path); | |
| 1465 | |
| 1466 fz_try(ctx) | |
| 1467 { | |
| 1468 fz_matrix orient; | |
| 1469 buf = fz_read_archive_entry(ctx, doc->zip, path); | |
| 1470 img = fz_new_image_from_buffer(ctx, buf); | |
| 1471 orient = fz_image_orientation_matrix(ctx, img); | |
| 1472 local_state.transform = fz_concat(orient, local_state.transform); | |
| 1473 fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params); | |
| 1474 } | |
| 1475 fz_always(ctx) | |
| 1476 { | |
| 1477 fz_drop_buffer(ctx, buf); | |
| 1478 fz_drop_image(ctx, img); | |
| 1479 } | |
| 1480 fz_catch(ctx) | |
| 1481 { | |
| 1482 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); | |
| 1483 fz_report_error(ctx); | |
| 1484 fz_warn(ctx, "svg: ignoring external image '%s'", href_att); | |
| 1485 } | |
| 1486 } | |
| 1487 else | |
| 1488 { | |
| 1489 fz_warn(ctx, "svg: ignoring external image '%s'", href_att); | |
| 1490 } | |
| 1491 | |
| 1492 } | |
| 1493 fz_always(ctx) | |
| 1494 svg_end_state(ctx, &local_state); | |
| 1495 fz_catch(ctx) | |
| 1496 fz_rethrow(ctx); | |
| 1497 } | |
| 1498 | |
| 1499 static fz_font * | |
| 1500 svg_load_font(fz_context *ctx, const svg_state *state) | |
| 1501 { | |
| 1502 int bold = state->is_bold; | |
| 1503 int italic = state->is_italic; | |
| 1504 int mono = 0; | |
| 1505 int serif = 1; | |
| 1506 | |
| 1507 /* scan font-family property for common fallback names */ | |
| 1508 | |
| 1509 if (!mono && strstr(state->font_family, "monospace")) mono = 1; | |
| 1510 if (!mono && strstr(state->font_family, "Courier")) mono = 1; | |
| 1511 | |
| 1512 if (serif && strstr(state->font_family, "sans-serif")) serif = 0; | |
| 1513 if (serif && strstr(state->font_family, "Arial")) serif = 0; | |
| 1514 if (serif && strstr(state->font_family, "Helvetica")) serif = 0; | |
| 1515 | |
| 1516 if (mono) { | |
| 1517 if (bold) { | |
| 1518 if (italic) return fz_new_base14_font(ctx, "Courier-BoldOblique"); | |
| 1519 else return fz_new_base14_font(ctx, "Courier-Bold"); | |
| 1520 } else { | |
| 1521 if (italic) return fz_new_base14_font(ctx, "Courier-Oblique"); | |
| 1522 else return fz_new_base14_font(ctx, "Courier"); | |
| 1523 } | |
| 1524 } else if (serif) { | |
| 1525 if (bold) { | |
| 1526 if (italic) return fz_new_base14_font(ctx, "Times-BoldItalic"); | |
| 1527 else return fz_new_base14_font(ctx, "Times-Bold"); | |
| 1528 } else { | |
| 1529 if (italic) return fz_new_base14_font(ctx, "Times-Italic"); | |
| 1530 else return fz_new_base14_font(ctx, "Times-Roman"); | |
| 1531 } | |
| 1532 } else { | |
| 1533 if (bold) { | |
| 1534 if (italic) return fz_new_base14_font(ctx, "Helvetica-BoldOblique"); | |
| 1535 else return fz_new_base14_font(ctx, "Helvetica-Bold"); | |
| 1536 } else { | |
| 1537 if (italic) return fz_new_base14_font(ctx, "Helvetica-Oblique"); | |
| 1538 else return fz_new_base14_font(ctx, "Helvetica"); | |
| 1539 } | |
| 1540 } | |
| 1541 } | |
| 1542 | |
| 1543 static fz_matrix | |
| 1544 svg_run_text_string(fz_context *ctx, fz_device *dev, fz_matrix trm, const char *s, const svg_state *state) | |
| 1545 { | |
| 1546 fz_font *font = NULL; | |
| 1547 fz_text *text = NULL; | |
| 1548 | |
| 1549 fz_var(font); | |
| 1550 fz_var(text); | |
| 1551 | |
| 1552 fz_try(ctx) | |
| 1553 { | |
| 1554 font = svg_load_font(ctx, state); | |
| 1555 text = fz_new_text(ctx); | |
| 1556 | |
| 1557 if (state->text_anchor > 0) | |
| 1558 { | |
| 1559 fz_matrix adv = fz_measure_string(ctx, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET); | |
| 1560 if (state->text_anchor == 1) | |
| 1561 trm.e -= (adv.e - trm.e) / 2; | |
| 1562 else if (state->text_anchor == 2) | |
| 1563 trm.e -= (adv.e - trm.e); | |
| 1564 } | |
| 1565 | |
| 1566 trm = fz_show_string(ctx, text, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET); | |
| 1567 | |
| 1568 if (state->fill_is_set) | |
| 1569 fz_fill_text(ctx, dev, text, | |
| 1570 state->transform, | |
| 1571 fz_device_rgb(ctx), state->fill_color, | |
| 1572 state->opacity, | |
| 1573 fz_default_color_params); | |
| 1574 if (state->stroke_is_set) | |
| 1575 fz_stroke_text(ctx, dev, text, | |
| 1576 state->stroke, | |
| 1577 state->transform, | |
| 1578 fz_device_rgb(ctx), state->stroke_color, | |
| 1579 state->opacity, | |
| 1580 fz_default_color_params); | |
| 1581 if (!state->fill_is_set && !state->stroke_is_set) | |
| 1582 fz_ignore_text(ctx, dev, text, state->transform); | |
| 1583 } | |
| 1584 fz_always(ctx) | |
| 1585 { | |
| 1586 fz_drop_text(ctx, text); | |
| 1587 fz_drop_font(ctx, font); | |
| 1588 } | |
| 1589 fz_catch(ctx) | |
| 1590 { | |
| 1591 fz_rethrow(ctx); | |
| 1592 } | |
| 1593 | |
| 1594 return trm; | |
| 1595 } | |
| 1596 | |
| 1597 static void | |
| 1598 svg_collapse_whitespace(char *start, int is_first, int is_last) | |
| 1599 { | |
| 1600 int c, last_c = (is_first ? ' ' : 0); | |
| 1601 char *s, *p; | |
| 1602 s = p = start; | |
| 1603 while ((c = *s++) != 0) | |
| 1604 { | |
| 1605 if (c == '\n' || c == '\r') | |
| 1606 continue; | |
| 1607 if (c == '\t') | |
| 1608 c = ' '; | |
| 1609 if (c == ' ' && last_c == ' ') | |
| 1610 continue; | |
| 1611 *p++ = last_c = c; | |
| 1612 } | |
| 1613 if (is_last && p > start && p[-1] == ' ') | |
| 1614 --p; | |
| 1615 *p = 0; | |
| 1616 } | |
| 1617 | |
| 1618 static fz_matrix | |
| 1619 svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state, | |
| 1620 float x, float y, int is_first, int is_last) | |
| 1621 { | |
| 1622 svg_state local_state; | |
| 1623 char font_family[100]; | |
| 1624 fz_xml *node; | |
| 1625 fz_matrix trm; | |
| 1626 int cif, cil; | |
| 1627 char *text; | |
| 1628 | |
| 1629 char *x_att = fz_xml_att(root, "x"); | |
| 1630 char *y_att = fz_xml_att(root, "y"); | |
| 1631 char *dx_att = fz_xml_att(root, "dx"); | |
| 1632 char *dy_att = fz_xml_att(root, "dy"); | |
| 1633 | |
| 1634 fz_try(ctx) | |
| 1635 { | |
| 1636 svg_begin_state(ctx, &local_state, inherit_state); | |
| 1637 | |
| 1638 svg_parse_common(ctx, doc, root, &local_state); | |
| 1639 svg_parse_font_attributes(ctx, doc, root, &local_state, font_family, sizeof font_family); | |
| 1640 | |
| 1641 trm = fz_scale(local_state.fontsize, -local_state.fontsize); | |
| 1642 trm.e = x; | |
| 1643 trm.f = y; | |
| 1644 | |
| 1645 if (x_att) trm.e = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize); | |
| 1646 if (y_att) trm.f = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize); | |
| 1647 | |
| 1648 if (dx_att) trm.e += svg_parse_length(dx_att, local_state.viewbox_w, local_state.fontsize); | |
| 1649 if (dy_att) trm.f += svg_parse_length(dy_att, local_state.viewbox_h, local_state.fontsize); | |
| 1650 | |
| 1651 cif = is_first; | |
| 1652 for (node = fz_xml_down(root); node; node = fz_xml_next(node)) | |
| 1653 { | |
| 1654 cil = is_last && !fz_xml_next(node); | |
| 1655 text = fz_xml_text(node); | |
| 1656 if (text) | |
| 1657 { | |
| 1658 svg_collapse_whitespace(text, cif, cil); | |
| 1659 trm = svg_run_text_string(ctx, dev, trm, text, &local_state); | |
| 1660 } | |
| 1661 else if (fz_xml_is_tag(node, "tspan")) | |
| 1662 trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil); | |
| 1663 else if (fz_xml_is_tag(node, "textPath")) | |
| 1664 trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil); | |
| 1665 cif = 0; | |
| 1666 } | |
| 1667 } | |
| 1668 fz_always(ctx) | |
| 1669 svg_end_state(ctx, &local_state); | |
| 1670 fz_catch(ctx) | |
| 1671 fz_rethrow(ctx); | |
| 1672 | |
| 1673 return trm; | |
| 1674 } | |
| 1675 | |
| 1676 static void | |
| 1677 svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state) | |
| 1678 { | |
| 1679 if (fz_xml_is_tag(root, "svg")) | |
| 1680 svg_run_svg(ctx, dev, doc, root, state); | |
| 1681 | |
| 1682 else if (fz_xml_is_tag(root, "g")) | |
| 1683 svg_run_g(ctx, dev, doc, root, state); | |
| 1684 | |
| 1685 else if (fz_xml_is_tag(root, "title")) | |
| 1686 ; | |
| 1687 else if (fz_xml_is_tag(root, "desc")) | |
| 1688 ; | |
| 1689 | |
| 1690 else if (fz_xml_is_tag(root, "defs")) | |
| 1691 ; | |
| 1692 else if (fz_xml_is_tag(root, "symbol")) | |
| 1693 ; | |
| 1694 | |
| 1695 else if (fz_xml_is_tag(root, "use")) | |
| 1696 svg_run_use(ctx, dev, doc, root, state); | |
| 1697 | |
| 1698 else if (fz_xml_is_tag(root, "path")) | |
| 1699 svg_run_path(ctx, dev, doc, root, state); | |
| 1700 else if (fz_xml_is_tag(root, "rect")) | |
| 1701 svg_run_rect(ctx, dev, doc, root, state); | |
| 1702 else if (fz_xml_is_tag(root, "circle")) | |
| 1703 svg_run_circle(ctx, dev, doc, root, state); | |
| 1704 else if (fz_xml_is_tag(root, "ellipse")) | |
| 1705 svg_run_ellipse(ctx, dev, doc, root, state); | |
| 1706 else if (fz_xml_is_tag(root, "line")) | |
| 1707 svg_run_line(ctx, dev, doc, root, state); | |
| 1708 else if (fz_xml_is_tag(root, "polyline")) | |
| 1709 svg_run_polyline(ctx, dev, doc, root, state); | |
| 1710 else if (fz_xml_is_tag(root, "polygon")) | |
| 1711 svg_run_polygon(ctx, dev, doc, root, state); | |
| 1712 | |
| 1713 else if (fz_xml_is_tag(root, "image")) | |
| 1714 svg_run_image(ctx, dev, doc, root, state); | |
| 1715 | |
| 1716 else if (fz_xml_is_tag(root, "text")) | |
| 1717 svg_run_text(ctx, dev, doc, root, state, 0, 0, 1, 1); | |
| 1718 | |
| 1719 else | |
| 1720 { | |
| 1721 /* ignore unrecognized tags */ | |
| 1722 } | |
| 1723 } | |
| 1724 | |
| 1725 void | |
| 1726 svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root) | |
| 1727 { | |
| 1728 char *version_att; | |
| 1729 char *w_att; | |
| 1730 char *h_att; | |
| 1731 char *viewbox_att; | |
| 1732 int version; | |
| 1733 | |
| 1734 if (!fz_xml_is_tag(root, "svg")) | |
| 1735 fz_throw(ctx, FZ_ERROR_SYNTAX, "expected svg element (found %s)", fz_xml_tag(root)); | |
| 1736 | |
| 1737 version_att = fz_xml_att(root, "version"); | |
| 1738 w_att = fz_xml_att(root, "width"); | |
| 1739 h_att = fz_xml_att(root, "height"); | |
| 1740 viewbox_att = fz_xml_att(root, "viewBox"); | |
| 1741 | |
| 1742 version = 10; | |
| 1743 if (version_att) | |
| 1744 version = fz_atof(version_att) * 10; | |
| 1745 | |
| 1746 if (version > 12) | |
| 1747 fz_warn(ctx, "svg document version is newer than we support"); | |
| 1748 | |
| 1749 /* If no width or height attributes, then guess from the viewbox */ | |
| 1750 if (w_att == NULL && h_att == NULL && viewbox_att != NULL) | |
| 1751 { | |
| 1752 float min_x, min_y, box_w, box_h; | |
| 1753 svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h); | |
| 1754 doc->width = box_w; | |
| 1755 doc->height = box_h; | |
| 1756 } | |
| 1757 else | |
| 1758 { | |
| 1759 doc->width = DEF_WIDTH; | |
| 1760 if (w_att) | |
| 1761 doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE); | |
| 1762 | |
| 1763 doc->height = DEF_HEIGHT; | |
| 1764 if (h_att) | |
| 1765 doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE); | |
| 1766 } | |
| 1767 } | |
| 1768 | |
| 1769 void | |
| 1770 svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, fz_matrix ctm) | |
| 1771 { | |
| 1772 svg_state state; | |
| 1773 | |
| 1774 svg_parse_document_bounds(ctx, doc, root); | |
| 1775 | |
| 1776 /* Initial graphics state */ | |
| 1777 state.transform = ctm; | |
| 1778 state.stroke = fz_new_stroke_state(ctx); | |
| 1779 state.use_depth = 0; | |
| 1780 | |
| 1781 state.viewport_w = DEF_WIDTH; | |
| 1782 state.viewport_h = DEF_HEIGHT; | |
| 1783 | |
| 1784 state.viewbox_w = DEF_WIDTH; | |
| 1785 state.viewbox_h = DEF_HEIGHT; | |
| 1786 state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2); | |
| 1787 | |
| 1788 state.fontsize = 12; | |
| 1789 | |
| 1790 state.opacity = 1; | |
| 1791 | |
| 1792 state.fill_rule = 0; | |
| 1793 | |
| 1794 state.fill_is_set = 1; | |
| 1795 state.fill_color[0] = 0; | |
| 1796 state.fill_color[1] = 0; | |
| 1797 state.fill_color[2] = 0; | |
| 1798 state.fill_opacity = 1; | |
| 1799 | |
| 1800 state.stroke_is_set = 0; | |
| 1801 state.stroke_color[0] = 0; | |
| 1802 state.stroke_color[1] = 0; | |
| 1803 state.stroke_color[2] = 0; | |
| 1804 state.stroke_opacity = 1; | |
| 1805 | |
| 1806 state.font_family = "serif"; | |
| 1807 state.is_bold = 0; | |
| 1808 state.is_italic = 0; | |
| 1809 state.text_anchor = 0; | |
| 1810 | |
| 1811 fz_try(ctx) | |
| 1812 { | |
| 1813 svg_run_svg(ctx, dev, doc, root, &state); | |
| 1814 } | |
| 1815 fz_always(ctx) | |
| 1816 { | |
| 1817 fz_drop_stroke_state(ctx, state.stroke); | |
| 1818 } | |
| 1819 fz_catch(ctx) | |
| 1820 { | |
| 1821 fz_rethrow(ctx); | |
| 1822 } | |
| 1823 } |
