The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

SVG::Rasterize - rasterize SVG content to pixel graphics

INHERITANCE

  SVG::Rasterize is a
    L<Class::Accessor|Class::Accessor>

VERSION

Version 0.000008

SYNOPSIS

    use SVG;
    use SVG::Rasterize;

    my $svg = SVG->new(width => 300, height => 200);
    $svg->line(x1 => 10, y1 => 20, x2 => 220, y2 => 150,
               style => {stroke => 'black', stroke-width => '2pt' });

    # add more svg content
    # .
    # .
    # .

    my $rasterize = SVG::Rasterize->new();
    $rasterize->rasterize(svg => $svg);
    $rasterize->write(type => 'png', file_name => 'out.png');

DESCRIPTION

SVG::Rasterize can be used to rasterize SVG objects to pixel graphics (currently png only) building on the Cairo library (by default, other underlying rasterization engines could be added). The direct rasterization of SVG files might be implemented in the future, right now you should have a look at SVG::Parser which can generate an SVG object from an svg file.

Motivation

In the past, I have used several programs to rasterize SVG graphics including Inkscape, Konqueror, Adobe Illustrator, and rsvg. While Inkscape was my favourite none of them made me entirely happy. There were always parts of the standard that I would have liked to use, but were unsupported.

So finally, I set out to write my own rasterization engine. The ultimate goal is complete compliance with the requirements for a Conforming Static SVG Viewer as described in the SVG specification: http://www.w3.org/TR/SVG11/conform.html#ConformingSVGViewers. Obviously, this is a long way to go. I do not know if any support for the dynamic features of SVG will ever be added. Anyway, the priority for SVG::Rasterize is accuracy, not speed.

Status

This release is a proof of concept. It mainly shows the successful deployment of Cairo. The support of transform attributes and the establishment of the initial viewport are fully implemented. However, the only things you can draw at the moment are lines with specified color and stroke width.

I hope that the interface described here will be largely stable. However, this is not guaranteed. Some features are documented as likely to change, but everything is subject to change at this early stage.

INTERFACE

Constructors

new

  $rasterize = SVG::Rasterize->new(%args)

Creates a new SVG::Rasterize object and calls init(%args). If you subclass SVG::Rasterize overload init, not new.

init goes through the arguments given to new. If a method of the same name exists it is called with the respective value as argument. This can be used for attribute initialization. Some of the values can be also given to rasterize to temporarily override the attribute values. The values of these overridable attributes are only validated once they are used by rasterize.

Supported arguments include:

  • svg (optional): A DOM object to render.

  • width (optional): width (in pixels) of the generated output image

  • height (optional): height (in pixels) of the generated output image

Any additional arguments are silently ignored. Note that the attribute values are only validated once their are used by rasterize.

Public Attributes

svg

Holds the DOM object to render. It does not have to be a SVG object, but it has to offer a getNodeName, a getAttributes, and a getChildren method analogous to those defined by the <SVG|SVG> class. If a different object is given to rasterize then the latter one overrules this value temporarily (i.e. without overwriting it).

width

The width of the generated output in pixels.

height

The height of the generated output in pixels.

There are other attributes that influence unit conversions, white space handling, and the choice of the underlying rasterization engine. See ADVANCED TOPICS.

Methods for Users

rasterize

  $rasterize->rasterize(%args)

Traverses through the given SVG content and renders the output. Does not return anything.

Examples:

  $rasterize->rasterize(svg => $svg);
  $rasterize->rasterize(svg => $svg, width => 640, height => 480);
  $rasterize->rasterize(svg => $svg, engine_class => 'My::Class');

Supported parameters:

  • svg (optional): DOM object to rasterize. If not specified the value of the svg attribute is used. One of them has to be set and has to provide the getNodeName, getAttributes, and getChildren DOM methods.

  • width (optional): width of the target image in pixels, temporarily overrides the width attribute.

  • height (optional): height of the target image in pixels, temporarily overrides the height attribute.

  • engine_class (optional): alternative engine class to SVG::Rasterize::Cairo, temporarily overrides the engine_class attribute. See SVG::Rasterize::Cairo for details on the interface. The value has to match the regular expression saved in PACKAGE_NAME.

  • normalize_attributes (optional): Influences White Space Handling, temporarily overrides the normalize_attributes attribute. Defaults to 1.

