| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 1 |  |  | 1 |  | 712 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 2 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 43 |  | 
| 3 |  |  |  |  |  |  | package App::WIoZ; | 
| 4 |  |  |  |  |  |  | { | 
| 5 |  |  |  |  |  |  | $App::WIoZ::VERSION = '0.004'; | 
| 6 |  |  |  |  |  |  | } | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | #use feature 'say'; | 
| 9 | 1 |  |  | 1 |  | 381 | use Moose; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | use Color::Mix; | 
| 11 |  |  |  |  |  |  | use Cairo; | 
| 12 |  |  |  |  |  |  | use Math::PlanePath::HilbertCurve; | 
| 13 |  |  |  |  |  |  | use Graphics::ColorNames; | 
| 14 |  |  |  |  |  |  | use App::WIoZ::Point; | 
| 15 |  |  |  |  |  |  | use App::WIoZ::Word; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | # ABSTRACT: App::WIoZ create a SVG or PNG image of a word cloud from a simple text file | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | =head1 NAME - App::WIoZ | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | App::WIoZ - a perl word cloud generator | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | =head1 VERSION | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | version 0.004 | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | App::WIoZ can create a SVG or PNG image of a word cloud from a simple text file with C<word;weight>. | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | App::WIoZ is an acronym for "Words for Io by Zeus", look for the Correggio painting to watch the cloud. | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | App::WIoZ is based on C<Wordle> strategy and C<yawc> perl clone. | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | Usage: | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | my $File = 'words.txt'; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | my $wioz = App::WIoZ->new( | 
| 40 |  |  |  |  |  |  | font_min => 18, font_max => 64, | 
| 41 |  |  |  |  |  |  | set_font => "DejaVuSans,normal,bold", | 
| 42 |  |  |  |  |  |  | filename => "testoutput", | 
| 43 |  |  |  |  |  |  | basecolor => '226666'); # violet | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | if (-f $File) { | 
| 46 |  |  |  |  |  |  | my @words = $wioz->read_words($File); | 
| 47 |  |  |  |  |  |  | $wioz->do_layout(@words); | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  | else { | 
| 50 |  |  |  |  |  |  | $wioz->chg_font("LiberationSans,normal,bold"); | 
| 51 |  |  |  |  |  |  | $wioz->update_colors('testoutput.sl.txt'); | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | watch C<doc/freq.pl> to create a C<words.txt> file. | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =head1 STATUS | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | App::WIoZ is actually a POC to play with Moose, Cairo or Math::PlanePath. | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | The use of an Hilbert curve to manage free space is for playing with Math::PlanePath modules. | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | Performance can be improved in free space matching, or in spiral strategy to find free space. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | Max and min font sizes can certainly be computed. | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | Feel free to clone this project on GitHub. | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =head1 SETTINGS | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | =head2 height | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | image height, default to 600 | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =cut | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | has 'height' => ( | 
| 77 |  |  |  |  |  |  | is => 'ro', isa => 'Int', default => 600 | 
| 78 |  |  |  |  |  |  | ); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | =head2 width | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | image width, default to 800 | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | =cut | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | has 'width' => ( | 
| 87 |  |  |  |  |  |  | is => 'ro', isa => 'Int', default => 800 | 
| 88 |  |  |  |  |  |  | ); | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | has 'center' => ( | 
| 91 |  |  |  |  |  |  | is => 'ro', isa => 'App::WIoZ::Point', | 
| 92 |  |  |  |  |  |  | lazy => 1, | 
| 93 |  |  |  |  |  |  | default => sub { | 
| 94 |  |  |  |  |  |  | my $self = shift; | 
| 95 |  |  |  |  |  |  | return App::WIoZ::Point->new( | 
| 96 |  |  |  |  |  |  | x => int($self->width/2), | 
| 97 |  |  |  |  |  |  | y => int($self->height/2)); | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  | ); | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | =head2 font_min, font_max | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | required min and max font size | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | =cut | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | has ['font_min','font_max'] => ( | 
| 108 |  |  |  |  |  |  | is => 'ro', required => 1, isa => 'Int' | 
| 109 |  |  |  |  |  |  | ); | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | =head2 set_font, chg_font, font | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | accessors for font name, type and weight | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | C<set_font> : set font in new WIoZ object, default is C<'LiberationSans,normal,bold'> | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | C<chg_font> : change font | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | C<font> : read font object | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | Usage : | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | $wioz = App::WIoZ->new( font_min => 18, font_max => 64, | 
| 124 |  |  |  |  |  |  | set_font => 'DejaVuSans,normal,bold'); | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | $fontname = $wioz->font->{font}; | 
| 127 |  |  |  |  |  |  | $wioz->chg_font('LiberationSans,normal,bold'); | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | =cut | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | has 'font' => ( | 
| 133 |  |  |  |  |  |  | isa    => 'HashRef', | 
| 134 |  |  |  |  |  |  | is => 'ro', lazy => 1, | 
| 135 |  |  |  |  |  |  | writer => 'chg_font', | 
| 136 |  |  |  |  |  |  | builder => '_set_font' | 
| 137 |  |  |  |  |  |  | ); | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | # for font builder | 
| 140 |  |  |  |  |  |  | has 'set_font' => ( is => 'rw',isa => 'Str' ); | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | sub _set_font { | 
| 143 |  |  |  |  |  |  | my ($self,$font) = @_; | 
| 144 |  |  |  |  |  |  | my ($fname,$ftype,$fweight) = split ',', ($self->set_font || ',,'); | 
| 145 |  |  |  |  |  |  | return ( { font => $fname || 'LiberationSans', | 
| 146 |  |  |  |  |  |  | type => $ftype || 'normal', | 
| 147 |  |  |  |  |  |  | weight => $fweight || 'bold' }); | 
| 148 |  |  |  |  |  |  | }; | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # for font change | 
| 151 |  |  |  |  |  |  | around 'chg_font' => sub  { | 
| 152 |  |  |  |  |  |  | my ($next,$self,$font) = @_; | 
| 153 |  |  |  |  |  |  | my ($fname,$ftype,$fweight) = split ',', $font; | 
| 154 |  |  |  |  |  |  | $self->$next( {font => $fname, type => $ftype, weight => $fweight}); | 
| 155 |  |  |  |  |  |  | }; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | has 'backcolor' => ( | 
| 158 |  |  |  |  |  |  | is => 'ro', isa => 'Str', | 
| 159 |  |  |  |  |  |  | default => 'white' | 
| 160 |  |  |  |  |  |  | ); | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | has 'cr' => ( | 
| 163 |  |  |  |  |  |  | is => 'rw', isa => 'Cairo::Context', | 
| 164 |  |  |  |  |  |  | lazy => 1, builder => '_create_cr' | 
| 165 |  |  |  |  |  |  | ); | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | has 'surface' => ( | 
| 168 |  |  |  |  |  |  | is => 'rw', isa => 'Cairo::ImageSurface', | 
| 169 |  |  |  |  |  |  | ); | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | has 'svgsurface' => ( | 
| 172 |  |  |  |  |  |  | is => 'rw', isa => 'Cairo::SvgSurface', | 
| 173 |  |  |  |  |  |  | ); | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | =head2 filename | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | file name output, extension C<.png> or C<.svg> will be added | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | =cut | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | has 'filename' => ( | 
| 182 |  |  |  |  |  |  | is => 'rw', isa => 'Str', | 
| 183 |  |  |  |  |  |  | ); | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | =head2 svg | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | produce a svg output, default value | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | set to 0 to write a png | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | =cut | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | has 'svg' => ( | 
| 194 |  |  |  |  |  |  | is => 'ro', isa => 'Int', default => 1 | 
| 195 |  |  |  |  |  |  | ); | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | has 'fcurve' => ( | 
| 198 |  |  |  |  |  |  | is => 'rw', isa => 'Math::PlanePath', | 
| 199 |  |  |  |  |  |  | ); | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | =head2 scale | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | Scale for the Hilbert Curve granularity default to 10 | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | Higer value produces better speed but more words recovery. | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | =cut | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | has 'scale' => ( | 
| 210 |  |  |  |  |  |  | is =>'ro', isa => 'Int', default => 10 # 20 better | 
| 211 |  |  |  |  |  |  | ); | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | has 'cused' => ( | 
| 214 |  |  |  |  |  |  | is => 'rw', isa => 'ArrayRef[Int]', default => sub {[]} | 
| 215 |  |  |  |  |  |  | ); | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | =head2 basecolor | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | Base color for color theme, default to 882222 | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | =cut | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | has 'basecolor' => ( | 
| 224 |  |  |  |  |  |  | is =>'ro', isa => 'Str', default => '882222' | 
| 225 |  |  |  |  |  |  | ); | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =head1 METHODS | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | =cut | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | sub _create_cr { | 
| 232 |  |  |  |  |  |  | my $self = shift; | 
| 233 |  |  |  |  |  |  | my $scale = $self->scale; | 
| 234 |  |  |  |  |  |  | my $hilbert = Math::PlanePath::HilbertCurve->new; | 
| 235 |  |  |  |  |  |  | $self->fcurve($hilbert); | 
| 236 |  |  |  |  |  |  | my $cr; | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | if ($self->svg) { | 
| 239 |  |  |  |  |  |  | my $svgsurface = Cairo::SvgSurface->create ($self->filename.'.svg', $self->width, $self->height); | 
| 240 |  |  |  |  |  |  | $self->svgsurface($svgsurface); | 
| 241 |  |  |  |  |  |  | $cr = Cairo::Context->create($svgsurface); | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | else { | 
| 244 |  |  |  |  |  |  | my $surface = Cairo::ImageSurface->create ('argb32', $self->width, $self->height); | 
| 245 |  |  |  |  |  |  | $self->surface($surface); | 
| 246 |  |  |  |  |  |  | $cr = Cairo::Context->create($surface); | 
| 247 |  |  |  |  |  |  | }; | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | $cr->save; | 
| 250 |  |  |  |  |  |  | $cr->rectangle (0, 0, $self->width, $self->height); | 
| 251 |  |  |  |  |  |  | my $po = Graphics::ColorNames->new; | 
| 252 |  |  |  |  |  |  | my @rgb = $po->rgb($self->backcolor); | 
| 253 |  |  |  |  |  |  | $cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0); | 
| 254 |  |  |  |  |  |  | $cr->fill; | 
| 255 |  |  |  |  |  |  | $cr->restore; | 
| 256 |  |  |  |  |  |  | return $cr; | 
| 257 |  |  |  |  |  |  | }; | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =head2 read_words | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | read words form file : C<word;weight> | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | Usage: | 
| 264 |  |  |  |  |  |  | my @words = $wioz->read_words($File); | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | =cut | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | sub read_words { | 
| 269 |  |  |  |  |  |  | my ($self, $filename) = @_; | 
| 270 |  |  |  |  |  |  | my ($weight_min, $weight_max) = (1000000000, 0); | 
| 271 |  |  |  |  |  |  | my @res = (); | 
| 272 |  |  |  |  |  |  | my $fh; | 
| 273 |  |  |  |  |  |  | open $fh, '<:utf8', $filename; | 
| 274 |  |  |  |  |  |  | my @L = <$fh>; | 
| 275 |  |  |  |  |  |  | close $fh; | 
| 276 |  |  |  |  |  |  | foreach my $l (@L) { | 
| 277 |  |  |  |  |  |  | my ($t,$n) = split /;/,$l; | 
| 278 |  |  |  |  |  |  | if ( $t && $n ) { | 
| 279 |  |  |  |  |  |  | $t =~ s/\s*$//g; $n =~ s/\s*$//g; | 
| 280 |  |  |  |  |  |  | #$all_weight += $n; | 
| 281 |  |  |  |  |  |  | $weight_max = $n if ( $n >$weight_max ); | 
| 282 |  |  |  |  |  |  | $weight_min = $n if ( $n <$weight_min ); | 
| 283 |  |  |  |  |  |  | my $w = new App::WIoZ::Word(text => $t, weight => $n, font => $self->font); | 
| 284 |  |  |  |  |  |  | push @res, $w; | 
| 285 |  |  |  |  |  |  | } else { | 
| 286 |  |  |  |  |  |  | warn "error line: $_"; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  | # set initial size and color | 
| 290 |  |  |  |  |  |  | my @color = Color::Mix->new->analogous($self->basecolor, 12, 12); | 
| 291 |  |  |  |  |  |  | foreach my $v (@res) { | 
| 292 |  |  |  |  |  |  | $v->size( (($v->weight - $weight_min) / ($weight_max - $weight_min)) * | 
| 293 |  |  |  |  |  |  | ($self->font_max - $self->font_min) + | 
| 294 |  |  |  |  |  |  | $self->font_min ); | 
| 295 |  |  |  |  |  |  | $v->color($color[int(rand(12))]); | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  | return @res; | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | =head2 update_colors | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | Read words position from file and update colors. | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | Usage: | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | $wioz->update_colors("file.sl.txt"); | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | =cut | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | sub update_colors{ | 
| 312 |  |  |  |  |  |  | my ($self, $filename) = @_; | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | open my $fh, '<:utf8', $filename or die $filename . ' : ' .$!; | 
| 315 |  |  |  |  |  |  | my @L = <$fh>; | 
| 316 |  |  |  |  |  |  | close $fh; | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | my @color = Color::Mix->new->analogous($self->basecolor, 12, 12); | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | # reset background | 
| 321 |  |  |  |  |  |  | $self->cr->rectangle (0, 0, $self->width, $self->height); | 
| 322 |  |  |  |  |  |  | my $po = Graphics::ColorNames->new; | 
| 323 |  |  |  |  |  |  | my @rgb = $po->rgb($self->backcolor); | 
| 324 |  |  |  |  |  |  | $self->cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0); | 
| 325 |  |  |  |  |  |  | $self->cr->fill; | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | foreach my $l (@L) { | 
| 328 |  |  |  |  |  |  | my ($show,$text,$size,$x,$y,$angle) = split /\t/,$l; | 
| 329 |  |  |  |  |  |  | #say "$text - $size - $angle"; | 
| 330 |  |  |  |  |  |  | my $w = App::WIoZ::Word->new(text => $text, size => $size, angle => $angle, show => $show, color => $color[int(rand(12))], font => $self->font); | 
| 331 |  |  |  |  |  |  | my $newc = App::WIoZ::Point->new( x => $x, y => $y); | 
| 332 |  |  |  |  |  |  | $w->update_size($self,$size); | 
| 333 |  |  |  |  |  |  | $w->update_c($newc); | 
| 334 |  |  |  |  |  |  | $self->_show_word($w); | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  | $self->_save_to_png if (!$self->svg); | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | =head2 do_layout | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | Compute words position, save result to svg or png image, save in C<filename.sl.txt> words positions to update colors. | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | Usage : | 
| 344 |  |  |  |  |  |  | $wioz->do_layout(@words); | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | =cut | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | sub do_layout { | 
| 349 |  |  |  |  |  |  | my ($self,@words) = @_; | 
| 350 |  |  |  |  |  |  | my $c = 0; | 
| 351 |  |  |  |  |  |  | my $current = undef; | 
| 352 |  |  |  |  |  |  | my @dx = (1, 1, 0, 0,-1,-1,-1,-1, 0, 0, 1, 1); | 
| 353 |  |  |  |  |  |  | my @dy = (0, 1, 1, 1, 1, 0, 0,-1,-1,-1,-1, 0); | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | #foreach my $w (@words) { | 
| 356 |  |  |  |  |  |  | foreach my $w (sort {$b->weight cmp $a->weight} @words) { | 
| 357 |  |  |  |  |  |  | # init | 
| 358 |  |  |  |  |  |  | $w->show(1); | 
| 359 |  |  |  |  |  |  | $w->update_size($self,$w->size) if (!$w->height && !$w->width); | 
| 360 |  |  |  |  |  |  | $current = $w if (! $current); | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | # process | 
| 363 |  |  |  |  |  |  | my $inside; | 
| 364 |  |  |  |  |  |  | my @ranges; | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | my ($x1, $y1) = my ($x, $y) = (int($self->width/2), int($self->height/2)); | 
| 367 |  |  |  |  |  |  | my $step = $self->scale; | 
| 368 |  |  |  |  |  |  | my $dir = 0; | 
| 369 |  |  |  |  |  |  | my $i = 0; | 
| 370 |  |  |  |  |  |  | do { | 
| 371 |  |  |  |  |  |  | # spiral | 
| 372 |  |  |  |  |  |  | my $newc = App::WIoZ::Point->new( x => int($x), y => int($y)); | 
| 373 |  |  |  |  |  |  | $x1 = $x1 + $dx[$i%12] * $step; | 
| 374 |  |  |  |  |  |  | $y1 = $y1 + $dy[$i%12] * $step; | 
| 375 |  |  |  |  |  |  | $x = $x1; $y = $y1; | 
| 376 |  |  |  |  |  |  | $step += 2 ; | 
| 377 |  |  |  |  |  |  | $w->update_c($newc); | 
| 378 |  |  |  |  |  |  | # is in free space | 
| 379 |  |  |  |  |  |  | $inside = ($w->p->x > 0 && $w->p->x <= $self->width && | 
| 380 |  |  |  |  |  |  | $w->p2->x > 0 && $w->p2->x <= $self->width && | 
| 381 |  |  |  |  |  |  | $w->p->y > 0 && $w->p->y <= $self->height && | 
| 382 |  |  |  |  |  |  | $w->p2->y > 0 && $w->p2->y <= $self->height) || 0; | 
| 383 |  |  |  |  |  |  | @ranges = $w->is_free($self) if $inside; | 
| 384 |  |  |  |  |  |  | # try some other strategy | 
| 385 |  |  |  |  |  |  | $i++; | 
| 386 |  |  |  |  |  |  | if ($i>60 || !$inside) { | 
| 387 |  |  |  |  |  |  | $i = 10; | 
| 388 |  |  |  |  |  |  | $step=$self->scale; | 
| 389 |  |  |  |  |  |  | my ($xt,$yt) = $self->_random_point($current->width,$current->height); | 
| 390 |  |  |  |  |  |  | ($x1, $y1) = ($x, $y) = ($current->p->x + $xt,$current->p->y - $yt); | 
| 391 |  |  |  |  |  |  | if ( ! $dir ) { | 
| 392 |  |  |  |  |  |  | $dir = 1; | 
| 393 |  |  |  |  |  |  | #say '  revert : '.$w->text; | 
| 394 |  |  |  |  |  |  | my @rdx = reverse @dx; | 
| 395 |  |  |  |  |  |  | my @rdy = reverse @dy; | 
| 396 |  |  |  |  |  |  | @dx = @rdx; @dy = @rdy; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  | else { | 
| 399 |  |  |  |  |  |  | $dir = 0; | 
| 400 |  |  |  |  |  |  | if ($w->size - 1 <= 5) { | 
| 401 |  |  |  |  |  |  | #say '  no place for : '.$w->text; | 
| 402 |  |  |  |  |  |  | $w->show(0); | 
| 403 |  |  |  |  |  |  | next; | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  | #say '  decrease : '.$w->text; | 
| 406 |  |  |  |  |  |  | $w->update_size($self,$w->size - 1); | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  | }; | 
| 409 |  |  |  |  |  |  | } while ( ! $inside  || scalar @ranges == 1 ); | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | # register used space | 
| 412 |  |  |  |  |  |  | map { if ($_) {push @{ $self->cused }, $_} } @ranges; | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | # show | 
| 415 |  |  |  |  |  |  | $self->_show_word($w) if ($w->show); | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | #$c++; last if $c > 2; | 
| 418 |  |  |  |  |  |  | } | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | $self->_save_to_png if (!$self->svg); | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | $self->_save_layout(@words); | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | sub _save_to_png { | 
| 427 |  |  |  |  |  |  | my $self = shift; | 
| 428 |  |  |  |  |  |  | $self->surface->write_to_png ($self->filename . '.png'); | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | # Save words position to a file. Usefull to update colors. | 
| 432 |  |  |  |  |  |  | sub _save_layout { | 
| 433 |  |  |  |  |  |  | my ($self, @words) = @_; | 
| 434 |  |  |  |  |  |  | my $fh; | 
| 435 |  |  |  |  |  |  | open $fh, '>:utf8', $self->filename . '.sl.txt'; | 
| 436 |  |  |  |  |  |  | foreach my $w (@words) { | 
| 437 |  |  |  |  |  |  | print $fh $w->show."\t".$w->text."\t".$w->size."\t".$w->c->x."\t".$w->c->y."\t".$w->angle."\n"; | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  | close $fh; | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | sub _show_word { | 
| 444 |  |  |  |  |  |  | my ($self,$w) = @_; | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | $self->cr->select_font_face( | 
| 447 |  |  |  |  |  |  | $w->font->{font},$w->font->{type},$w->font->{weight}); | 
| 448 |  |  |  |  |  |  | $self->cr->set_font_size($w->size); | 
| 449 |  |  |  |  |  |  | my $po = Graphics::ColorNames->new; | 
| 450 |  |  |  |  |  |  | my @rgb = $po->rgb($w->color); | 
| 451 |  |  |  |  |  |  | $self->cr->set_source_rgb ($rgb[0]/255.0, $rgb[1]/255.0, $rgb[2]/255.0); | 
| 452 |  |  |  |  |  |  | #say '  '.$w->text.' '.$w->color; | 
| 453 |  |  |  |  |  |  | if ($w->angle < 0) { | 
| 454 |  |  |  |  |  |  | $self->cr->save; | 
| 455 |  |  |  |  |  |  | $self->cr->move_to($w->p->x+$w->width,$w->p->y); | 
| 456 |  |  |  |  |  |  | $self->cr->rotate($w->angle); | 
| 457 |  |  |  |  |  |  | $self->cr->show_text($w->text); | 
| 458 |  |  |  |  |  |  | $self->cr->restore; | 
| 459 |  |  |  |  |  |  | } | 
| 460 |  |  |  |  |  |  | else { | 
| 461 |  |  |  |  |  |  | $self->cr->move_to($w->p->x,$w->p->y); | 
| 462 |  |  |  |  |  |  | $self->cr->show_text($w->text); | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | } | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | sub _random_point { | 
| 468 |  |  |  |  |  |  | my ($self,$width, $height) = @_; | 
| 469 |  |  |  |  |  |  | my $x = rand( $width * 0.8 ) + $width * 0.1 ; | 
| 470 |  |  |  |  |  |  | my $y = rand( $height * 0.8 ) + $height * 0.1 ; | 
| 471 |  |  |  |  |  |  | return ($x, $y); | 
| 472 |  |  |  |  |  |  | } | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | =head1 Git | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | L<https://github.com/yvesago/WIoZ/> | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =head1 AUTHORS | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | Yves Agostini, C<< <yveago@cpan.org> >> | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | Copyright 2013 - Yves Agostini | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | This program is free software and may be modified or distributed under the same terms as Perl itself. | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | =cut | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | 1; |