SVG-Rasterize

 view release on metacpan or  search on metacpan

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

	if($points_str =~ $RE_POLY{POINTS_SPLIT}) {
	    push(@points, [$1, $2]);
	    $points_str = $3;
	}
	else { last }
    }

    my $engine = $self->{engine};
    my $result;
    if($engine->can('draw_polyline')) {
	$result = $engine->draw_polyline($state, @points);
    }
    else {
	$result = $engine->draw_path
	    ($state,
	     ['M', @{shift(@points)}],
	     map { ['L', @$_] } @points);
    }

    if($points_str) { $self->ie_at_po($state->node_attributes->{points}) }
    else            { return $result }
}

sub _process_polygon {
    my ($self, $state, %args) = @_;

    return if($args{queued});

    my $points_str = $state->node_attributes->{points};

    return if(!$points_str);

    my @points;
    while($points_str) {
	if($points_str =~ $RE_POLY{POINTS_SPLIT}) {
	    push(@points, [$1, $2]);
	    $points_str = $3;
	}
	else { last }
    }

    my $engine = $self->{engine};
    my $result;
    if($engine->can('draw_polygon')) {
	$result = $engine->draw_polygon($state, @points);
    }
    else {
	$result = $engine->draw_path
	    ($state,
	     ['M', @{shift(@points)}],
	     (map { ['L', @$_] } @points),
	     ['Z']);
    }

    if($points_str) { $self->ie_at_po($state->node_attributes->{points}) }
    else            { return $result }
}

################################### Text ##################################

sub _process_cdata {
    my ($self, $state, %args) = @_;

    return if($args{queued});

    foreach(sort { $a->{atomID} <=> $b->{atomID} } @{$state->text_atoms}) {
	$self->{engine}->draw_text($state,
				   $_->{x}, $_->{y}, $_->{rotate},
				   $_->{cdata});
    }

    return;
}

sub _process_tspan {}

sub _process_text {
    my ($self, $state, %args) = @_;

    return if($args{queued});

    my $text_atoms = $state->text_atoms;

    return if(!$text_atoms or !@$text_atoms);

    my $attributes = $state->node_attributes;

=for bidi reordering

    my $blockID     = 0;
    my @block_atoms = grep { $_->{blockID} == $blockID } @$text_atoms;
    while(@block_atoms) {
	

	$blockID++;
	@block_atoms = grep { $_->{blockID} == $blockID } @$text_atoms;
    }

=cut

    # The following section will have to be revised in depth for
    # right-to-left and particularly for top-to-bottom text.

    # init variables with first chunk
    my $chunkID     = 0;
    my @chunk_atoms = grep { $_->{chunkID} == $chunkID } @$text_atoms;

    # init current text position
    # Note that the atom properties are also set to 0 if undefined.
    # This means that for the first atom of the first chunk, x and
    # y are always set even if we do not have a text_width method.
    my $ctp_x = $chunk_atoms[0]->{x} ||= 0;
    my $ctp_y = $chunk_atoms[0]->{y} ||= 0;

    my $engine = $self->{engine};
    eval { $engine->text_width($state, '') };
    my $can_text_width =
	SVG::Rasterize::Exception::Setting->caught ? 0 : 1;

    # loop through chunks (@chunk_atoms is updated at end of loop)
    while(@chunk_atoms) {
	my $x = 0;
	my $y = 0;

	if($can_text_width) {
	    foreach(@chunk_atoms) {
		my $width = $self->{engine}->text_width
		    ($_->{state}, $_->{cdata});
	    
		$_->{offset}       = [$x, $y];
		$_->{displacement} =
		    [defined($width) ? ($_->{dx} || 0) + $width : undef,
		     ($_->{dy} || 0)];
		$x += $_->{displacement}->[0];
		$y += $_->{displacement}->[1];
	    }

	    # TODO: What to do if right-to-left?
	    if(!$attributes->{'text-anchor'} or
	       $attributes->{'text-anchor'} eq 'start')
	    {
		# nothing to do for left-to-right
	    }
	    elsif($attributes->{'text-anchor'} eq 'middle') {
		$ctp_x -= $x / 2;
		$ctp_y -= $y / 2;  # unsure if this is what we want
	    }
	    else {
		# attribute validation should have made sure that
		# it must be 'end' now
		$ctp_x -= $x;
		$ctp_y -= $y;  # unsure if this is what we want
	    }

	    foreach(@chunk_atoms) {
		$_->{x} = $ctp_x + $_->{offset}->[0];
		$_->{y} = $ctp_y + $_->{offset}->[1];
	    }
	}

	$chunkID++;
	@chunk_atoms = grep { $_->{chunkID} == $chunkID } @$text_atoms;
	if(@chunk_atoms) {
	    $ctp_x = $chunk_atoms[0]->{x} || ($ctp_x + $x);
	    $ctp_y = $chunk_atoms[0]->{y} || ($ctp_y + $y);
	}
    }

    return;
}