If width (the same applies to height) is 0 it is treated as not set. If you encounter any scenario where you would wish an explicit size of 0 to be treated in some other way let me know.

If width and/or height are not specified they have to have absolute values in the root SVG element. If both the root SVG element and the rasterize method have width and/or height settings then the rasterize parameters determine the size of the output image and the specified SVG viewport is mapped to this image taking the viewBox and preserveAspectRatio attributes into account if they are present. See http://www.w3.org/TR/SVG11/coords.html#ViewportSpace for details.

The user can influence the rasterization process via hooks. See the Hooks section below.

write

  $rasterize->write(%args)

Writes the rendered image to a file.

Example:

  $rasterize->write(type => 'png', file_name => 'foo.png');

The supported parameters depend on the rasterization backend. The write method hands all parameters over to the backend. See write in SVG::Rasterize::Cairo for an example.

Methods for Subclass Developers

init

  $rasterize->init(%args)

If you overload init, your method should also call this one.

For each given argument, init calls the accessor with the same name to initialize the attribute. If such an accessor (or in fact, any method of that name) does not exist a warning is printed and the argument is ignored. Readonly attributes that are allowed to be set at initialization time are set separately at the beginning.

Class Methods

multiply_matrices

2D affine transformation can be represented by 3 x 3 matrices of the form:

  ( a  c  e )
  ( b  d  f )
  ( 0  0  1 )

In this case, the concatenation of such transformations is represented by canonical matrix multiplication. This method takes two ARRAY references of the form [a, b, c, d, e, f] whose entries correspond to the matrix entries above and returns an ARRAY reference with 6 entries representing the product matrix.

The method can be called either as subroutine or as class method or as object method:

  $product = multiply_matrices($m, $n);
  $product = SVG::Rasterize->multiply_matrices($m, $n);
  $product = $rasterize->multiply_matrices($m, $n);

Note that multiply_matrices does not perform any input check. It expects that you provide (at least) two ARRAY references with (at least) 6 numbers each. If you pass more parameters then the last two are used. If they contain more than 6 entries then the first 6 are used.

ADVANCED TOPICS

Units

SVG supports the absolute units px, pt, pc, cm, mm, in, and the relative units em, ex, and %. Lengths can also be given as numbers without unit which is then interpreted as px. See http://www.w3.org/TR/SVG11/coords.html#Units.

SVG::Rasterize stores default values for unit conversion rates as class variables. You can either change these values or the corresponding object variables. If you have only one SVG::Rasterize object both approaches have the same effect.

The default values are listed below. Except px_per_in, they are taken from the CSS specification. See http://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units. The default for px_per_in is arbitrarily set to 90.

Currently, the relative units listed above are not supported by SVG::Rasterize.

Unit conversions:

  • px_per_in

    Pixels per inch. Defaults to 90.

  • dpi

    Alias for px_per_in. This is realized via a typeglob copy:

      *dpi = \&px_per_in
  • in_per_cm

    Inches per centimeter. Defaults to 1/2.54. This is the internationally defined value. I do not see why I should prohibit a change, but it would hardly make sense.

  • in_per_mm

    Inches per millimeter. Defaults to 1/25.4. This is the internationally defined value. I do not see why I should prohibit a change, but it would hardly make sense.

  • in_per_pt

    Inches per point. Defaults to 1/72. According to [1], this default was introduced by the Postscript language. There are other definitions. However, the CSS specification is quite firm about it.

  • in_per_pc

    Inches per pica. Defaults to 1/6. According to the CSS specification, 12pc equal 1pt.

  • map_abs_length

      $number = $rasterize->map_abs_length($length)
      $number = $rasterize->map_abs_length($number, $unit)

    This method takes a length and returns the corresponding value in px according to the conversion rates above. Surrounding white space is not allowed.

    Examples:

      $x = $rasterize->map_abs_length('5.08cm');  # returns 180
      $x = $rasterize->map_abs_length(10);        # returns 10
      $x = $rasterize->map_abs_length(10, 'pt')   # returns 12.5
      $x = $rasterize->map_abs_length('  1in ');  # croaks
      $x = $rasterize->map_abs_length('50%')      # croaks

    The unit has to be absolute, em, ex, and % trigger an exception. See map_length in SVG::Rasterize::State.

    There are two different interfaces. You can either pass one string or the number and unit separately. NB: In the second case, the input is not validated. This interface is meant for situations where the length string has already been parsed (namely in map_length in SVG::Rasterize::State) to avoid duplicate validation. The number is expected to match A_NUMBER and the unit to match UNIT (see below). However, it is still checked if the unit is absolute.

The corresponding class attributes are:

  • PX_PER_IN

    Defaults to 90.

  • DPI

    Alias for PX_PER_IN. This is realized via a typeglob copy

      *DPI = \$PX_PER_IN
  • IN_PER_CM

    Defaults to 1/2.54.

  • IN_PER_MM

    Defaults to 1/25.4.

  • IN_PER_PT

    Defaults to 1/72.

  • IN_PER_PC

    Defaults to 1/6.

Lengths versus Numbers

The SVG specification determines which values (e.g. in attributes) are lengths (numbers possibly with a unit) and which are numbers (without a unit). Some attributes have to be numbers although a length would make sense (e.g. in the viewBox attribute or a translate in a transform attribute). SVG::Rasterize aims for strict compliance with the specification. However, in the future there might be a relax attribute which when turned to 1 or higher allows a more and more relaxed interpretation.

White Space Handling

The XML specification (http://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize) states that an attribute value unless it is of the type CDATA shall be normalized such that leading and trailing white space is removed and internal white space is flattened to single space characters. XML entities can complicate this normalization, see the specification for details.

If the SVG tree to be rasterized by SVG::Rasterize comes out of an parsed XML document then the parser should have performed this normalization already. However, the tree might also be constructed directly using the SVG module. In order to prevent SVG::Rasterization from choking on an attribute like stroke-width="2pt " it performs by default an additional normalization run:

  $value =~ s/^$WSP*//;
  $value =~ s/$WSP*$//;
  $value =~ s/$WSP+/ /g;

where

  $WSP = qr/[\x{20}\x{9}\x{D}\x{A}]/;  # space, tab, CR, LF

To prevent this normalization, you can set the normalize_attributes attribute to a false value.

Hooks

The rasterize method traverses through the SVG tree and creates an SVG::Rasterize::State object for each node (at least each node of relevance). Hooks allow you to execute your own subroutines at given steps of this traversal. However, the whole hook business is experimental at the moment and likely to change. Right now, to set your own hooks you can set one of the following attributes to a code reference of your choice.

Currently, there are three hooks:

  • before_node_hook

    Executed at encounter of a new node right before the new SVG::Rasterize::State object is created. It is called as an object method and receives the SVG::Rasterize object, the node object (during DOM tree traversal, otherwise undef), the node name, and the the node attributes (as HASH reference). The attribute values have already been normalized at this stage (see White Space Handling above).

  • start_node_hook

    Executed right after creation of the SVG::Rasterize::State object. The attributes have been parsed, properties and matrix have been set etc. The method receives the SVG::Rasterize object and the SVG::Rasterize::State object.

  • end_node_hook

    Executed right before the SVG::Rasterize::State runs out of scope because the current node is done with. The method receives the SVG::Rasterize object and the SVG::Rasterize::State object.

Examples:

  $rasterize->start_node_hook(sub { ... })

Rasterization Backend

SVG::Rasterize does not render pixel graphics itself. By default, it uses the cairo library through its Perl bindings. The interface is provided by SVG::Rasterize::Cairo. However, this interface could also be implemented by other backends. In the future, it will be documented in SVG::Rasterize::Cairo. Currently, it has to be consisered unstable, though, and the documentation is sparse.

engine

  $rasterize->engine

This attribute holds the interface object to the rasterization backend, by default a SVG::Rasterize::Cairo object. The object is created by the rasterize method.

The attribute is readonly, but, of course, you are able to manipulate the object directly via its methods. However, this is not part of the normal workflow and you do this on your own risk ;-).

DIAGNOSTICS

Exceptions

Not documented, yet. Sorry.

Warnings

Not documented, yet. Sorry.

Error processing

The SVG documentation specifies how SVG interpreters should react to certain incidents. The relevant section can be found here: http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing.

This section will describe how some of these instructions are implemented by SVG::Rasterize and how it reacts in some other situations in which the specification does not give instructions. However, at the moment, SVG::Rasterize usually croaks whenever it is unhappy.

Skew of 90°

