eBookReaderSwitch/mupdf/source/svg/svg-run.c

1397 lines
34 KiB
C
Raw Normal View History

#include "mupdf/fitz.h"
#include "svg-imp.h"
#include <string.h>
#include <math.h>
/* default page size */
#define DEF_WIDTH 612
#define DEF_HEIGHT 792
#define DEF_FONTSIZE 12
#define MAX_USE_DEPTH 100
typedef struct svg_state_s svg_state;
struct svg_state_s
{
fz_matrix transform;
fz_stroke_state stroke;
int use_depth;
float viewport_w, viewport_h;
float viewbox_w, viewbox_h, viewbox_size;
float fontsize;
float opacity;
int fill_rule;
int fill_is_set;
float fill_color[3];
float fill_opacity;
int stroke_is_set;
float stroke_color[3];
float stroke_opacity;
};
static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state);
static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state);
static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
{
float opacity = state->opacity * state->fill_opacity;
if (path)
fz_fill_path(ctx, dev, path, state->fill_rule, state->transform, fz_device_rgb(ctx), state->fill_color, opacity, fz_default_color_params);
}
static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
{
float opacity = state->opacity * state->stroke_opacity;
if (path)
fz_stroke_path(ctx, dev, path, &state->stroke, state->transform, fz_device_rgb(ctx), state->stroke_color, opacity, fz_default_color_params);
}
static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
{
if (state->fill_is_set)
svg_fill(ctx, dev, doc, path, state);
if (state->stroke_is_set)
svg_stroke(ctx, dev, doc, path, state);
}
/*
We use the MAGIC number 0.551915 as a bezier subdivision to approximate
a quarter circle arc. The reasons for this can be found here:
http://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf
*/
static const float MAGIC_CIRCLE = 0.551915f;
static void approx_circle(fz_context *ctx, fz_path *path, float cx, float cy, float rx, float ry)
{
float mx = rx * MAGIC_CIRCLE;
float my = ry * MAGIC_CIRCLE;
fz_moveto(ctx, path, cx, cy+ry);
fz_curveto(ctx, path, cx + mx, cy + ry, cx + rx, cy + my, cx + rx, cy);
fz_curveto(ctx, path, cx + rx, cy - my, cx + mx, cy - ry, cx, cy - ry);
fz_curveto(ctx, path, cx - mx, cy - ry, cx - rx, cy - my, cx - rx, cy);
fz_curveto(ctx, path, cx - rx, cy + my, cx - mx, cy + ry, cx, cy + ry);
fz_closepath(ctx, path);
}
static void
svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
char *x_att = fz_xml_att(node, "x");
char *y_att = fz_xml_att(node, "y");
char *w_att = fz_xml_att(node, "width");
char *h_att = fz_xml_att(node, "height");
char *rx_att = fz_xml_att(node, "rx");
char *ry_att = fz_xml_att(node, "ry");
float x = 0;
float y = 0;
float w = 0;
float h = 0;
float rx = 0;
float ry = 0;
fz_path *path;
svg_parse_common(ctx, doc, node, &local_state);
if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
if (rx_att && !ry_att)
ry = rx;
if (ry_att && !rx_att)
rx = ry;
if (rx > w * 0.5f)
rx = w * 0.5f;
if (ry > h * 0.5f)
ry = h * 0.5f;
if (w <= 0 || h <= 0)
return;
path = fz_new_path(ctx);
fz_try(ctx)
{
if (rx == 0 || ry == 0)
{
fz_moveto(ctx, path, x, y);
fz_lineto(ctx, path, x + w, y);
fz_lineto(ctx, path, x + w, y + h);
fz_lineto(ctx, path, x, y + h);
}
else
{
float rxs = rx * MAGIC_CIRCLE;
float rys = rx * MAGIC_CIRCLE;
fz_moveto(ctx, path, x + w - rx, y);
fz_curveto(ctx, path, x + w - rxs, y, x + w, y + rys, x + w, y + ry);
fz_lineto(ctx, path, x + w, y + h - ry);
fz_curveto(ctx, path, x + w, y + h - rys, x + w - rxs, y + h, x + w - rx, y + h);
fz_lineto(ctx, path, x + rx, y + h);
fz_curveto(ctx, path, x + rxs, y + h, x, y + h - rys, x, y + h - rx);
fz_lineto(ctx, path, x, y + rx);
fz_curveto(ctx, path, x, y + rxs, x + rxs, y, x + rx, y);
}
fz_closepath(ctx, path);
svg_draw_path(ctx, dev, doc, path, &local_state);
}
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
static void
svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
char *cx_att = fz_xml_att(node, "cx");
char *cy_att = fz_xml_att(node, "cy");
char *r_att = fz_xml_att(node, "r");
float cx = 0;
float cy = 0;
float r = 0;
fz_path *path;
svg_parse_common(ctx, doc, node, &local_state);
if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12);
if (r <= 0)
return;
path = fz_new_path(ctx);
fz_try(ctx)
{
approx_circle(ctx, path, cx, cy, r, r);
svg_draw_path(ctx, dev, doc, path, &local_state);
}
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
static void
svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
char *cx_att = fz_xml_att(node, "cx");
char *cy_att = fz_xml_att(node, "cy");
char *rx_att = fz_xml_att(node, "rx");
char *ry_att = fz_xml_att(node, "ry");
float cx = 0;
float cy = 0;
float rx = 0;
float ry = 0;
fz_path *path;
svg_parse_common(ctx, doc, node, &local_state);
if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
if (rx <= 0 || ry <= 0)
return;
path = fz_new_path(ctx);
fz_try(ctx)
{
approx_circle(ctx, path, cx, cy, rx, ry);
svg_draw_path(ctx, dev, doc, path, &local_state);
}
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
static void
svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
char *x1_att = fz_xml_att(node, "x1");
char *y1_att = fz_xml_att(node, "y1");
char *x2_att = fz_xml_att(node, "x2");
char *y2_att = fz_xml_att(node, "y2");
float x1 = 0;
float y1 = 0;
float x2 = 0;
float y2 = 0;
svg_parse_common(ctx, doc, node, &local_state);
if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize);
if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize);
if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize);
if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize);
if (local_state.stroke_is_set)
{
fz_path *path = fz_new_path(ctx);
fz_try(ctx)
{
fz_moveto(ctx, path, x1, y1);
fz_lineto(ctx, path, x2, y2);
svg_stroke(ctx, dev, doc, path, &local_state);
}
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
}
static fz_path *
svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose)
{
fz_path *path;
const char *str = fz_xml_att(node, "points");
float number;
float args[2];
int nargs;
int isfirst;
if (!str)
return NULL;
isfirst = 1;
nargs = 0;
path = fz_new_path(ctx);
fz_try(ctx)
{
while (*str)
{
while (svg_is_whitespace_or_comma(*str))
str ++;
if (svg_is_digit(*str))
{
str = svg_lex_number(&number, str);
args[nargs++] = number;
}
if (nargs == 2)
{
if (isfirst)
{
fz_moveto(ctx, path, args[0], args[1]);
isfirst = 0;
}
else
{
fz_lineto(ctx, path, args[0], args[1]);
}
nargs = 0;
}
}
}
fz_catch(ctx)
{
fz_drop_path(ctx, path);
fz_rethrow(ctx);
}
return path;
}
static void
svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
svg_parse_common(ctx, doc, node, &local_state);
if (local_state.stroke_is_set)
{
fz_path *path = svg_parse_polygon_imp(ctx, doc, node, 0);
fz_try(ctx)
svg_stroke(ctx, dev, doc, path, &local_state);
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
}
static void
svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
fz_path *path;
svg_parse_common(ctx, doc, node, &local_state);
path = svg_parse_polygon_imp(ctx, doc, node, 1);
fz_try(ctx)
svg_draw_path(ctx, dev, doc, path, &local_state);
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
static void
svg_add_arc_segment(fz_context *ctx, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
{
float t, d;
fz_point p;
while (th1 < th0)
th1 += FZ_PI * 2;
d = FZ_PI / 180; /* 1-degree precision */
if (iscw)
{
for (t = th0 + d; t < th1 - d/2; t += d)
{
p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
fz_lineto(ctx, path, p.x, p.y);
}
}
else
{
th0 += FZ_PI * 2;
for (t = th0 - d; t > th1 + d/2; t -= d)
{
p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
fz_lineto(ctx, path, p.x, p.y);
}
}
}
static float
angle_between(const fz_point u, const fz_point v)
{
float det = u.x * v.y - u.y * v.x;
float sign = (det < 0 ? -1 : 1);
float magu = u.x * u.x + u.y * u.y;
float magv = v.x * v.x + v.y * v.y;
float udotv = u.x * v.x + u.y * v.y;
float t = udotv / (magu * magv);
/* guard against rounding errors when near |1| (where acos will return NaN) */
if (t < -1) t = -1;
if (t > 1) t = 1;
return sign * acosf(t);
}
static void
svg_add_arc(fz_context *ctx, fz_path *path,
float size_x, float size_y, float rotation_angle,
int is_large_arc, int is_clockwise,
float point_x, float point_y)
{
fz_matrix rotmat, revmat;
fz_matrix mtx;
fz_point pt;
float rx, ry;
float x1, y1, x2, y2;
float x1t, y1t;
float cxt, cyt, cx, cy;
float t1, t2, t3;
float sign;
float th1, dth;
pt = fz_currentpoint(ctx, path);
x1 = pt.x;
y1 = pt.y;
x2 = point_x;
y2 = point_y;
rx = size_x;
ry = size_y;
if (is_clockwise != is_large_arc)
sign = 1;
else
sign = -1;
rotmat = fz_rotate(rotation_angle);
revmat = fz_rotate(-rotation_angle);
/* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
/* Conversion from endpoint to center parameterization */
/* F.6.6.1 -- ensure radii are positive and non-zero */
rx = fabsf(rx);
ry = fabsf(ry);
if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
{
fz_lineto(ctx, path, x2, y2);
return;
}
/* F.6.5.1 */
pt.x = (x1 - x2) / 2;
pt.y = (y1 - y2) / 2;
pt = fz_transform_vector(pt, revmat);
x1t = pt.x;
y1t = pt.y;
/* F.6.6.2 -- ensure radii are large enough */
t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
if (t1 > 1)
{
rx = rx * sqrtf(t1);
ry = ry * sqrtf(t1);
}
/* F.6.5.2 */
t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
t3 = t1 / t2;
/* guard against rounding errors; sqrt of negative numbers is bad for your health */
if (t3 < 0) t3 = 0;
t3 = sqrtf(t3);
cxt = sign * t3 * (rx * y1t) / ry;
cyt = sign * t3 * -(ry * x1t) / rx;
/* F.6.5.3 */
pt.x = cxt;
pt.y = cyt;
pt = fz_transform_vector(pt, rotmat);
cx = pt.x + (x1 + x2) / 2;
cy = pt.y + (y1 + y2) / 2;
/* F.6.5.4 */
{
fz_point coord1, coord2, coord3, coord4;
coord1.x = 1;
coord1.y = 0;
coord2.x = (x1t - cxt) / rx;
coord2.y = (y1t - cyt) / ry;
coord3.x = (x1t - cxt) / rx;
coord3.y = (y1t - cyt) / ry;
coord4.x = (-x1t - cxt) / rx;
coord4.y = (-y1t - cyt) / ry;
th1 = angle_between(coord1, coord2);
dth = angle_between(coord3, coord4);
if (dth < 0 && !is_clockwise)
dth += ((FZ_PI / 180) * 360);
if (dth > 0 && is_clockwise)
dth -= ((FZ_PI / 180) * 360);
}
mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
svg_add_arc_segment(ctx, path, mtx, th1, th1 + dth, is_clockwise);
fz_lineto(ctx, path, point_x, point_y);
}
static fz_path *
svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
{
fz_path *path;
fz_point p;
float x1, y1, x2, y2;
int cmd;
float number;
float args[7];
int nargs;
/* saved control point for smooth curves */
int reset_smooth = 1;
float smooth_x = 0.0f;
float smooth_y = 0.0f;
cmd = 0;
nargs = 0;
path = fz_new_path(ctx);
fz_try(ctx)
{
fz_moveto(ctx, path, 0.0f, 0.0f); /* for the case of opening 'm' */
while (*str)
{
while (svg_is_whitespace_or_comma(*str))
str ++;
if (svg_is_digit(*str))
{
str = svg_lex_number(&number, str);
if (nargs == nelem(args))
fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow in path data");
args[nargs++] = number;
}
else if (svg_is_alpha(*str))
{
if (nargs != 0)
fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data (wrong number of parameters to '%c')", cmd);
cmd = *str++;
}
else if (*str == 0)
{
break;
}
else
{
fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data: '%c'", *str);
}
if (reset_smooth)
{
smooth_x = 0.0f;
smooth_y = 0.0f;
}
reset_smooth = 1;
switch (cmd)
{
case 'M':
if (nargs == 2)
{
fz_moveto(ctx, path, args[0], args[1]);
nargs = 0;
cmd = 'L'; /* implicit lineto after */
}
break;
case 'm':
if (nargs == 2)
{
p = fz_currentpoint(ctx, path);
fz_moveto(ctx, path, p.x + args[0], p.y + args[1]);
nargs = 0;
cmd = 'l'; /* implicit lineto after */
}
break;
case 'Z':
case 'z':
if (nargs == 0)
{
fz_closepath(ctx, path);
}
break;
case 'L':
if (nargs == 2)
{
fz_lineto(ctx, path, args[0], args[1]);
nargs = 0;
}
break;
case 'l':
if (nargs == 2)
{
p = fz_currentpoint(ctx, path);
fz_lineto(ctx, path, p.x + args[0], p.y + args[1]);
nargs = 0;
}
break;
case 'H':
if (nargs == 1)
{
p = fz_currentpoint(ctx, path);
fz_lineto(ctx, path, args[0], p.y);
nargs = 0;
}
break;
case 'h':
if (nargs == 1)
{
p = fz_currentpoint(ctx, path);
fz_lineto(ctx, path, p.x + args[0], p.y);
nargs = 0;
}
break;
case 'V':
if (nargs == 1)
{
p = fz_currentpoint(ctx, path);
fz_lineto(ctx, path, p.x, args[0]);
nargs = 0;
}
break;
case 'v':
if (nargs == 1)
{
p = fz_currentpoint(ctx, path);
fz_lineto(ctx, path, p.x, p.y + args[0]);
nargs = 0;
}
break;
case 'C':
reset_smooth = 0;
if (nargs == 6)
{
fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]);
smooth_x = args[4] - args[2];
smooth_y = args[5] - args[3];
nargs = 0;
}
break;
case 'c':
reset_smooth = 0;
if (nargs == 6)
{
p = fz_currentpoint(ctx, path);
fz_curveto(ctx, path,
p.x + args[0], p.y + args[1],
p.x + args[2], p.y + args[3],
p.x + args[4], p.y + args[5]);
smooth_x = args[4] - args[2];
smooth_y = args[5] - args[3];
nargs = 0;
}
break;
case 'S':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
fz_curveto(ctx, path,
p.x + smooth_x, p.y + smooth_y,
args[0], args[1],
args[2], args[3]);
smooth_x = args[2] - args[0];
smooth_y = args[3] - args[1];
nargs = 0;
}
break;
case 's':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
fz_curveto(ctx, path,
p.x + smooth_x, p.y + smooth_y,
p.x + args[0], p.y + args[1],
p.x + args[2], p.y + args[3]);
smooth_x = args[2] - args[0];
smooth_y = args[3] - args[1];
nargs = 0;
}
break;
case 'Q':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
x1 = args[0];
y1 = args[1];
x2 = args[2];
y2 = args[3];
fz_curveto(ctx, path,
(p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
(x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
x2, y2);
smooth_x = x2 - x1;
smooth_y = y2 - y1;
nargs = 0;
}
break;
case 'q':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
x1 = args[0] + p.x;
y1 = args[1] + p.y;
x2 = args[2] + p.x;
y2 = args[3] + p.y;
fz_curveto(ctx, path,
(p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
(x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
x2, y2);
smooth_x = x2 - x1;
smooth_y = y2 - y1;
nargs = 0;
}
break;
case 'T':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
x1 = p.x + smooth_x;
y1 = p.y + smooth_y;
x2 = args[0];
y2 = args[1];
fz_curveto(ctx, path,
(p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
(x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
x2, y2);
smooth_x = x2 - x1;
smooth_y = y2 - y1;
nargs = 0;
}
break;
case 't':
reset_smooth = 0;
if (nargs == 4)
{
p = fz_currentpoint(ctx, path);
x1 = p.x + smooth_x;
y1 = p.y + smooth_y;
x2 = args[0] + p.x;
y2 = args[1] + p.y;
fz_curveto(ctx, path,
(p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
(x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
x2, y2);
smooth_x = x2 - x1;
smooth_y = y2 - y1;
nargs = 0;
}
break;
case 'A':
if (nargs == 7)
{
svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
nargs = 0;
}
break;
case 'a':
if (nargs == 7)
{
p = fz_currentpoint(ctx, path);
svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y);
nargs = 0;
}
break;
case 0:
if (nargs != 0)
fz_throw(ctx, FZ_ERROR_GENERIC, "path data must begin with a command");
break;
default:
fz_throw(ctx, FZ_ERROR_GENERIC, "unrecognized command in path data: '%c'", cmd);
}
}
}
fz_catch(ctx)
{
fz_drop_path(ctx, path);
fz_rethrow(ctx);
}
return path;
}
static void
svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
const char *d_att = fz_xml_att(node, "d");
/* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */
svg_parse_common(ctx, doc, node, &local_state);
if (d_att)
{
fz_path *path = svg_parse_path_data(ctx, doc, d_att);
fz_try(ctx)
svg_draw_path(ctx, dev, doc, path, &local_state);
fz_always(ctx)
fz_drop_path(ctx, path);
fz_catch(ctx)
fz_rethrow(ctx);
}
}
/* svg, symbol, image, foreignObject establish new viewports */
void
svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
{
char *w_att = fz_xml_att(node, "width");
char *h_att = fz_xml_att(node, "height");
if (w_att)
state->viewport_w = svg_parse_length(w_att, state->viewbox_w, state->fontsize);
if (h_att)
state->viewport_h = svg_parse_length(h_att, state->viewbox_h, state->fontsize);
}
static void
svg_lex_viewbox(const char *s, float *x, float *y, float *w, float *h)
{
while (svg_is_whitespace_or_comma(*s)) ++s;
if (svg_is_digit(*s)) s = svg_lex_number(x, s);
while (svg_is_whitespace_or_comma(*s)) ++s;
if (svg_is_digit(*s)) s = svg_lex_number(y, s);
while (svg_is_whitespace_or_comma(*s)) ++s;
if (svg_is_digit(*s)) s = svg_lex_number(w, s);
while (svg_is_whitespace_or_comma(*s)) ++s;
if (svg_is_digit(*s)) s = svg_lex_number(h, s);
}
static int
svg_parse_preserve_aspect_ratio(const char *att, int *x, int *y)
{
*x = *y = 1;
if (strstr(att, "none")) return 0;
if (strstr(att, "xMin")) *x = 0;
if (strstr(att, "xMid")) *x = 1;
if (strstr(att, "xMax")) *x = 2;
if (strstr(att, "YMin")) *y = 0;
if (strstr(att, "YMid")) *y = 1;
if (strstr(att, "YMax")) *y = 2;
return 1;
}
/* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */
void
svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
{
char *viewbox_att = fz_xml_att(node, "viewBox");
char *preserve_att = fz_xml_att(node, "preserveAspectRatio");
if (viewbox_att)
{
/* scale and translate to fit [minx miny minx+w miny+h] to [0 0 viewport.w viewport.h] */
float min_x, min_y, box_w, box_h, sx, sy;
int align_x=1, align_y=1, preserve=1;
float pad_x=0, pad_y=0;
svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
sx = state->viewport_w / box_w;
sy = state->viewport_h / box_h;
if (preserve_att)
preserve = svg_parse_preserve_aspect_ratio(preserve_att, &align_x, &align_y);
if (preserve)
{
sx = sy = fz_min(sx, sy);
if (align_x == 1) pad_x = (box_w * sx - state->viewport_w) / 2;
if (align_x == 2) pad_x = (box_w * sx - state->viewport_w);
if (align_y == 1) pad_y = (box_h * sy - state->viewport_h) / 2;
if (align_y == 2) pad_y = (box_h * sy - state->viewport_h);
state->transform = fz_concat(fz_translate(-pad_x, -pad_y), state->transform);
}
state->transform = fz_concat(fz_scale(sx, sy), state->transform);
state->transform = fz_concat(fz_translate(-min_x, -min_y), state->transform);
state->viewbox_w = box_w;
state->viewbox_h = box_h;
state->viewbox_size = sqrtf(box_w*box_w + box_h*box_h) / sqrtf(2);
}
}
/* parse transform and presentation attributes */
void
svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
{
fz_stroke_state *stroke = &state->stroke;
char *transform_att = fz_xml_att(node, "transform");
char *font_size_att = fz_xml_att(node, "font-size");
// TODO: all font stuff
char *style_att = fz_xml_att(node, "style");
// TODO: clip, clip-path, clip-rule
char *opacity_att = fz_xml_att(node, "opacity");
char *fill_att = fz_xml_att(node, "fill");
char *fill_rule_att = fz_xml_att(node, "fill-rule");
char *fill_opacity_att = fz_xml_att(node, "fill-opacity");
char *stroke_att = fz_xml_att(node, "stroke");
char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity");
char *stroke_width_att = fz_xml_att(node, "stroke-width");
char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap");
char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin");
char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit");
// TODO: stroke-dasharray, stroke-dashoffset
// TODO: marker, marker-start, marker-mid, marker-end
// TODO: overflow
// TODO: mask
/* Dirty hack scans of CSS style */
if (style_att)
{
svg_parse_color_from_style(ctx, doc, style_att,
&state->fill_is_set, state->fill_color,
&state->stroke_is_set, state->stroke_color);
}
if (transform_att)
{
state->transform = svg_parse_transform(ctx, doc, transform_att, state->transform);
}
if (font_size_att)
{
state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize);
}
if (opacity_att)
{
state->opacity = svg_parse_number(opacity_att, 0, 1, state->opacity);
}
if (fill_att)
{
if (!strcmp(fill_att, "none"))
{
state->fill_is_set = 0;
}
else
{
state->fill_is_set = 1;
svg_parse_color(ctx, doc, fill_att, state->fill_color);
}
}
if (fill_opacity_att)
state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity);
if (fill_rule_att)
{
if (!strcmp(fill_rule_att, "nonzero"))
state->fill_rule = 1;
if (!strcmp(fill_rule_att, "evenodd"))
state->fill_rule = 0;
}
if (stroke_att)
{
if (!strcmp(stroke_att, "none"))
{
state->stroke_is_set = 0;
}
else
{
state->stroke_is_set = 1;
svg_parse_color(ctx, doc, stroke_att, state->stroke_color);
}
}
if (stroke_opacity_att)
state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity);
if (stroke_width_att)
{
if (!strcmp(stroke_width_att, "inherit"))
;
else
stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize);
}
else
{
stroke->linewidth = 1;
}
if (stroke_linecap_att)
{
if (!strcmp(stroke_linecap_att, "butt"))
stroke->start_cap = FZ_LINECAP_BUTT;
if (!strcmp(stroke_linecap_att, "round"))
stroke->start_cap = FZ_LINECAP_ROUND;
if (!strcmp(stroke_linecap_att, "square"))
stroke->start_cap = FZ_LINECAP_SQUARE;
}
else
{
stroke->start_cap = FZ_LINECAP_BUTT;
}
stroke->dash_cap = stroke->start_cap;
stroke->end_cap = stroke->start_cap;
if (stroke_linejoin_att)
{
if (!strcmp(stroke_linejoin_att, "miter"))
stroke->linejoin = FZ_LINEJOIN_MITER;
if (!strcmp(stroke_linejoin_att, "round"))
stroke->linejoin = FZ_LINEJOIN_ROUND;
if (!strcmp(stroke_linejoin_att, "bevel"))
stroke->linejoin = FZ_LINEJOIN_BEVEL;
}
else
{
stroke->linejoin = FZ_LINEJOIN_MITER;
}
if (stroke_miterlimit_att)
{
if (!strcmp(stroke_miterlimit_att, "inherit"))
;
else
stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize);
}
else
{
stroke->miterlimit = 4.0f;
}
}
static void
svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
fz_xml *node;
char *w_att = fz_xml_att(root, "width");
char *h_att = fz_xml_att(root, "height");
char *viewbox_att = fz_xml_att(root, "viewBox");
/* get default viewport from viewBox if width and/or height is missing */
if (viewbox_att && (!w_att || !h_att))
{
float x, y;
svg_lex_viewbox(viewbox_att, &x, &y, &local_state.viewbox_w, &local_state.viewbox_h);
if (!w_att) local_state.viewport_w = local_state.viewbox_w;
if (!h_att) local_state.viewport_h = local_state.viewbox_h;
}
svg_parse_viewport(ctx, doc, root, &local_state);
svg_parse_viewbox(ctx, doc, root, &local_state);
svg_parse_common(ctx, doc, root, &local_state);
for (node = fz_xml_down(root); node; node = fz_xml_next(node))
svg_run_element(ctx, dev, doc, node, &local_state);
}
static void
svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
fz_xml *node;
svg_parse_common(ctx, doc, root, &local_state);
for (node = fz_xml_down(root); node; node = fz_xml_next(node))
svg_run_element(ctx, dev, doc, node, &local_state);
}
static void
svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
fz_xml *node;
svg_parse_viewport(ctx, doc, use, &local_state);
svg_parse_viewbox(ctx, doc, use, &local_state);
svg_parse_common(ctx, doc, use, &local_state);
for (node = fz_xml_down(symbol); node; node = fz_xml_next(node))
svg_run_element(ctx, dev, doc, node, &local_state);
}
static void
svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
char *xlink_href_att = fz_xml_att(root, "xlink:href");
char *x_att = fz_xml_att(root, "x");
char *y_att = fz_xml_att(root, "y");
float x = 0;
float y = 0;
if (++local_state.use_depth > MAX_USE_DEPTH)
{
fz_warn(ctx, "svg: too much recursion");
return;
}
svg_parse_common(ctx, doc, root, &local_state);
if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
if (xlink_href_att && xlink_href_att[0] == '#')
{
fz_xml *linked = fz_tree_lookup(ctx, doc->idmap, xlink_href_att + 1);
if (linked)
{
if (fz_xml_is_tag(linked, "symbol"))
svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state);
else
svg_run_element(ctx, dev, doc, linked, &local_state);
return;
}
}
fz_warn(ctx, "svg: cannot find linked symbol");
}
static void
svg_run_image(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
{
svg_state local_state = *inherit_state;
float x=0, y=0, w=0, h=0;
const char *data;
static const char *jpeg_uri = "data:image/jpeg;base64,";
static const char *png_uri = "data:image/png;base64,";
char *href_att = fz_xml_att(root, "xlink:href");
char *x_att = fz_xml_att(root, "x");
char *y_att = fz_xml_att(root, "y");
char *w_att = fz_xml_att(root, "width");
char *h_att = fz_xml_att(root, "height");
svg_parse_common(ctx, doc, root, &local_state);
if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
if (w <= 0 || h <= 0)
return;
if (!href_att)
return;
local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
local_state.transform = fz_concat(fz_scale(w, h), local_state.transform);
if (!strncmp(href_att, jpeg_uri, strlen(jpeg_uri)))
data = href_att + strlen(jpeg_uri);
else if (!strncmp(href_att, png_uri, strlen(png_uri)))
data = href_att + strlen(png_uri);
else
data = NULL;
if (data)
{
fz_image *img = NULL;
fz_buffer *buf;
fz_var(img);
buf = fz_new_buffer_from_base64(ctx, data, 0);
fz_try(ctx)
{
img = fz_new_image_from_buffer(ctx, buf);
fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
}
fz_always(ctx)
{
fz_drop_buffer(ctx, buf);
fz_drop_image(ctx, img);
}
fz_catch(ctx)
fz_warn(ctx, "svg: ignoring embedded image '%s'", href_att);
}
else if (doc->zip)
{
char path[2048];
fz_buffer *buf = NULL;
fz_image *img = NULL;
fz_var(buf);
fz_var(img);
fz_strlcpy(path, doc->base_uri, sizeof path);
fz_strlcat(path, "/", sizeof path);
fz_strlcat(path, href_att, sizeof path);
fz_urldecode(path);
fz_cleanname(path);
fz_try(ctx)
{
buf = fz_read_archive_entry(ctx, doc->zip, path);
img = fz_new_image_from_buffer(ctx, buf);
fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
}
fz_always(ctx)
{
fz_drop_buffer(ctx, buf);
fz_drop_image(ctx, img);
}
fz_catch(ctx)
fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
}
else
{
fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
}
}
static void
svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state)
{
if (fz_xml_is_tag(root, "svg"))
svg_run_svg(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "g"))
svg_run_g(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "title"))
;
else if (fz_xml_is_tag(root, "desc"))
;
else if (fz_xml_is_tag(root, "defs"))
;
else if (fz_xml_is_tag(root, "symbol"))
;
else if (fz_xml_is_tag(root, "use"))
svg_run_use(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "path"))
svg_run_path(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "rect"))
svg_run_rect(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "circle"))
svg_run_circle(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "ellipse"))
svg_run_ellipse(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "line"))
svg_run_line(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "polyline"))
svg_run_polyline(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "polygon"))
svg_run_polygon(ctx, dev, doc, root, state);
else if (fz_xml_is_tag(root, "image"))
svg_run_image(ctx, dev, doc, root, state);
#if 0
else if (fz_xml_is_tag(root, "text"))
svg_run_text(ctx, dev, doc, root);
else if (fz_xml_is_tag(root, "tspan"))
svg_run_text_span(ctx, dev, doc, root);
else if (fz_xml_is_tag(root, "tref"))
svg_run_text_ref(ctx, dev, doc, root);
else if (fz_xml_is_tag(root, "textPath"))
svg_run_text_path(ctx, dev, doc, root);
#endif
else
{
/* ignore unrecognized tags */
}
}
void
svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root)
{
char *version_att;
char *w_att;
char *h_att;
char *viewbox_att;
int version;
if (!fz_xml_is_tag(root, "svg"))
fz_throw(ctx, FZ_ERROR_GENERIC, "expected svg element (found %s)", fz_xml_tag(root));
version_att = fz_xml_att(root, "version");
w_att = fz_xml_att(root, "width");
h_att = fz_xml_att(root, "height");
viewbox_att = fz_xml_att(root, "viewBox");
version = 10;
if (version_att)
version = fz_atof(version_att) * 10;
if (version > 12)
fz_warn(ctx, "svg document version is newer than we support");
/* If no width or height attributes, then guess from the viewbox */
if (w_att == NULL && h_att == NULL && viewbox_att != NULL)
{
float min_x, min_y, box_w, box_h;
svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
doc->width = box_w;
doc->height = box_h;
}
else
{
doc->width = DEF_WIDTH;
if (w_att)
doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE);
doc->height = DEF_HEIGHT;
if (h_att)
doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE);
}
}
void
svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, fz_matrix ctm)
{
svg_state state;
svg_parse_document_bounds(ctx, doc, root);
/* Initial graphics state */
state.transform = ctm;
state.stroke = fz_default_stroke_state;
state.use_depth = 0;
state.viewport_w = DEF_WIDTH;
state.viewport_h = DEF_HEIGHT;
state.viewbox_w = DEF_WIDTH;
state.viewbox_h = DEF_HEIGHT;
state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2);
state.fontsize = 12;
state.opacity = 1;
state.fill_rule = 0;
state.fill_is_set = 1;
state.fill_color[0] = 0;
state.fill_color[1] = 0;
state.fill_color[2] = 0;
state.fill_opacity = 1;
state.stroke_is_set = 0;
state.stroke_color[0] = 0;
state.stroke_color[1] = 0;
state.stroke_color[2] = 0;
state.stroke_opacity = 1;
svg_run_svg(ctx, dev, doc, root, &state);
}