sub _process_node {
    my ($self, $state, %args) = @_;

    if($state->defer_rasterization && !$args{flush}) {
	$self->{_rasterization_queue} ||= [];
	push(@{$self->{_rasterization_queue}}, $state);
	$args{queued} = 1;
    }

    my $this_node_name = $state->node_name;
    return $self->_process_path($state, %args)
	if($this_node_name eq 'path');
    return $self->_process_rect($state, %args)
	if($this_node_name eq 'rect');
    return $self->_process_circle($state, %args)
	if($this_node_name eq 'circle');
    return $self->_process_ellipse($state, %args)
	if($this_node_name eq 'ellipse');
    return $self->_process_line($state, %args)
	if($this_node_name eq 'line');
    return $self->_process_polyline($state, %args)
	if($this_node_name eq 'polyline');
    return $self->_process_polygon($state, %args)
	if($this_node_name eq 'polygon');
    return $self->_process_cdata($state, %args)
	if($this_node_name eq '#text');
    return $self->_process_tspan($state, %args)
	if($this_node_name eq 'tspan');
    return $self->_process_text($state, %args)
	if($this_node_name eq 'text');

    return;
}

###########################################################################
#                                                                         #
#                             Tree Traversal                              #
#                                                                         #
###########################################################################

sub in_error {
    my ($self, $exception) = @_;
    my $state              = SVG::Rasterize::State->new
	(rasterize       => $self,
	 node_name       => 'g',
	 node_attributes => {},
	 cdata           => undef,
	 child_nodes     => undef);

    $self->in_error_hook->($self, $state);

    die $exception;
}

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

    foreach(@{$self->{_rasterization_queue} || []}) {
	$self->_process_node($_, flush => 1);
    }
    $self->{_rasterization_queue} = undef;

    return;
}

sub _process_normalize_attributes {
    my ($self, $normalize, $attr) = @_;
    my %attributes                = %{$attr || {}};  # copy before
                                                     # manipulation

    if($attributes{style} and ref($attributes{style}) eq 'HASH') {
	my $style = '';
	foreach(keys %{$attributes{style}}) {
	    next if(!defined($attributes{style}->{$_}));
	    $style .= ';' if($style);
	    $style .= sprintf("%s:%s", lc($_), $attributes{style}->{$_});
	}
	$attributes{style} = $style;
    }

    if($normalize) {
	foreach(keys %attributes) {
	    next if(ref($attributes{$_}));
	    $attributes{$_} =~ s/^$WSP*//;
	    $attributes{$_} =~ s/$WSP*$//;
	    $attributes{$_} =~ s/$WSP+/ /g;
	}
    }

    return \%attributes;
}

###########################################################################
#                                                                         #
#                              DOM specific                               #
#                                                                         #
###########################################################################