DEPENDENCIES

  • Class::Accessor, version 0.30 or higher

  • SVG, version 2.37 or higher

  • Cairo, version 1.061 or higher

    With respect to the actual code, the dependency on Cairo is not strict. The code only requires Cairo in case no other rasterization engine is specified. However, if you do not provide a different rasterization backend, which would probably at least require a wrapper written by you, then you cannot do anything without Cairo. Therefore I have included it as a strict dependency. Feel free take that out of the Makefile.PL if you know what you are doing.

  • Params::Validate, version 0.91 or higher

  • Test::More, version 0.86 or higher

  • Test::Exception, version 0.27 or higher

  • Scalar::Util, version 1.19 or higher

BUGS AND LIMITATIONS

Bugs

No bugs have been reported. Please report any bugs or feature requests to bug-svg-rasterize at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=SVG-Rasterize. I will be notified, and then you will automatically be notified of progress on your bug as I make changes.

Caveats

eval BLOCK and $SIG{__DIE__}

Several methods in this distribution use eval BLOCK statements without setting a local $SIG{__DIE__}. Therefore, a $SIG{__DIE__} installed somewhere else can be triggered by these statements. See die and eval in perlfunc and $^S in perlvar.

Thread safety

I do not know much about threads and how to make a module thread safe. No specific measures have been taken to achieve thread safety of this distribution.

Parameter Checking

SVG::Rasterize uses Params::Validate for parameter validation. However, it currently does not do that as thoroughly as one would wish for. Do not rely on that wrong stuff will not pass unnoticed.

Test Suite

The test suite covers essential features, but is far from exhaustive.

INTERNALS

Regular Expressions

The following regular expressions are used at different locations of the code to validate or extract user input. They are listed in the INTERNALS section because it is not part of the interface where exactly they are used. They are documented for inspection only. They are compiled into other expressions so changing them will probably not achieve what you might expect. The exception to this rule is the PACKAGE_NAME variable. The following items are global variables in the SVG::Rasterize package. They are more or less a direct translation of parts of the Backus Naur form given by the SVG specification for the transform attribute (http://www.w3.org/TR/SVG11/coords.html#TransformAttribute).

  • PACKAGE_PART

      qr/[a-zA-Z][a-zA-Z0-9\_]*/
  • PACKAGE_NAME

      qr/^$PACKAGE_PART(?:\:\:PACKAGE_PART)*$/

    Package names given to methods in this class, namely the engine_class parameters have to match this regular expression. I am not sure which package names exactly are allowed. If you know where in the Perl manpages or the Camel book this is described, please point me to it. If this pattern is too strict for your favourite package name, you can change this variable.

  • INTEGER

      qr/[\+\-]?\d+/;

    Note that this allows leading zeroes like '00030'. This is for compliance with the SVG specification.

  • FRACTION

      qr/[\+\-]?(?:\d*\.\d+|\d+\.)/;

    Floating point number in non-scientific notation. Note that this allows leading zeroes like '000.123'. This is for compliance with the SVG specification.

  • EXPONENT

      qr/[eE][\+\-]?\d+/;
  • FLOAT

      qr/$FRACTION$EXPONENT?|$INTEGER$EXPONENT/;

    Floating point number in decimal or scientific notation.

  • P_NUMBER

      qr/$INTEGER|$FRACTION/;

    Number allowed in CSS compliant style properties: integer or float in decimal notation.

  • A_NUMBER

      qr/$INTEGER|$FLOAT/;

    Number allowed in XML attributes. Integer or float in either decimal or scientific notation.

  • UNIT

      qr/em|ex|px|pt|pc|cm|mm|in|\%/;
  • P_LENGTH

      qr/$P_NUMBER$UNIT?/;
  • A_LENGTH

      qr/$A_NUMBER$UNIT?/;

Internal Methods

  • _in_error

  • _create_engine

  • _process_initial_viewport_length

  • _initial_viewport

  • _draw_line

FOOTNOTES

  • [1] Yannis Haralambous: Fonts & Encodings. O'Reilly, 2007.

    Tons of information about what the author calls the "digital space for writing".

SEE ALSO

ACKNOWLEDGEMENTS

This distribution builds heavily on the cairo library and its Perl bindings.

AUTHOR

Lutz Gehlen, <perl at lutzgehlen.de>

LICENSE AND COPYRIGHT

Copyright 2010 Lutz Gehlen.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 976:

Non-ASCII character seen before =encoding in '90°'. Assuming UTF-8