SVG-Rasterize
view release on metacpan or search on metacpan
lib/SVG/Rasterize/State/Text.pm view on Meta::CPAN
Version 0.003008
=cut
our $VERSION = '0.003008';
__PACKAGE__->mk_accessors(qw());
__PACKAGE__->mk_ro_accessors(qw(text_atoms
x_buffer
y_buffer
dx_buffer
dy_buffer
rotate_buffer));
###########################################################################
# #
# 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);
}
};
}
###########################################################################
# #
# Init Process #
# #
###########################################################################
sub process_node_extra {
my ($self) = @_;
my $founder;
# process position attributes
my $attributes = $self->node_attributes;
foreach(qw(x y dx dy rotate)) {
if(defined($attributes->{$_})) {
$self->{$_.'_buffer'} =
[map { $self->map_length($_) }
split($RE_LENGTH{LENGTHS_SPLIT}, $attributes->{$_})];
}
}
# collect position information
if($self->node_name eq '#text' and my $cdata = $self->cdata) {
my $ancestor = $self->parent;
my $buffers = {'x' => [],
'y' => [],
dx => [],
dy => [],
rotate => []};
while(1) {
$self->ex_co_pt if(!defined($ancestor));
foreach(keys %$buffers) {
my $name = $_.'_buffer';
if(defined(my $content = $ancestor->$name)) {
push(@{$buffers->{$_}}, $content);
}
}
if($SVG::Rasterize::TEXT_ROOT_ELEMENTS{$ancestor->node_name}) {
$founder = $ancestor;
last;
}
else {
$ancestor = $ancestor->parent;
}
}
# split cdata into atoms
my @atoms = ();
while($cdata) {
my $found = 0;
my $atom = {state => $self};
weaken $atom->{state};
BUFFER:
foreach(keys %$buffers) {
while(@{$buffers->{$_}}) {
# there are still levels possibly with data
if(@{$buffers->{$_}->[0]}) {
# there are still entries, so we take one
if($_ eq 'rotate') {
# rotate receives special behaviour:
# If there are too few elements on one
# level then the last one is used. We
# only go to the next level if this one
# never had elements. This is
# implemented here by never shifting the
# last remaining element.
$atom->{$_} = @{$buffers->{$_}->[0]} > 1
? shift(@{$buffers->{$_}->[0]})
: $buffers->{$_}->[0]->[0];
}
else {
$atom->{$_} = shift(@{$buffers->{$_}->[0]});
}
$found = 1;
next BUFFER;
}
else {
# this level is empty, discard and try next
shift(@{$buffers->{$_}});
}
}
# if we arrive here we have not found anything
$atom->{$_} = undef;
}
# If we have found something we create a new atom.
# Otherwise we attach the rest of the string to the last
# atom.
if($found) {
$atom->{cdata} = substr($cdata, 0, 1, '');
if(defined($atom->{x}) or defined($atom->{y})) {
# new absolute position creates new chunk;
# this implies a new block
$atom->{new_chunk} = 1;
}
push(@atoms, $atom);
}
elsif(!@atoms) {
$atom->{cdata} = $cdata;
$cdata = '';
# We are at the first atom. Under certain
# circumstances, we need to start a new block.
# TODO: user $founder's last atom to check whether
# âglyph-orientation-horizontalâ or
# âglyph-orientation-verticalâ have changed. Maybe
# I should split whenever this is set explicitly
# and remerge later if it doesn't have an effect?
push(@atoms, $atom);
}
else {
$atoms[-1]->{cdata} .= $cdata;
$cdata = '';
}
}
$self->add_text_atoms(@atoms);
}
}
###########################################################################
# #
# Accessors #
# #
###########################################################################
###########################################################################
# #
# Retrieve Data #
# #
###########################################################################
###########################################################################
# #
# Change Data #
# #
###########################################################################
sub add_text_atoms {
my ($self, @atoms) = @_;
validate_with
(params => \@atoms,
spec => [({type => HASHREF}) x @atoms],
on_fail => sub { SVG::Rasterize->ex_pv($_[0]) });
$self->{text_atoms} ||= [];
foreach(@atoms) {
my $lcb = sub {
my ($value) = @_;
return 1 if(!defined($value));
return $value =~ $RE_LENGTH{p_A_LENGTH} ? 1 : 0;
};
my $ncb = sub {
my ($value) = @_;
return 1 if(!defined($value));
return $value =~ $RE_NUMBER{p_A_NUMBER} ? 1 : 0;
};
validate_with
(params => [%$_],
spec => {state => {isa => 'SVG::Rasterize::State'},
'x' => {type => UNDEF|SCALAR,
callbacks => {length => $lcb}},
'y' => {type => UNDEF|SCALAR,
callbacks => {length => $lcb}},
dx => {type => UNDEF|SCALAR,
callbacks => {length => $lcb}},
dy => {type => UNDEF|SCALAR,
callbacks => {length => $lcb}},
rotate => {type => UNDEF|SCALAR,
callbacks => {length => $ncb}},
new_chunk => {type => BOOLEAN,
optional => 1},
new_block => {type => BOOLEAN,
optional => 1},
cdata => {type => SCALAR}},
on_fail => sub { SVG::Rasterize->ex_pv($_[0]) });
if($SVG::Rasterize::TEXT_ROOT_ELEMENTS{$self->node_name}) {
if(@{$self->{text_atoms}}) {
if($_->{new_chunk}) {
$_->{chunkID} =
$self->{text_atoms}->[-1]->{chunkID} + 1;
}
else {
$_->{chunkID} =
$self->{text_atoms}->[-1]->{chunkID};
}
if($_->{new_chunk} or $_->{new_block}) {
$_->{blockID} =
$self->{text_atoms}->[-1]->{blockID} + 1;
}
else {
$_->{blockID} =
$self->{text_atoms}->[-1]->{blockID};
}
$_->{atomID} = $self->{text_atoms}->[-1]->{atomID}
+ length($self->{text_atoms}->[-1]->{cdata});
}
else {
$_->{chunkID} = 0;
$_->{blockID} = 0;
$_->{atomID} = 0;
}
}
push(@{$self->{text_atoms}}, $_);
}
if($SVG::Rasterize::TEXT_ROOT_ELEMENTS{$self->node_name}) {
return;
}
else {
my $parent = $self->parent;
while(1) {
$self->ex_co_pt if(!$parent);
if($SVG::Rasterize::TEXT_ROOT_ELEMENTS{$parent->node_name}) {
return $parent->add_text_atoms(@atoms);
}
else { $parent = $parent->parent }
}
}
}
1;
__END__
=pod
=head1 DESCRIPTION
Text and text content nodes need special functionality. The hardest
part belongs to the character data nodes, which contain the actual
text. Their characters might have specific position settings, which
may be inherited from upstream C<tspan>, C<text>, or C<textPath>
nodes.
This class implements a L<process_node_extra|/process_node_extra>
method overloading the empty one of
L<SVG::Rasterize::State|SVG::Rasterize::State>. This method splits
character data into atoms where an atom (this term is coined by me
while chunk and block are taken from the C<SVG> specification) is a
sequence of characters that can be handed to the rasterization
engine in one go. Note that the atoms defined here might be split up
further directly before rasterization due to bidirectional text.
=head1 INTERFACE
=head2 Constructors
=head3 new/init
Inherited from L<SVG::Rasterize::State|SVG::Rasterize::State>.
=head2 Public Attributes
=head3 text_atoms
Readonly attribute. Holds the reference to an array of HASH
references. The array is filled during initialization. Each hash has
the following entries:
=over 4
=item * x: explicit x value if given
=item * y: explicit y value if given
=item * dx: explicit dx value if given
=item * dy: explicit dy value if given
=item * rotate: explicit rotate value if given
=item * chunkID: number of the text chunk this atom belongs to
=item * new_chunk
only present for first atom of each chunk, leftover of processing,
should not be used
=item * blockID
number of the independent text block this atom belongs to
=item * new_block
only present for first atom of each block, leftover of processing,
should not be used
=item * atomID
number of the atom, equal to the length of text before this atom
thereby allowing further splitting without renumbering
=item * cdata: the actual characters of this atom
=item * state: weak reference to this object.
=back
=head3 x_buffer
ARRAY reference, list of explicit x positions that this element
still has to offer.
=head3 y_buffer
Same for y.
=head3 dx_buffer
Same for dx.
=head3 dy_buffer
Same for dy.
=head3 rotate_buffer
Same for rotate.
=head2 Methods for Users
None.
=head2 Methods for Developers
=head3 process_node_extra
Overloads the empty method of
L<SVG::Rasterize::State|SVG::Rasterize::State>.
Has two modes of operation. For elements, it saves the position
attributes if present into buffer copies from which they can be
shifted by character data child nodes.
For character data nodes, it splits the string into atoms assigning
position values (if provided by ancestors) to the characters.
=head3 add_text_atoms
Called by L<process_node_extra|/process_node_extra>. Takes a list of
atom HASH references and pushes them to the
L<text_atoms|/text_atoms> list. Calls itself on the ancestor C<text>
or C<textPath> element. At this call, it sets L<chunkID|/chunkID>,
L<blockID|/blockID>, and L<atomID|/atomID>.
=head1 DIAGNOSTICS
=head2 Exceptions
Not documented, yet. Sorry.
=head2 Warnings
( run in 0.912 second using v1.01-cache-2.11-cpan-df04353d9ac )