sub _process_node_object {
    my ($self, $node, %args) = @_;

    my %state_args =
       (node            => $node,
        node_name       => $node->getNodeName,
	node_attributes => $self->_process_normalize_attributes
	    ($args{normalize_attributes}, scalar($node->getAttributes)));

    $state_args{cdata} = $state_args{node_name} eq '#text'
	    ? $node->getData : undef;

    my $child_nodes = $node->getChildNodes;
    if($node->isa('SVG::Element')) {
	# For a SVG::Element we can just take the child nodes
	# directly, because this gives us only the child elements
	# anyway. We have to take care of comments, though.
	# A copy is made to enable manipulation in hooks without
	# changing the node object.
	$state_args{child_nodes} = defined($child_nodes)
	    ? [grep { !$IGNORED_NODES{$_->getNodeName} } @$child_nodes]
	    : undef;

	# extrawurst for text elements in SVG.pm
	if(my $cdata = $node->getData) {
	    $state_args{child_nodes} ||= [];
	    push(@{$state_args{child_nodes}},
		 SVG::Rasterize::TextNode->new(data => $cdata));
	}
    }
    else {
	# For a generic DOM node we only take the children which
	# are either element or text nodes. Note that this excludes
	# comment nodes.
	$state_args{child_nodes} = [];
	foreach(@{$child_nodes || []}) {
	    my $type = $_->getType;
	    next if($type != 1 and $type != 3);
	    next if(!$IGNORED_NODES{$_->getNodeName});
	    push(@{$state_args{child_nodes}}, $_);
	}
    }

    return(%state_args);
}

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

    # process initial node and establish initial viewport
    my $node       = $args{svg}->getNodeName eq 'document'
	? $args{svg}->firstChild : $args{svg};
    my %state_args = $self->_process_node_object($node, %args);

    # if an external current color has been set we integrate it here
    if(exists($args{current_color})) {
	$state_args{node_attributes}->{color} = $args{current_color};
    }

    $self->_initial_viewport($state_args{node_attributes}, \%args);
    if(!$args{width}) {
	warn "Surface width is 0, nothing to do.\n";
	return;
    }
    if(!$args{height}) {
	warn "Surface height is 0, nothing to do.\n";
	return;
    }

    $self->_create_engine(\%args);

    my @buffer = $self->before_node_hook->($self,
					   %state_args,
					   rasterize => $self,
					   matrix    => $args{matrix});
    $self->ex_ho_bn_on if(!@buffer or @buffer % 2);
    $self->{state} = SVG::Rasterize::State->new(@buffer);
    $self->start_node_hook->($self, $self->{state});
    $self->_process_node($self->{state});

    # traverse the node tree
    my @stack = ();
    while($self->{state}) {
	$node = $self->{state}->shift_child_node;
	if($node) {
	    push(@stack, $self->{state});
	    @buffer = $self->before_node_hook->
		($self,

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

Defaults to 1/2.54.

=item * IN_PER_MM

Defaults to 1/25.4.

=item * IN_PER_PT

Defaults to 1/72.

=item * IN_PER_PC

Defaults to 1/6.

=back

=head2 Hooks

The L<rasterize|/rasterize> method traverses through the C<SVG> tree
and creates an L<SVG::Rasterize::State|SVG::Rasterize::State> object
for each node (node means here element or text node if relevant,
attributes are not treated as nodes). 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. If you use any of the existing hooks or wish for other ones
you may want to let me know because this will certainly influence
the stability and development of this interface.

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 four hooks:

=over 4

=item * before_node_hook

Executed at encounter of a new node right before the new
L<SVG::Rasterize::State|SVG::Rasterize::State> object is created.
It is called as an object method and receives the hash that would be
passed to the L<SVG::Rasterize::State|SVG::Rasterize::State>
constructor. It contains the elements listed below. A custom
C<before_node_hook> is expected to return a hash of the same form
which will then be handed over to the
L<SVG::Rasterize::State|SVG::Rasterize::State> constructor. The
default C<before_node_hook> just returns the input hash.

The argument hash always contains the following elements:

=over 4

=item * C<rasterize>: the C<SVG::Rasterize> object

=item * C<node>: the node object

=item * C<node_name>: the C<DOM> node name

=item * C<node_attributes>: the attributes as HASH reference,
already L<normalized|/White Space Handling>

=item * C<cdata>: string or C<undef>

=item * C<child_nodes>: ARRAY reference with node objects or
C<undef>.

=back

In addition, it may contain the following elements:

=over 4

=item * C<matrix>: ARRAY reference with six numbers (see
L<multiply_matrices|/multiply_matrices>); this element is present
when processing the root node

=item * C<parent>: the parent
L<SVG::Rasterize::State|SVG::Rasterize::State> object; this element
is present when B<not> processing the root node.

=back

=item * start_node_hook

Executed right after creation of the
L<SVG::Rasterize::State|SVG::Rasterize::State> object. The
attributes have been parsed,
L<properties|SVG::Rasterize::State/properties> and
L<matrix|SVG::Rasterize::State/matrix> have been set etc. The method
receives the C<SVG::Rasterize> object and the
L<SVG::Rasterize::State|SVG::Rasterize::State> object as parameters.

=item * end_node_hook

Executed right before a
L<SVG::Rasterize::State|SVG::Rasterize::State> object runs out of
scope because the respective node is done with. The method receives
the C<SVG::Rasterize> object and the
L<SVG::Rasterize::State|SVG::Rasterize::State> object as parameters.

=item * in_error_hook

Executed right before C<die> when the document is in error (see L<In
error|/In error> below. Receives the C<SVG::Rasterize> object and a
newly created L<SVG::Rasterize::State|SVG::Rasterize::State> object
as parameters.

=back

B<Examples:>

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

Some hooks have non-trivial defaults. Therefore C<SVG::Rasterize>
provides the following methods to restore the default behaviour:

=over 4

=item * restore_before_node_hook

=item * restore_start_node_hook

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

Expects two vectors and returns the angle between them in C<rad>. No
parameter validation is performed. If one of the vectors is C<0>
(and only if it is exactly C<0>), C<undef> is returned.

=item * _split_path_data

Expects a path data string. This is expected (without validation) to
be defined. Everything else is checked within the method.

Returns a list. The first entry is either C<1> or C<0> indicating if
an error has occured (i.e. if the string is not fully valid). The
rest is a list of ARRAY references containing the instructions to
draw the path.

=item * _process_rect

Expects a L<SVG::Rasterize::State|SVG::Rasterize::State> object and
optionally a hash of options. If the option C<queued> is set to a
true value, nothing is done.

Expects that C<< $state->node_attributes >> have been validated.

The rest is handed over to the rasterization backend (which has its
own expectations).

=item * _process_circle

Same es L<_process_rect|/_process_rect>.

=item * _process_ellipse

Same es L<_process_rect|/_process_rect>.

=item * _process_line

Same es L<_process_rect|/_process_rect>.

=item * _process_polyline

Same es L<_process_path|/_process_path>.

=item * _process_polygon

Same es L<_process_path|/_process_path>.

=item * _process_text

Expects a L<SVG::Rasterize::State|SVG::Rasterize::State> object and
optionally a hash of options. If the option C<queued> is set to a
true value, nothing is done.

Expects that C<< $state->node_attributes >> have been
validated. Determines C<text-anchor> and the absolute rasterization
position for each
L<text atom|SVG::Rasterize::State::Text/DESCRIPTION>.

=item * _process_tspan

Currently does not do anything. All text processing is done by
either L<_process_text|/_process_text> or
L<_process_cdata|/_process_cdata>. Might be deleted in the future.

=item * _process_cdata

Expects a L<SVG::Rasterize::State|SVG::Rasterize::State> object and
optionally a hash of options. If the option C<queued> is set to a
true value, nothing is done.

Expects that C<< $state->node_attributes >> have been
validated. Calls the C<draw_text> method of the rasterization engine
on each of its atoms in the right order.

=item * make_ro_accessor

This piece of documentation is mainly here to make the C<POD>
coverage test happy. C<SVG::Rasterize> overloads C<make_ro_accessor>
to make the readonly accessors throw an exception object (of class
C<SVG::Rasterize::Exception::Attribute>) instead of just croaking.

=back

=head1 FOOTNOTES

=over 4

=item * [1] Yannis Haralambous: Fonts & Encodings. O'Reilly, 2007.

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

=back

=head1 SEE ALSO

=over 4

=item * L<http://www.w3.org/TR/SVG11/>

=item * L<SVG|SVG>

=item * L<SVG::Parser|SVG::Parser>

=item * L<Cairo|Cairo>

=item * L<Exception::Class|Exception::Class>

=back


=head1 ACKNOWLEDGEMENTS

This distribution builds heavily on the
L<cairo|http://www.cairographics.org/> library and its
L<Perl bindings|Cairo>.


=head1 AUTHOR

Lutz Gehlen, C<< <perl at lutzgehlen.de> >>


=head1 LICENSE AND COPYRIGHT



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