SVG-Rasterize

 view release on metacpan or  search on metacpan

lib/SVG/Rasterize/State.pm  view on Meta::CPAN

package SVG::Rasterize::State;
use base Class::Accessor;

use warnings;
use strict;

use 5.008009;

use Params::Validate qw(:all);
use Scalar::Util qw(blessed looks_like_number);
use List::Util qw(min max);

use SVG::Rasterize::Regexes qw(:whitespace
                               :attributes);
use SVG::Rasterize::Specification qw(:all);
use SVG::Rasterize::Properties;
use SVG::Rasterize::Colors;
use SVG::Rasterize::Exception qw(:all);

# $Id: State.pm 6675 2011-05-02 05:35:09Z powergnom $

=head1 NAME

C<SVG::Rasterize::State> - state of settings during traversal

=head1 VERSION

Version 0.003007

=cut

our $VERSION = '0.003007';


__PACKAGE__->mk_accessors(qw());

__PACKAGE__->mk_ro_accessors(qw(parent
                                rasterize
                                node_name
                                cdata
                                node
                                matrix
                                properties
                                defer_rasterization
                                text_atoms));

###########################################################################
#                                                                         #
#                      Class Variables and Methods                        # 
#                                                                         #
###########################################################################

sub make_ro_accessor {
    my($class, $field) = @_;

    return sub {
        my $self = shift;

        if (@_) {
            my $caller = caller;
            $self->ex_at_ro("${class}->${field}");
        }
        else {
            return $self->get($field);
        }
    };
}

use constant PI => 3.14159265358979;

*multiply_matrices = \&SVG::Rasterize::multiply_matrices;

###########################################################################
#                                                                         #
#                             Init Process                                #
#                                                                         #
###########################################################################

sub new {
    my ($class, @args) = @_;

    my $self = bless {}, $class;
    return $self->init(@args);
}

sub init {
    my ($self, @args) = @_;
    my %args          = validate_with
	(params  => \@args,
	 spec    =>
	     {rasterize       => {isa      => 'SVG::Rasterize'},
	      parent          => {isa      => 'SVG::Rasterize::State',
				  optional => 1},
	      node_name       => {type     => SCALAR},
	      node_attributes => {type     => HASHREF},
	      cdata           => {type     => SCALAR|UNDEF},
	      node            => {type     => OBJECT,
				  optional => 1},
	      child_nodes     => {type     => ARRAYREF|UNDEF},
	      matrix          => {type     => ARRAYREF,
				  optional => 1}},
	on_fail => sub { SVG::Rasterize->ex_pv($_[0]) });

    # read only and private arguments
    if(exists($args{parent})) { $self->{parent} = $args{parent} }
    $self->{rasterize} = $args{rasterize};

    $self->{node_name}       = $args{node_name};
    $self->{node_attributes} = $args{node_attributes};
    $self->{child_nodes}     = $args{child_nodes};
    $self->{cdata}           = $args{cdata};
    $self->{node}            = $args{node}   if(exists($args{node}));
    $self->{matrix}          = $args{matrix} if(exists($args{matrix}));

    # check matrix and set default
    $self->{matrix} ||= [1, 0, 0, 1, 0, 0];
    foreach(@{$self->{matrix}}) {
	$self->ex_pm_ma_nu('undef') if(!defined($_));
	$self->ex_pm_ma_nu($_)      if(!looks_like_number($_));
    }

    # rebless if necessary
    my $treat_as_text = 0;
    while(my ($key, $value) = each %SVG::Rasterize::TEXT_ROOT_ELEMENTS) {
	next if(!$value);
	$treat_as_text = 1 if($self->{node_name} eq $key);
	$treat_as_text = 1 if(spec_has_child($key, $self->{node_name}));

	if($args{node_name} eq '#text' and $self->{parent}) {
	    my $p_node_name = $self->{parent}->node_name;
	    $treat_as_text = 1 if($p_node_name eq $key);
	    $treat_as_text = 1 if(spec_has_child($key, $p_node_name));
	}
    }
    if($treat_as_text) {
	bless $self, 'SVG::Rasterize::State::Text';
    }

    $self->_process_node;

    return $self;
}

