| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Text::KnuthPlass; |
|
2
|
|
|
|
|
|
|
require XSLoader; |
|
3
|
3
|
|
|
3
|
|
124237
|
use constant DEBUG => 0; |
|
|
3
|
|
|
|
|
15
|
|
|
|
3
|
|
|
|
|
334
|
|
|
4
|
3
|
|
|
3
|
|
18
|
use constant purePerl => 0; # 1: do NOT load XS routines |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
111
|
|
|
5
|
3
|
|
|
3
|
|
14
|
use warnings; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
71
|
|
|
6
|
3
|
|
|
3
|
|
14
|
use strict; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
74
|
|
|
7
|
3
|
|
|
3
|
|
14
|
use List::Util qw/min/; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
351
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '1.07'; # VERSION |
|
10
|
|
|
|
|
|
|
our $LAST_UPDATE = '1.07'; # manually update whenever file is edited |
|
11
|
|
|
|
|
|
|
|
|
12
|
3
|
|
|
3
|
|
1742
|
use Data::Dumper; |
|
|
3
|
|
|
|
|
23263
|
|
|
|
3
|
|
|
|
|
349
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# disable XS usage for debug, etc. |
|
15
|
|
|
|
|
|
|
if (!purePerl) { |
|
16
|
|
|
|
|
|
|
eval { XSLoader::load("Text::KnuthPlass", $VERSION); } or die $@; |
|
17
|
|
|
|
|
|
|
# Or else there's a Perl version to fall back on |
|
18
|
|
|
|
|
|
|
# does camelCase in Perl get automatically changed to camel_case? |
|
19
|
|
|
|
|
|
|
# _computeCost() in Perl vs _compute_cost() in XS |
|
20
|
|
|
|
|
|
|
# _computeSum() in Perl vs _compute_sum() in XS |
|
21
|
|
|
|
|
|
|
# _init_nodelist() |
|
22
|
|
|
|
|
|
|
# _cleanup() |
|
23
|
|
|
|
|
|
|
# _active_to_breaks() |
|
24
|
|
|
|
|
|
|
# _mainloop() |
|
25
|
|
|
|
|
|
|
} |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
package Text::KnuthPlass::Element; |
|
28
|
3
|
|
|
3
|
|
25
|
use base 'Class::Accessor'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
1704
|
|
|
29
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors("width"); |
|
30
|
|
|
|
|
|
|
sub new { |
|
31
|
319
|
|
|
319
|
|
465
|
my $self = shift; |
|
32
|
319
|
|
|
|
|
1286
|
return bless { 'width' => 0, @_ }, $self; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
sub is_penalty { |
|
35
|
65
|
|
|
65
|
|
1886
|
return shift->isa("Text::KnuthPlass::Penalty"); |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
sub is_glue { |
|
38
|
0
|
|
|
0
|
|
0
|
return shift->isa("Text::KnuthPlass::Glue"); |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
sub is_box { |
|
41
|
0
|
|
|
0
|
|
0
|
return shift->isa("Text::KnuthPlass::Box"); |
|
42
|
|
|
|
|
|
|
} |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
package Text::KnuthPlass::Box; |
|
45
|
3
|
|
|
3
|
|
5552
|
use base 'Text::KnuthPlass::Element'; |
|
|
3
|
|
|
|
|
8
|
|
|
|
3
|
|
|
|
|
1171
|
|
|
46
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors("value"); |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub _txt { # different from other _txt() defs |
|
49
|
0
|
|
|
0
|
|
0
|
return "[".$_[0]->value()."/".$_[0]->width()."]"; |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
package Text::KnuthPlass::Glue; |
|
53
|
3
|
|
|
3
|
|
23
|
use base 'Text::KnuthPlass::Element'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
922
|
|
|
54
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors("stretch", "shrink"); |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub new { |
|
57
|
122
|
|
|
122
|
|
182
|
my $self = shift; |
|
58
|
122
|
|
|
|
|
186
|
return $self->SUPER::new('stretch' => 0, 'shrink' => 0, @_); |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
sub _txt { # different from other _txt() defs |
|
61
|
0
|
|
|
0
|
|
0
|
return sprintf "<%.2f+%.2f-%.2f>", $_[0]->width(), $_[0]->stretch(), $_[0]->shrink(); |
|
62
|
|
|
|
|
|
|
} |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
package Text::KnuthPlass::Penalty; |
|
65
|
3
|
|
|
3
|
|
36
|
use base 'Text::KnuthPlass::Element'; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
907
|
|
|
66
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors("penalty", "flagged", "shrink"); |
|
67
|
|
|
|
|
|
|
sub new { |
|
68
|
40
|
|
|
40
|
|
307
|
my $self = shift; |
|
69
|
40
|
|
|
|
|
78
|
return $self->SUPER::new('flagged' => 0, 'shrink' => 0, @_); |
|
70
|
|
|
|
|
|
|
} |
|
71
|
|
|
|
|
|
|
sub _txt { # different from other _txt() defs |
|
72
|
0
|
|
0
|
0
|
|
0
|
return "(".$_[0]->penalty().($_[0]->flagged() &&"!").")"; |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
package Text::KnuthPlass::Breakpoint; |
|
76
|
3
|
|
|
3
|
|
29
|
use base 'Text::KnuthPlass::Element'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
763
|
|
|
77
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw/position demerits ratio line fitnessClass totals previous/); |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
package Text::KnuthPlass::DummyHyphenator; |
|
80
|
3
|
|
|
3
|
|
20
|
use base 'Class::Accessor'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
304
|
|
|
81
|
|
|
|
|
|
|
sub hyphenate { |
|
82
|
58
|
|
|
58
|
|
436
|
return $_[1]; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
package Text::KnuthPlass; |
|
86
|
3
|
|
|
3
|
|
18
|
use base 'Class::Accessor'; |
|
|
3
|
|
|
|
|
25
|
|
|
|
3
|
|
|
|
|
215
|
|
|
87
|
3
|
|
|
3
|
|
19
|
use Carp qw/croak/; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
9156
|
|
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# these settings are settable via new(%opts) |
|
90
|
|
|
|
|
|
|
my %defaults = ( |
|
91
|
|
|
|
|
|
|
'infinity' => 10000, |
|
92
|
|
|
|
|
|
|
'tolerance' => 30, # maximum allowable ratio (way out of reasonable!) |
|
93
|
|
|
|
|
|
|
'hyphenpenalty' => 50, |
|
94
|
|
|
|
|
|
|
'demerits' => { 'line' => 10, 'flagged' => 100, 'fitness' => 3000 }, |
|
95
|
|
|
|
|
|
|
'space' => { 'width' => 3, 'stretch' => 6, 'shrink' => 9 }, |
|
96
|
|
|
|
|
|
|
'linelengths' => [ 78 ], # character count (fixed pitch) |
|
97
|
|
|
|
|
|
|
'measure' => sub { length $_[0] }, |
|
98
|
|
|
|
|
|
|
'hyphenator' => |
|
99
|
|
|
|
|
|
|
# TBD min_suffix 3 for English and many, but not all, languages. %opt |
|
100
|
|
|
|
|
|
|
eval { require Text::Hyphen }? Text::Hyphen->new('min_suffix' => 3): |
|
101
|
|
|
|
|
|
|
Text::KnuthPlass::DummyHyphenator->new(), |
|
102
|
|
|
|
|
|
|
'purePerl' => 0, # 1: use pure Perl code, not XS CURRENTLY UNUSED |
|
103
|
|
|
|
|
|
|
'const' => 0, # width (char or points) to reduce line length to allow |
|
104
|
|
|
|
|
|
|
# for word-split hyphen without overflow into margin |
|
105
|
|
|
|
|
|
|
# CURRENTLY UNUSED |
|
106
|
|
|
|
|
|
|
'indent' => 0, # global paragraph indentation width |
|
107
|
|
|
|
|
|
|
); |
|
108
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(keys %defaults); |
|
109
|
|
|
|
|
|
|
sub new { |
|
110
|
7
|
|
|
7
|
1
|
285128
|
my $self = shift; |
|
111
|
|
|
|
|
|
|
# hash elements in new() override whatever's in %default |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# tack on any new() overrides of defaults |
|
114
|
7
|
|
|
|
|
2482
|
return bless {%defaults, @_}, $self; |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head1 NAME |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Text::KnuthPlass - Breaks paragraphs into lines using the TeX (Knuth-Plass) algorithm |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
To use with plain text, indentation of 2. NOTE that you should also |
|
124
|
|
|
|
|
|
|
set the shrinkability of spaces to 0 in the new() call: |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
use Text::KnuthPlass; |
|
127
|
|
|
|
|
|
|
my $typesetter = Text::KnuthPlass->new( |
|
128
|
|
|
|
|
|
|
'indent' => 2, # two characters, |
|
129
|
|
|
|
|
|
|
# set space shrinkability to 0 |
|
130
|
|
|
|
|
|
|
'space' => { 'width' => 3, 'stretch' => 6, 'shrink' -> 0 }, |
|
131
|
|
|
|
|
|
|
# can let 'measure' default to character count |
|
132
|
|
|
|
|
|
|
# default line lengths to 78 characters |
|
133
|
|
|
|
|
|
|
); |
|
134
|
|
|
|
|
|
|
my @lines = $typesetter->typeset($paragraph); |
|
135
|
|
|
|
|
|
|
... |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
for my $line (@lines) { |
|
138
|
|
|
|
|
|
|
for my $node (@{$line->{'nodes'}}) { |
|
139
|
|
|
|
|
|
|
if ($node->isa("Text::KnuthPlass::Box")) { |
|
140
|
|
|
|
|
|
|
# a Box is a word or word fragment (no hyphen on fragment) |
|
141
|
|
|
|
|
|
|
print $node->value(); |
|
142
|
|
|
|
|
|
|
} elsif ($node->isa("Text::KnuthPlass::Glue")) { |
|
143
|
|
|
|
|
|
|
# a Glue is (at least) a single space, but you can look at |
|
144
|
|
|
|
|
|
|
# the line's 'ratio' to insert additional spaces to |
|
145
|
|
|
|
|
|
|
# justify the line. we also are glossing over the skipping |
|
146
|
|
|
|
|
|
|
# of any final glue at the end of the line |
|
147
|
|
|
|
|
|
|
print " "; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
# ignoring Penalty (word split point) within line |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
if ($line->{'nodes'}[-1]->is_penalty()) { print "-"; } |
|
152
|
|
|
|
|
|
|
print "\n"; |
|
153
|
|
|
|
|
|
|
} |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
To use with PDF::Builder: (also PDF::API2) |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
my $text = $page->text(); |
|
158
|
|
|
|
|
|
|
$text->font($font, 12); |
|
159
|
|
|
|
|
|
|
$text->leading(13.5); |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
my $t = Text::KnuthPlass->new( |
|
162
|
|
|
|
|
|
|
'indent' => 2*$text->text_width('M'), # 2 ems |
|
163
|
|
|
|
|
|
|
'measure' => sub { $text->text_width(shift) }, |
|
164
|
|
|
|
|
|
|
'linelengths' => [235] # points |
|
165
|
|
|
|
|
|
|
); |
|
166
|
|
|
|
|
|
|
my @lines = $t->typeset($paragraph); |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
my $y = 500; # PDF decreases y down the page |
|
169
|
|
|
|
|
|
|
for my $line (@lines) { |
|
170
|
|
|
|
|
|
|
$x = 50; # left margin |
|
171
|
|
|
|
|
|
|
for my $node (@{$line->{'nodes'}}) { |
|
172
|
|
|
|
|
|
|
$text->translate($x,$y); |
|
173
|
|
|
|
|
|
|
if ($node->isa("Text::KnuthPlass::Box")) { |
|
174
|
|
|
|
|
|
|
# a Box is a word or word fragment (no hyphen on fragment) |
|
175
|
|
|
|
|
|
|
$text->text($node->value()); |
|
176
|
|
|
|
|
|
|
$x += $node->width(); |
|
177
|
|
|
|
|
|
|
} elsif ($node->isa("Text::KnuthPlass::Glue")) { |
|
178
|
|
|
|
|
|
|
# a Glue is a variable-width space |
|
179
|
|
|
|
|
|
|
$x += $node->width() + $line->{'ratio'} * |
|
180
|
|
|
|
|
|
|
($line->{'ratio'} < 0 ? $node->shrink(): $node->stretch()); |
|
181
|
|
|
|
|
|
|
# we also are glossing over the skipping |
|
182
|
|
|
|
|
|
|
# of any final glue at the end of the line |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
# ignoring Penalty (word split point) within line |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
# explicitly add a hyphen at a line-ending split word |
|
187
|
|
|
|
|
|
|
if ($line->{'nodes'}[-1]->is_penalty()) { $text->text("-"); } |
|
188
|
|
|
|
|
|
|
$y -= $text->leading(); # go to next line down |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head1 METHODS |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=head2 $t = Text::KnuthPlass->new(%opts) |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
The constructor takes a number of options. The most important ones are: |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=over |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=item measure |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
A subroutine reference to determine the width of a piece of text. This |
|
202
|
|
|
|
|
|
|
defaults to C, which is what you want if you're |
|
203
|
|
|
|
|
|
|
typesetting plain monospaced text. You will need to change this to plug |
|
204
|
|
|
|
|
|
|
into your font metrics if you're doing something graphical. For PDF::Builder |
|
205
|
|
|
|
|
|
|
(also PDF::API2), this would be the C method (alias |
|
206
|
|
|
|
|
|
|
C), which returns the width of a string (in the present font |
|
207
|
|
|
|
|
|
|
and size) in points. |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
'measure' => sub { length(shift) }, # default, for character output |
|
210
|
|
|
|
|
|
|
'measure' => sub { $text->advancewidth(shift) }, # PDF::Builder/API2 |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=item linelengths |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
This is an array of line lengths. For instance, C< [30,40,50] > will |
|
215
|
|
|
|
|
|
|
typeset a triangle-shaped piece of text with three lines. What if the |
|
216
|
|
|
|
|
|
|
text spills over to more than three lines? In that case, the final value |
|
217
|
|
|
|
|
|
|
in the array is used for all further lines. So to typeset an ordinary |
|
218
|
|
|
|
|
|
|
block-shaped column of text, you only need specify an array with one |
|
219
|
|
|
|
|
|
|
value: the default is C< [78] >. Note that this default would be the |
|
220
|
|
|
|
|
|
|
character count, rather than points (as needed by PDF::Builder or PDF::API2). |
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
'linelengths' => [$lw, $lw, $lw-6, $lw-6, $lw], |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
This would set the first two lines in the paragraph to C<$lw> length, the next |
|
225
|
|
|
|
|
|
|
two to 6 less (such as for a float inset), and finally back to full length. |
|
226
|
|
|
|
|
|
|
At each line, the first element is consumed, but the last element is never |
|
227
|
|
|
|
|
|
|
removed. Any paragraph indentation set will result in a shorter-appearing |
|
228
|
|
|
|
|
|
|
first line, which actually has blank space at its beginning. Start output of |
|
229
|
|
|
|
|
|
|
the first line at the same C value as you do the other lines. |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Setting C in the C (constructor) call resets the internal |
|
232
|
|
|
|
|
|
|
line length list to the new elements, overwriting anything that was already |
|
233
|
|
|
|
|
|
|
there (such as any remaining line lengths left over from a previous C call). Subsequent C calls will continue to consume the existing |
|
234
|
|
|
|
|
|
|
line length list, until the last element is reached. You can either reset the |
|
235
|
|
|
|
|
|
|
list for the next paragraph with the C call, or call the |
|
236
|
|
|
|
|
|
|
C method to get or set the list. |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=item indent |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
This sets the global (default) paragraph indentation, unless overridden |
|
241
|
|
|
|
|
|
|
on a per-paragraph basis by |
|
242
|
|
|
|
|
|
|
an C entry in a C call. The units are the same as for |
|
243
|
|
|
|
|
|
|
C and C. A "Box" of value C<''> and width of C is |
|
244
|
|
|
|
|
|
|
inserted before the first node of the paragraph. Your rendering code should |
|
245
|
|
|
|
|
|
|
know how to handle this by starting at the same C coordinate as other lines, |
|
246
|
|
|
|
|
|
|
and then moving right (or left) by the indicated amount. |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
'indent' => 2, # 2 character indentation |
|
249
|
|
|
|
|
|
|
'indent' => 2*$text->text_width('M'), # 2 ems indentation |
|
250
|
|
|
|
|
|
|
'indent' => -3, # 3 character OUTdent |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
If the value is negative, a negative-width space Box is added. The overall line |
|
253
|
|
|
|
|
|
|
will be longer than other lines, by that amount. Again, your rendering code |
|
254
|
|
|
|
|
|
|
should handle this in a similar manner as with a positive indentation, but |
|
255
|
|
|
|
|
|
|
move I by the indicated amount. Be careful to have your starting C |
|
256
|
|
|
|
|
|
|
value far enough to the right that text will not end up being written off-page. |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=item tolerance |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
How much leeway we have in leaving wider spaces than the algorithm |
|
261
|
|
|
|
|
|
|
would prefer. The C is the maximum C glue expansion value to |
|
262
|
|
|
|
|
|
|
I in a possible solution, before discarding this solution as so |
|
263
|
|
|
|
|
|
|
infeasible as to be a waste of time to pursue further. Most of the time, the |
|
264
|
|
|
|
|
|
|
C is going to have a value in the 1 to 3 range. One approach is to |
|
265
|
|
|
|
|
|
|
try with C 1>, and if no successful layout is found, try |
|
266
|
|
|
|
|
|
|
again with 2, and then 3 and perhaps even 4. |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=item hyphenator |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
An object which hyphenates words. If you have the C product |
|
271
|
|
|
|
|
|
|
installed (which is highly recommended), then a C object is |
|
272
|
|
|
|
|
|
|
instantiated by default; if not, an object of the class |
|
273
|
|
|
|
|
|
|
C is instantiated - this simply finds |
|
274
|
|
|
|
|
|
|
no hyphenation points at all. So to turn hyphenation off, set |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
'hyphenator' => Text::KnuthPlass::DummyHyphenator->new() |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
To typeset non-English text, pass in a C-like object which |
|
279
|
|
|
|
|
|
|
responds to the C method, returning a list of hyphen positions for |
|
280
|
|
|
|
|
|
|
that particular language (native C defaults to American English |
|
281
|
|
|
|
|
|
|
hyphenation rules). (See C for the interface.) |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=item space |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Fine tune space (glue) width, stretchability, and shrinkability. |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
'space' => { 'width' => 3, 'stretch' => 6, 'shrink' => 9 }, |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
For typesetting |
|
290
|
|
|
|
|
|
|
constant width text or output to a text file (characters), we suggest setting |
|
291
|
|
|
|
|
|
|
the C value to 0. This prevents the glue spaces from being shrunk to |
|
292
|
|
|
|
|
|
|
less than one character wide, which could result in either no spaces between |
|
293
|
|
|
|
|
|
|
words, or overflow into the right margin. |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
'space' => { 'width' => 3, 'stretch' => 6, 'shrink' => 0 }, |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=item infinity |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
The default value for I is, as is customary in TeX, 10000. While this |
|
300
|
|
|
|
|
|
|
is a far cry from the real infinity, so long as it is substantially larger than |
|
301
|
|
|
|
|
|
|
any other demerit or penalty, it should take precedence in calculations. Both |
|
302
|
|
|
|
|
|
|
positive and negative C are used in the code for various purposes, |
|
303
|
|
|
|
|
|
|
including a C<+inf> penalty for something absolutely forbidden, and C<-inf> for |
|
304
|
|
|
|
|
|
|
something absolutely required (such as a line break at the end of a paragraph). |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
'infinity' => 10000, |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=item hyphenpenalty |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Set the penalty for an end-of-line hyphen at 50. You may want to try a somewhat |
|
311
|
|
|
|
|
|
|
higher value, such as 100+, if you see too much hyphenation on output. Remember |
|
312
|
|
|
|
|
|
|
that excessively short lines are prone to splitting words and being hyphenated, |
|
313
|
|
|
|
|
|
|
no matter what the penalty is. |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
'hyphenpenalty' => 50, |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
There does not appear to be anything in the code to find and prevent multiple |
|
318
|
|
|
|
|
|
|
contiguous (adjacent) hyphenated lines, nor to prevent the penultimate |
|
319
|
|
|
|
|
|
|
(next-to-last) line from being hyphenated, nor to prevent the hyphenation of |
|
320
|
|
|
|
|
|
|
a line where you anticipate the paragraph to be split between columns. |
|
321
|
|
|
|
|
|
|
Something may be done in the future about these three special cases, which |
|
322
|
|
|
|
|
|
|
are considered to not be good typesetting. |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=item demerits |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
Various demerits used in calculating penalties, including I, which is |
|
327
|
|
|
|
|
|
|
used when line tightness (C) changes by more than one class between two |
|
328
|
|
|
|
|
|
|
lines. |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
'demerits' => { 'line' => 10, 'flagged' => 100, 'fitness' => 3000 }, |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=back |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
There may be other options for fine-tuning the output. If you know your way |
|
335
|
|
|
|
|
|
|
around TeX, dig into the source to find out what they are. At some point, |
|
336
|
|
|
|
|
|
|
this package will support additional tuning by allowing the setting of more |
|
337
|
|
|
|
|
|
|
parameters which are currently hard-coded. Please let us know if you found any |
|
338
|
|
|
|
|
|
|
more parameters that would be useful to allow additional tuning! |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# more options, not currently implemented |
|
343
|
|
|
|
|
|
|
# 'purePerl' => 0, # 1: use pure Perl code, not XS. currently is hard-coded |
|
344
|
|
|
|
|
|
|
# at top, as new() appears to be too late to call xload() |
|
345
|
|
|
|
|
|
|
# 'const' => 0, # width (char or points) to reduce line length to allow |
|
346
|
|
|
|
|
|
|
# hyphenated word's hyphen not to overhang into right |
|
347
|
|
|
|
|
|
|
# margin (constant width or character output), or result |
|
348
|
|
|
|
|
|
|
# in slight tightening that may end up too much (ratio too |
|
349
|
|
|
|
|
|
|
# negative). Still looking at it. |
|
350
|
|
|
|
|
|
|
# TBD |
|
351
|
|
|
|
|
|
|
# 'hangingp' => 0, # use hanging punctuation (last character in a line is |
|
352
|
|
|
|
|
|
|
# punctuation, including split-word hyphen) to write |
|
353
|
|
|
|
|
|
|
# that punctuation over into the right margin. Some "very |
|
354
|
|
|
|
|
|
|
# fine" typesetting overhangs a per-character (and font) |
|
355
|
|
|
|
|
|
|
# percentage on left and right, and even letters too. |
|
356
|
|
|
|
|
|
|
# 'dropcap' => { 'lines' => 3, 'scale' => 2.5, .... }, |
|
357
|
|
|
|
|
|
|
# indent first 'lines' lines of the paragraph to provide |
|
358
|
|
|
|
|
|
|
# space for an oversized letter with some movement up and |
|
359
|
|
|
|
|
|
|
# left. Letter is taken from $paragraph text. If paragraph |
|
360
|
|
|
|
|
|
|
# doesn't have enough lines, pad with blank lines so that |
|
361
|
|
|
|
|
|
|
# no need to indent following paragraph! Usually just for |
|
362
|
|
|
|
|
|
|
# first paragraph in a section (as with SC), so need a way |
|
363
|
|
|
|
|
|
|
# to cancel for subsequent paragraphs (if on by default). |
|
364
|
|
|
|
|
|
|
# TBD but this one might better belong in PDF::Builder |
|
365
|
|
|
|
|
|
|
# 'smallcap' => { 'words' => 1, ... }, |
|
366
|
|
|
|
|
|
|
# small caps on first line text. 'words' is 1 to SC first |
|
367
|
|
|
|
|
|
|
# word (or remainder after DropCap does its thing), 0 is |
|
368
|
|
|
|
|
|
|
# no SC, -1 is entire line, >0 is that many words (up to |
|
369
|
|
|
|
|
|
|
# end of first line). Usually just for first paragraph in |
|
370
|
|
|
|
|
|
|
# a section (as with DC), so need a way to cancel for |
|
371
|
|
|
|
|
|
|
# subsequent paragraphs (if on by default). |
|
372
|
|
|
|
|
|
|
# Note that your rendering code should take care of any additional top margin |
|
373
|
|
|
|
|
|
|
# (interparagraph space). Settings may be added for other things to fine-tune |
|
374
|
|
|
|
|
|
|
# the output. |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=head2 $t->typeset($paragraph_string, %opts) |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
This is the main interface to the algorithm, made up of the constituent |
|
379
|
|
|
|
|
|
|
parts below. It takes a paragraph of text and returns a list of lines (array |
|
380
|
|
|
|
|
|
|
of hashes) if suitable breakpoints could be found. |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
The typesetter currently allows several options: |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=over |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=item indent |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Override the global paragraph indentation value B |
|
389
|
|
|
|
|
|
|
This can be useful for |
|
390
|
|
|
|
|
|
|
instances such as I indenting the first paragraph in a section. |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
'indent' => 0, # default set in new() is 2ems |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=item linelengths |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
The array of line lengths may be set here, in C. As with C, it |
|
397
|
|
|
|
|
|
|
will override whatever existing line lengths array is left over from |
|
398
|
|
|
|
|
|
|
earlier operations. |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=back |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Possibly (in the future) many other global settings set in C may be |
|
403
|
|
|
|
|
|
|
overridden on a per-paragraph basis in C. |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
The returned list has the following structure: |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
( |
|
408
|
|
|
|
|
|
|
{ 'nodes' => \@nodes, 'ratio' => $ratio }, |
|
409
|
|
|
|
|
|
|
{ 'nodes' => \@nodes, 'ratio' => $ratio }, |
|
410
|
|
|
|
|
|
|
... |
|
411
|
|
|
|
|
|
|
) |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
The node list in each element will be a list of objects. Each object |
|
414
|
|
|
|
|
|
|
will be either C, C |
|
415
|
|
|
|
|
|
|
or C. See below for more on these. |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
The C is the amount of stretch or shrink which should be applied to |
|
418
|
|
|
|
|
|
|
each glue element in this line. The corrected width of each glue node |
|
419
|
|
|
|
|
|
|
should be: |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
$node->width() + $line->{'ratio'} * |
|
422
|
|
|
|
|
|
|
($line->{'ratio'} < 0 ? $node->shrink() : $node->stretch()); |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
Each box, glue or penalty node has a C attribute. Boxes have |
|
425
|
|
|
|
|
|
|
Cs, which are the text which went into them (including a wide null |
|
426
|
|
|
|
|
|
|
blank for paragraph indentation, a special case); glue has C |
|
427
|
|
|
|
|
|
|
and C to determine how much it should vary in width. That should |
|
428
|
|
|
|
|
|
|
be all you need for basic typesetting; for more, see the source, and see |
|
429
|
|
|
|
|
|
|
the original Knuth-Plass paper in "Digital Typography". |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
Why I rather than something like I? Per |
|
432
|
|
|
|
|
|
|
L, this code is ported from the Javascript product |
|
433
|
|
|
|
|
|
|
B. |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
This method is a thin wrapper around the three methods below. |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=cut |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# indent entry in options applies only to this paragraph. |
|
440
|
|
|
|
|
|
|
# linelengths OK to change global value. |
|
441
|
|
|
|
|
|
|
sub typeset { |
|
442
|
3
|
|
|
3
|
1
|
34
|
my ($t, $paragraph, %opts) = @_; |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# if give linelengths, need to set (replace) global value |
|
445
|
3
|
50
|
|
|
|
17
|
if (defined $opts{'linelengths'}) { |
|
446
|
0
|
|
|
|
|
0
|
$t->{'linelengths'} = $opts{'linelengths'}; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# break up the text into a collection (list) of box, glue, penalty nodes |
|
450
|
3
|
|
|
|
|
15
|
my @nodes = $t->break_text_into_nodes($paragraph, %opts); |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# if indenting first line of paragraph, add a Box for that blank |
|
453
|
3
|
|
|
|
|
10
|
my $indent = $t->{'indent'}; # global indent |
|
454
|
3
|
50
|
|
|
|
11
|
$indent = $opts{'indent'} if defined $opts{'indent'}; # local override |
|
455
|
3
|
100
|
|
|
|
12
|
if ($indent) { # non-zero amount? could be + or - |
|
456
|
1
|
|
|
|
|
5
|
unshift @nodes, Text::KnuthPlass::Box->new( |
|
457
|
|
|
|
|
|
|
'width' => $indent, |
|
458
|
|
|
|
|
|
|
'value' => '' |
|
459
|
|
|
|
|
|
|
); |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# figure best set of breakpoints (lowest cost) |
|
463
|
3
|
|
|
|
|
16
|
my @breakpoints = $t->break(\@nodes); |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
# quit if nothing found (need to increase tolerance) |
|
466
|
3
|
50
|
|
|
|
19
|
return unless @breakpoints; |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# group nodes into lines according to breakpoints |
|
469
|
3
|
|
|
|
|
13
|
my @lines = $t->breakpoints_to_lines(\@breakpoints, \@nodes); |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
# Remove final penalty and glue from last line in paragraph |
|
472
|
3
|
50
|
|
|
|
10
|
if (@lines) { |
|
473
|
3
|
|
|
|
|
4
|
pop @{ $lines[-1]->{'nodes'} } ; |
|
|
3
|
|
|
|
|
8
|
|
|
474
|
3
|
|
|
|
|
11
|
pop @{ $lines[-1]->{'nodes'} } ; |
|
|
3
|
|
|
|
|
8
|
|
|
475
|
|
|
|
|
|
|
} |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# trim off one linelengths element per line output, but keep last one |
|
478
|
3
|
|
|
|
|
4
|
my @temp = @{ $t->{'linelengths'} }; |
|
|
3
|
|
|
|
|
11
|
|
|
479
|
3
|
|
|
|
|
19
|
splice(@temp, 0, min(scalar(@lines), scalar(@temp)-1)); |
|
480
|
3
|
|
|
|
|
10
|
$t->{'linelengths'} = \@temp; |
|
481
|
|
|
|
|
|
|
|
|
482
|
3
|
|
|
|
|
92
|
return @lines; |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 $t->line_lengths() |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=over |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=item @list = $t->line_lengths() # Get |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=item $t->line_lengths(@list) # Set |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
Get or set the C list of allowed line lengths. This permits you to |
|
494
|
|
|
|
|
|
|
do more elaborate operations on this array than simply replacing (resetting) it, |
|
495
|
|
|
|
|
|
|
as done in the C and C methods. For example, at the bottom of |
|
496
|
|
|
|
|
|
|
a page, you might cancel any further inset for a float, by deleting all but the |
|
497
|
|
|
|
|
|
|
last element of the list. |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
my @temp_LL = $t->line_lengths(); |
|
500
|
|
|
|
|
|
|
# cancel remaining line shortening |
|
501
|
|
|
|
|
|
|
splice(@temp_LL, 0, scalar(@temp_LL)-1); |
|
502
|
|
|
|
|
|
|
$t->line_lengths(@temp_LL); |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
On a "Set" request, you must have at least one length element in the list. If |
|
505
|
|
|
|
|
|
|
the list is empty, it is assumed to be a "Get" request. |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=back |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=cut |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub line_lengths { |
|
512
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
513
|
|
|
|
|
|
|
|
|
514
|
0
|
0
|
|
|
|
0
|
if (@_) { # Set |
|
515
|
0
|
|
|
|
|
0
|
$self->{'linelengths'} = \@_; |
|
516
|
0
|
|
|
|
|
0
|
return; |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
} else { # Get |
|
519
|
0
|
|
|
|
|
0
|
return @{ $self->{'linelengths'} }; |
|
|
0
|
|
|
|
|
0
|
|
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
} |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
=head2 $t->break_text_into_nodes($paragraph_string, %opts) |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
This turns a paragraph into a list of box/glue/penalty nodes. It's |
|
526
|
|
|
|
|
|
|
fairly basic, and designed to be overloaded. It should also support |
|
527
|
|
|
|
|
|
|
multiple justification styles (centering, ragged right, etc.) but this |
|
528
|
|
|
|
|
|
|
will come in a future release; right now, it just does full |
|
529
|
|
|
|
|
|
|
justification. |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=head3 'style' => "string_name" |
|
532
|
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=over |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=item "justify" |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
Fully justify the text (flush left I right). This is the B, |
|
538
|
|
|
|
|
|
|
and currently I |
|
539
|
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=item "left" |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
Not yet implemented. This will be flush left, ragged right (reversed for |
|
543
|
|
|
|
|
|
|
RTL scripts). |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=item "right" |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Not yet implemented. This will be flush right, ragged left (reversed for |
|
548
|
|
|
|
|
|
|
RTL scripts). |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=item "center" |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Implemented, but not yet fully tested. |
|
553
|
|
|
|
|
|
|
This is centered text within the indicated line width. |
|
554
|
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
=back |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
If you are doing clever typography or using non-Western languages you |
|
558
|
|
|
|
|
|
|
may find that you will want to break text into nodes yourself, and pass |
|
559
|
|
|
|
|
|
|
the list of nodes to the methods below, instead of using this method. |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=cut |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
sub _add_word { |
|
564
|
122
|
|
|
122
|
|
175
|
my ($self, $word, $nodes_r) = @_; |
|
565
|
122
|
|
|
|
|
197
|
my @elems = $self->hyphenator()->hyphenate($word); |
|
566
|
122
|
|
|
|
|
6645
|
for (0..$#elems) { |
|
567
|
156
|
|
|
|
|
171
|
push @{$nodes_r}, Text::KnuthPlass::Box->new( |
|
|
156
|
|
|
|
|
276
|
|
|
568
|
|
|
|
|
|
|
'width' => $self->measure()->($elems[$_]), |
|
569
|
|
|
|
|
|
|
'value' => $elems[$_] |
|
570
|
|
|
|
|
|
|
); |
|
571
|
156
|
100
|
|
|
|
300
|
if ($_ != $#elems) { |
|
572
|
34
|
|
|
|
|
38
|
push @{$nodes_r}, Text::KnuthPlass::Penalty->new( |
|
|
34
|
|
|
|
|
67
|
|
|
573
|
|
|
|
|
|
|
'flagged' => 1, 'penalty' => $self->hyphenpenalty()); |
|
574
|
|
|
|
|
|
|
} |
|
575
|
|
|
|
|
|
|
} |
|
576
|
122
|
|
|
|
|
177
|
return; |
|
577
|
|
|
|
|
|
|
} |
|
578
|
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
sub break_text_into_nodes { |
|
580
|
6
|
|
|
6
|
1
|
559
|
my ($self, $text, %opts) = @_; |
|
581
|
6
|
|
|
|
|
10
|
my @nodes; |
|
582
|
6
|
|
|
|
|
123
|
my @words = split /\s+/, $text; |
|
583
|
|
|
|
|
|
|
|
|
584
|
6
|
|
|
|
|
12
|
my $style; |
|
585
|
6
|
50
|
|
|
|
24
|
$style = $opts{'style'} if defined $opts{'style'}; |
|
586
|
6
|
|
50
|
|
|
32
|
$style ||= "justify"; # default |
|
587
|
|
|
|
|
|
|
|
|
588
|
6
|
|
|
|
|
26
|
$self->{'emwidth'} = $self->measure()->("M"); |
|
589
|
6
|
|
|
|
|
47
|
$self->{'spacewidth'} = $self->measure()->(" "); |
|
590
|
6
|
|
|
|
|
38
|
$self->{'spacestretch'} = $self->{'spacewidth'} * $self->space()->{'width'} / $self->space()->{'stretch'}; |
|
591
|
|
|
|
|
|
|
# shrink of 0 desired in constant width or text output |
|
592
|
6
|
100
|
|
|
|
101
|
if ($self->space()->{'shrink'} == 0) { |
|
593
|
1
|
|
|
|
|
11
|
$self->{'spaceshrink'} = 0; |
|
594
|
|
|
|
|
|
|
} else { |
|
595
|
5
|
|
|
|
|
50
|
$self->{'spaceshrink'} = $self->{'spacewidth'} * $self->space()->{'width'} / $self->space()->{'shrink'}; |
|
596
|
|
|
|
|
|
|
} |
|
597
|
|
|
|
|
|
|
|
|
598
|
6
|
|
|
|
|
81
|
my $spacing_type = "_add_space_$style"; |
|
599
|
6
|
|
|
|
|
15
|
my $start = "_start_$style"; |
|
600
|
6
|
|
|
|
|
26
|
$self->$start(\@nodes); |
|
601
|
|
|
|
|
|
|
|
|
602
|
6
|
|
|
|
|
19
|
for (0..$#words) { my $word = $words[$_]; |
|
|
122
|
|
|
|
|
156
|
|
|
603
|
122
|
|
|
|
|
222
|
$self->_add_word($word, \@nodes); |
|
604
|
122
|
|
|
|
|
230
|
$self->$spacing_type(\@nodes,$_ == $#words); |
|
605
|
|
|
|
|
|
|
} |
|
606
|
6
|
|
|
|
|
44
|
return @nodes; |
|
607
|
|
|
|
|
|
|
} |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
# fully justified (flush left and right) |
|
610
|
|
|
|
|
|
|
sub _start_justify { |
|
611
|
6
|
|
|
6
|
|
10
|
return; |
|
612
|
|
|
|
|
|
|
} |
|
613
|
|
|
|
|
|
|
sub _add_space_justify { |
|
614
|
122
|
|
|
122
|
|
175
|
my ($self, $nodes_r, $final) = @_; |
|
615
|
122
|
100
|
|
|
|
167
|
if ($final) { |
|
616
|
|
|
|
|
|
|
# last line of paragraph, ends with required break (-inf) |
|
617
|
6
|
|
|
|
|
9
|
push @{$nodes_r}, |
|
|
6
|
|
|
|
|
13
|
|
|
618
|
|
|
|
|
|
|
$self->glueclass()->new( |
|
619
|
|
|
|
|
|
|
'width' => 0, |
|
620
|
|
|
|
|
|
|
'stretch' => $self->infinity(), |
|
621
|
|
|
|
|
|
|
'shrink' => 0 |
|
622
|
|
|
|
|
|
|
), |
|
623
|
|
|
|
|
|
|
$self->penaltyclass()->new( |
|
624
|
|
|
|
|
|
|
'width' => 0, |
|
625
|
|
|
|
|
|
|
'penalty' => -$self->infinity(), |
|
626
|
|
|
|
|
|
|
'flagged' => 1 |
|
627
|
|
|
|
|
|
|
); |
|
628
|
|
|
|
|
|
|
} else { |
|
629
|
|
|
|
|
|
|
# NOT last line of paragraph |
|
630
|
116
|
|
|
|
|
173
|
push @{$nodes_r}, $self->glueclass()->new( |
|
631
|
|
|
|
|
|
|
'width' => $self->{'spacewidth'}, |
|
632
|
|
|
|
|
|
|
'stretch' => $self->{'spacestretch'}, |
|
633
|
116
|
|
|
|
|
116
|
'shrink' => $self->{'spaceshrink'} |
|
634
|
|
|
|
|
|
|
); |
|
635
|
|
|
|
|
|
|
} |
|
636
|
122
|
|
|
|
|
189
|
return; |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
# centered within line (NOT TESTED) |
|
640
|
|
|
|
|
|
|
sub _start_center { |
|
641
|
0
|
|
|
0
|
|
0
|
my ($self, $nodes_r) = @_; |
|
642
|
0
|
|
|
|
|
0
|
push @{$nodes_r}, |
|
643
|
|
|
|
|
|
|
Text::KnuthPlass::Box->new('value' => ""), |
|
644
|
|
|
|
|
|
|
Text::KnuthPlass::Glue->new( |
|
645
|
|
|
|
|
|
|
'width' => 0, |
|
646
|
0
|
|
|
|
|
0
|
'stretch' => 2*$self->{'emwidth'}, |
|
647
|
|
|
|
|
|
|
'shrink' => 0 |
|
648
|
|
|
|
|
|
|
); |
|
649
|
0
|
|
|
|
|
0
|
return; |
|
650
|
|
|
|
|
|
|
} |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub _add_space_center { |
|
653
|
0
|
|
|
0
|
|
0
|
my ($self, $nodes_r, $final) = @_; |
|
654
|
0
|
0
|
|
|
|
0
|
if ($final) { |
|
655
|
|
|
|
|
|
|
# last line of paragraph, ends with required break (-inf) |
|
656
|
0
|
|
|
|
|
0
|
push @{$nodes_r}, Text::KnuthPlass::Glue->new( |
|
657
|
|
|
|
|
|
|
'width' => 0, |
|
658
|
0
|
|
|
|
|
0
|
'stretch' => 2*$self->{'emwidth'}, |
|
659
|
|
|
|
|
|
|
'shrink' => 0 |
|
660
|
|
|
|
|
|
|
), |
|
661
|
|
|
|
|
|
|
Text::KnuthPlass::Penalty->new( |
|
662
|
|
|
|
|
|
|
'width' => 0, |
|
663
|
|
|
|
|
|
|
'penalty' => -$self->infinity(), |
|
664
|
|
|
|
|
|
|
'flagged' => 0 |
|
665
|
|
|
|
|
|
|
); |
|
666
|
|
|
|
|
|
|
} else { |
|
667
|
|
|
|
|
|
|
# NOT last line of paragraph |
|
668
|
0
|
|
|
|
|
0
|
push @{$nodes_r}, Text::KnuthPlass::Glue->new( |
|
669
|
|
|
|
|
|
|
'width' => 0, |
|
670
|
|
|
|
|
|
|
'stretch' => 2*$self->{'emwidth'}, |
|
671
|
|
|
|
|
|
|
'shrink' => 0 |
|
672
|
|
|
|
|
|
|
), |
|
673
|
|
|
|
|
|
|
Text::KnuthPlass::Penalty->new( |
|
674
|
|
|
|
|
|
|
'width' => 0, |
|
675
|
|
|
|
|
|
|
'penalty' => 0, |
|
676
|
|
|
|
|
|
|
'flagged' => 0 |
|
677
|
|
|
|
|
|
|
), |
|
678
|
|
|
|
|
|
|
Text::KnuthPlass::Glue->new( |
|
679
|
|
|
|
|
|
|
'width' => $self->{'spacewidth'}, |
|
680
|
|
|
|
|
|
|
'stretch' => -4*$self->{'emwidth'}, |
|
681
|
|
|
|
|
|
|
'shrink' => 0 |
|
682
|
|
|
|
|
|
|
), |
|
683
|
|
|
|
|
|
|
Text::KnuthPlass::Box->new('value' => ""), |
|
684
|
|
|
|
|
|
|
Text::KnuthPlass::Penalty->new( |
|
685
|
|
|
|
|
|
|
'width' => 0, |
|
686
|
|
|
|
|
|
|
'penalty' => $self->infinity(), |
|
687
|
|
|
|
|
|
|
'flagged' => 0 |
|
688
|
|
|
|
|
|
|
), |
|
689
|
|
|
|
|
|
|
Text::KnuthPlass::Glue->new( |
|
690
|
|
|
|
|
|
|
'width' => 0, |
|
691
|
0
|
|
|
|
|
0
|
'stretch' => 2*$self->{'emwidth'}, |
|
692
|
|
|
|
|
|
|
'shrink' => 0 |
|
693
|
|
|
|
|
|
|
), |
|
694
|
|
|
|
|
|
|
} |
|
695
|
0
|
|
|
|
|
0
|
return; |
|
696
|
|
|
|
|
|
|
} |
|
697
|
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# left justified (ragged right) not yet implemented, just handle as 'justified' |
|
699
|
|
|
|
|
|
|
sub _start_left { |
|
700
|
|
|
|
|
|
|
#my ($self, $nodes_r) = @_; |
|
701
|
|
|
|
|
|
|
#return; |
|
702
|
0
|
|
|
0
|
|
0
|
return _start_justify(@_); |
|
703
|
|
|
|
|
|
|
} |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
sub _add_space_left { |
|
706
|
|
|
|
|
|
|
#my ($self, $nodes_r, $final) = @_; |
|
707
|
|
|
|
|
|
|
#return; |
|
708
|
0
|
|
|
0
|
|
0
|
return _add_space_justify(@_); |
|
709
|
|
|
|
|
|
|
} |
|
710
|
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
# right justified (ragged left) not yet implemented, just handle as 'justified' |
|
712
|
|
|
|
|
|
|
sub _start_right { |
|
713
|
|
|
|
|
|
|
#my ($self, $nodes_r) = @_; |
|
714
|
|
|
|
|
|
|
#return; |
|
715
|
0
|
|
|
0
|
|
0
|
return _start_justify(@_); |
|
716
|
|
|
|
|
|
|
} |
|
717
|
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
sub _add_space_right { |
|
719
|
|
|
|
|
|
|
#my ($self, $nodes_r, $final) = @_; |
|
720
|
|
|
|
|
|
|
#return; |
|
721
|
0
|
|
|
0
|
|
0
|
return _add_space_justify(@_); |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
=head2 break |
|
725
|
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
This implements the main body of the algorithm; it turns a list of nodes |
|
727
|
|
|
|
|
|
|
(produced from the above method) into a list of breakpoint objects. |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
=cut |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
sub break { |
|
732
|
4
|
|
|
4
|
1
|
1749
|
my ($self, $nodes) = @_; |
|
733
|
4
|
|
|
|
|
25
|
$self->{'sum'} = {'width' => 0, 'stretch' => 0, 'shrink' => 0 }; |
|
734
|
4
|
|
|
|
|
39
|
$self->_init_nodelist(); |
|
735
|
|
|
|
|
|
|
# shouldn't ever happen, but just in case... |
|
736
|
4
|
50
|
33
|
|
|
31
|
if (!$self->{'linelengths'} || ref $self->{'linelengths'} ne "ARRAY") { |
|
737
|
0
|
|
|
|
|
0
|
croak "No linelengths set"; |
|
738
|
|
|
|
|
|
|
} |
|
739
|
|
|
|
|
|
|
|
|
740
|
4
|
|
|
|
|
26
|
for (0..$#$nodes) { |
|
741
|
291
|
|
|
|
|
1964
|
my $node = $nodes->[$_]; |
|
742
|
291
|
100
|
33
|
|
|
671
|
if ($node->isa("Text::KnuthPlass::Box")) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
743
|
144
|
|
|
|
|
241
|
$self->{'sum'}{'width'} += $node->width(); |
|
744
|
|
|
|
|
|
|
} elsif ($node->isa("Text::KnuthPlass::Glue")) { |
|
745
|
115
|
50
|
33
|
|
|
361
|
if ($_ > 0 and $nodes->[$_-1]->isa("Text::KnuthPlass::Box")) { |
|
746
|
115
|
|
|
|
|
7603
|
$self->_mainloop($node, $_, $nodes); |
|
747
|
|
|
|
|
|
|
} |
|
748
|
115
|
|
|
|
|
288
|
$self->{'sum'}{'width'} += $node->width(); |
|
749
|
115
|
|
|
|
|
970
|
$self->{'sum'}{'stretch'} += $node->stretch(); |
|
750
|
115
|
|
|
|
|
889
|
$self->{'sum'}{'shrink'} += $node->shrink(); |
|
751
|
|
|
|
|
|
|
} elsif ($node->is_penalty() and $node->penalty() != $self->infinity()) { |
|
752
|
32
|
|
|
|
|
2840
|
$self->_mainloop($node, $_, $nodes); |
|
753
|
|
|
|
|
|
|
} |
|
754
|
|
|
|
|
|
|
} |
|
755
|
|
|
|
|
|
|
|
|
756
|
4
|
|
|
|
|
36
|
my @retval = reverse $self->_active_to_breaks(); |
|
757
|
4
|
|
|
|
|
21
|
$self->_cleanup(); |
|
758
|
4
|
|
|
|
|
13
|
return @retval; |
|
759
|
|
|
|
|
|
|
} |
|
760
|
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
sub _computeCost { # _compute_cost() in XS |
|
762
|
0
|
|
|
0
|
|
0
|
my ($self, $start, $end, $active, $currentLine, $nodes) = @_; |
|
763
|
0
|
|
|
|
|
0
|
warn "Computing cost from $start to $end\n" if DEBUG; |
|
764
|
0
|
|
|
|
|
0
|
warn sprintf "Sum width: %f\n", $self->{'sum'}{'width'} if DEBUG; |
|
765
|
0
|
|
|
|
|
0
|
warn sprintf "Total width: %f\n", $self->{'totals'}{'width'} if DEBUG; |
|
766
|
0
|
|
|
|
|
0
|
my $width = $self->{'sum'}{'width'} - $active->totals()->{'width'}; |
|
767
|
0
|
|
|
|
|
0
|
my $stretch = 0; my $shrink = 0; |
|
|
0
|
|
|
|
|
0
|
|
|
768
|
0
|
|
|
|
|
0
|
my $linelength = $currentLine <= @{$self->linelengths()}? |
|
769
|
|
|
|
|
|
|
$self->{'linelengths'}[$currentLine-1]: |
|
770
|
0
|
0
|
|
|
|
0
|
$self->{'linelengths'}[-1]; |
|
771
|
|
|
|
|
|
|
#$linelength -= $self->{'const'}; # allow space for split word hyphen |
|
772
|
|
|
|
|
|
|
# allow for in renderer |
|
773
|
|
|
|
|
|
|
|
|
774
|
0
|
0
|
0
|
|
|
0
|
warn "Adding penalty width" if($nodes->[$end]->is_penalty()) and DEBUG; |
|
775
|
0
|
|
|
|
|
0
|
warn sprintf "Width %f, linelength %f\n", $width, $linelength if DEBUG; |
|
776
|
|
|
|
|
|
|
|
|
777
|
0
|
0
|
|
|
|
0
|
if ($width < $linelength) { |
|
|
|
0
|
|
|
|
|
|
|
778
|
0
|
|
|
|
|
0
|
$stretch = $self->{'sum'}{'stretch'} - $active->totals()->{'stretch'}; |
|
779
|
0
|
|
|
|
|
0
|
warn sprintf "Stretch %f\n", $stretch if DEBUG; |
|
780
|
0
|
0
|
|
|
|
0
|
if ($stretch > 0) { |
|
781
|
0
|
|
|
|
|
0
|
return ($linelength - $width) / $stretch; |
|
782
|
0
|
|
|
|
|
0
|
} else { return $self->infinity(); } |
|
783
|
|
|
|
|
|
|
} elsif ($width > $linelength) { |
|
784
|
0
|
|
|
|
|
0
|
$shrink = $self->{'sum'}{'shrink'} - $active->totals()->{'shrink'}; |
|
785
|
0
|
|
|
|
|
0
|
warn sprintf "Shrink %f\n", $shrink if DEBUG; |
|
786
|
0
|
0
|
|
|
|
0
|
if ($shrink > 0) { |
|
787
|
0
|
|
|
|
|
0
|
return ($linelength - $width) / $shrink; |
|
788
|
0
|
|
|
|
|
0
|
} else { return $self->infinity(); } |
|
789
|
0
|
|
|
|
|
0
|
} else { return 0; } |
|
790
|
|
|
|
|
|
|
} |
|
791
|
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
sub _computeSum { # _compute_sum() in XS |
|
793
|
0
|
|
|
0
|
|
0
|
my ($self, $index, $nodes) = @_; |
|
794
|
|
|
|
|
|
|
my $result = { |
|
795
|
|
|
|
|
|
|
'width' => $self->{'sum'}{'width'}, |
|
796
|
|
|
|
|
|
|
'stretch' => $self->{'sum'}{'stretch'}, |
|
797
|
0
|
|
|
|
|
0
|
'shrink' => $self->{'sum'}{'shrink'} |
|
798
|
|
|
|
|
|
|
}; |
|
799
|
0
|
|
|
|
|
0
|
for ($index..$#$nodes) { |
|
800
|
0
|
0
|
0
|
|
|
0
|
if ($nodes->[$_]->isa("Text::KnuthPlass::Glue")) { |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
801
|
0
|
|
|
|
|
0
|
$result->{'width'} += $nodes->[$_]->width(); |
|
802
|
0
|
|
|
|
|
0
|
$result->{'stretch'} += $nodes->[$_]->stretch(); |
|
803
|
0
|
|
|
|
|
0
|
$result->{'shrink'} += $nodes->[$_]->shrink(); |
|
804
|
|
|
|
|
|
|
} elsif ($nodes->[$_]->isa("Text::KnuthPlass::Box") or |
|
805
|
|
|
|
|
|
|
($nodes->[$_]->is_penalty() and $nodes->[$_]->penalty() == |
|
806
|
|
|
|
|
|
|
-$self->infinity() and $_ > $index)) { |
|
807
|
0
|
|
|
|
|
0
|
last; |
|
808
|
|
|
|
|
|
|
} |
|
809
|
|
|
|
|
|
|
} |
|
810
|
0
|
|
|
|
|
0
|
return $result; |
|
811
|
|
|
|
|
|
|
} |
|
812
|
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub _init_nodelist { # Overridden by XS, same name in XS |
|
814
|
|
|
|
|
|
|
my $self = shift; |
|
815
|
|
|
|
|
|
|
$self->{'activeNodes'} = [ |
|
816
|
|
|
|
|
|
|
Text::KnuthPlass::Breakpoint->new( |
|
817
|
|
|
|
|
|
|
'position' => 0, |
|
818
|
|
|
|
|
|
|
'demerits' => 0, |
|
819
|
|
|
|
|
|
|
'ratio' => 0, |
|
820
|
|
|
|
|
|
|
'line' => 0, |
|
821
|
|
|
|
|
|
|
'fitnessClass' => 0, |
|
822
|
|
|
|
|
|
|
'totals' => { 'width' => 0, 'stretch' => 0, 'shrink' => 0} |
|
823
|
|
|
|
|
|
|
) |
|
824
|
|
|
|
|
|
|
]; |
|
825
|
|
|
|
|
|
|
return; |
|
826
|
|
|
|
|
|
|
} |
|
827
|
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
# same name in XS, but has quite a bit of code |
|
829
|
|
|
|
|
|
|
sub _cleanup { return; } |
|
830
|
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
sub _active_to_breaks { # Overridden by XS, same name in XS |
|
832
|
|
|
|
|
|
|
my $self = shift; |
|
833
|
|
|
|
|
|
|
return unless @{$self->{'activeNodes'}}; |
|
834
|
|
|
|
|
|
|
my @breaks; |
|
835
|
|
|
|
|
|
|
my $best = Text::KnuthPlass::Breakpoint->new('demerits' => ~0); |
|
836
|
|
|
|
|
|
|
for (@{$self->{'activeNodes'}}) { |
|
837
|
|
|
|
|
|
|
$best = $_ if $_->demerits() < $best->demerits(); |
|
838
|
|
|
|
|
|
|
} |
|
839
|
|
|
|
|
|
|
while ($best) { |
|
840
|
|
|
|
|
|
|
push @breaks, { 'position' => $best->position(), |
|
841
|
|
|
|
|
|
|
'ratio' => $best->ratio() |
|
842
|
|
|
|
|
|
|
}; |
|
843
|
|
|
|
|
|
|
$best = $best->previous(); |
|
844
|
|
|
|
|
|
|
} |
|
845
|
|
|
|
|
|
|
return @breaks; |
|
846
|
|
|
|
|
|
|
} |
|
847
|
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
sub _mainloop { # same name in XS |
|
849
|
|
|
|
|
|
|
my ($self, $node, $index, $nodes) = @_; |
|
850
|
|
|
|
|
|
|
my $next; my $ratio = 0; my $demerits = 0; my @candidates; |
|
851
|
|
|
|
|
|
|
my $badness; my $currentLine = 0; my $tmpSum; my $currentClass = 0; |
|
852
|
|
|
|
|
|
|
my $active = $self->{'activeNodes'}[0]; |
|
853
|
|
|
|
|
|
|
my $ptr = 0; |
|
854
|
|
|
|
|
|
|
while ($active) { |
|
855
|
|
|
|
|
|
|
my @candidates = ( # four fitness classes? |
|
856
|
|
|
|
|
|
|
# (tight, normal, loose, very loose) |
|
857
|
|
|
|
|
|
|
{'demerits' => ~0}, |
|
858
|
|
|
|
|
|
|
{'demerits' => ~0}, |
|
859
|
|
|
|
|
|
|
{'demerits' => ~0}, |
|
860
|
|
|
|
|
|
|
{'demerits' => ~0} |
|
861
|
|
|
|
|
|
|
); |
|
862
|
|
|
|
|
|
|
warn "Outer\n" if DEBUG; |
|
863
|
|
|
|
|
|
|
while ($active) { |
|
864
|
|
|
|
|
|
|
my $next = $self->{'activeNodes'}[++$ptr]; |
|
865
|
|
|
|
|
|
|
warn "Inner loop\n" if DEBUG; |
|
866
|
|
|
|
|
|
|
$currentLine = $active->line()+1; |
|
867
|
|
|
|
|
|
|
$ratio = $self->_computeCost($active->position(), |
|
868
|
|
|
|
|
|
|
$index, |
|
869
|
|
|
|
|
|
|
$active, |
|
870
|
|
|
|
|
|
|
$currentLine, |
|
871
|
|
|
|
|
|
|
$nodes); |
|
872
|
|
|
|
|
|
|
warn "Got a ratio of $ratio, node is ".$node->_txt()."\n" if DEBUG; |
|
873
|
|
|
|
|
|
|
if ($ratio < -1 or |
|
874
|
|
|
|
|
|
|
($node->is_penalty() and |
|
875
|
|
|
|
|
|
|
$node->penalty() == -$self->infinity())) { |
|
876
|
|
|
|
|
|
|
warn "Dropping a node\n" if DEBUG; |
|
877
|
|
|
|
|
|
|
$self->{'activeNodes'} = [ grep {$_ != $active} @{$self->{'activeNodes'}} ]; |
|
878
|
|
|
|
|
|
|
$ptr--; |
|
879
|
|
|
|
|
|
|
} |
|
880
|
|
|
|
|
|
|
if (-1 <= $ratio and $ratio <= $self->tolerance()) { |
|
881
|
|
|
|
|
|
|
$badness = 100 * $ratio**3; |
|
882
|
|
|
|
|
|
|
warn "Badness is $badness\n" if DEBUG; |
|
883
|
|
|
|
|
|
|
if ($node->is_penalty() and $node->penalty() > 0) { |
|
884
|
|
|
|
|
|
|
$demerits = $self->demerits()->{'line'} + $badness + |
|
885
|
|
|
|
|
|
|
$node->penalty(); |
|
886
|
|
|
|
|
|
|
} elsif ($node->is_penalty() and $node->penalty() != -$self->infinity()) { |
|
887
|
|
|
|
|
|
|
$demerits = $self->demerits()->{'line'} + $badness - |
|
888
|
|
|
|
|
|
|
$node->penalty(); |
|
889
|
|
|
|
|
|
|
} else { |
|
890
|
|
|
|
|
|
|
$demerits = $self->demerits()->{'line'} + $badness; |
|
891
|
|
|
|
|
|
|
} |
|
892
|
|
|
|
|
|
|
$demerits *= $demerits; # demerits**2 |
|
893
|
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
if ($node->is_penalty() and $nodes->[$active->position()]->is_penalty()) { |
|
895
|
|
|
|
|
|
|
$demerits += $self->demerits()->{'flagged'} * |
|
896
|
|
|
|
|
|
|
$node->flagged() * |
|
897
|
|
|
|
|
|
|
$nodes->[$active->position()]->flagged(); |
|
898
|
|
|
|
|
|
|
} |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
if ($ratio < -0.5) { $currentClass = 0; } # tight |
|
901
|
|
|
|
|
|
|
elsif ($ratio <= 0.5) { $currentClass = 1; } # normal |
|
902
|
|
|
|
|
|
|
elsif ($ratio <= 1 ) { $currentClass = 2; } # loose |
|
903
|
|
|
|
|
|
|
else { $currentClass = 3; } # very loose |
|
904
|
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
# bad fitness if changes by more than 1 class |
|
906
|
|
|
|
|
|
|
$demerits += $self->demerits()->{'fitness'} |
|
907
|
|
|
|
|
|
|
if abs($currentClass - $active->fitnessClass()) > 1; |
|
908
|
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
$demerits += $active->demerits(); |
|
910
|
|
|
|
|
|
|
if ($demerits < $candidates[$currentClass]->{'demerits'}) { |
|
911
|
|
|
|
|
|
|
warn "Setting c $currentClass\n" if DEBUG; |
|
912
|
|
|
|
|
|
|
$candidates[$currentClass] = { |
|
913
|
|
|
|
|
|
|
'active' => $active, |
|
914
|
|
|
|
|
|
|
'demerits' => $demerits, |
|
915
|
|
|
|
|
|
|
'ratio' => $ratio |
|
916
|
|
|
|
|
|
|
}; |
|
917
|
|
|
|
|
|
|
} |
|
918
|
|
|
|
|
|
|
} |
|
919
|
|
|
|
|
|
|
$active = $next; |
|
920
|
|
|
|
|
|
|
#warn "Active is now $active" if DEBUG; |
|
921
|
|
|
|
|
|
|
last if !$active || |
|
922
|
|
|
|
|
|
|
$active->line() >= $currentLine; |
|
923
|
|
|
|
|
|
|
} |
|
924
|
|
|
|
|
|
|
warn "Post inner loop\n" if DEBUG; |
|
925
|
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
$tmpSum = $self->_computeSum($index, $nodes); |
|
927
|
|
|
|
|
|
|
for (0..3) { |
|
928
|
|
|
|
|
|
|
my $c = $candidates[$_]; |
|
929
|
|
|
|
|
|
|
if ($c->{'demerits'} < ~0) { |
|
930
|
|
|
|
|
|
|
my $newnode = Text::KnuthPlass::Breakpoint->new( |
|
931
|
|
|
|
|
|
|
'position' => $index, |
|
932
|
|
|
|
|
|
|
'demerits' => $c->{'demerits'}, |
|
933
|
|
|
|
|
|
|
'ratio' => $c->{'ratio'}, |
|
934
|
|
|
|
|
|
|
'line' => $c->{'active'}->line() + 1, |
|
935
|
|
|
|
|
|
|
'fitnessClass' => $_, |
|
936
|
|
|
|
|
|
|
'totals' => $tmpSum, |
|
937
|
|
|
|
|
|
|
'previous' => $c->{'active'} |
|
938
|
|
|
|
|
|
|
); |
|
939
|
|
|
|
|
|
|
if ($active) { |
|
940
|
|
|
|
|
|
|
warn "Before\n" if DEBUG; |
|
941
|
|
|
|
|
|
|
my @newlist; |
|
942
|
|
|
|
|
|
|
for (@{$self->{'activeNodes'}}) { |
|
943
|
|
|
|
|
|
|
if ($_ == $active) { push @newlist, $newnode; } |
|
944
|
|
|
|
|
|
|
push @newlist, $_; |
|
945
|
|
|
|
|
|
|
} |
|
946
|
|
|
|
|
|
|
$ptr++; |
|
947
|
|
|
|
|
|
|
$self->{'activeNodes'} = [ @newlist ]; |
|
948
|
|
|
|
|
|
|
# grep {; |
|
949
|
|
|
|
|
|
|
# ($_ == $active) ? ($newnode, $active) : ($_) |
|
950
|
|
|
|
|
|
|
#} @{$self->{'activeNodes'}} |
|
951
|
|
|
|
|
|
|
# ]; |
|
952
|
|
|
|
|
|
|
} else { |
|
953
|
|
|
|
|
|
|
warn "After\n" if DEBUG; |
|
954
|
|
|
|
|
|
|
push @{$self->{'activeNodes'}}, $newnode; |
|
955
|
|
|
|
|
|
|
} |
|
956
|
|
|
|
|
|
|
#warn @{$self->{'activeNodes'}} if DEBUG; |
|
957
|
|
|
|
|
|
|
} # demerits check |
|
958
|
|
|
|
|
|
|
} # fitness class 0..3 loop |
|
959
|
|
|
|
|
|
|
} # while $active loop |
|
960
|
|
|
|
|
|
|
return; |
|
961
|
|
|
|
|
|
|
} |
|
962
|
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
=head2 @lines = $t->breakpoints_to_lines(\@breakpoints, \@nodes) |
|
964
|
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
And this takes the breakpoints and the nodes, and assembles them into |
|
966
|
|
|
|
|
|
|
lines. |
|
967
|
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
=cut |
|
969
|
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
sub breakpoints_to_lines { |
|
971
|
3
|
|
|
3
|
1
|
7
|
my ($self, $breakpoints, $nodes) = @_; |
|
972
|
3
|
|
|
|
|
6
|
my @lines; |
|
973
|
3
|
|
|
|
|
5
|
my $linestart = 0; |
|
974
|
3
|
|
|
|
|
9
|
for my $x (1 .. $#$breakpoints) { $_ = $breakpoints->[$x]; |
|
|
18
|
|
|
|
|
22
|
|
|
975
|
18
|
|
|
|
|
21
|
my $position = $_->{'position'}; |
|
976
|
18
|
|
|
|
|
22
|
my $r = $_->{'ratio'}; |
|
977
|
18
|
|
|
|
|
36
|
for ($linestart..$#$nodes) { |
|
978
|
33
|
100
|
66
|
|
|
143
|
if ($nodes->[$_]->isa("Text::KnuthPlass::Box") or |
|
|
|
|
66
|
|
|
|
|
|
979
|
|
|
|
|
|
|
($nodes->[$_]->is_penalty() and |
|
980
|
|
|
|
|
|
|
$nodes->[$_]->penalty() ==-$self->infinity())) { |
|
981
|
18
|
|
|
|
|
21
|
$linestart = $_; |
|
982
|
18
|
|
|
|
|
23
|
last; |
|
983
|
|
|
|
|
|
|
} |
|
984
|
|
|
|
|
|
|
} |
|
985
|
|
|
|
|
|
|
push @lines, { |
|
986
|
|
|
|
|
|
|
'ratio' => $r, |
|
987
|
|
|
|
|
|
|
'position' => $_->{'position'}, |
|
988
|
18
|
|
|
|
|
36
|
'nodes' => [ @{$nodes}[$linestart..$position] ] |
|
|
18
|
|
|
|
|
53
|
|
|
989
|
|
|
|
|
|
|
}; |
|
990
|
18
|
|
|
|
|
36
|
$linestart = $_->{'position'}; |
|
991
|
|
|
|
|
|
|
} |
|
992
|
|
|
|
|
|
|
#if ($linestart < $#$nodes) { |
|
993
|
|
|
|
|
|
|
# push @lines, { 'ratio' => 1, 'position' => $#$nodes, |
|
994
|
|
|
|
|
|
|
# 'nodes' => [ @{$nodes}[$linestart+1..$#$nodes] ]}; |
|
995
|
|
|
|
|
|
|
#} |
|
996
|
3
|
|
|
|
|
9
|
return @lines; |
|
997
|
|
|
|
|
|
|
} |
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
=head2 boxclass() |
|
1000
|
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
=head2 glueclass() |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
=head2 penaltyclass() |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
For subclassers. |
|
1006
|
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
=cut |
|
1008
|
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
sub boxclass { |
|
1010
|
0
|
|
|
0
|
1
|
0
|
return "Text::KnuthPlass::Box"; |
|
1011
|
|
|
|
|
|
|
} |
|
1012
|
|
|
|
|
|
|
sub glueclass { |
|
1013
|
122
|
|
|
122
|
1
|
246
|
return "Text::KnuthPlass::Glue"; |
|
1014
|
|
|
|
|
|
|
} |
|
1015
|
|
|
|
|
|
|
sub penaltyclass { |
|
1016
|
6
|
|
|
6
|
1
|
22
|
return "Text::KnuthPlass::Penalty"; |
|
1017
|
|
|
|
|
|
|
} |
|
1018
|
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1020
|
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
originally written by Simon Cozens, C<< >> |
|
1022
|
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
since 2020, maintained by Phil Perry |
|
1024
|
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
1026
|
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
This module is a Perl translation (originally by Simon Cozens) of Bram Stein's |
|
1028
|
|
|
|
|
|
|
"Typeset" Javascript Knuth-Plass implementation. |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=head1 BUGS |
|
1031
|
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
Please report any bugs or feature requests to the I section of |
|
1033
|
|
|
|
|
|
|
C. |
|
1034
|
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
Do NOT under ANY circumstances open a PR (Pull Request) to report a bug. It is |
|
1036
|
|
|
|
|
|
|
a waste of both your and our time and effort. Open a regular ticket (issue), |
|
1037
|
|
|
|
|
|
|
and attach a Perl (.pl) program illustrating the problem, if possible. If you |
|
1038
|
|
|
|
|
|
|
believe that you have a program patch, and offer to share it as a PR, we may |
|
1039
|
|
|
|
|
|
|
give the go-ahead. Unsolicited PRs may be closed without further action. |
|
1040
|
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
|
1042
|
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
Copyright (c) 2011 Simon Cozens. |
|
1044
|
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
Copyright (c) 2020-2022 Phil M Perry. |
|
1046
|
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
This program is released under the following license: Perl, GPL |
|
1048
|
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
=cut |
|
1050
|
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
1; # End of Text::KnuthPlass |