svg-builder.cpp revision 1db439af43130c9695dbbb661e893d56006bb072
/** \file
* Native PDF import using libpoppler.
*
* Authors:
* miklos erdelyi
*
* Copyright (C) 2007 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef HAVE_POPPLER
#include "svg-builder.h"
#include "pdf-parser.h"
#include <png.h>
#include "document-private.h"
#include "xml/document.h"
#include "svg/path-string.h"
#include "svg/css-ostringstream.h"
#include "svg/svg-color.h"
#include "color.h"
#include "unit-constants.h"
#include "io/stringstream.h"
#include "io/base64stream.h"
#include "libnr/nr-matrix-ops.h"
#include "libnr/nr-macros.h"
#include "libnrtype/font-instance.h"
#include "Function.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Stream.h"
#include "Page.h"
#include "UnicodeMap.h"
#include "GlobalParams.h"
namespace Inkscape {
namespace Extension {
namespace Internal {
//#define IFTRACE(_code) _code
/**
* \struct SvgTransparencyGroup
* \brief Holds information about a PDF transparency group
*/
struct SvgTransparencyGroup {
double bbox[6];
bool isolated;
bool knockout;
bool for_softmask;
};
/**
* \class SvgBuilder
*
*/
_is_top_level = true;
_init();
// Set default preference settings
}
_is_top_level = false;
_init();
}
SvgBuilder::~SvgBuilder() {
}
void SvgBuilder::_init() {
_in_text_object = false;
_need_font_update = true;
_invalidated_style = true;
_font_style = NULL;
initial_state.group_depth = 0;
}
}
/**
* \brief Sets groupmode of the current container to 'layer' and sets its label if given
*/
if (layer_name) {
}
}
/**
* \brief Sets the current container's opacity
*/
}
void SvgBuilder::saveState() {
new_state.group_depth = 0;
pushGroup();
}
void SvgBuilder::restoreState() {
popGroup();
}
}
_container = node;
return node;
}
} else {
TRACE(("popNode() called when stack is empty\n"));
}
return node;
}
// Set as a layer if this is a top-level group
static int layer_count = 1;
if ( layer_count > 1 ) {
} else {
}
}
return _container;
}
popNode();
}
return _container;
}
return _container;
}
static gchar *svgConvertRGBToText(double r, double g, double b) {
"#%02x%02x%02x",
}
double r = (double)color->r / 65535.0;
double g = (double)color->g / 65535.0;
double b = (double)color->b / 65535.0;
return svgConvertRGBToText(r, g, b);
}
}
}
/**
* \brief Generates a SVG path string from poppler's data structure
*/
int i, j;
for ( i = 0 ; i < path->getNumSubpaths() ; ++i ) {
if (subpath->getNumPoints() > 0) {
j = 1;
while (j < subpath->getNumPoints()) {
j += 3;
} else {
++j;
}
}
}
}
}
}
/**
* \brief Sets stroke style from poppler's GfxState data structure
* Uses the given SPCSSAttr for storing the style properties
*/
// Check line width
// Ignore stroke
return;
}
if (urltext) {
}
} else {
}
// Opacity
// Line width
// Line cap
switch (state->getLineCap()) {
case 0:
break;
case 1:
break;
case 2:
break;
}
// Line join
switch (state->getLineJoin()) {
case 0:
break;
case 1:
break;
case 2:
break;
}
// Miterlimit
// Line dash
double *dash_pattern;
int dash_length;
double dash_start;
if ( dash_length > 0 ) {
for ( int i = 0 ; i < dash_length ; i++ ) {
os_array << dash_pattern[i];
if (i < (dash_length - 1)) {
os_array << ",";
}
}
os_offset << dash_start;
} else {
}
}
/**
* \brief Sets fill style from poppler's GfxState data structure
* Uses the given SPCSSAttr for storing the style properties.
*/
if (urltext) {
}
} else {
}
// Opacity
// Fill rule
}
/**
* \brief Sets style properties from poppler's GfxState data structure
* \return SPCSSAttr with all the relevant properties set
*/
if (fill) {
} else {
}
if (stroke) {
} else {
}
return css;
}
/**
* \brief Emits the current path in poppler's GfxState data structure
* Can be used to do filling and stroking at once.
*
* \param fill whether the path should be filled
* \param stroke whether the path should be stroked
* \param even_odd whether the even-odd rule should be used when filling the path
*/
// Set style
}
/**
* \brief Emits the current path in poppler's GfxState data structure
* The path is set to be filled with the given shading.
*/
bool even_odd) {
// Set style
if (id) {
} else {
return;
}
if (even_odd) {
}
// Remove the clipping path emitted before the 'sh' operator
int up_walk = 0;
if (clip_path_url) {
// Obtain clipping path's id from the URL
if (clip_obj) {
clip_obj->deleteObject();
}
break;
}
up_walk++;
}
}
/**
* \brief Clips to the current path set in GfxState
* \param state poppler's data structure
* \param even_odd whether the even-odd rule should be applied
*/
pushGroup();
}
// Create the clipPath repr
// Create the path
if (even_odd) {
}
// Append clipPath to defs and get id
}
/**
* \brief Fills the given array with the current container's transform, if set
* \param transform array of doubles to be filled
* \return true on success; false on invalid transformation
*/
if (valid) {
for ( int i = 0 ; i < 6 ; i++ ) {
}
return true;
} else {
return false;
}
}
/**
* \brief Sets the transformation matrix of the current container
*/
}
}
/**
* \brief Checks whether the given pattern type can be represented in SVG
* Used by PdfParser to decide when to do fallback operations.
*/
return true;
}
return false;
return true;
}
}
return false;
}
/**
* \brief Creates a pattern from poppler's data structure
* Handles linear and radial gradients. Creates a new PdfParser and uses it to
* build a tiling pattern.
* \return an url pointing to the created pattern
*/
shading_pattern->getMatrix());
}
} else {
return NULL;
}
return urltext;
}
/**
* \brief Creates a tiling pattern from poppler's data structure
* Creates a sub-page PdfParser and uses it to parse the pattern's content stream.
* \return id of the created pattern
*/
// Set pattern transform matrix
// Set pattern tiling
// FIXME: don't ignore XStep and YStep
// Convert BBox for PdfParser
// Create new SvgBuilder and sub-page PdfParser
&box);
// Get pattern color space
: state->getFillColorSpace() );
}
// Generate the SVG pattern
// Cleanup
delete pdf_parser;
delete pattern_builder;
// Append the pattern to defs
return id;
}
/**
* \brief Creates a linear or radial gradient from poppler's data structure
* \param shading poppler's data structure for the shading
* \param matrix gradient transformation, can be null
* \param for_shading true if we're creating this for a shading operator; false otherwise
* \return id of the created object
*/
int num_funcs;
// FIXME: the inner circle's radius is ignored here
} else { // Unsupported shading type
return NULL;
}
// If needed, flip the gradient transform around the y axis
if (matrix) {
if ( !for_shading && _is_top_level ) {
pat_matrix *= flip;
}
}
}
return NULL;
}
return id;
}
#define EPSILON 0.0001
/**
* \brief Adds a stop with the given properties to the gradient's representation
*/
os_opacity << gray;
color_text = "#ffffff";
} else {
os_opacity << opacity;
}
}
} else {
return false;
}
// Convert it to RGB
return true;
}
#define INT_EPSILON 8
return false;
} else {
}
// Add stops from all the stitched functions
for ( int i = 0 ; i < num_funcs ; i++ ) {
bool is_continuation = false;
if ( i > 0 ) { // Compare to previous stop
is_continuation = true;
}
}
// Add stops
if ( !is_continuation ) {
}
}
}
} else { // Unsupported function type
return false;
}
return true;
}
/**
* \brief Sets _invalidated_style to true to indicate that styles have to be updated
* Used for text output when glyphs are buffered till a font change
*/
if (_in_text_object) {
_invalidated_style = true;
}
}
/**
* This array holds info about translating font weight names to more or less CSS equivalents
*/
static char *font_weight_translator[][2] = {
{"bold", "bold"},
{"light", "300"},
{"black", "900"},
{"heavy", "900"},
{"ultrabold", "800"},
{"extrabold", "800"},
{"demibold", "600"},
{"semibold", "600"},
{"medium", "500"},
{"book", "normal"},
{"regular", "normal"},
{"roman", "normal"},
{"normal", "normal"},
{"ultralight", "200"},
{"extralight", "200"},
{"thin", "100"}
};
/**
* \brief Updates _font_style according to the font set in parameter state
*/
TRACE(("updateFont()\n"));
_need_font_update = false;
if (_font_style) {
//sp_repr_css_attr_unref(_font_style);
}
// Store original name
if (font->getOrigName()) {
} else {
_font_specification = "Arial";
}
// Prune the font name to get the correct font family name
// In a PDF font names can look like this: IONIPB+MetaPlusBold-Italic
char *font_family = NULL;
char *font_style = NULL;
char *font_style_lowercase = NULL;
if (plus_sign) {
} else {
}
char *style_delim = NULL;
style_delim[0] = 0;
}
// Font family
} else {
}
// Font style
} else if (font_style) {
}
}
// Font variant -- default 'normal' value
// Font weight
char *css_font_weight = NULL;
css_font_weight = "normal";
css_font_weight = "bold";
} else {
}
} else if (font_style) {
// Apply the font weight translations
for ( int i = 0 ; i < num_translations ; i++ ) {
}
}
} else {
css_font_weight = "normal";
}
if (css_font_weight) {
}
if (font_style_lowercase) {
}
// Font stretch
switch (font_stretch) {
case GfxFont::UltraCondensed:
stretch_value = "ultra-condensed";
break;
case GfxFont::ExtraCondensed:
stretch_value = "extra-condensed";
break;
stretch_value = "condensed";
break;
case GfxFont::SemiCondensed:
stretch_value = "semi-condensed";
break;
stretch_value = "normal";
break;
case GfxFont::SemiExpanded:
stretch_value = "semi-expanded";
break;
stretch_value = "expanded";
break;
case GfxFont::ExtraExpanded:
stretch_value = "extra-expanded";
break;
case GfxFont::UltraExpanded:
stretch_value = "ultra-expanded";
break;
default:
break;
}
if ( stretch_value != NULL ) {
}
// Font size
if ( font_matrix[0] != 0.0 ) {
}
}
// Writing mode
} else {
}
_invalidated_style = true;
}
/**
* \brief Shifts the current text position by the given amount (specified in text space)
*/
} else {
_text_position[0] += shift_value;
}
}
/**
* \brief Updates current text position
*/
}
/**
* \brief Flushes the buffered characters
*/
_flushText();
// Update text matrix
double max_scale;
} else {
}
// Calculate new text matrix
0.0, 0.0);
// Cancel out scaling by font size in text matrix
for ( int i = 0 ; i < 4 ; i++ ) {
new_text_matrix[i] /= max_scale;
}
}
}
/**
* \brief Writes the buffered characters to the SVG document
*/
void SvgBuilder::_flushText() {
// Ignore empty strings
return;
}
const SvgGlyph& first_glyph = (*i);
// Ignore invisible characters
if ( render_mode == 3 ) {
return;
}
// Set text matrix
bool new_tspan = true;
bool same_coords[2] = {true, true};
unsigned int glyphs_in_a_row = 0;
// Output all buffered glyphs
while (1) {
// Check if we need to make a new tspan
if (glyph.style_changed) {
new_tspan = true;
new_tspan = true;
}
}
// Create tspan node if needed
if (tspan_node) {
// Set the x and y coordinate arrays
if ( same_coords[0] ) {
} else {
}
if ( same_coords[1] ) {
} else {
}
if ( glyphs_in_a_row > 1 ) {
}
// Add text content node to tspan
// Clear temporary buffers
text_buffer.clear();
glyphs_in_a_row = 0;
}
break;
} else {
// Set style and unref SPCSSAttr if it won't be needed anymore
}
}
new_tspan = false;
}
if ( glyphs_in_a_row > 0 ) {
// Check if we have the same coordinates
for ( int p = 0 ; p < 2 ; p++ ) {
same_coords[p] = false;
}
}
}
// Append the coordinates to their respective strings
// Append the character to the text buffer
i++;
}
}
if (_need_font_update) {
}
}
/**
* \brief Adds the specified character to the text buffer
* Takes care of converting it to UTF-8 and generates a new style repr if style
* has changed since the last call.
*/
// Skip beginning space
_text_position += delta;
return;
}
// Allow only one space in a row
_text_position += delta;
return;
}
_text_position += delta;
// Convert the character to UTF-8 since that's our SVG document's encoding
delete enc;
}
int code_size = 0;
for ( int i = 0 ; i < uLen ; i++ ) {
code_size += u_map->mapUnicode(u[i], (char *)&new_glyph.code[code_size], sizeof(new_glyph.code) - code_size);
}
// Copy current style if it has changed since the previous glyph
new_glyph.style_changed = true;
// Set style
_invalidated_style = false;
} else {
new_glyph.style_changed = false;
// Point to previous glyph's style information
}
}
}
_in_text_object = true;
_invalidated_style = true; // Force copying of current state
}
_flushText();
// TODO: clip if render_mode >= 4
_in_text_object = false;
}
/**
* Helper functions for supporting direct PNG output into a base64 encoded stream
*/
{
for ( unsigned i = 0 ; i < length ; i++ ) {
}
}
{
}
/**
* \brief Creates an <image> element containing the given ImageStream as a PNG
*
*/
// Create PNG write struct
return NULL;
}
// Create PNG info struct
return NULL;
}
// Set error handler
return NULL;
}
// Decide whether we should embed this image
double attr_value = 1.0;
if (embed_image) {
} else {
static int counter = 0;
return NULL;
}
}
// Set header data
if ( !invert_alpha && !alpha_only ) {
}
if (alpha_only) {
8, /* bit_depth */
} else {
8, /* bit_depth */
}
// Write the file header
// Convert pixels
if (alpha_only) {
if (color_map) {
} else {
}
image_stream->reset();
// Convert grayscale values
for ( int y = 0 ; y < height ; y++ ) {
if (color_map) {
} else {
for ( int x = 0 ; x < width ; x++ ) {
if ( row[x] ^ invert_bit ) {
*buf_ptr++ = 0;
} else {
*buf_ptr++ = 255;
}
}
}
}
delete buffer;
} else if (color_map) {
image_stream->reset();
// Convert RGB values
if (mask_colors) {
for ( int y = 0 ; y < height ; y++ ) {
for ( int x = 0 ; x < width ; x++ ) {
// Check each color component against the mask
for ( int i = 0; i < color_map->getNumPixelComps() ; i++) {
break;
}
}
// Advance to the next pixel
dest++;
}
// Write it to the PNG
}
} else {
for ( int i = 0 ; i < height ; i++ ) {
}
}
delete buffer;
} else { // A colormap must be provided, so quit
if (!embed_image) {
}
return NULL;
}
delete image_stream;
// Close PNG
// Create repr
// Set transformation
if (_is_top_level) {
}
// Create href
if (embed_image) {
// Append format specification to the URI
} else {
}
return image_node;
}
/**
* \brief Creates a <mask> with the specified width and height and adds to <defs>
* If we're not the top-level SvgBuilder, creates a <defs> too and adds the mask to it.
* \return the created XML node
*/
// Append mask to defs
if (_is_top_level) {
} else { // Work around for renderer bug when mask isn't defined in pattern
static int mask_count = 0;
// Create <defs> node
}
}
}
if (image_node) {
}
}
bool invert) {
// Create a rectangle
// Get current fill style and set it on the rectangle
// Scaling 1x1 surfaces might not work so skip setting a mask with this size
if (mask_image_node) {
// Create the mask
// Remove unnecessary transformation from the mask image
}
}
// Add the rectangle to the container
}
bool invert_mask) {
if ( mask_image_node && image_node ) {
// Create mask for the image
// Remove unnecessary transformation from the mask image
// Scale the mask to the size of the image
// Set mask and add image
}
if (mask_image_node) {
}
if (image_node) {
}
}
mask_color_map, NULL, true);
if ( mask_image_node && image_node ) {
// Create mask for the image
// Remove unnecessary transformation from the mask image
// Set mask and add image
}
if (mask_image_node) {
}
if (image_node) {
}
}
/**
* \brief Starts building a new transparency group
*/
bool for_softmask) {
// Push node stack
pushNode("svg:g");
// Setup new transparency group
// Push onto the stack
}
// Restore node stack
popNode();
}
/**
* \brief Places the current transparency group into the current container
*/
// Pop the stack
delete transpGroup;
}
/**
* \brief Creates a mask using the current transparency group as its content
*/
// Create mask
// Add the softmask content to it
// Apply the mask
pushGroup();
// Pop the stack
delete transpGroup;
}
popGroup();
}
}
} } } /* namespace Inkscape, Extension, Internal */
#endif /* HAVE_POPPLER */
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :