App-WIoZ
view release on metacpan or search on metacpan
lib/App/WIoZ.pm view on Meta::CPAN
use strict;
use warnings;
package App::WIoZ;
{
$App::WIoZ::VERSION = '0.004';
}
#use feature 'say';
use Moose;
use Color::Mix;
use Cairo;
use Math::PlanePath::HilbertCurve;
use Graphics::ColorNames;
use App::WIoZ::Point;
use App::WIoZ::Word;
# ABSTRACT: App::WIoZ create a SVG or PNG image of a word cloud from a simple text file
=head1 NAME - App::WIoZ
App::WIoZ - a perl word cloud generator
=head1 VERSION
version 0.004
=head1 DESCRIPTION
App::WIoZ can create a SVG or PNG image of a word cloud from a simple text file with C<word;weight>.
App::WIoZ is an acronym for "Words for Io by Zeus", look for the Correggio painting to watch the cloud.
App::WIoZ is based on C<Wordle> strategy and C<yawc> perl clone.
Usage:
my $File = 'words.txt';
my $wioz = App::WIoZ->new(
font_min => 18, font_max => 64,
set_font => "DejaVuSans,normal,bold",
filename => "testoutput",
basecolor => '226666'); # violet
if (-f $File) {
my @words = $wioz->read_words($File);
$wioz->do_layout(@words);
}
else {
$wioz->chg_font("LiberationSans,normal,bold");
$wioz->update_colors('testoutput.sl.txt');
}
watch C<doc/freq.pl> to create a C<words.txt> file.
=head1 STATUS
App::WIoZ is actually a POC to play with Moose, Cairo or Math::PlanePath.
The use of an Hilbert curve to manage free space is for playing with Math::PlanePath modules.
Performance can be improved in free space matching, or in spiral strategy to find free space.
Max and min font sizes can certainly be computed.
Feel free to clone this project on GitHub.
=head1 SETTINGS
=head2 height
image height, default to 600
=cut
has 'height' => (
is => 'ro', isa => 'Int', default => 600
);
=head2 width
image width, default to 800
=cut
has 'width' => (
is => 'ro', isa => 'Int', default => 800
);
has 'center' => (
is => 'ro', isa => 'App::WIoZ::Point',
lazy => 1,
default => sub {
my $self = shift;
return App::WIoZ::Point->new(
x => int($self->width/2),
y => int($self->height/2));
}
);
=head2 font_min, font_max
required min and max font size
=cut
has ['font_min','font_max'] => (
is => 'ro', required => 1, isa => 'Int'
);
=head2 set_font, chg_font, font
accessors for font name, type and weight
C<set_font> : set font in new WIoZ object, default is C<'LiberationSans,normal,bold'>
C<chg_font> : change font
C<font> : read font object
Usage :
$wioz = App::WIoZ->new( font_min => 18, font_max => 64,
set_font => 'DejaVuSans,normal,bold');
$fontname = $wioz->font->{font};
$wioz->chg_font('LiberationSans,normal,bold');
=cut
has 'font' => (
isa => 'HashRef',
is => 'ro', lazy => 1,
writer => 'chg_font',
builder => '_set_font'
);
# for font builder
has 'set_font' => ( is => 'rw',isa => 'Str' );
sub _set_font {
my ($self,$font) = @_;
my ($fname,$ftype,$fweight) = split ',', ($self->set_font || ',,');
return ( { font => $fname || 'LiberationSans',
type => $ftype || 'normal',
weight => $fweight || 'bold' });
};
# for font change
around 'chg_font' => sub {
my ($next,$self,$font) = @_;
my ($fname,$ftype,$fweight) = split ',', $font;
$self->$next( {font => $fname, type => $ftype, weight => $fweight});
};
has 'backcolor' => (
is => 'ro', isa => 'Str',
default => 'white'
);
has 'cr' => (
is => 'rw', isa => 'Cairo::Context',
lazy => 1, builder => '_create_cr'
);
has 'surface' => (
is => 'rw', isa => 'Cairo::ImageSurface',
);
has 'svgsurface' => (
is => 'rw', isa => 'Cairo::SvgSurface',
);
=head2 filename
file name output, extension C<.png> or C<.svg> will be added
=cut
has 'filename' => (
is => 'rw', isa => 'Str',
);
=head2 svg
produce a svg output, default value
set to 0 to write a png
=cut
has 'svg' => (
is => 'ro', isa => 'Int', default => 1
);
has 'fcurve' => (
is => 'rw', isa => 'Math::PlanePath',
);
=head2 scale
Scale for the Hilbert Curve granularity default to 10
Higer value produces better speed but more words recovery.
=cut
has 'scale' => (
is =>'ro', isa => 'Int', default => 10 # 20 better
);
has 'cused' => (
is => 'rw', isa => 'ArrayRef[Int]', default => sub {[]}
lib/App/WIoZ.pm view on Meta::CPAN
has 'basecolor' => (
is =>'ro', isa => 'Str', default => '882222'
);
=head1 METHODS
=cut
sub _create_cr {
my $self = shift;
my $scale = $self->scale;
my $hilbert = Math::PlanePath::HilbertCurve->new;
$self->fcurve($hilbert);
my $cr;
if ($self->svg) {
my $svgsurface = Cairo::SvgSurface->create ($self->filename.'.svg', $self->width, $self->height);
$self->svgsurface($svgsurface);
$cr = Cairo::Context->create($svgsurface);
}
else {
my $surface = Cairo::ImageSurface->create ('argb32', $self->width, $self->height);
$self->surface($surface);
$cr = Cairo::Context->create($surface);
};
$cr->save;
$cr->rectangle (0, 0, $self->width, $self->height);
my $po = Graphics::ColorNames->new;
my @rgb = $po->rgb($self->backcolor);
$cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0);
$cr->fill;
$cr->restore;
return $cr;
};
=head2 read_words
read words form file : C<word;weight>
Usage:
my @words = $wioz->read_words($File);
=cut
sub read_words {
my ($self, $filename) = @_;
my ($weight_min, $weight_max) = (1000000000, 0);
my @res = ();
my $fh;
open $fh, '<:utf8', $filename;
my @L = <$fh>;
close $fh;
foreach my $l (@L) {
my ($t,$n) = split /;/,$l;
if ( $t && $n ) {
$t =~ s/\s*$//g; $n =~ s/\s*$//g;
#$all_weight += $n;
$weight_max = $n if ( $n >$weight_max );
$weight_min = $n if ( $n <$weight_min );
my $w = new App::WIoZ::Word(text => $t, weight => $n, font => $self->font);
push @res, $w;
} else {
warn "error line: $_";
}
}
# set initial size and color
my @color = Color::Mix->new->analogous($self->basecolor, 12, 12);
foreach my $v (@res) {
$v->size( (($v->weight - $weight_min) / ($weight_max - $weight_min)) *
($self->font_max - $self->font_min) +
$self->font_min );
$v->color($color[int(rand(12))]);
}
return @res;
}
=head2 update_colors
Read words position from file and update colors.
Usage:
$wioz->update_colors("file.sl.txt");
=cut
sub update_colors{
my ($self, $filename) = @_;
open my $fh, '<:utf8', $filename or die $filename . ' : ' .$!;
my @L = <$fh>;
close $fh;
my @color = Color::Mix->new->analogous($self->basecolor, 12, 12);
# reset background
$self->cr->rectangle (0, 0, $self->width, $self->height);
my $po = Graphics::ColorNames->new;
my @rgb = $po->rgb($self->backcolor);
$self->cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0);
$self->cr->fill;
foreach my $l (@L) {
my ($show,$text,$size,$x,$y,$angle) = split /\t/,$l;
#say "$text - $size - $angle";
my $w = App::WIoZ::Word->new(text => $text, size => $size, angle => $angle, show => $show, color => $color[int(rand(12))], font => $self->font);
my $newc = App::WIoZ::Point->new( x => $x, y => $y);
$w->update_size($self,$size);
$w->update_c($newc);
$self->_show_word($w);
}
$self->_save_to_png if (!$self->svg);
}
=head2 do_layout
Compute words position, save result to svg or png image, save in C<filename.sl.txt> words positions to update colors.
Usage :
$wioz->do_layout(@words);
=cut
sub do_layout {
my ($self,@words) = @_;
my $c = 0;
my $current = undef;
my @dx = (1, 1, 0, 0,-1,-1,-1,-1, 0, 0, 1, 1);
my @dy = (0, 1, 1, 1, 1, 0, 0,-1,-1,-1,-1, 0);
#foreach my $w (@words) {
foreach my $w (sort {$b->weight cmp $a->weight} @words) {
# init
$w->show(1);
$w->update_size($self,$w->size) if (!$w->height && !$w->width);
$current = $w if (! $current);
# process
my $inside;
my @ranges;
my ($x1, $y1) = my ($x, $y) = (int($self->width/2), int($self->height/2));
my $step = $self->scale;
my $dir = 0;
my $i = 0;
do {
# spiral
my $newc = App::WIoZ::Point->new( x => int($x), y => int($y));
$x1 = $x1 + $dx[$i%12] * $step;
$y1 = $y1 + $dy[$i%12] * $step;
$x = $x1; $y = $y1;
$step += 2 ;
$w->update_c($newc);
# is in free space
$inside = ($w->p->x > 0 && $w->p->x <= $self->width &&
$w->p2->x > 0 && $w->p2->x <= $self->width &&
$w->p->y > 0 && $w->p->y <= $self->height &&
$w->p2->y > 0 && $w->p2->y <= $self->height) || 0;
@ranges = $w->is_free($self) if $inside;
# try some other strategy
$i++;
if ($i>60 || !$inside) {
$i = 10;
$step=$self->scale;
my ($xt,$yt) = $self->_random_point($current->width,$current->height);
($x1, $y1) = ($x, $y) = ($current->p->x + $xt,$current->p->y - $yt);
if ( ! $dir ) {
$dir = 1;
#say ' revert : '.$w->text;
my @rdx = reverse @dx;
my @rdy = reverse @dy;
@dx = @rdx; @dy = @rdy;
}
else {
$dir = 0;
if ($w->size - 1 <= 5) {
#say ' no place for : '.$w->text;
$w->show(0);
next;
}
#say ' decrease : '.$w->text;
$w->update_size($self,$w->size - 1);
}
};
} while ( ! $inside || scalar @ranges == 1 );
# register used space
map { if ($_) {push @{ $self->cused }, $_} } @ranges;
# show
$self->_show_word($w) if ($w->show);
#$c++; last if $c > 2;
}
$self->_save_to_png if (!$self->svg);
$self->_save_layout(@words);
}
sub _save_to_png {
my $self = shift;
$self->surface->write_to_png ($self->filename . '.png');
}
# Save words position to a file. Usefull to update colors.
sub _save_layout {
my ($self, @words) = @_;
my $fh;
open $fh, '>:utf8', $self->filename . '.sl.txt';
foreach my $w (@words) {
print $fh $w->show."\t".$w->text."\t".$w->size."\t".$w->c->x."\t".$w->c->y."\t".$w->angle."\n";
}
close $fh;
}
sub _show_word {
my ($self,$w) = @_;
$self->cr->select_font_face(
$w->font->{font},$w->font->{type},$w->font->{weight});
$self->cr->set_font_size($w->size);
my $po = Graphics::ColorNames->new;
my @rgb = $po->rgb($w->color);
$self->cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0);
#say ' '.$w->text.' '.$w->color;
if ($w->angle < 0) {
$self->cr->save;
$self->cr->move_to($w->p->x+$w->width,$w->p->y);
$self->cr->rotate($w->angle);
$self->cr->show_text($w->text);
$self->cr->restore;
}
else {
$self->cr->move_to($w->p->x,$w->p->y);
$self->cr->show_text($w->text);
}
}
sub _random_point {
my ($self,$width, $height) = @_;
my $x = rand( $width * 0.8 ) + $width * 0.1 ;
my $y = rand( $height * 0.8 ) + $height * 0.1 ;
return ($x, $y);
}
=head1 Git
L<https://github.com/yvesago/WIoZ/>
=head1 AUTHORS
Yves Agostini, C<< <yveago@cpan.org> >>
=head1 LICENSE AND COPYRIGHT
Copyright 2013 - Yves Agostini
This program is free software and may be modified or distributed under the same terms as Perl itself.
=cut
1;
( run in 0.770 second using v1.01-cache-2.11-cpan-5735350b133 )