sub _process_transform_attribute {
    my ($self)    = @_;
    my $transform = $self->{node_attributes}->{transform};

    return if(!$transform);

    # dissect string into single transformation strings
    my @atoms = ();
    my $str   = $transform;
    while($str) {
	if($str =~ $RE_TRANSFORM{TRANSFORM_SPLIT}) {
	    push(@atoms, $1);
	    $str = $2;
	}
	else { $self->ex_pa('transform', $transform) }
    }
    
    # process the single transformations
    my $sm = $self->{matrix};
    foreach(@atoms) {
	my ($type, $param_str) = $_ =~ $RE_TRANSFORM{TRANSFORM_CAPTURE};
	my @params             = split(/$CWSP/, $param_str);

	my $cm;  # current matrix
	if   ($type eq 'matrix') { $cm = [@params] }
	elsif($type eq 'translate') {
	    $cm = [1, 0, 0, 1, $params[0], $params[1] || 0];
	}

lib/SVG/Rasterize/State.pm  view on Meta::CPAN

		else {
		    $size = $self->{rasterize}->relative_font_size
			($properties->{$_});
		    if(defined($size)) {
			$properties->{$_} = $self->map_length($size);
		    }
		    else {
			$self->ex_pa('font-size', $properties->{$_});
		    }
		}
	    }
	}
	if($_ eq 'font-weight') {
	    if($properties->{$_} eq 'normal') {
		$properties->{$_} = 400;
	    }
	    elsif($properties->{$_} eq 'bold') {
		$properties->{$_} = 700;
	    }
	    if($properties->{$_} eq 'bolder') {
		# TODO
		$self->ex_us_si(q{font-weight 'bolder'});
	    }
	    elsif($properties->{$_} eq 'lighter') {
		# TODO
		$self->ex_us_si(q{font-weight 'lighter'});
	    }
	    elsif($properties->{$_} !~ /^[1-9]00$/) {
		$self->ex_pa('font-weight', $properties->{$_});
	    }
	}
	if($_ eq 'font-stretch') {
	    if($properties->{$_} eq 'narrower') {
		# TODO
		$self->ex_us_si(q{font-stretch 'narrower'});
	    }
	    elsif($properties->{$_} eq 'wider') {
		# TODO
		$self->ex_us_si(q{font-stretch 'wider'});
	    }
	}
    }

    $self->{properties} = $properties;
    return $properties;  # just a return value that makes sense, if
                         # you change it check #text return above
}

sub process_node_extra {}

sub _process_node {
    my ($self)     = @_;
    my $name       = $self->{node_name};
    my $attributes = $self->{node_attributes};

    # element validation either as a child or as existing element
    if($self->{parent}) {
	# allowed child element?
	my $p_node_name = $self->{parent}->node_name;
	if($name eq '#text') {
	    if(!spec_has_pcdata($p_node_name)) {
		$self->ie_el($name, $p_node_name);
	    }	    
	}
	else {
	    if(!spec_has_child($p_node_name, $name)) {
		$self->ie_el($name, $p_node_name);
	    }
	}
    }
    else {
	# as root element we accept any existing element
	$self->ie_el($name) if(!spec_is_element($name));
    }

    # Attribute validation except for cdata nodes. They have no
    # attributes and the Specification modules do not hold
    # validation information for them.
    unless($name eq '#text') {
	my @attr_buffer = %$attributes;
	validate_with(params  => \@attr_buffer,
		      spec    => spec_attribute_validation($name),
		      on_fail => sub { $self->ie_at_pv($_[0]) });
    }

    # defer rasterization
    if($SVG::Rasterize::DEFER_RASTERIZATION{$name} or
       $self->{parent} and $self->{parent}->defer_rasterization)
    {
	$self->{defer_rasterization} = 1;
    }

    # apply transformations
    if($self->{parent} and $self->{parent}->{matrix}) {
	$self->{matrix} = multiply_matrices($self->{parent}->{matrix},
					    $self->{matrix});
    }
    $self->_process_transform_attribute  if($attributes->{transform});
    $self->_process_viewBox_attribute    if($attributes->{viewBox});
    $self->_process_style_properties;
    $self->process_node_extra;

    return;
}

###########################################################################
#                                                                         #
#                               Accessors                                 # 
#                                                                         #
###########################################################################

sub node_attributes {
    my ($self) = @_;

    $self->{node_attributes} ||= {};
    return $self->{node_attributes};
}

###########################################################################
#                                                                         #
#                             Retrieve Data                               #
#                                                                         #
###########################################################################

sub map_length {
    my ($self, @args) = @_;

    validate_with(params  => \@args,
		  spec    => [{regex => $RE_LENGTH{p_A_LENGTH}}],
		  on_fail => sub { $self->ie_pv($_[0]) });

    my ($number, $unit) =
	$args[0] =~ /^($RE_NUMBER{A_NUMBER})($RE_LENGTH{UNIT}?)$/;
    
    if(!$unit)           { return $number }
    elsif($unit eq 'em') { $self->ex_us_is("Unit em") }

lib/SVG/Rasterize/State.pm  view on Meta::CPAN

#                                                                         #
###########################################################################

sub shift_child_node {
    my ($self) = @_;

    return shift(@{$self->{child_nodes} || []});
}

1;


__END__

=pod

=head1 DESCRIPTION

An instance of this class saves one state during the traversal
through an C<SVG> tree. At encounter of a new child element the old
state is pushed to a stack and retrieved later. A state saves the
current transformation matrix, style settings and so on. Part of
this functionality overlaps with the ability of L<Cairo|Cairo> to
push its state onto a stack, but I do not want to entirely rely on
that because I am not sure if everything can be handled in that way
and also because future alternative backends might not have this
feature.

This class is instanced only by L<SVG::Rasterize|SVG::Rasterize>.
The information of this document will mainly be interesting for
maintainers of L<SVG::Rasterize|SVG::Rasterize> and possibly for
advanced users who want to write L<hooks|SVG::Rasterize/Hooks>.

=head1 INTERFACE

=head2 Constructors

=head3 new

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

Creates a new C<SVG::Rasterize::State> object and calls
C<init(%args)>. If you subclass C<SVG::Rasterize::State> overload
L<init|/init>, not C<new>.

Supported arguments:

=over 4

=item * rasterize (mandatory): L<SVG::Rasterize|SVG::Rasterize>
object

=item * parent (optional): the parent state object, always
expected except for the root

=item * node_name (mandatory): defined scalar, name of the current
node

=item * node_attributes (mandatory): HASH reference

=item * cdata (mandatory): C<undef> or scalar (no reference)

=item * child_nodes (mandatory): C<undef> or an ARRAY reference

Array entries are not further validated as they are not used within
this object. Do not just provide the return value of
C<getChildNodes> on a node object, because modification of the array
(e.g. by L<shift_child_node|/shift_child_node>) will (usually)
affect the list saved in the node object itself. Make a copy,
e.g. C<< [@{$node->getChildNodes}] >>. Note that changing the
objects in the list will still affect the child nodes saved in the
node object unless you perform some kind of deep cloning.

=item * node (optional): must be a blessed reference; unused, but
available for hooks

=item * matrix (optional): must be an ARRAY reference if provided

=back

=head2 Public Attributes

=head3 parent

Can only be set at construction time. Stores a reference to
the parent state object.

=head3 rasterize

Can only be set at construction time. Stores a reference to
the L<SVG::Rasterize|SVG::Rasterize> object.

=head3 node

Can only be set at construction time. If the C<SVG> data to
rasterize are provided as an L<SVG|SVG> object (or, in fact, some
C<DOM> object in general) this attribute stores the node object for
which this state object was created. All processing uses the
L<node_name|/node_name> and L<node_attributes|/node_attributes>
attributes which are always present. It is also recommended that you
use these instead of C<node> wherever possible. For example,
C<< $node->getAttributes >> might be undefined or not normalized
(see L<White Space Handling|SVG::Rasterize/White Space Handling> in
C<SVG::Rasterize>).

This attribute is only provided for use in hooks. Note that it is
not validated. If set at all it holds a blessed reference, but
nothing else is checked (within this class).

=head3 node_name

Can only be set at construction time. Stores the name of the current
node even if L<node|/node> above is C<undef>. If it differs from
C<< $node->getNodeName >> (usage not recommended), C<node_name> is
used.

=head3 node_attributes

Can only be set at construction time (any arguments to the accessor
are silently ignored). Stores the attributes of the current node as
a HASH reference even if L<node|/node> above is C<undef>. The
accessor does not create a copy of the hash, so changes will affect
the hash stored in the object. This is on purpose to give you full
control e.g. inside a L<hook|SVG::Rasterize/Hooks>. In case the node
has no attributes an empty HASH reference is returned. If the
content differs from C<< $node->getAttributes >> (usage not
recommended), C<node_attributes> is used.

=head3 cdata

Can only be set on construction time. If the node is a character
data node, those character data can be stored in here.

=head3 matrix

Readonly attribute (you can change the contents, of course, but this
is considered a hack bypassing the interface). Stores an ARRAY
reference with 6 numbers C<[a, b, c, d, e, f]> such that the matrix

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

represents the map from coordinates in the current user coordinate
system to the output pixel grid. See
L<multiply_matrices|SVG::Rasterize/multiply_matrices> in
C<SVG::Rasterize> for more background.

Before you use the matrix directly have a look at
L<transform|/transform> below.

=head3 defer_rasterization

Readonly attribute. Some elements (namely C<text> elements) can only
be rasterized once their entire content is known (e.g. for alignment
issues). If an C<SVG::Rasterize::State> object is initialized with
such an element or if the parent state is deferring rasterization
then this attribute is set to C<1> at construction time. The content
is then only rasterized once the root element of this subtree
(i.e. the element whose parent is not deferring) is about to run out
of scope.

=head2 Methods for Users

The distinction between users and developers is a bit arbitrary
because these methods are only interesting for users who write
hooks which makes them almost a developer.

=head3 map_length

  $state->map_length($length)

This method takes a length and returns the corresponding value in
C<px> according to the conversion rates described in the L<ADVANCED
TOPICS|SVG::Rasterize/Units> section of
C<SVG::Rasterize>. Surrounding white space is not allowed.

B<Examples:>

  $x = $rasterize->map_length('5.08cm');  # returns 180
  $x = $rasterize->map_length(10);        # returns 10
  $x = $rasterize->map_length('  1in ');  # error
  $x = $rasterize->map_length('50%')      # depends on context

Note that there is no C<< $state->map_length($number, $unit) >>
interface like in
L<map_abs_length|SVG::Rasterize/map_abs_length> in
C<SVG::Rasterize>. It can be added on request.



( run in 1.366 second using v1.01-cache-2.11-cpan-df04353d9ac )