1 /** \file
2 * Native PDF import using libpoppler.
3 *
4 * Authors:
5 * miklos erdelyi
6 *
7 * Copyright (C) 2007 Authors
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 *
11 */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #ifdef HAVE_POPPLER
19 #include "svg-builder.h"
20 #include "pdf-parser.h"
22 #include <png.h>
24 #include "document-private.h"
25 #include "xml/document.h"
26 #include "xml/node.h"
27 #include "xml/repr.h"
28 #include "svg/svg.h"
29 #include "svg/path-string.h"
30 #include "svg/css-ostringstream.h"
31 #include "svg/svg-color.h"
32 #include "color.h"
33 #include "unit-constants.h"
34 #include "io/stringstream.h"
35 #include "io/base64stream.h"
36 #include "libnr/nr-matrix-ops.h"
37 #include "libnr/nr-macros.h"
38 #include "libnrtype/font-instance.h"
40 #include "Function.h"
41 #include "GfxState.h"
42 #include "GfxFont.h"
43 #include "Stream.h"
44 #include "Page.h"
45 #include "UnicodeMap.h"
46 #include "GlobalParams.h"
48 namespace Inkscape {
49 namespace Extension {
50 namespace Internal {
52 //#define IFTRACE(_code) _code
53 #define IFTRACE(_code)
55 #define TRACE(_args) IFTRACE(g_print _args)
58 /**
59 * \class SvgBuilder
60 *
61 */
63 SvgBuilder::SvgBuilder() {
64 _in_text_object = false;
65 _need_font_update = true;
66 _invalidated_style = true;
67 _font_style = NULL;
68 _current_font = NULL;
69 _current_state = NULL;
70 }
72 SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref) {
73 _doc = document;
74 _docname = docname;
75 _xref = xref;
76 _xml_doc = sp_document_repr_doc(_doc);
77 _container = _root = _doc->rroot;
78 _root->setAttribute("xml:space", "preserve");
79 SvgBuilder();
80 }
82 SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) {
83 _doc = parent->_doc;
84 _docname = parent->_docname;
85 _xref = parent->_xref;
86 _xml_doc = parent->_xml_doc;
87 _container = this->_root = root;
88 SvgBuilder();
89 }
91 SvgBuilder::~SvgBuilder() {
92 }
94 void SvgBuilder::setDocumentSize(double width, double height) {
95 sp_repr_set_svg_double(_root, "width", width);
96 sp_repr_set_svg_double(_root, "height", height);
97 this->_width = width;
98 this->_height = height;
99 }
101 /**
102 * \brief Sets groupmode of the current container to 'layer' and sets its label if given
103 */
104 void SvgBuilder::setAsLayer(char *layer_name) {
105 _container->setAttribute("inkscape:groupmode", "layer");
106 if (layer_name) {
107 _container->setAttribute("inkscape:label", layer_name);
108 }
109 }
111 void SvgBuilder::saveState() {
112 _group_depth.push_back(0);
113 pushGroup();
114 }
116 void SvgBuilder::restoreState() {
117 while (_group_depth.back() > 0) {
118 popGroup();
119 }
120 _group_depth.pop_back();
121 }
123 Inkscape::XML::Node *SvgBuilder::pushGroup() {
124 Inkscape::XML::Node *node = _xml_doc->createElement("svg:g");
125 _container->appendChild(node);
126 _container = node;
127 Inkscape::GC::release(node);
128 _group_depth.back()++;
129 // Set as a layer if this is a top-level group
130 if ( _container->parent() == _root ) {
131 static int layer_count = 1;
132 if ( layer_count > 1 ) {
133 gchar *layer_name = g_strdup_printf("%s%d", _docname, layer_count);
134 setAsLayer(layer_name);
135 g_free(layer_name);
136 } else {
137 setAsLayer(_docname);
138 }
139 }
141 return _container;
142 }
144 Inkscape::XML::Node *SvgBuilder::popGroup() {
145 if (_container != _root) { // Pop if the current container isn't root
146 _container = _container->parent();
147 _group_depth[_group_depth.size()-1] = --_group_depth.back();
148 }
150 return _container;
151 }
153 Inkscape::XML::Node *SvgBuilder::getContainer() {
154 return _container;
155 }
157 static gchar *svgConvertRGBToText(double r, double g, double b) {
158 static gchar tmp[1023] = {0};
159 snprintf(tmp, 1023,
160 "#%02x%02x%02x",
161 CLAMP(SP_COLOR_F_TO_U(r), 0, 255),
162 CLAMP(SP_COLOR_F_TO_U(g), 0, 255),
163 CLAMP(SP_COLOR_F_TO_U(b), 0, 255));
164 return (gchar *)&tmp;
165 }
167 static gchar *svgConvertGfxRGB(GfxRGB *color) {
168 double r = color->r / 65535.0;
169 double g = color->g / 65535.0;
170 double b = color->b / 65535.0;
171 return svgConvertRGBToText(r, g, b);
172 }
174 static void svgSetTransform(Inkscape::XML::Node *node, double c0, double c1,
175 double c2, double c3, double c4, double c5) {
176 NR::Matrix matrix(c0, c1, c2, c3, c4, c5);
177 gchar *transform_text = sp_svg_transform_write(matrix);
178 node->setAttribute("transform", transform_text);
179 g_free(transform_text);
180 }
182 static void svgSetTransform(Inkscape::XML::Node *node, double *transform) {
183 svgSetTransform(node, transform[0], transform[1], transform[2], transform[3],
184 transform[4], transform[5]);
185 }
187 /**
188 * \brief Generates a SVG path string from poppler's data structure
189 */
190 static gchar *svgInterpretPath(GfxPath *path) {
191 GfxSubpath *subpath;
192 Inkscape::SVG::PathString pathString;
193 int i, j;
194 for ( i = 0 ; i < path->getNumSubpaths() ; ++i ) {
195 subpath = path->getSubpath(i);
196 if (subpath->getNumPoints() > 0) {
197 pathString.moveTo(subpath->getX(0), subpath->getY(0));
198 j = 1;
199 while (j < subpath->getNumPoints()) {
200 if (subpath->getCurve(j)) {
201 pathString.curveTo(subpath->getX(j), subpath->getY(j),
202 subpath->getX(j+1), subpath->getY(j+1),
203 subpath->getX(j+2), subpath->getY(j+2));
205 j += 3;
206 } else {
207 pathString.lineTo(subpath->getX(j), subpath->getY(j));
208 ++j;
209 }
210 }
211 if (subpath->isClosed()) {
212 pathString.closePath();
213 }
214 }
215 }
217 return g_strdup(pathString.c_str());
218 }
220 /**
221 * \brief Sets stroke style from poppler's GfxState data structure
222 * Uses the given SPCSSAttr for storing the style properties
223 */
224 void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) {
226 // Check line width
227 if ( state->getLineWidth() <= 0.0 ) {
228 // Ignore stroke
229 sp_repr_css_set_property(css, "stroke", "none");
230 return;
231 }
233 // Stroke color/pattern
234 if ( state->getStrokeColorSpace()->getMode() == csPattern ) {
235 gchar *urltext = _createPattern(state->getStrokePattern(), state, true);
236 sp_repr_css_set_property(css, "stroke", urltext);
237 if (urltext) {
238 g_free(urltext);
239 }
240 } else {
241 GfxRGB stroke_color;
242 state->getStrokeRGB(&stroke_color);
243 sp_repr_css_set_property(css, "stroke", svgConvertGfxRGB(&stroke_color));
244 }
246 // Opacity
247 Inkscape::CSSOStringStream os_opacity;
248 os_opacity << state->getStrokeOpacity();
249 sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str());
251 // Line width
252 Inkscape::CSSOStringStream os_width;
253 os_width << state->getLineWidth();
254 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
256 // Line cap
257 switch (state->getLineCap()) {
258 case 0:
259 sp_repr_css_set_property(css, "stroke-linecap", "butt");
260 break;
261 case 1:
262 sp_repr_css_set_property(css, "stroke-linecap", "round");
263 break;
264 case 2:
265 sp_repr_css_set_property(css, "stroke-linecap", "square");
266 break;
267 }
269 // Line join
270 switch (state->getLineJoin()) {
271 case 0:
272 sp_repr_css_set_property(css, "stroke-linejoin", "miter");
273 break;
274 case 1:
275 sp_repr_css_set_property(css, "stroke-linejoin", "round");
276 break;
277 case 2:
278 sp_repr_css_set_property(css, "stroke-linejoin", "bevel");
279 break;
280 }
282 // Miterlimit
283 Inkscape::CSSOStringStream os_ml;
284 os_ml << state->getMiterLimit();
285 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
287 // Line dash
288 double *dash_pattern;
289 int dash_length;
290 double dash_start;
291 state->getLineDash(&dash_pattern, &dash_length, &dash_start);
292 if ( dash_length > 0 ) {
293 Inkscape::CSSOStringStream os_array;
294 for ( int i = 0 ; i < dash_length ; i++ ) {
295 os_array << dash_pattern[i];
296 if (i < (dash_length - 1)) {
297 os_array << ",";
298 }
299 }
300 sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str());
302 Inkscape::CSSOStringStream os_offset;
303 os_offset << dash_start;
304 sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str());
305 } else {
306 sp_repr_css_set_property(css, "stroke-dasharray", "none");
307 sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
308 }
309 }
311 /**
312 * \brief Sets fill style from poppler's GfxState data structure
313 * Uses the given SPCSSAttr for storing the style properties.
314 */
315 void SvgBuilder::_setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) {
317 // Fill color/pattern
318 if ( state->getFillColorSpace()->getMode() == csPattern ) {
319 gchar *urltext = _createPattern(state->getFillPattern(), state);
320 sp_repr_css_set_property(css, "fill", urltext);
321 if (urltext) {
322 g_free(urltext);
323 }
324 } else {
325 GfxRGB fill_color;
326 state->getFillRGB(&fill_color);
327 sp_repr_css_set_property(css, "fill", svgConvertGfxRGB(&fill_color));
328 }
330 // Opacity
331 Inkscape::CSSOStringStream os_opacity;
332 os_opacity << state->getFillOpacity();
333 sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str());
335 // Fill rule
336 sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero");
337 }
339 /**
340 * \brief Sets style properties from poppler's GfxState data structure
341 * \return SPCSSAttr with all the relevant properties set
342 */
343 SPCSSAttr *SvgBuilder::_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) {
344 SPCSSAttr *css = sp_repr_css_attr_new();
345 if (fill) {
346 _setFillStyle(css, state, even_odd);
347 } else {
348 sp_repr_css_set_property(css, "fill", "none");
349 }
351 if (stroke) {
352 _setStrokeStyle(css, state);
353 } else {
354 sp_repr_css_set_property(css, "stroke", "none");
355 }
357 return css;
358 }
360 /**
361 * \brief Emits the current path in poppler's GfxState data structure
362 * Can be used to do filling and stroking at once.
363 *
364 * \param fill whether the path should be filled
365 * \param stroke whether the path should be stroked
366 * \param even_odd whether the even-odd rule should be used when filling the path
367 */
368 void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) {
369 Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
370 gchar *pathtext = svgInterpretPath(state->getPath());
371 path->setAttribute("d", pathtext);
372 g_free(pathtext);
374 // Set style
375 SPCSSAttr *css = _setStyle(state, fill, stroke, even_odd);
376 sp_repr_css_change(path, css, "style");
377 sp_repr_css_attr_unref(css);
379 _container->appendChild(path);
380 Inkscape::GC::release(path);
381 }
383 /**
384 * \brief Clips to the current path set in GfxState
385 * \param state poppler's data structure
386 * \param even_odd whether the even-odd rule should be applied
387 */
388 void SvgBuilder::clip(GfxState *state, bool even_odd) {
389 pushGroup();
390 setClipPath(state, even_odd);
391 }
393 void SvgBuilder::setClipPath(GfxState *state, bool even_odd) {
394 // Create the clipPath repr
395 Inkscape::XML::Node *clip_path = _xml_doc->createElement("svg:clipPath");
396 clip_path->setAttribute("clipPathUnits", "userSpaceOnUse");
397 // Create the path
398 Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
399 gchar *pathtext = svgInterpretPath(state->getPath());
400 path->setAttribute("d", pathtext);
401 g_free(pathtext);
402 clip_path->appendChild(path);
403 Inkscape::GC::release(path);
404 // Append clipPath to defs and get id
405 SP_OBJECT_REPR (SP_DOCUMENT_DEFS (_doc))->appendChild(clip_path);
406 gchar *urltext = g_strdup_printf ("url(#%s)", clip_path->attribute("id"));
407 Inkscape::GC::release(clip_path);
408 _container->setAttribute("clip-path", urltext);
409 g_free(urltext);
410 }
412 /**
413 * \brief Fills the given array with the current container's transform, if set
414 * \param transform array of doubles to be filled
415 * \return true on success; false on invalid transformation
416 */
417 bool SvgBuilder::getTransform(double *transform) {
418 NR::Matrix svd;
419 gchar const *tr = _container->attribute("transform");
420 bool valid = sp_svg_transform_read(tr, &svd);
421 if (valid) {
422 for ( int i = 0 ; i < 6 ; i++ ) {
423 transform[i] = svd[i];
424 }
425 return true;
426 } else {
427 return false;
428 }
429 }
431 /**
432 * \brief Sets the transformation matrix of the current container
433 */
434 void SvgBuilder::setTransform(double c0, double c1, double c2, double c3,
435 double c4, double c5) {
437 TRACE(("setTransform: %f %f %f %f %f %f\n", c0, c1, c2, c3, c4, c5));
438 svgSetTransform(_container, c0, c1, c2, c3, c4, c5);
439 }
441 void SvgBuilder::setTransform(double *transform) {
442 setTransform(transform[0], transform[1], transform[2], transform[3],
443 transform[4], transform[5]);
444 }
446 /**
447 * \brief Checks whether the given pattern type can be represented in SVG
448 * Used by PdfParser to decide when to do fallback operations.
449 */
450 bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) {
451 if ( pattern != NULL ) {
452 if ( pattern->getType() == 2 ) { // shading pattern
453 GfxShading *shading = ((GfxShadingPattern *)pattern)->getShading();
454 int shadingType = shading->getType();
455 if ( shadingType == 2 || // axial shading
456 shadingType == 3 ) { // radial shading
457 return true;
458 }
459 return false;
460 } else if ( pattern->getType() == 1 ) { // tiling pattern
461 return true;
462 }
463 }
465 return false;
466 }
468 /**
469 * \brief Creates a pattern from poppler's data structure
470 * Handles linear and radial gradients. Creates a new PdfParser and uses it to
471 * build a tiling pattern.
472 * \return an url pointing to the created pattern
473 */
474 gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke) {
475 gchar *id = NULL;
476 if ( pattern != NULL ) {
477 if ( pattern->getType() == 2 ) { // Shading pattern
478 id = _createGradient((GfxShadingPattern*)pattern);
479 } else if ( pattern->getType() == 1 ) { // Tiling pattern
480 id = _createTilingPattern((GfxTilingPattern*)pattern, state, is_stroke);
481 }
482 } else {
483 return NULL;
484 }
485 gchar *urltext = g_strdup_printf ("url(#%s)", id);
486 g_free(id);
487 return urltext;
488 }
490 /**
491 * \brief Creates a tiling pattern from poppler's data structure
492 * Creates a sub-page PdfParser and uses it to parse the pattern's content stream.
493 * \return id of the created pattern
494 */
495 gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern,
496 GfxState *state, bool is_stroke) {
498 Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern");
499 // Set pattern transform matrix
500 double *p2u = tiling_pattern->getMatrix();
501 NR::Matrix pat_matrix(p2u[0], p2u[1], p2u[2], p2u[3], p2u[4], p2u[5]);
502 gchar *transform_text = sp_svg_transform_write(pat_matrix);
503 pattern_node->setAttribute("patternTransform", transform_text);
504 g_free(transform_text);
505 pattern_node->setAttribute("patternUnits", "userSpaceOnUse");
506 // Set pattern tiling
507 // FIXME: don't ignore XStep and YStep
508 double *bbox = tiling_pattern->getBBox();
509 sp_repr_set_svg_double(pattern_node, "x", 0.0);
510 sp_repr_set_svg_double(pattern_node, "y", 0.0);
511 sp_repr_set_svg_double(pattern_node, "width", bbox[2] - bbox[0]);
512 sp_repr_set_svg_double(pattern_node, "height", bbox[3] - bbox[1]);
514 // Convert BBox for PdfParser
515 PDFRectangle box;
516 box.x1 = bbox[0];
517 box.y1 = bbox[1];
518 box.x2 = bbox[2];
519 box.y2 = bbox[3];
520 // Create new SvgBuilder and sub-page PdfParser
521 SvgBuilder *pattern_builder = new SvgBuilder(this, pattern_node);
522 PdfParser *pdf_parser = new PdfParser(_xref, pattern_builder, tiling_pattern->getResDict(),
523 &box);
524 // Get pattern color space
525 GfxPatternColorSpace *pat_cs = (GfxPatternColorSpace *)( is_stroke ? state->getStrokeColorSpace()
526 : state->getFillColorSpace() );
527 // Set fill/stroke colors if this is an uncolored tiling pattern
528 GfxColorSpace *cs = NULL;
529 if ( tiling_pattern->getPaintType() == 2 && ( cs = pat_cs->getUnder() ) ) {
530 GfxState *pattern_state = pdf_parser->getState();
531 pattern_state->setFillColorSpace(cs->copy());
532 pattern_state->setFillColor(state->getFillColor());
533 pattern_state->setStrokeColorSpace(cs->copy());
534 pattern_state->setStrokeColor(state->getFillColor());
535 }
537 // Generate the SVG pattern
538 pdf_parser->parse(tiling_pattern->getContentStream());
540 // Cleanup
541 delete pdf_parser;
542 delete pattern_builder;
544 // Append the pattern to defs
545 SP_OBJECT_REPR (SP_DOCUMENT_DEFS (_doc))->appendChild(pattern_node);
546 gchar *id = g_strdup(pattern_node->attribute("id"));
547 Inkscape::GC::release(pattern_node);
549 return id;
550 }
552 /**
553 * \brief Creates a linear or radial gradient from poppler's data structure
554 * \return id of the created object
555 */
556 gchar *SvgBuilder::_createGradient(GfxShadingPattern *shading_pattern) {
557 GfxShading *shading = shading_pattern->getShading();
558 Inkscape::XML::Node *gradient;
559 Function *func;
560 int num_funcs;
561 bool extend0, extend1;
563 if ( shading->getType() == 2 ) { // Axial shading
564 gradient = _xml_doc->createElement("svg:linearGradient");
565 GfxAxialShading *axial_shading = (GfxAxialShading*)shading;
566 double x1, y1, x2, y2;
567 axial_shading->getCoords(&x1, &y1, &x2, &y2);
568 sp_repr_set_svg_double(gradient, "x1", x1);
569 sp_repr_set_svg_double(gradient, "y1", y1);
570 sp_repr_set_svg_double(gradient, "x2", x2);
571 sp_repr_set_svg_double(gradient, "y2", y2);
572 extend0 = axial_shading->getExtend0();
573 extend1 = axial_shading->getExtend1();
574 num_funcs = axial_shading->getNFuncs();
575 func = axial_shading->getFunc(0);
576 } else if (shading->getType() == 3) { // Radial shading
577 gradient = _xml_doc->createElement("svg:radialGradient");
578 GfxRadialShading *radial_shading = (GfxRadialShading*)shading;
579 double x1, y1, r1, x2, y2, r2;
580 radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2);
581 // FIXME: the inner circle's radius is ignored here
582 sp_repr_set_svg_double(gradient, "fx", x1);
583 sp_repr_set_svg_double(gradient, "fy", y1);
584 sp_repr_set_svg_double(gradient, "cx", x2);
585 sp_repr_set_svg_double(gradient, "cy", y2);
586 sp_repr_set_svg_double(gradient, "r", r2);
587 extend0 = radial_shading->getExtend0();
588 extend1 = radial_shading->getExtend1();
589 num_funcs = radial_shading->getNFuncs();
590 func = radial_shading->getFunc(0);
591 } else { // Unsupported shading type
592 return NULL;
593 }
594 gradient->setAttribute("gradientUnits", "userSpaceOnUse");
595 // Flip the gradient transform around the y axis
596 double *p2u = shading_pattern->getMatrix();
597 NR::Matrix pat_matrix(p2u[0], p2u[1], p2u[2], p2u[3], p2u[4], p2u[5]);
598 NR::Matrix flip(1.0, 0.0, 0.0, -1.0, 0.0, _height * PT_PER_PX);
599 pat_matrix *= flip;
600 gchar *transform_text = sp_svg_transform_write(pat_matrix);
601 gradient->setAttribute("gradientTransform", transform_text);
602 g_free(transform_text);
604 if ( extend0 && extend1 ) {
605 gradient->setAttribute("spreadMethod", "pad");
606 }
608 if ( num_funcs > 1 || !_addStopsToGradient(gradient, func, 1.0) ) {
609 Inkscape::GC::release(gradient);
610 return NULL;
611 }
613 Inkscape::XML::Node *defs = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (_doc));
614 defs->appendChild(gradient);
615 gchar *id = g_strdup(gradient->attribute("id"));
616 Inkscape::GC::release(gradient);
618 return id;
619 }
621 #define EPSILON 0.0001
622 bool SvgBuilder::_addSamplesToGradient(Inkscape::XML::Node *gradient,
623 SampledFunction *func, double offset0,
624 double offset1, double opacity) {
626 // Check whether this sampled function can be converted to color stops
627 int sample_size = func->getSampleSize(0);
628 if ( sample_size != 2 )
629 return false;
630 int num_comps = func->getOutputSize();
631 if ( num_comps != 3 )
632 return false;
634 double *samples = func->getSamples();
635 unsigned stop_count = gradient->childCount();
636 bool is_continuation = false;
637 // Check if this sampled function is the continuation of the previous one
638 if ( stop_count > 0 ) {
639 // Get previous stop
640 Inkscape::XML::Node *prev_stop = gradient->nthChild(stop_count-1);
641 // Read its properties
642 double prev_offset;
643 sp_repr_get_double(prev_stop, "offset", &prev_offset);
644 SPCSSAttr *css = sp_repr_css_attr(prev_stop, "style");
645 guint32 prev_stop_color = sp_svg_read_color(sp_repr_css_property(css, "stop-color", NULL), 0);
646 sp_repr_css_attr_unref(css);
647 // Convert colors
648 double r = SP_RGBA32_R_F (prev_stop_color);
649 double g = SP_RGBA32_G_F (prev_stop_color);
650 double b = SP_RGBA32_B_F (prev_stop_color);
651 if ( fabs(prev_offset - offset0) < EPSILON &&
652 fabs(samples[0] - r) < EPSILON &&
653 fabs(samples[1] - g) < EPSILON &&
654 fabs(samples[2] - b) < EPSILON ) {
656 is_continuation = true;
657 }
658 }
660 int i = is_continuation ? num_comps : 0;
661 while (i < sample_size*num_comps) {
662 Inkscape::XML::Node *stop = _xml_doc->createElement("svg:stop");
663 SPCSSAttr *css = sp_repr_css_attr_new();
664 Inkscape::CSSOStringStream os_opacity;
665 os_opacity << opacity;
666 sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str());
667 gchar c[64];
668 sp_svg_write_color (c, 64, SP_RGBA32_F_COMPOSE (samples[i], samples[i+1], samples[i+2], 1.0));
669 sp_repr_css_set_property(css, "stop-color", c);
670 sp_repr_css_change(stop, css, "style");
671 sp_repr_css_attr_unref(css);
672 sp_repr_set_css_double(stop, "offset", ( i < num_comps ) ? offset0 : offset1);
674 gradient->appendChild(stop);
675 Inkscape::GC::release(stop);
676 i += num_comps;
677 }
679 return true;
680 }
682 bool SvgBuilder::_addStopsToGradient(Inkscape::XML::Node *gradient, Function *func,
683 double opacity) {
685 int type = func->getType();
686 if ( type == 0 ) { // Sampled
687 SampledFunction *sampledFunc = (SampledFunction*)func;
688 _addSamplesToGradient(gradient, sampledFunc, 0.0, 1.0, opacity);
689 } else if ( type == 3 ) { // Stitching
690 StitchingFunction *stitchingFunc = (StitchingFunction*)func;
691 double *bounds = stitchingFunc->getBounds();
692 int num_funcs = stitchingFunc->getNumFuncs();
693 // Add samples from all the stitched functions
694 for ( int i = 0 ; i < num_funcs ; i++ ) {
695 Function *func = stitchingFunc->getFunc(i);
696 if ( func->getType() != 0 ) // Only sampled functions are supported
697 continue;
699 SampledFunction *sampledFunc = (SampledFunction*)func;
700 _addSamplesToGradient(gradient, sampledFunc, bounds[i],
701 bounds[i+1], opacity);
702 }
703 } else { // Unsupported function type
704 return false;
705 }
707 return true;
708 }
710 /**
711 * \brief Sets _invalidated_style to true to indicate that styles have to be updated
712 * Used for text output when glyphs are buffered till a font change
713 */
714 void SvgBuilder::updateStyle(GfxState *state) {
715 if (_in_text_object) {
716 _invalidated_style = true;
717 _current_state = state;
718 }
719 }
721 /**
722 * This array holds info about translating font weight names to more or less CSS equivalents
723 */
724 static char *font_weight_translator[][2] = {
725 {"bold", "bold"},
726 {"light", "300"},
727 {"black", "900"},
728 {"heavy", "900"},
729 {"ultrabold", "800"},
730 {"extrabold", "800"},
731 {"demibold", "600"},
732 {"semibold", "600"},
733 {"medium", "500"},
734 {"book", "normal"},
735 {"regular", "normal"},
736 {"roman", "normal"},
737 {"normal", "normal"},
738 {"ultralight", "200"},
739 {"extralight", "200"},
740 {"thin", "100"}
741 };
743 /**
744 * \brief Updates _font_style according to the font set in parameter state
745 */
746 void SvgBuilder::updateFont(GfxState *state) {
748 TRACE(("updateFont()\n"));
749 _need_font_update = false;
751 if (_font_style) {
752 //sp_repr_css_attr_unref(_font_style);
753 }
754 _font_style = sp_repr_css_attr_new();
755 GfxFont *font = state->getFont();
756 // Store original name
757 if (font->getOrigName()) {
758 _font_specification = font->getOrigName()->getCString();
759 } else {
760 _font_specification = font->getName()->getCString();
761 }
763 // Prune the font name to get the correct font family name
764 // In a PDF font names can look like this: IONIPB+MetaPlusBold-Italic
765 char *font_family = NULL;
766 char *font_style = NULL;
767 char *font_style_lowercase = NULL;
768 char *plus_sign = strstr(_font_specification, "+");
769 if (plus_sign) {
770 font_family = g_strdup(plus_sign + 1);
771 _font_specification = plus_sign + 1;
772 } else {
773 font_family = g_strdup(_font_specification);
774 }
775 char *style_delim = NULL;
776 if ( ( style_delim = g_strrstr(font_family, "-") ) ||
777 ( style_delim = g_strrstr(font_family, ",") ) ) {
778 font_style = style_delim + 1;
779 font_style_lowercase = g_ascii_strdown(font_style, -1);
780 style_delim[0] = 0;
781 }
783 // Font family
784 if (font->getFamily()) {
785 const gchar *family = font->getFamily()->getCString();
786 sp_repr_css_set_property(_font_style, "font-family", font->getFamily()->getCString());
787 } else {
788 sp_repr_css_set_property(_font_style, "font-family", font_family);
789 }
791 // Font style
792 if (font->isItalic()) {
793 sp_repr_css_set_property(_font_style, "font-style", "italic");
794 } else if (font_style) {
795 if ( strstr(font_style_lowercase, "italic") ||
796 strstr(font_style_lowercase, "slanted") ) {
797 sp_repr_css_set_property(_font_style, "font-style", "italic");
798 } else if (strstr(font_style_lowercase, "oblique")) {
799 sp_repr_css_set_property(_font_style, "font-style", "oblique");
800 }
801 }
803 // Font variant -- default 'normal' value
804 sp_repr_css_set_property(_font_style, "font-variant", "normal");
806 // Font weight
807 GfxFont::Weight font_weight = font->getWeight();
808 char *css_font_weight = NULL;
809 if ( font_weight != GfxFont::WeightNotDefined ) {
810 if ( font_weight == GfxFont::W400 ) {
811 css_font_weight = "normal";
812 } else if ( font_weight == GfxFont::W700 ) {
813 css_font_weight = "bold";
814 } else {
815 gchar weight_num[4] = "100";
816 weight_num[0] = (gchar)( '1' + (font_weight - GfxFont::W100) );
817 sp_repr_css_set_property(_font_style, "font-weight", (gchar *)&weight_num);
818 }
819 } else if (font_style) {
820 // Apply the font weight translations
821 int num_translations = sizeof(font_weight_translator) / ( 2 * sizeof(char *) );
822 for ( int i = 0 ; i < num_translations ; i++ ) {
823 if (strstr(font_style_lowercase, font_weight_translator[i][0])) {
824 css_font_weight = font_weight_translator[i][1];
825 }
826 }
827 } else {
828 css_font_weight = "normal";
829 }
830 if (css_font_weight) {
831 sp_repr_css_set_property(_font_style, "font-weight", css_font_weight);
832 }
833 g_free(font_family);
834 if (font_style_lowercase) {
835 g_free(font_style_lowercase);
836 }
838 // Font stretch
839 GfxFont::Stretch font_stretch = font->getStretch();
840 gchar *stretch_value = NULL;
841 switch (font_stretch) {
842 case GfxFont::UltraCondensed:
843 stretch_value = "ultra-condensed";
844 break;
845 case GfxFont::ExtraCondensed:
846 stretch_value = "extra-condensed";
847 break;
848 case GfxFont::Condensed:
849 stretch_value = "condensed";
850 break;
851 case GfxFont::SemiCondensed:
852 stretch_value = "semi-condensed";
853 break;
854 case GfxFont::Normal:
855 stretch_value = "normal";
856 break;
857 case GfxFont::SemiExpanded:
858 stretch_value = "semi-expanded";
859 break;
860 case GfxFont::Expanded:
861 stretch_value = "expanded";
862 break;
863 case GfxFont::ExtraExpanded:
864 stretch_value = "extra-expanded";
865 break;
866 case GfxFont::UltraExpanded:
867 stretch_value = "ultra-expanded";
868 break;
869 default:
870 break;
871 }
872 if ( stretch_value != NULL ) {
873 sp_repr_css_set_property(_font_style, "font-stretch", stretch_value);
874 }
876 // Font size
877 Inkscape::CSSOStringStream os_font_size;
878 double *text_matrix = state->getTextMat();
879 double w_scale = sqrt( text_matrix[0] * text_matrix[0] + text_matrix[2] * text_matrix[2] );
880 double h_scale = sqrt( text_matrix[1] * text_matrix[1] + text_matrix[3] * text_matrix[3] );
881 double max_scale;
882 if ( w_scale > h_scale ) {
883 max_scale = w_scale;
884 } else {
885 max_scale = h_scale;
886 }
887 double css_font_size = max_scale * state->getFontSize();
888 if ( font->getType() == fontType3 ) {
889 double *font_matrix = font->getFontMatrix();
890 if ( font_matrix[0] != 0.0 ) {
891 css_font_size *= font_matrix[3] / font_matrix[0];
892 }
893 }
895 os_font_size << css_font_size;
896 sp_repr_css_set_property(_font_style, "font-size", os_font_size.str().c_str());
898 // Writing mode
899 if ( font->getWMode() == 0 ) {
900 sp_repr_css_set_property(_font_style, "writing-mode", "lr");
901 } else {
902 sp_repr_css_set_property(_font_style, "writing-mode", "tb");
903 }
905 // Calculate new text matrix
906 NR::Matrix new_text_matrix(text_matrix[0] * state->getHorizScaling(),
907 text_matrix[1] * state->getHorizScaling(),
908 -text_matrix[2], -text_matrix[3],
909 0.0, 0.0);
911 if ( fabs( max_scale - 1.0 ) > EPSILON ) {
912 // Cancel out scaling by font size in text matrix
913 for ( int i = 0 ; i < 4 ; i++ ) {
914 new_text_matrix[i] /= max_scale;
915 }
916 }
917 _text_matrix = new_text_matrix;
918 _font_scaling = max_scale;
919 _current_font = font;
920 _invalidated_style = true;
921 }
923 /**
924 * \brief Shifts the current text position by the given amount (specified in text space)
925 */
926 void SvgBuilder::updateTextShift(GfxState *state, double shift) {
927 double shift_value = -shift * 0.001 * fabs(state->getFontSize());
928 if (state->getFont()->getWMode()) {
929 _text_position[1] += shift_value;
930 } else {
931 _text_position[0] += shift_value;
932 }
933 }
935 /**
936 * \brief Updates current text position
937 */
938 void SvgBuilder::updateTextPosition(double tx, double ty) {
939 NR::Point new_position(tx, ty);
940 _text_position = new_position;
941 }
943 /**
944 * \brief Flushes the buffered characters
945 */
946 void SvgBuilder::updateTextMatrix(GfxState *state) {
947 _flushText();
948 updateFont(state); // Update text matrix
949 }
951 /**
952 * \brief Writes the buffered characters to the SVG document
953 */
954 void SvgBuilder::_flushText() {
955 // Ignore empty strings
956 if ( _glyphs.size() < 1 ) {
957 _glyphs.clear();
958 return;
959 }
960 std::vector<SvgGlyph>::iterator i = _glyphs.begin();
961 const SvgGlyph& first_glyph = (*i);
962 int render_mode = first_glyph.render_mode;
963 // Ignore invisible characters
964 if ( render_mode == 3 ) {
965 _glyphs.clear();
966 return;
967 }
969 Inkscape::XML::Node *text_node = _xml_doc->createElement("svg:text");
970 // Set text matrix
971 NR::Matrix text_transform(_text_matrix);
972 text_transform[4] = first_glyph.position[0];
973 text_transform[5] = first_glyph.position[1];
974 gchar *transform = sp_svg_transform_write(text_transform);
975 text_node->setAttribute("transform", transform);
976 g_free(transform);
978 bool new_tspan = true;
979 unsigned int glyphs_in_a_row = 0;
980 Inkscape::XML::Node *tspan_node = NULL;
981 Glib::ustring x_coords;
982 Glib::ustring y_coords;
983 Glib::ustring text_buffer;
984 bool is_vertical = !strcmp(sp_repr_css_property(_font_style, "writing-mode", "lr"), "tb"); // FIXME
986 // Output all buffered glyphs
987 while (1) {
988 const SvgGlyph& glyph = (*i);
989 std::vector<SvgGlyph>::iterator prev_iterator = i - 1;
990 // Check if we need to make a new tspan
991 if (glyph.style_changed) {
992 new_tspan = true;
993 } else if ( i != _glyphs.begin() ) {
994 const SvgGlyph& prev_glyph = (*prev_iterator);
995 if ( !( ( glyph.dy == 0.0 && prev_glyph.dy == 0.0 &&
996 glyph.text_position[1] == prev_glyph.text_position[1] ) ||
997 ( glyph.dx == 0.0 && prev_glyph.dx == 0.0 &&
998 glyph.text_position[0] == prev_glyph.text_position[0] ) ) ) {
999 new_tspan = true;
1000 }
1001 }
1003 // Create tspan node if needed
1004 if ( new_tspan || i == _glyphs.end() ) {
1005 if (tspan_node) {
1006 // Set the x and y coordinate arrays
1007 tspan_node->setAttribute("x", x_coords.c_str());
1008 tspan_node->setAttribute("y", y_coords.c_str());
1009 TRACE(("tspan content: %s\n", text_buffer.c_str()));
1010 if ( glyphs_in_a_row > 1 ) {
1011 tspan_node->setAttribute("sodipodi:role", "line");
1012 }
1013 // Add text content node to tspan
1014 Inkscape::XML::Node *text_content = _xml_doc->createTextNode(text_buffer.c_str());
1015 tspan_node->appendChild(text_content);
1016 Inkscape::GC::release(text_content);
1017 text_node->appendChild(tspan_node);
1018 // Clear temporary buffers
1019 x_coords.clear();
1020 y_coords.clear();
1021 text_buffer.clear();
1022 Inkscape::GC::release(tspan_node);
1023 glyphs_in_a_row = 0;
1024 }
1025 if ( i == _glyphs.end() ) {
1026 sp_repr_css_attr_unref((*prev_iterator).style);
1027 break;
1028 } else {
1029 tspan_node = _xml_doc->createElement("svg:tspan");
1030 tspan_node->setAttribute("inkscape:font-specification", glyph.font_specification);
1031 // Set style and unref SPCSSAttr if it won't be needed anymore
1032 sp_repr_css_change(tspan_node, glyph.style, "style");
1033 if ( glyph.style_changed && i != _glyphs.begin() ) { // Free previous style
1034 sp_repr_css_attr_unref((*prev_iterator).style);
1035 }
1036 }
1037 new_tspan = false;
1038 }
1039 if ( glyphs_in_a_row > 0 ) {
1040 x_coords.append(" ");
1041 y_coords.append(" ");
1042 }
1043 // Append the coordinates to their respective strings
1044 NR::Point delta_pos( glyph.text_position - first_glyph.text_position );
1045 delta_pos[1] += glyph.rise;
1046 delta_pos[1] *= -1.0; // flip it
1047 delta_pos *= _font_scaling;
1048 Inkscape::CSSOStringStream os_x;
1049 os_x << delta_pos[0];
1050 x_coords.append(os_x.str());
1051 Inkscape::CSSOStringStream os_y;
1052 os_y << delta_pos[1];
1053 y_coords.append(os_y.str());
1055 // Append the character to the text buffer
1056 text_buffer.append((char *)&glyph.code, 1);
1058 glyphs_in_a_row++;
1059 i++;
1060 }
1061 _container->appendChild(text_node);
1062 Inkscape::GC::release(text_node);
1064 _glyphs.clear();
1065 }
1067 void SvgBuilder::beginString(GfxState *state, GooString *s) {
1068 if (_need_font_update) {
1069 updateFont(state);
1070 }
1071 IFTRACE(double *m = state->getTextMat());
1072 TRACE(("tm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
1073 IFTRACE(m = _current_font->getFontMatrix());
1074 TRACE(("fm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
1075 IFTRACE(m = state->getCTM());
1076 TRACE(("ctm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
1077 }
1079 /**
1080 * \brief Adds the specified character to the text buffer
1081 * Takes care of converting it to UTF-8 and generates a new style repr if style
1082 * has changed since the last call.
1083 */
1084 void SvgBuilder::addChar(GfxState *state, double x, double y,
1085 double dx, double dy,
1086 double originX, double originY,
1087 CharCode code, int nBytes, Unicode *u, int uLen) {
1090 bool is_space = ( uLen == 1 && u[0] == 32 );
1091 // Skip beginning space
1092 if ( is_space && _glyphs.size() < 1 ) {
1093 return;
1094 }
1095 // Allow only one space in a row
1096 if ( is_space && _glyphs[_glyphs.size() - 1].code_size == 1 &&
1097 _glyphs[_glyphs.size() - 1].code[0] == 32 ) {
1098 NR::Point delta(dx, dy);
1099 _text_position += delta;
1100 return;
1101 }
1103 SvgGlyph new_glyph;
1104 new_glyph.is_space = is_space;
1105 new_glyph.position = NR::Point( x - originX, y - originY );
1106 new_glyph.text_position = _text_position;
1107 new_glyph.dx = dx;
1108 new_glyph.dy = dy;
1109 NR::Point delta(dx, dy);
1110 _text_position += delta;
1112 // Convert the character to UTF-8 since that's our SVG document's encoding
1113 static UnicodeMap *u_map = NULL;
1114 if ( u_map == NULL ) {
1115 GooString *enc = new GooString("UTF-8");
1116 u_map = globalParams->getUnicodeMap(enc);
1117 u_map->incRefCnt();
1118 delete enc;
1119 }
1120 int code_size = 0;
1121 for ( int i = 0 ; i < uLen ; i++ ) {
1122 code_size += u_map->mapUnicode(u[i], (char *)&new_glyph.code[code_size], sizeof(new_glyph.code) - code_size);
1123 }
1124 new_glyph.code_size = code_size;
1126 // Copy current style if it has changed since the previous glyph
1127 if (_invalidated_style || _glyphs.size() == 0 ) {
1128 new_glyph.style_changed = true;
1129 int render_mode = state->getRender();
1130 // Set style
1131 bool has_fill = !( render_mode & 1 );
1132 bool has_stroke = ( render_mode & 3 ) == 1 || ( render_mode & 3 ) == 2;
1133 new_glyph.style = _setStyle(state, has_fill, has_stroke);
1134 new_glyph.render_mode = render_mode;
1135 sp_repr_css_merge(new_glyph.style, _font_style); // Merge with font style
1136 _invalidated_style = false;
1137 } else {
1138 new_glyph.style_changed = false;
1139 // Point to previous glyph's style information
1140 const SvgGlyph& prev_glyph = _glyphs.back();
1141 new_glyph.style = prev_glyph.style;
1142 new_glyph.render_mode = prev_glyph.render_mode;
1143 }
1144 new_glyph.font_specification = _font_specification;
1145 new_glyph.rise = state->getRise();
1147 _glyphs.push_back(new_glyph);
1148 }
1150 void SvgBuilder::endString(GfxState *state) {
1151 }
1153 void SvgBuilder::beginTextObject(GfxState *state) {
1154 _in_text_object = true;
1155 _invalidated_style = true; // Force copying of current state
1156 _current_state = state;
1157 }
1159 void SvgBuilder::endTextObject(GfxState *state) {
1160 _flushText();
1161 // TODO: clip if render_mode >= 4
1162 _in_text_object = false;
1163 }
1165 /**
1166 * Helper functions for supporting direct PNG output into a base64 encoded stream
1167 */
1168 void png_write_base64stream(png_structp png_ptr, png_bytep data, png_size_t length)
1169 {
1170 Inkscape::IO::Base64OutputStream *stream =
1171 (Inkscape::IO::Base64OutputStream*)png_get_io_ptr(png_ptr); // Get pointer to stream
1172 for ( unsigned i = 0 ; i < length ; i++ ) {
1173 stream->put((int)data[i]);
1174 }
1175 }
1177 void png_flush_base64stream(png_structp png_ptr)
1178 {
1179 Inkscape::IO::Base64OutputStream *stream =
1180 (Inkscape::IO::Base64OutputStream*)png_get_io_ptr(png_ptr); // Get pointer to stream
1181 stream->flush();
1182 }
1184 /**
1185 * \brief Creates an <image> element containing the given ImageStream as a PNG
1186 *
1187 */
1188 Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height,
1189 GfxImageColorMap *color_map, int *mask_colors, bool alpha_only, bool invert_alpha) {
1191 // Create PNG write struct
1192 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1193 if ( png_ptr == NULL ) {
1194 return NULL;
1195 }
1196 // Create PNG info struct
1197 png_infop info_ptr = png_create_info_struct(png_ptr);
1198 if ( info_ptr == NULL ) {
1199 png_destroy_write_struct(&png_ptr, NULL);
1200 return NULL;
1201 }
1202 // Set error handler
1203 if (setjmp(png_ptr->jmpbuf)) {
1204 png_destroy_write_struct(&png_ptr, &info_ptr);
1205 return NULL;
1206 }
1208 // Set read/write functions
1209 Inkscape::IO::StringOutputStream base64_string;
1210 Inkscape::IO::Base64OutputStream base64_stream(base64_string);
1211 FILE *fp;
1212 gchar *file_name;
1213 bool embed_image = true;
1214 if (embed_image) {
1215 base64_stream.setColumnWidth(0); // Disable line breaks
1216 png_set_write_fn(png_ptr, &base64_stream, png_write_base64stream, png_flush_base64stream);
1217 } else {
1218 static int counter = 0;
1219 file_name = g_strdup_printf("createImage%d.png", counter++);
1220 fp = fopen(file_name, "wb");
1221 if ( fp == NULL ) {
1222 png_destroy_write_struct(&png_ptr, &info_ptr);
1223 g_free(file_name);
1224 return NULL;
1225 }
1226 png_init_io(png_ptr, fp);
1227 }
1229 // Set header data
1230 if (!invert_alpha) {
1231 png_set_invert_alpha(png_ptr);
1232 }
1233 png_color_8 sig_bit;
1234 if (alpha_only) {
1235 png_set_IHDR(png_ptr, info_ptr,
1236 width,
1237 height,
1238 8, /* bit_depth */
1239 PNG_COLOR_TYPE_GRAY,
1240 PNG_INTERLACE_NONE,
1241 PNG_COMPRESSION_TYPE_BASE,
1242 PNG_FILTER_TYPE_BASE);
1243 sig_bit.red = 0;
1244 sig_bit.green = 0;
1245 sig_bit.blue = 0;
1246 sig_bit.gray = 8;
1247 sig_bit.alpha = 0;
1248 } else {
1249 png_set_IHDR(png_ptr, info_ptr,
1250 width,
1251 height,
1252 8, /* bit_depth */
1253 PNG_COLOR_TYPE_RGB_ALPHA,
1254 PNG_INTERLACE_NONE,
1255 PNG_COMPRESSION_TYPE_BASE,
1256 PNG_FILTER_TYPE_BASE);
1257 sig_bit.red = 8;
1258 sig_bit.green = 8;
1259 sig_bit.blue = 8;
1260 sig_bit.alpha = 8;
1261 }
1262 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1263 png_set_bgr(png_ptr);
1264 // Write the file header
1265 png_write_info(png_ptr, info_ptr);
1267 // Convert pixels
1268 ImageStream *image_stream;
1269 if (alpha_only) {
1270 if (color_map) {
1271 image_stream = new ImageStream(str, width, color_map->getNumPixelComps(),
1272 color_map->getBits());
1273 } else {
1274 image_stream = new ImageStream(str, width, 1, 1);
1275 }
1276 image_stream->reset();
1278 // Convert grayscale values
1279 if (color_map) {
1280 unsigned char *buffer = new unsigned char[width];
1281 for ( int y = 0 ; y < height ; y++ ) {
1282 unsigned char *row = image_stream->getLine();
1283 color_map->getGrayLine(row, buffer, width);
1284 png_write_row(png_ptr, (png_bytep)buffer);
1285 }
1286 delete buffer;
1287 } else {
1288 for ( int y = 0 ; y < height ; y++ ) {
1289 unsigned char *row = image_stream->getLine();
1290 png_write_row(png_ptr, (png_bytep)row);
1291 }
1292 }
1293 } else if (color_map) {
1294 image_stream = new ImageStream(str, width,
1295 color_map->getNumPixelComps(),
1296 color_map->getBits());
1297 image_stream->reset();
1299 // Convert RGB values
1300 unsigned int *buffer = new unsigned int[width];
1301 if (mask_colors) {
1302 for ( int y = 0 ; y < height ; y++ ) {
1303 unsigned char *row = image_stream->getLine();
1304 color_map->getRGBLine(row, buffer, width);
1306 unsigned int *dest = buffer;
1307 for ( int x = 0 ; x < width ; x++ ) {
1308 // Check each color component against the mask
1309 for ( int i = 0; i < color_map->getNumPixelComps() ; i++) {
1310 if ( row[i] < mask_colors[2*i] * 255 ||
1311 row[i] > mask_colors[2*i + 1] * 255 ) {
1312 *dest = *dest | 0xff000000;
1313 break;
1314 }
1315 }
1316 // Advance to the next pixel
1317 row += color_map->getNumPixelComps();
1318 dest++;
1319 }
1320 // Write it to the PNG
1321 png_write_row(png_ptr, (png_bytep)buffer);
1322 }
1323 } else {
1324 for ( int i = 0 ; i < height ; i++ ) {
1325 unsigned char *row = image_stream->getLine();
1326 memset((void*)buffer, 0xff, sizeof(int) * width);
1327 color_map->getRGBLine(row, buffer, width);
1328 png_write_row(png_ptr, (png_bytep)buffer);
1329 }
1330 }
1331 delete buffer;
1333 } else { // A colormap must be provided, so quit
1334 png_destroy_write_struct(&png_ptr, &info_ptr);
1335 if (!embed_image) {
1336 fclose(fp);
1337 g_free(file_name);
1338 }
1339 return NULL;
1340 }
1341 delete image_stream;
1342 str->close();
1343 // Close PNG
1344 png_write_end(png_ptr, info_ptr);
1345 png_destroy_write_struct(&png_ptr, &info_ptr);
1346 base64_stream.close();
1348 // Create repr
1349 Inkscape::XML::Node *image_node = _xml_doc->createElement("svg:image");
1350 sp_repr_set_svg_double(image_node, "width", width);
1351 sp_repr_set_svg_double(image_node, "height", height);
1352 // Set transformation
1353 svgSetTransform(image_node, 1.0/(double)width, 0.0, 0.0, -1.0/(double)height, 0.0, 1.0);
1355 // Create href
1356 if (embed_image) {
1357 // Append format specification to the URI
1358 Glib::ustring& png_data = base64_string.getString();
1359 png_data.insert(0, "data:image/png;base64,");
1360 image_node->setAttribute("xlink:href", png_data.c_str());
1361 } else {
1362 fclose(fp);
1363 image_node->setAttribute("xlink:href", file_name);
1364 g_free(file_name);
1365 }
1367 return image_node;
1368 }
1370 void SvgBuilder::addImage(GfxState *state, Stream *str, int width, int height,
1371 GfxImageColorMap *color_map, int *mask_colors) {
1373 Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, mask_colors);
1374 if (image_node) {
1375 _container->appendChild(image_node);
1376 Inkscape::GC::release(image_node);
1377 }
1378 }
1381 } } } /* namespace Inkscape, Extension, Internal */
1383 #endif /* HAVE_POPPLER */
1385 /*
1386 Local Variables:
1387 mode:c++
1388 c-file-style:"stroustrup"
1389 c-file-offsets:((innamespace . 0)(inline-open . 0))
1390 indent-tabs-mode:nil
1391 fill-column:99
1392 End:
1393 */
1394 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :