#include "fitz-imp.h"
#include "glyph-cache-imp.h"
typedef struct svg_device_s svg_device;
typedef struct tile_s tile;
typedef struct font_s font;
typedef struct glyph_s glyph;
typedef struct image_s image;
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 image_s
{
int id;
fz_image *image;
};
struct svg_device_s
{
fz_device super;
int text_as_text;
int reuse_images;
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;
int num_images;
int max_images;
image *images;
};
/* 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(fz_context *ctx, 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(ctx, 1024);
sdev->defs = fz_new_output_with_buffer(ctx, sdev->defs_buffer);
}
sdev->out = sdev->defs;
}
return sdev->out;
}
static fz_output *
end_def(fz_context *ctx, 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_data(ctx, sdev->out, sdev->defs_buffer->data, sdev->defs_buffer->len);
sdev->defs_buffer->len = 0;
}
return sdev->out;
}
/* Helper functions */
static void
svg_path_moveto(fz_context *ctx, void *arg, float x, float y)
{
fz_output *out = (fz_output *)arg;
fz_write_printf(ctx, out, "M %g %g ", x, y);
}
static void
svg_path_lineto(fz_context *ctx, void *arg, float x, float y)
{
fz_output *out = (fz_output *)arg;
fz_write_printf(ctx, out, "L %g %g ", x, y);
}
static void
svg_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3)
{
fz_output *out = (fz_output *)arg;
fz_write_printf(ctx, out, "C %g %g %g %g %g %g ", x1, y1, x2, y2, x3, y3);
}
static void
svg_path_close(fz_context *ctx, void *arg)
{
fz_output *out = (fz_output *)arg;
fz_write_printf(ctx, out, "Z ");
}
static const fz_path_walker svg_path_walker =
{
svg_path_moveto,
svg_path_lineto,
svg_path_curveto,
svg_path_close
};
static void
svg_dev_path(fz_context *ctx, svg_device *sdev, const fz_path *path)
{
fz_write_printf(ctx, sdev->out, " d=\"");
fz_walk_path(ctx, path, &svg_path_walker, sdev->out);
fz_write_printf(ctx, sdev->out, "\"");
}
static void
svg_dev_ctm(fz_context *ctx, 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_write_printf(ctx, 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(fz_context *ctx, svg_device *sdev, const 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;
exp = stroke_state->linewidth/exp;
if (exp < 1)
exp = 1;
fz_write_printf(ctx, out, " stroke-width=\"%g\"", exp);
fz_write_printf(ctx, 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_write_printf(ctx, out, " stroke-dasharray=");
for (i = 0; i < stroke_state->dash_len; i++)
fz_write_printf(ctx, out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]);
fz_write_printf(ctx, out, "\"");
if (stroke_state->dash_phase != 0)
fz_write_printf(ctx, out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase);
}
if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS)
fz_write_printf(ctx, out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit);
fz_write_printf(ctx, out, " stroke-linejoin=\"%s\"",
(stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" :
(stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter")));
}
static unsigned int
svg_hex_color(fz_context *ctx, fz_colorspace *colorspace, const float *color)
{
float rgb[3];
int r, g, b;
if (colorspace != fz_device_rgb(ctx))
{
fz_convert_color(ctx, fz_device_rgb(ctx), rgb, colorspace, color);
color = rgb;
}
r = fz_clampi(255 * color[0] + 0.5f, 0, 255);
g = fz_clampi(255 * color[1] + 0.5f, 0, 255);
b = fz_clampi(255 * color[2] + 0.5f, 0, 255);
return (r << 16) | (g << 8) | b;
}
static void
svg_dev_fill_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha)
{
fz_output *out = sdev->out;
if (colorspace)
{
int rgb = svg_hex_color(ctx, colorspace, color);
if (rgb != 0) /* black is the default value */
fz_write_printf(ctx, out, " fill=\"#%06x\"", rgb);
}
else
fz_write_printf(ctx, out, " fill=\"none\"");
if (alpha != 1)
fz_write_printf(ctx, out, " fill-opacity=\"%g\"", alpha);
}
static void
svg_dev_stroke_color(fz_context *ctx, svg_device *sdev, fz_colorspace *colorspace, const float *color, float alpha)
{
fz_output *out = sdev->out;
if (colorspace)
fz_write_printf(ctx, out, " fill=\"none\" stroke=\"#%06x\"", svg_hex_color(ctx, colorspace, color));
else
fz_write_printf(ctx, out, " fill=\"none\" stroke=\"none\"");
if (alpha != 1)
fz_write_printf(ctx, out, " stroke-opacity=\"%g\"", alpha);
}
static void
svg_font_family(fz_context *ctx, char buf[], int size, const char *name)
{
/* Remove "ABCDEF+" prefix and "-Bold" suffix. */
char *p = strchr(name, '+');
if (p) fz_strlcpy(buf, p+1, size);
else fz_strlcpy(buf, name, size);
p = strrchr(buf, '-');
if (p) *p = 0;
}
static int
find_first_char(fz_context *ctx, const fz_text_span *span, int i)
{
for (; i < span->len; ++i)
if (span->items[i].ucs >= 0)
return i;
return i;
}
static int
find_next_line_break(fz_context *ctx, const fz_text_span *span, const fz_matrix *inv_tm, int i)
{
fz_point p, old_p;
old_p.x = span->items[i].x;
old_p.y = span->items[i].y;
fz_transform_point(&old_p, inv_tm);
for (++i; i < span->len; ++i)
{
if (span->items[i].ucs >= 0)
{
p.x = span->items[i].x;
p.y = span->items[i].y;
fz_transform_point(&p, inv_tm);
if (span->wmode == 0)
{
if (p.y != old_p.y)
return i;
}
else
{
if (p.x != old_p.x)
return i;
}
old_p = p;
}
}
return i;
}
static float
svg_cluster_advance(fz_context *ctx, const fz_text_span *span, int i, int end)
{
int n = 1;
while (i + n < end && span->items[i + n].gid == -1)
++n;
if (n > 1)
return fz_advance_glyph(ctx, span->font, span->items[i].gid, span->wmode) / n;
return 0; /* this value is never used (since n==1) */
}
static void
svg_dev_text_span(fz_context *ctx, svg_device *sdev, const fz_matrix *ctm, const fz_text_span *span)
{
fz_output *out = sdev->out;
char font_family[100];
int is_bold, is_italic;
fz_matrix tm, inv_tm, final_tm;
fz_point p;
float font_size;
fz_text_item *it;
int start, end, i;
float cluster_advance = 0;
if (span->len == 0)
{
fz_write_printf(ctx, out, "/>\n");
return;
}
tm = span->trm;
font_size = fz_matrix_expansion(&tm);
final_tm.a = tm.a / font_size;
final_tm.b = tm.b / font_size;
final_tm.c = -tm.c / font_size;
final_tm.d = -tm.d / font_size;
final_tm.e = 0;
final_tm.f = 0;
fz_invert_matrix(&inv_tm, &final_tm);
fz_concat(&final_tm, &final_tm, ctm);
tm.e = span->items[0].x;
tm.f = span->items[0].y;
svg_font_family(ctx, font_family, sizeof font_family, fz_font_name(ctx, span->font));
is_bold = fz_font_is_bold(ctx, span->font);
is_italic = fz_font_is_italic(ctx, span->font);
fz_write_printf(ctx, out, " xml:space=\"preserve\"");
fz_write_printf(ctx, out, " transform=\"matrix(%M)\"", &final_tm);
fz_write_printf(ctx, out, " font-size=\"%g\"", font_size);
fz_write_printf(ctx, out, " font-family=\"%s\"", font_family);
if (is_bold) fz_write_printf(ctx, out, " font-weight=\"bold\"");
if (is_italic) fz_write_printf(ctx, out, " font-style=\"italic\"");
if (span->wmode != 0) fz_write_printf(ctx, out, " writing-mode=\"tb\"");
fz_write_byte(ctx, out, '>');
start = find_first_char(ctx, span, 0);
while (start < span->len)
{
end = find_next_line_break(ctx, span, &inv_tm, start);
p.x = span->items[start].x;
p.y = span->items[start].y;
fz_transform_point(&p, &inv_tm);
if (span->items[start].gid >= 0)
cluster_advance = svg_cluster_advance(ctx, span, start, end);
if (span->wmode == 0)
fz_write_printf(ctx, out, "items[i];
if (it->gid >= 0)
cluster_advance = svg_cluster_advance(ctx, span, i, end);
if (it->ucs >= 0)
{
if (it->gid >= 0)
{
p.x = it->x;
p.y = it->y;
fz_transform_point(&p, &inv_tm);
}
else
{
/* we have no glyph (such as in a ligature) -- advance a bit */
if (span->wmode == 0)
p.x += font_size * cluster_advance;
else
p.y += font_size * cluster_advance;
}
fz_write_printf(ctx, out, " %g", span->wmode == 0 ? p.x : p.y);
}
}
fz_write_printf(ctx, out, "\">");
for (i = start; i < end; ++i)
{
it = &span->items[i];
if (it->ucs >= 0)
{
int c = it->ucs;
if (c >= 32 && c <= 127 && c != '<' && c != '&' && c != '>')
fz_write_byte(ctx, out, c);
else
fz_write_printf(ctx, out, "%04x;", c);
}
}
fz_write_printf(ctx, out, "");
start = find_first_char(ctx, span, end);
}
fz_write_printf(ctx, out, "\n");
}
static font *
svg_dev_text_span_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text_span *span, const fz_matrix *ctm)
{
svg_device *sdev = (svg_device*)dev;
fz_output *out = sdev->out;
int i, font_idx;
font *fnt;
fz_matrix shift = fz_identity;
for (font_idx = 0; font_idx < sdev->num_fonts; font_idx++)
{
if (sdev->fonts[font_idx].font == span->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, span->font);
sdev->num_fonts++;
}
fnt = &sdev->fonts[font_idx];
for (i=0; i < span->len; i++)
{
fz_text_item *it = &span->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;
path = fz_outline_glyph(ctx, span->font, gid, &fz_identity);
if (path)
{
fz_bound_path(ctx, path, NULL, &fz_identity, &rect);
shift.e = -rect.x0;
shift.f = -rect.y0;
fz_transform_path(ctx, path, &shift);
out = start_def(ctx, sdev);
fz_write_printf(ctx, out, "\n", fnt->id, gid);
fz_write_printf(ctx, out, "\n");
fz_drop_path(ctx, path);
}
else
{
fz_bound_glyph(ctx, span->font, gid, &fz_identity, &rect);
shift.e = -rect.x0;
shift.f = -rect.y0;
out = start_def(ctx, sdev);
fz_write_printf(ctx, out, "\n", fnt->id, gid);
fz_run_t3_glyph(ctx, span->font, gid, &shift, dev);
}
fz_write_printf(ctx, out, "\n");
out = end_def(ctx, sdev);
fnt->sentlist[gid].x_off = rect.x0;
fnt->sentlist[gid].y_off = rect.y0;
}
}
return fnt;
}
static void
svg_dev_text_span_as_paths_fill(fz_context *ctx, fz_device *dev, const fz_text_span *span, const fz_matrix *ctm,
fz_colorspace *colorspace, const float *color, float alpha, font *fnt)
{
svg_device *sdev = (svg_device*)dev;
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 = span->trm.a;
local_trm.b = span->trm.b;
local_trm.c = span->trm.c;
local_trm.d = span->trm.d;
local_trm.e = 0;
local_trm.f = 0;
for (i=0; i < span->len; i++)
{
fz_text_item *it = &span->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_write_printf(ctx, out, "