summaryrefslogtreecommitdiff
path: root/source/fitz/svg-device.c
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2013-10-09 13:59:51 +0100
committerRobin Watts <robin.watts@artifex.com>2013-10-09 14:54:52 +0100
commit4338ecbcb672f5b7bc988980c7dc0232efe625d0 (patch)
treef2cc93766038bc4e57783bcba0f894910134b507 /source/fitz/svg-device.c
parent9ea6ca1c95f5dbfd4db20369618f095ded634027 (diff)
downloadmupdf-4338ecbcb672f5b7bc988980c7dc0232efe625d0.tar.xz
SVG: Support Type3 fonts and stroked fonts.
Diffstat (limited to 'source/fitz/svg-device.c')
-rw-r--r--source/fitz/svg-device.c232
1 files changed, 203 insertions, 29 deletions
diff --git a/source/fitz/svg-device.c b/source/fitz/svg-device.c
index f26e8e13..11194abc 100644
--- a/source/fitz/svg-device.c
+++ b/source/fitz/svg-device.c
@@ -33,6 +33,10 @@ 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;
@@ -45,6 +49,41 @@ struct svg_device_s
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
@@ -100,11 +139,16 @@ svg_dev_ctm(svg_device *sdev, const fz_matrix *ctm)
}
static void
-svg_dev_stroke_state(svg_device *sdev, fz_stroke_state *stroke_state)
+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);
+ 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")));
@@ -260,14 +304,13 @@ svg_dev_text(svg_device *sdev, const fz_matrix *ctm, fz_text *text)
fz_printf(out, "\n</text>\n");
}
-static void
-svg_dev_text_as_paths(svg_device *sdev, const fz_matrix *ctm, fz_text *text,
- fz_colorspace *colorspace, float *color, float alpha)
+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;
- fz_matrix local_trm, local_trm2;
font *fnt;
fz_matrix shift = { 1, 0, 0, 1, 0, 0};
@@ -315,21 +358,42 @@ svg_dev_text_as_paths(svg_device *sdev, const fz_matrix *ctm, fz_text *text,
if (fnt->sentlist[gid].x_off == FLT_MIN)
{
/* Need to send this one */
- fz_path *path = fz_outline_glyph(sdev->ctx, text->font, gid, &fz_identity);
fz_rect rect;
- fz_bound_path(ctx, path, NULL, &fz_identity, &rect);
+ fz_path *path;
+ fz_bound_glyph(ctx, text->font, gid, &fz_identity, &rect);
shift.e = -rect.x0;
shift.f = -rect.y0;
- fz_transform_path(ctx, path, &shift);
+ out = start_def(sdev);
fz_printf(out, "<symbol id=\"font_%x_%x\">", fnt->id, gid);
- fz_printf(out, "<path");
- svg_dev_path(sdev, path);
- fz_printf(out, "/>\n");
+ path = fz_outline_glyph(sdev->ctx, text->font, gid, &shift);
+ if (path)
+ {
+ fz_printf(out, "<path");
+ svg_dev_path(sdev, path);
+ fz_printf(out, "/>\n");
+ }
+ else
+ {
+ fz_run_t3_glyph(ctx, text->font, gid, &shift, dev);
+ }
fz_printf(out, "</symbol>");
+ 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;
@@ -360,6 +424,47 @@ svg_dev_text_as_paths(svg_device *sdev, const fz_matrix *ctm, fz_text *text,
}
}
+static void
+svg_dev_text_as_paths_stroke(fz_device *dev, fz_text *text,
+ fz_stroke_state *stroke, 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, "<use xlink:href=\"#font_%x_%x\"", fnt->id, gid);
+ svg_dev_stroke_state(sdev, stroke, &local_trm2);
+ svg_dev_ctm(sdev, &local_trm2);
+ svg_dev_stroke_color(sdev, colorspace, color, alpha);
+ fz_printf(out, "/>\n");
+ }
+}
+
/* Entry points */
static void
@@ -387,7 +492,7 @@ svg_dev_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke, cons
fz_printf(out, "<path");
svg_dev_ctm(sdev, ctm);
- svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_state(sdev, stroke, &fz_identity);
svg_dev_stroke_color(sdev, colorspace, color, alpha);
svg_dev_path(sdev, path);
fz_printf(out, "/>\n");
@@ -397,23 +502,26 @@ static void
svg_dev_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
{
svg_device *sdev = dev->user;
- fz_output *out = sdev->out;
+ fz_output *out;
int num = sdev->id++;
+ out = start_def(sdev);
fz_printf(out, "<clipPath id=\"cp%d\">\n", num);
fz_printf(out, "<path");
svg_dev_ctm(sdev, ctm);
svg_dev_path(sdev, path);
if (even_odd)
fz_printf(out, " fill-rule=\"evenodd\"");
- fz_printf(out, "/>\n</clipPath>\n<g clip-path=\"url(#cp%d)\">\n", num);
+ fz_printf(out, "/>\n</clipPath>\n");
+ out = end_def(sdev);
+ fz_printf(out, "<g clip-path=\"url(#cp%d)\">\n", num);
}
static void
svg_dev_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
{
svg_device *sdev = dev->user;
- fz_output *out = sdev->out;
+ fz_output *out;
fz_context *ctx = dev->ctx;
fz_rect bounds;
int num = sdev->id++;
@@ -421,14 +529,17 @@ svg_dev_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_
fz_bound_path(ctx, path, stroke, ctm, &bounds);
+ out = start_def(sdev);
fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n",
num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
fz_printf(out, "<path");
svg_dev_ctm(sdev, ctm);
- svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_state(sdev, stroke, &fz_identity);
svg_dev_stroke_color(sdev, fz_device_rgb(ctx), white, 1);
svg_dev_path(sdev, path);
- fz_printf(out, "/>\n</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+ fz_printf(out, "/>\n</mask>\n");
+ out = end_def(sdev);
+ fz_printf(out, "<g mask=\"url(#ma%d)\">\n", num);
}
static void
@@ -437,11 +548,13 @@ svg_dev_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
{
svg_device *sdev = dev->user;
fz_output *out = sdev->out;
+ font *fnt;
fz_printf(out, "<text");
svg_dev_fill_color(sdev, colorspace, color, 0.0f);
svg_dev_text(sdev, ctm, text);
- svg_dev_text_as_paths(sdev, ctm, text, colorspace, color, alpha);
+ fnt = svg_dev_text_as_paths_defs(dev, text, ctm);
+ svg_dev_text_as_paths_fill(dev, text, ctm, colorspace, color, alpha, fnt);
}
static void
@@ -450,12 +563,13 @@ svg_dev_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, cons
{
svg_device *sdev = dev->user;
fz_output *out = sdev->out;
+ font *fnt;
fz_printf(out, "<text");
- svg_dev_ctm(sdev, ctm);
- svg_dev_stroke_state(sdev, stroke);
- svg_dev_stroke_color(sdev, colorspace, color, alpha);
+ svg_dev_fill_color(sdev, colorspace, color, 0.0f);
svg_dev_text(sdev, ctm, text);
+ fnt = svg_dev_text_as_paths_defs(dev, text, ctm);
+ svg_dev_text_as_paths_stroke(dev, text, stroke, ctm, colorspace, color, alpha, fnt);
}
static void
@@ -467,41 +581,60 @@ svg_dev_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accum
fz_rect bounds;
int num = sdev->id++;
float white[3] = { 255, 255, 255 };
+ font *fnt;
fz_bound_text(ctx, text, NULL, ctm, &bounds);
+ out = start_def(sdev);
fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n",
num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
fz_printf(out, "<text");
- svg_dev_fill_color(sdev, fz_device_rgb(ctx), white, 1.0f);
+ svg_dev_fill_color(sdev, fz_device_rgb(ctx), white, 0.0f);
svg_dev_text(sdev, ctm, text);
- fz_printf(out, "</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+ fnt = svg_dev_text_as_paths_defs(dev, text, ctm);
+ svg_dev_text_as_paths_fill(dev, text, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+ fz_printf(out, "</mask>\n");
+ out = end_def(sdev);
+ fz_printf(out, "<g mask=\"url(#ma%d)\">\n", num);
}
static void
svg_dev_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
{
svg_device *sdev = dev->user;
- fz_output *out = sdev->out;
+ fz_output *out;
fz_context *ctx = dev->ctx;
fz_rect bounds;
int num = sdev->id++;
float white[3] = { 255, 255, 255 };
+ font *fnt;
fz_bound_text(ctx, text, NULL, ctm, &bounds);
+ out = start_def(sdev);
fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\">\n",
num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
fz_printf(out, "<text");
- svg_dev_stroke_state(sdev, stroke);
- svg_dev_stroke_color(sdev, fz_device_rgb(ctx), white, 1.0f);
+ svg_dev_stroke_state(sdev, stroke, &fz_identity);
+ svg_dev_stroke_color(sdev, fz_device_rgb(ctx), white, 0.0f);
svg_dev_text(sdev, ctm, text);
- fz_printf(out, "</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+ fnt = svg_dev_text_as_paths_defs(dev, text, ctm);
+ svg_dev_text_as_paths_stroke(dev, text, stroke, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+ fz_printf(out, "</mask>\n");
+ out = end_def(sdev);
+ fz_printf(out, "<g mask=\"url(#ma%d)\">\n", num);
}
static void
svg_dev_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+ float black[3] = { 0, 0, 0};
+
+ fz_printf(out, "<text");
+ svg_dev_fill_color(sdev, fz_device_rgb(sdev->ctx), black, 0.0f);
+ svg_dev_text(sdev, ctm, text);
}
static void
@@ -586,6 +719,42 @@ static void
svg_dev_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
fz_colorspace *colorspace, float *color, float alpha)
{
+ svg_device *sdev = (svg_device *)dev->user;
+ fz_context *ctx = dev->ctx;
+ fz_output *out;
+ fz_matrix local_ctm = *ctm;
+ fz_matrix scale = { 1.0f/image->w, 0, 0, 1.0f/image->h, 0, 0};
+ int mask = sdev->id++;
+
+ fz_concat(&local_ctm, &scale, ctm);
+ out = start_def(sdev);
+ fz_printf(out, "<mask id=\"ma%d\"><image", mask);
+ fz_printf(out, " width=\"%dpx\" height=\"%dpx\" xlink:href=\"data:", image->w, image->h);
+ switch (image->buffer == NULL ? FZ_IMAGE_JPX : image->buffer->params.type)
+ {
+ case FZ_IMAGE_JPEG:
+ fz_printf(out, "image/jpeg;base64,");
+ send_data_base64(out, image->buffer->buffer);
+ break;
+ case FZ_IMAGE_PNG:
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, image->buffer->buffer);
+ break;
+ default:
+ {
+ fz_buffer *buf = fz_image_as_png(ctx, image, image->w, image->h);
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, buf);
+ fz_drop_buffer(ctx, buf);
+ break;
+ }
+ }
+ fz_printf(out, "\"/></mask>\n");
+ out = end_def(sdev);
+ fz_printf(out, "<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", image->w, image->h);
+ svg_dev_fill_color(sdev, colorspace, color, alpha);
+ svg_dev_ctm(sdev, &local_ctm);
+ fz_printf(out, " mask=\"url(#ma%d)\"/>\n", mask);
}
static void
@@ -634,7 +803,7 @@ static int
svg_dev_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
{
svg_device *sdev = (svg_device *)dev->user;
- fz_output *out = sdev->out;
+ fz_output *out;
fz_context *ctx = dev->ctx;
int num;
tile *t;
@@ -666,6 +835,7 @@ svg_dev_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, flo
/* The first thing we do is to capture the contents of the pattern
* as a symbol we can reuse. */
+ out = start_def(sdev);
fz_printf(out, "<symbol id=\"pac%d\">\n", t->pattern);
return 0;
@@ -732,6 +902,7 @@ svg_dev_end_tile(fz_device *dev)
if (cp != -1)
fz_printf(out, "</g>\n");
fz_printf(out, "</pattern>\n");
+ out = end_def(sdev);
/* Finally, fill a rectangle with the pattern. */
fz_printf(out, "<rect");
@@ -748,6 +919,8 @@ svg_dev_free_user(fz_device *dev)
fz_output *out = sdev->out;
fz_free(ctx, sdev->tiles);
+ fz_drop_buffer(ctx, sdev->defs_buffer);
+ fz_close_output(sdev->defs);
fz_printf(out, "</svg>\n");
@@ -763,6 +936,7 @@ fz_device *fz_new_svg_device(fz_context *ctx, fz_output *out, float page_width,
{
sdev->ctx = ctx;
sdev->out = out;
+ sdev->out_store = out;
sdev->id = 0;
dev = fz_new_device(ctx, sdev);