#include "mupdf/fitz.h"
typedef struct svg_device_s svg_device;
typedef struct tile_s tile;
typedef struct font_s font;
typedef struct glyph_s glyph;
struct tile_s
{
int pattern;
fz_matrix ctm;
fz_rect view;
fz_rect area;
fz_point step;
};
struct glyph_s
{
float x_off;
float y_off;
};
struct font_s
{
int id;
fz_font *font;
int max_sentlist;
glyph *sentlist;
};
struct svg_device_s
{
fz_context *ctx;
fz_output *out;
fz_output *out_store;
fz_output *defs;
fz_buffer *defs_buffer;
int def_count;
int id;
int num_tiles;
int max_tiles;
tile *tiles;
int num_fonts;
int max_fonts;
font *fonts;
};
/* SVG is awkward about letting us define things within symbol definitions
* so we have to delay definitions until after the symbol definition ends. */
static fz_output *
start_def(svg_device *sdev)
{
sdev->def_count++;
if (sdev->def_count == 2)
{
if (sdev->defs == NULL)
{
if (sdev->defs_buffer == NULL)
sdev->defs_buffer = fz_new_buffer(sdev->ctx, 1024);
sdev->defs = fz_new_output_with_buffer(sdev->ctx, sdev->defs_buffer);
}
sdev->out = sdev->defs;
}
return sdev->out;
}
static fz_output *
end_def(svg_device *sdev)
{
if (sdev->def_count > 0)
sdev->def_count--;
if (sdev->def_count == 1)
sdev->out = sdev->out_store;
if (sdev->def_count == 0 && sdev->defs_buffer != NULL)
{
fz_write(sdev->out, sdev->defs_buffer->data, sdev->defs_buffer->len);
sdev->defs_buffer->len = 0;
}
return sdev->out;
}
/* Helper functions */
static void
svg_dev_path(svg_device *sdev, fz_path *path)
{
fz_output *out = sdev->out;
float x, y;
int i, k;
fz_printf(out, " d=\"");
for (i = 0, k = 0; i < path->cmd_len; i++)
{
switch (path->cmds[i])
{
case FZ_MOVETO:
x = path->coords[k++];
y = path->coords[k++];
fz_printf(out, "M %g %g ", x, y);
break;
case FZ_LINETO:
x = path->coords[k++];
y = path->coords[k++];
fz_printf(out, "L %g %g ", x, y);
break;
case FZ_CURVETO:
x = path->coords[k++];
y = path->coords[k++];
fz_printf(out, "C %g %g ", x, y);
x = path->coords[k++];
y = path->coords[k++];
fz_printf(out, "%g %g ", x, y);
x = path->coords[k++];
y = path->coords[k++];
fz_printf(out, "%g %g ", x, y);
break;
case FZ_CLOSE_PATH:
fz_printf(out, "Z ");
break;
}
}
fz_printf(out, "\"");
}
static void
svg_dev_ctm(svg_device *sdev, const fz_matrix *ctm)
{
fz_output *out = sdev->out;
if (ctm->a != 1.0 || ctm->b != 0 || ctm->c != 0 || ctm->d != 1.0 || ctm->e != 0 || ctm->f != 0)
{
fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
ctm->a, ctm->b, ctm->c, ctm->d, ctm->e, ctm->f);
}
}
static void
svg_dev_stroke_state(svg_device *sdev, fz_stroke_state *stroke_state, const fz_matrix *ctm)
{
fz_output *out = sdev->out;
float exp;
exp = fz_matrix_expansion(ctm);
if (exp == 0)
exp = 1;
fz_printf(out, " stroke-width=\"%g\"", stroke_state->linewidth/exp);
fz_printf(out, " stroke-linecap=\"%s\"",
(stroke_state->start_cap == FZ_LINECAP_SQUARE ? "square" :
(stroke_state->start_cap == FZ_LINECAP_ROUND ? "round" : "butt")));
if (stroke_state->dash_len != 0)
{
int i;
fz_printf(out, " stroke-dasharray=");
for (i = 0; i < stroke_state->dash_len; i++)
fz_printf(out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]);
fz_printf(out, "\"");
if (stroke_state->dash_phase != 0)
fz_printf(out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase);
}
if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS)
fz_printf(out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit);
fz_printf(out, " stroke-linejoin=\"%s\"",
(stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" :
(stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter")));
}
static void
svg_dev_fill_color(svg_device *sdev, fz_colorspace *colorspace, float *color, float alpha)
{
fz_context *ctx = sdev->ctx;
fz_output *out = sdev->out;
float rgb[FZ_MAX_COLORS];
if (colorspace != fz_device_rgb(ctx))
{
/* If it's not rgb, make it rgb */
colorspace->to_rgb(ctx, colorspace, color, rgb);
color = rgb;
}
if (color[0] == 0 && color[1] == 0 && color[2] == 0)
{
/* don't send a fill, as it will be assumed to be black */
}
else
fz_printf(out, " fill=\"rgb(%d,%d,%d)\"", (int)(255*color[0] + 0.5), (int)(255*color[1] + 0.5), (int)(255*color[2]+0.5));
if (alpha != 1)
fz_printf(out, " fill-opacity=\"%g\"", alpha);
}
static void
svg_dev_stroke_color(svg_device *sdev, fz_colorspace *colorspace, float *color, float alpha)
{
fz_context *ctx = sdev->ctx;
fz_output *out = sdev->out;
float rgb[FZ_MAX_COLORS];
if (colorspace != fz_device_rgb(ctx))
{
/* If it's not rgb, make it rgb */
colorspace->to_rgb(ctx, colorspace, color, rgb);
color = rgb;
}
fz_printf(out, " fill=\"none\" stroke=\"rgb(%d,%d,%d)\"", (int)(255*color[0] + 0.5), (int)(255*color[1] + 0.5), (int)(255*color[2]+0.5));
if (alpha != 1)
fz_printf(out, " stroke-opacity=\"%g\"", alpha);
}
static inline int
is_xml_wspace(int c)
{
return (c == 9 || /* TAB */
c == 0x0a || /* HT */
c == 0x0b || /* LF */
c == 0x20);
}
static void
svg_dev_text(svg_device *sdev, const fz_matrix *ctm, fz_text *text)
{
fz_output *out = sdev->out;
int i;
fz_matrix inverse;
fz_matrix local_trm;
float size;
int start, is_wspace, was_wspace;
/* Rely on the fact that trm.{e,f} == 0 */
size = fz_matrix_expansion(&text->trm);
local_trm.a = text->trm.a / size;
local_trm.b = text->trm.b / size;
local_trm.c = -text->trm.c / size;
local_trm.d = -text->trm.d / size;
local_trm.e = 0;
local_trm.f = 0;
fz_invert_matrix(&inverse, &local_trm);
fz_concat(&local_trm, &local_trm, ctm);
fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f);
fz_printf(out, " font-size=\"%g\"", size);
fz_printf(out, " font-family=\"%s\"", text->font->name);
/* Leading (and repeated) whitespace presents a problem for SVG
* text, so elide it here. */
for (start=0; start < text->len; start++)
{
fz_text_item *it = &text->items[start];
if (!is_xml_wspace(it->ucs))
break;
}
fz_printf(out, " x=");
was_wspace = 0;
for (i=start; i < text->len; i++)
{
fz_text_item *it = &text->items[i];
fz_point p;
is_wspace = is_xml_wspace(it->ucs);
if (is_wspace && was_wspace)
continue;
was_wspace = is_wspace;
p.x = it->x;
p.y = it->y;
fz_transform_point(&p, &inverse);
fz_printf(out, "%c%g", i == start ? '\"' : ' ', p.x);
}
fz_printf(out, "\" y=");
was_wspace = 0;
for (i=start; i < text->len; i++)
{
fz_text_item *it = &text->items[i];
fz_point p;
is_wspace = is_xml_wspace(it->ucs);
if (is_wspace && was_wspace)
continue;
was_wspace = is_wspace;
p.x = it->x;
p.y = it->y;
fz_transform_point(&p, &inverse);
fz_printf(out, "%c%g", i == start ? '\"' : ' ', p.y);
}
fz_printf(out, "\">\n");
was_wspace = 0;
for (i=start; i < text->len; i++)
{
fz_text_item *it = &text->items[i];
int c = it->ucs;
is_wspace = is_xml_wspace(c);
if (is_wspace && was_wspace)
continue;
was_wspace = is_wspace;
if (c >= 32 && c <= 127 && c != '<' && c != '&')
fz_printf(out, "%c", c);
else
fz_printf(out, "%04x;", c);
}
fz_printf(out, "\n\n");
}
static font *
svg_dev_text_as_paths_defs(fz_device *dev, fz_text *text, const fz_matrix *ctm)
{
svg_device *sdev = dev->user;
fz_context *ctx = sdev->ctx;
fz_output *out = sdev->out;
int i, font_idx;
font *fnt;
fz_matrix shift = { 1, 0, 0, 1, 0, 0};
for (font_idx = 0; font_idx < sdev->num_fonts; font_idx++)
{
if (sdev->fonts[font_idx].font == text->font)
break;
}
if (font_idx == sdev->num_fonts)
{
/* New font */
if (font_idx == sdev->max_fonts)
{
int newmax = sdev->max_fonts * 2;
if (newmax == 0)
newmax = 4;
sdev->fonts = fz_resize_array(ctx, sdev->fonts, newmax, sizeof(*sdev->fonts));
memset(&sdev->fonts[font_idx], 0, (newmax - font_idx) * sizeof(sdev->fonts[0]));
sdev->max_fonts = newmax;
}
sdev->fonts[font_idx].id = sdev->id++;
sdev->fonts[font_idx].font = fz_keep_font(ctx, text->font);
sdev->num_fonts++;
}
fnt = &sdev->fonts[font_idx];
for (i=0; i < text->len; i++)
{
fz_text_item *it = &text->items[i];
int gid = it->gid;
if (gid < 0)
continue;
if (gid >= fnt->max_sentlist)
{
int j;
fnt->sentlist = fz_resize_array(ctx, fnt->sentlist, gid+1, sizeof(fnt->sentlist[0]));
for (j = fnt->max_sentlist; j <= gid; j++)
{
fnt->sentlist[j].x_off = FLT_MIN;
fnt->sentlist[j].y_off = FLT_MIN;
}
fnt->max_sentlist = gid+1;
}
if (fnt->sentlist[gid].x_off == FLT_MIN)
{
/* Need to send this one */
fz_rect rect;
fz_path *path;
fz_bound_glyph(ctx, text->font, gid, &fz_identity, &rect);
shift.e = -rect.x0;
shift.f = -rect.y0;
out = start_def(sdev);
fz_printf(out, "", fnt->id, gid);
path = fz_outline_glyph(sdev->ctx, text->font, gid, &shift);
if (path)
{
fz_printf(out, "\n");
}
else
{
fz_run_t3_glyph(ctx, text->font, gid, &shift, dev);
}
fz_printf(out, "");
out = end_def(sdev);
fnt->sentlist[gid].x_off = rect.x0;
fnt->sentlist[gid].y_off = rect.y0;
}
}
return fnt;
}
static void
svg_dev_text_as_paths_fill(fz_device *dev, fz_text *text, const fz_matrix *ctm,
fz_colorspace *colorspace, float *color, float alpha, font *fnt)
{
svg_device *sdev = dev->user;
fz_output *out = sdev->out;
fz_matrix local_trm, local_trm2;
int i;
fz_matrix shift = { 1, 0, 0, 1, 0, 0};
/* Rely on the fact that trm.{e,f} == 0 */
local_trm.a = text->trm.a;
local_trm.b = text->trm.b;
local_trm.c = text->trm.c;
local_trm.d = text->trm.d;
local_trm.e = 0;
local_trm.f = 0;
for (i=0; i < text->len; i++)
{
fz_text_item *it = &text->items[i];
int gid = it->gid;
if (gid < 0)
continue;
shift.e = fnt->sentlist[gid].x_off;
shift.f = fnt->sentlist[gid].y_off;
local_trm.e = it->x;
local_trm.f = it->y;
fz_concat(&local_trm2, &local_trm, ctm);
fz_concat(&local_trm2, &shift, &local_trm2);
fz_printf(out, "