| blib/lib/PDF/Builder/Content/Text.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 206 | 1418 | 14.5 |
| branch | 87 | 670 | 12.9 |
| condition | 44 | 360 | 12.2 |
| subroutine | 18 | 43 | 41.8 |
| pod | 14 | 15 | 93.3 |
| total | 369 | 2506 | 14.7 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package PDF::Builder::Content::Text; | ||||||
| 2 | |||||||
| 3 | 38 | 38 | 290 | use base 'PDF::Builder::Content'; | |||
| 38 | 95 | ||||||
| 38 | 3941 | ||||||
| 4 | |||||||
| 5 | 38 | 38 | 243 | use strict; | |||
| 38 | 101 | ||||||
| 38 | 752 | ||||||
| 6 | 38 | 38 | 210 | use warnings; | |||
| 38 | 107 | ||||||
| 38 | 846 | ||||||
| 7 | 38 | 38 | 200 | use Carp; | |||
| 38 | 85 | ||||||
| 38 | 2088 | ||||||
| 8 | 38 | 38 | 248 | use List::Util qw(min max); | |||
| 38 | 172 | ||||||
| 38 | 576566 | ||||||
| 9 | #use Data::Dumper; # for debugging | ||||||
| 10 | # $Data::Dumper::Sortkeys = 1; # hash keys in sorted order | ||||||
| 11 | |||||||
| 12 | our $VERSION = '3.025'; # VERSION | ||||||
| 13 | our $LAST_UPDATE = '3.025'; # manually update whenever code is changed | ||||||
| 14 | |||||||
| 15 | =head1 NAME | ||||||
| 16 | |||||||
| 17 | PDF::Builder::Content::Text - additional specialized text-related formatting methods. Inherits from L |
||||||
| 18 | |||||||
| 19 | B |
||||||
| 20 | I |
||||||
| 21 | type object (e.g., $page->gfx()->method()), you may have to change to a I |
||||||
| 22 | type object (e.g., $page->text()->method()). | ||||||
| 23 | |||||||
| 24 | =head1 METHODS | ||||||
| 25 | |||||||
| 26 | =cut | ||||||
| 27 | |||||||
| 28 | sub new { | ||||||
| 29 | 20 | 20 | 1 | 67 | my ($class) = @_; | ||
| 30 | 20 | 145 | my $self = $class->SUPER::new(@_); | ||||
| 31 | 20 | 124 | $self->textstart(); | ||||
| 32 | 20 | 62 | return $self; | ||||
| 33 | } | ||||||
| 34 | |||||||
| 35 | =head2 Single Lines from a String | ||||||
| 36 | |||||||
| 37 | =over | ||||||
| 38 | |||||||
| 39 | =item $width = $content->text_left($text, %opts) | ||||||
| 40 | |||||||
| 41 | Alias for C |
||||||
| 42 | C |
||||||
| 43 | |||||||
| 44 | Adds text to the page (left justified), at the current position. | ||||||
| 45 | Note that there is no maximum width, and nothing to keep you from overflowing | ||||||
| 46 | the physical page on the right! | ||||||
| 47 | The width used (in points) is B |
||||||
| 48 | |||||||
| 49 | =back | ||||||
| 50 | |||||||
| 51 | =cut | ||||||
| 52 | |||||||
| 53 | sub text_left { | ||||||
| 54 | 0 | 0 | 1 | 0 | my ($self, $text, @opts) = @_; | ||
| 55 | |||||||
| 56 | 0 | 0 | return $self->text($text, @opts); | ||||
| 57 | } | ||||||
| 58 | |||||||
| 59 | =over | ||||||
| 60 | |||||||
| 61 | =item $width = $content->text_center($text, %opts) | ||||||
| 62 | |||||||
| 63 | As C |
||||||
| 64 | |||||||
| 65 | Adds text to the page (centered). | ||||||
| 66 | The width used (in points) is B |
||||||
| 67 | |||||||
| 68 | =back | ||||||
| 69 | |||||||
| 70 | =cut | ||||||
| 71 | |||||||
| 72 | sub text_center { | ||||||
| 73 | 6 | 6 | 1 | 43 | my ($self, $text, @opts) = @_; | ||
| 74 | |||||||
| 75 | 6 | 40 | my $width = $self->advancewidth($text, @opts); | ||||
| 76 | 6 | 44 | return $self->text($text, 'indent' => -($width/2), @opts); | ||||
| 77 | } | ||||||
| 78 | |||||||
| 79 | =over | ||||||
| 80 | |||||||
| 81 | =item $width = $content->text_right($text, %opts) | ||||||
| 82 | |||||||
| 83 | As C |
||||||
| 84 | |||||||
| 85 | Adds text to the page (right justified). | ||||||
| 86 | Note that there is no maximum width, and nothing to keep you from overflowing | ||||||
| 87 | the physical page on the left! | ||||||
| 88 | The width used (in points) is B |
||||||
| 89 | |||||||
| 90 | =back | ||||||
| 91 | |||||||
| 92 | =cut | ||||||
| 93 | |||||||
| 94 | sub text_right { | ||||||
| 95 | 3 | 3 | 1 | 15 | my ($self, $text, @opts) = @_; | ||
| 96 | |||||||
| 97 | 3 | 14 | my $width = $self->advancewidth($text, @opts); | ||||
| 98 | 3 | 21 | return $self->text($text, 'indent' => -$width, @opts); | ||||
| 99 | } | ||||||
| 100 | |||||||
| 101 | =over | ||||||
| 102 | |||||||
| 103 | =item $width = $content->text_justified($text, $width, %opts) | ||||||
| 104 | |||||||
| 105 | As C |
||||||
| 106 | last resort) C |
||||||
| 107 | (available) C<$width>. Note that if the desired width is I |
||||||
| 108 | natural width taken by the text, it will be I |
||||||
| 109 | same three routines. | ||||||
| 110 | |||||||
| 111 | The unchanged C<$width> is B |
||||||
| 112 | change it (e.g., overflow). | ||||||
| 113 | |||||||
| 114 | B |
||||||
| 115 | |||||||
| 116 | =over | ||||||
| 117 | |||||||
| 118 | =item 'nocs' => value | ||||||
| 119 | |||||||
| 120 | If this option value is 1 (default 0), do B |
||||||
| 121 | spacing. This is useful for connected characters, such as fonts for Arabic, | ||||||
| 122 | Devanagari, Latin cursive handwriting, etc. You don't want to add additional | ||||||
| 123 | space between characters during justification, which would disconnect them. | ||||||
| 124 | |||||||
| 125 | I |
||||||
| 126 | nocs is 1. This is to make up for the lack of added/subtracted intercharacter | ||||||
| 127 | spacing. | ||||||
| 128 | |||||||
| 129 | =item 'wordsp' => value | ||||||
| 130 | |||||||
| 131 | The percentage of one space character (default 100) that is the maximum amount | ||||||
| 132 | to add to (each) interword spacing to expand the line. | ||||||
| 133 | If C |
||||||
| 134 | |||||||
| 135 | =item 'charsp' => value | ||||||
| 136 | |||||||
| 137 | If adding interword space didn't do enough, the percentage of one em (default | ||||||
| 138 | 100) that is the maximum amount to add to (each) intercharacter spacing to | ||||||
| 139 | further expand the line. | ||||||
| 140 | If C |
||||||
| 141 | |||||||
| 142 | =item 'wordspa' => value | ||||||
| 143 | |||||||
| 144 | If adding intercharacter space didn't do enough, the percentage of one space | ||||||
| 145 | character (default 100) that is the maximum I |
||||||
| 146 | (each) interword spacing to further expand the line. | ||||||
| 147 | If C |
||||||
| 148 | |||||||
| 149 | =item 'charspa' => value | ||||||
| 150 | |||||||
| 151 | If adding more interword space didn't do enough, the percentage of one em | ||||||
| 152 | (default 100) that is the maximum I |
||||||
| 153 | intercharacter spacing to further expand the line. | ||||||
| 154 | If C |
||||||
| 155 | |||||||
| 156 | =item 'condw' => value | ||||||
| 157 | |||||||
| 158 | The percentage of one space character (default 25) that is the maximum amount | ||||||
| 159 | to subtract from (each) interword spacing to condense the line. | ||||||
| 160 | If C |
||||||
| 161 | |||||||
| 162 | =item 'condc' => value | ||||||
| 163 | |||||||
| 164 | If removing interword space didn't do enough, the percentage of one em | ||||||
| 165 | (default 10) that is the maximum amount to subtract from (each) intercharacter | ||||||
| 166 | spacing to further condense the line. | ||||||
| 167 | If C |
||||||
| 168 | |||||||
| 169 | =back | ||||||
| 170 | |||||||
| 171 | If expansion (or reduction) wordspace and charspace changes didn't do enough | ||||||
| 172 | to make the line fit the desired width, use C |
||||||
| 173 | condensing the line to fit. | ||||||
| 174 | |||||||
| 175 | =back | ||||||
| 176 | |||||||
| 177 | =cut | ||||||
| 178 | |||||||
| 179 | sub text_justified { | ||||||
| 180 | 4 | 4 | 1 | 20 | my ($self, $text, $width, %opts) = @_; | ||
| 181 | # copy dashed option names to the preferred undashed names | ||||||
| 182 | 4 | 50 | 33 | 19 | if (defined $opts{'-wordsp'} && !defined $opts{'wordsp'}) { $opts{'wordsp'} = delete($opts{'-wordsp'}); } | ||
| 0 | 0 | ||||||
| 183 | 4 | 50 | 33 | 20 | if (defined $opts{'-charsp'} && !defined $opts{'charsp'}) { $opts{'charsp'} = delete($opts{'-charsp'}); } | ||
| 0 | 0 | ||||||
| 184 | 4 | 50 | 33 | 15 | if (defined $opts{'-wordspa'} && !defined $opts{'wordspa'}) { $opts{'wordspa'} = delete($opts{'-wordspa'}); } | ||
| 0 | 0 | ||||||
| 185 | 4 | 50 | 33 | 15 | if (defined $opts{'-charspa'} && !defined $opts{'charspa'}) { $opts{'charspa'} = delete($opts{'-charspa'}); } | ||
| 0 | 0 | ||||||
| 186 | 4 | 50 | 33 | 17 | if (defined $opts{'-condw'} && !defined $opts{'condw'}) { $opts{'condw'} = delete($opts{'-condw'}); } | ||
| 0 | 0 | ||||||
| 187 | 4 | 50 | 33 | 15 | if (defined $opts{'-condc'} && !defined $opts{'condc'}) { $opts{'condc'} = delete($opts{'-condc'}); } | ||
| 0 | 0 | ||||||
| 188 | 4 | 50 | 33 | 15 | if (defined $opts{'-nocs'} && !defined $opts{'nocs'}) { $opts{'nocs'} = delete($opts{'-nocs'}); } | ||
| 0 | 0 | ||||||
| 189 | |||||||
| 190 | # optional parameters to control how expansion or condensation are done | ||||||
| 191 | # 1. expand interword space up to 100% of 1 space | ||||||
| 192 | 4 | 50 | 14 | my $wordsp = defined($opts{'wordsp'})? $opts{'wordsp'}: 100; | |||
| 193 | # 2. expand intercharacter space up to 100% of 1em | ||||||
| 194 | 4 | 50 | 11 | my $charsp = defined($opts{'charsp'})? $opts{'charsp'}: 100; | |||
| 195 | # 3. expand interword space up to another 100% of 1 space | ||||||
| 196 | 4 | 50 | 15 | my $wordspa = defined($opts{'wordspa'})? $opts{'wordspa'}: 100; | |||
| 197 | # 4. expand intercharacter space up to another 100% of 1em | ||||||
| 198 | 4 | 50 | 16 | my $charspa = defined($opts{'charspa'})? $opts{'charspa'}: 100; | |||
| 199 | # 5. condense interword space up to 25% of 1 space | ||||||
| 200 | 4 | 50 | 11 | my $condw = defined($opts{'condw'})? $opts{'condw'}: 25; | |||
| 201 | # 6. condense intercharacter space up to 10% of 1em | ||||||
| 202 | 4 | 50 | 175 | my $condc = defined($opts{'condc'})? $opts{'condc'}: 10; | |||
| 203 | # 7. if still short or long, hscale() | ||||||
| 204 | |||||||
| 205 | 4 | 50 | 163 | my $nocs = defined($opts{'nocs'})? $opts{'nocs'}: 0; | |||
| 206 | 4 | 50 | 17 | if ($nocs) { | |||
| 207 | 0 | 0 | $charsp = $charspa = $condc = 0; | ||||
| 208 | 0 | 0 | $wordsp *= 2; | ||||
| 209 | 0 | 0 | $wordspa *= 2; | ||||
| 210 | 0 | 0 | $condw *= 2; | ||||
| 211 | } | ||||||
| 212 | |||||||
| 213 | # with original wordspace, charspace, and hscale settings | ||||||
| 214 | # note that we do NOT change any existing charspace here | ||||||
| 215 | 4 | 21 | my $length = $self->advancewidth($text, %opts); | ||||
| 216 | 4 | 13 | my $overage = $length - $width; # > 0, raw text is too wide, < 0, narrow | ||||
| 217 | |||||||
| 218 | 4 | 10 | my ($i, @chars, $val, $limit); | ||||
| 219 | 4 | 20 | my $hs = $self->hscale(); # save old settings and reset to 0 | ||||
| 220 | 4 | 16 | my $ws = $self->wordspace(); | ||||
| 221 | 4 | 15 | my $cs = $self->charspace(); | ||||
| 222 | 4 | 19 | $self->hscale(100); $self->wordspace(0); $self->charspace(0); | ||||
| 4 | 20 | ||||||
| 4 | 19 | ||||||
| 223 | |||||||
| 224 | # not near perfect fit? not within .1 pt of fitting | ||||||
| 225 | 4 | 50 | 17 | if (abs($overage) > 0.1) { | |||
| 226 | |||||||
| 227 | # how many interword spaces can we change with wordspace? | ||||||
| 228 | 4 | 22 | my $num_spaces = 0; | ||||
| 229 | # how many intercharacter spaces can be added to or removed? | ||||||
| 230 | 4 | 8 | my $num_chars = -1; | ||||
| 231 | 4 | 27 | @chars = split //, $text; | ||||
| 232 | 4 | 19 | for ($i=0; $i | ||||
| 233 | 78 | 100 | 133 | if ($chars[$i] eq ' ') { $num_spaces++; } # TBD other whitespace? | |||
| 16 | 23 | ||||||
| 234 | 78 | 140 | $num_chars++; # count spaces as characters, too | ||||
| 235 | } | ||||||
| 236 | 4 | 21 | my $em = $self->advancewidth('M'); | ||||
| 237 | 4 | 17 | my $sp = $self->advancewidth(' '); | ||||
| 238 | |||||||
| 239 | 4 | 50 | 20 | if ($overage > 0) { | |||
| 240 | # too wide: need to condense it | ||||||
| 241 | # 1. subtract from interword space, up to -$condw/100 $sp | ||||||
| 242 | 0 | 0 | 0 | 0 | if ($overage > 0 && $num_spaces > 0 && $condw > 0) { | ||
| 0 | |||||||
| 243 | 0 | 0 | $val = $overage/$num_spaces; | ||||
| 244 | 0 | 0 | $limit = $condw/100*$sp; | ||||
| 245 | 0 | 0 | 0 | if ($val > $limit) { $val = $limit; } | |||
| 0 | 0 | ||||||
| 246 | 0 | 0 | $self->wordspace(-$val); | ||||
| 247 | 0 | 0 | $overage -= $val*$num_spaces; | ||||
| 248 | } | ||||||
| 249 | # 2. subtract from intercharacter space, up to -$condc/100 $em | ||||||
| 250 | 0 | 0 | 0 | 0 | if ($overage > 0 && $num_chars > 0 && $condc > 0) { | ||
| 0 | |||||||
| 251 | 0 | 0 | $val = $overage/$num_chars; | ||||
| 252 | 0 | 0 | $limit = $condc/100*$em; | ||||
| 253 | 0 | 0 | 0 | if ($val > $limit) { $val = $limit; } | |||
| 0 | 0 | ||||||
| 254 | 0 | 0 | $self->charspace(-$val); | ||||
| 255 | 0 | 0 | $overage -= $val*$num_chars; | ||||
| 256 | } | ||||||
| 257 | # 3. nothing more to do than scale down with hscale() | ||||||
| 258 | } else { | ||||||
| 259 | # too narrow: need to expand it (usual case) | ||||||
| 260 | 4 | 12 | $overage = -$overage; # working with positive value is easier | ||||
| 261 | # 1. add to interword space, up to $wordsp/100 $sp | ||||||
| 262 | 4 | 50 | 33 | 30 | if ($overage > 0 && $num_spaces > 0 && $wordsp > 0) { | ||
| 33 | |||||||
| 263 | 4 | 12 | $val = $overage/$num_spaces; | ||||
| 264 | 4 | 10 | $limit = $wordsp/100*$sp; | ||||
| 265 | 4 | 100 | 12 | if ($val > $limit) { $val = $limit; } | |||
| 1 | 2 | ||||||
| 266 | 4 | 15 | $self->wordspace($val); | ||||
| 267 | 4 | 11 | $overage -= $val*$num_spaces; | ||||
| 268 | } | ||||||
| 269 | # 2. add to intercharacter space, up to $charsp/100 $em | ||||||
| 270 | 4 | 50 | 66 | 34 | if ($overage > 0 && $num_chars > 0 && $charsp > 0) { | ||
| 66 | |||||||
| 271 | 1 | 3 | $val = $overage/$num_chars; | ||||
| 272 | 1 | 3 | $limit = $charsp/100*$em; | ||||
| 273 | 1 | 50 | 4 | if ($val > $limit) { $val = $limit; } | |||
| 0 | 0 | ||||||
| 274 | 1 | 4 | $self->charspace($val); | ||||
| 275 | 1 | 9 | $overage -= $val*$num_chars; | ||||
| 276 | } | ||||||
| 277 | # 3. add to interword space, up to $wordspa/100 $sp additional | ||||||
| 278 | 4 | 0 | 33 | 17 | if ($overage > 0 && $num_spaces > 0 && $wordspa > 0) { | ||
| 33 | |||||||
| 279 | 0 | 0 | $val = $overage/$num_spaces; | ||||
| 280 | 0 | 0 | $limit = $wordspa/100*$sp; | ||||
| 281 | 0 | 0 | 0 | if ($val > $limit) { $val = $limit; } | |||
| 0 | 0 | ||||||
| 282 | 0 | 0 | $self->wordspace($val+$self->wordspace()); | ||||
| 283 | 0 | 0 | $overage -= $val*$num_spaces; | ||||
| 284 | } | ||||||
| 285 | # 4. add to intercharacter space, up to $charspa/100 $em additional | ||||||
| 286 | 4 | 0 | 33 | 19 | if ($overage > 0 && $num_chars > 0 && $charspa > 0) { | ||
| 33 | |||||||
| 287 | 0 | 0 | $val = $overage/$num_chars; | ||||
| 288 | 0 | 0 | $limit = $charspa/100*$em; | ||||
| 289 | 0 | 0 | 0 | if ($val > $limit) { $val = $limit; } | |||
| 0 | 0 | ||||||
| 290 | 0 | 0 | $self->charspace($val+$self->charspace()); | ||||
| 291 | 0 | 0 | $overage -= $val*$num_chars; | ||||
| 292 | } | ||||||
| 293 | # 5. nothing more to do than scale up with hscale() | ||||||
| 294 | } | ||||||
| 295 | |||||||
| 296 | # last ditch effort to fill the line: use hscale() | ||||||
| 297 | # temporarily resets hscale to expand width of line to match $width | ||||||
| 298 | # wordspace and charspace are already (temporarily) at max/min | ||||||
| 299 | 4 | 50 | 14 | if ($overage > 0.1) { | |||
| 300 | 0 | 0 | $self->hscale(100*($width/$self->advancewidth($text, %opts))); | ||||
| 301 | } | ||||||
| 302 | |||||||
| 303 | } # original $overage was not near 0 | ||||||
| 304 | # do the output, with wordspace, charspace, and possiby hscale changed | ||||||
| 305 | 4 | 26 | $self->text($text, %opts); | ||||
| 306 | |||||||
| 307 | # restore settings | ||||||
| 308 | 4 | 22 | $self->hscale($hs); $self->wordspace($ws); $self->charspace($cs); | ||||
| 4 | 22 | ||||||
| 4 | 17 | ||||||
| 309 | |||||||
| 310 | 4 | 26 | return $width; | ||||
| 311 | } | ||||||
| 312 | |||||||
| 313 | =head2 Multiple Lines from a String | ||||||
| 314 | |||||||
| 315 | The string is split at regular blanks (spaces), x20, to find the longest | ||||||
| 316 | substring that will fit the C<$width>. | ||||||
| 317 | If a single word is longer than C<$width>, it will overflow. | ||||||
| 318 | To stay strictly within the desired bounds, set the option | ||||||
| 319 | C |
||||||
| 320 | |||||||
| 321 | =head3 Hyphenation | ||||||
| 322 | |||||||
| 323 | If hyphenation is enabled, those methods which split up a string into multiple | ||||||
| 324 | lines (the "text fill", paragraph, and section methods) will attempt to split | ||||||
| 325 | up the word that overflows the line, in order to pack the text even more | ||||||
| 326 | tightly ("greedy" line splitting). There are a number of controls over where a | ||||||
| 327 | word may be split, but note that there is nothing language-specific (i.e., | ||||||
| 328 | following a given language's rules for where a word may be split). This is left | ||||||
| 329 | to other packages. | ||||||
| 330 | |||||||
| 331 | There are hard coded minimums of 2 letters before the split, and 2 letters after | ||||||
| 332 | the split. See C |
||||||
| 333 | line splitting makes any attempt to prevent widows and orphans, prevent | ||||||
| 334 | splitting of the last word in a column or page, or otherwise engage in | ||||||
| 335 | I |
||||||
| 336 | |||||||
| 337 | =over | ||||||
| 338 | |||||||
| 339 | =item 'hyphenate' => value | ||||||
| 340 | |||||||
| 341 | 0: no hyphenation (B |
||||||
| 342 | splitting at a soft hyphen (\xAD). Unicode hyphen (U+2010) and non-splitting | ||||||
| 343 | hyphen (U+2011) are ignored as split points. | ||||||
| 344 | |||||||
| 345 | =item 'spHH' => value | ||||||
| 346 | |||||||
| 347 | 0: do I |
||||||
| 348 | |||||||
| 349 | =item 'spOP' => value | ||||||
| 350 | |||||||
| 351 | 0: do I |
||||||
| 352 | |||||||
| 353 | =item 'spDR' => value | ||||||
| 354 | |||||||
| 355 | 0: do I |
||||||
| 356 | |||||||
| 357 | =item 'spLR' => value | ||||||
| 358 | |||||||
| 359 | 0: do I |
||||||
| 360 | |||||||
| 361 | =item 'spCC' => value | ||||||
| 362 | |||||||
| 363 | 0: do I |
||||||
| 364 | uppercase letter, 1: I |
||||||
| 365 | |||||||
| 366 | =item 'spRB' => value | ||||||
| 367 | |||||||
| 368 | 0: do I |
||||||
| 369 | 1: I |
||||||
| 370 | move! | ||||||
| 371 | |||||||
| 372 | =item 'spFS' => value | ||||||
| 373 | |||||||
| 374 | 0: do I |
||||||
| 375 | 1: I |
||||||
| 376 | super desperation move, and the split will probably make no linguistic sense! | ||||||
| 377 | |||||||
| 378 | =item 'min_prefix' => value | ||||||
| 379 | |||||||
| 380 | Minimum number of letters I |
||||||
| 381 | The B |
||||||
| 382 | |||||||
| 383 | =item 'min_suffix' => value | ||||||
| 384 | |||||||
| 385 | Minimum number of letters I |
||||||
| 386 | The B |
||||||
| 387 | |||||||
| 388 | =back | ||||||
| 389 | |||||||
| 390 | =head3 Methods | ||||||
| 391 | |||||||
| 392 | =cut | ||||||
| 393 | |||||||
| 394 | # splits input text (on spaces) into words, glues them back together until | ||||||
| 395 | # have filled desired (available) width. return the new line and remaining | ||||||
| 396 | # text. runs of spaces should be preserved. if the first word of a line does | ||||||
| 397 | # not fit within the alloted space, and cannot be split short enough, just | ||||||
| 398 | # accept the overflow. | ||||||
| 399 | sub _text_fill_line { | ||||||
| 400 | 20 | 20 | 72 | my ($self, $text, $width, $over, %opts) = @_; | |||
| 401 | # copy dashed option names to the preferred undashed names | ||||||
| 402 | 20 | 50 | 33 | 62 | if (defined $opts{'-hyphenate'} && !defined $opts{'hyphenate'}) { $opts{'hyphenate'} = delete($opts{'-hyphenate'}); } | ||
| 0 | 0 | ||||||
| 403 | 20 | 50 | 33 | 56 | if (defined $opts{'-lang'} && !defined $opts{'lang'}) { $opts{'lang'} = delete($opts{'-lang'}); } | ||
| 0 | 0 | ||||||
| 404 | 20 | 50 | 33 | 54 | if (defined $opts{'-nosplit'} && !defined $opts{'nosplit'}) { $opts{'nosplit'} = delete($opts{'-nosplit'}); } | ||
| 0 | 0 | ||||||
| 405 | |||||||
| 406 | # options of interest | ||||||
| 407 | 20 | 50 | 55 | my $hyphenate = defined($opts{'hyphenate'})? $opts{'hyphenate'}: 0; # default off | |||
| 408 | #my $lang = defined($opts{'lang'})? $opts{'lang'}: 'en'; # English rules by default | ||||||
| 409 | 20 | 41 | my $lang = 'basic'; | ||||
| 410 | #my $nosplit = defined($opts{'nosplit'})? $opts{'nosplit'}: ''; # indexes NOT to split at, given | ||||||
| 411 | # as string of integers | ||||||
| 412 | # my @noSplit = split /[,\s]+/, $nosplit; # normally empty array | ||||||
| 413 | # 1. indexes start at 0 (split after character N not permitted) | ||||||
| 414 | # 2. SHYs (soft hyphens) should be skipped | ||||||
| 415 | # 3. need to map entire string's indexes to each word under | ||||||
| 416 | # consideration for splitting (hyphenation) | ||||||
| 417 | |||||||
| 418 | # TBD should we consider any non-ASCII spaces? | ||||||
| 419 | # don't split on non-breaking space (required blank). | ||||||
| 420 | 20 | 111 | my @txt = split(/\x20/, $text); | ||||
| 421 | 20 | 51 | my @line = (); | ||||
| 422 | 20 | 34 | local $"; # intent is that reset of separator ($") is local to block | ||||
| 423 | 20 | 42 | $"=' '; ## no critic | ||||
| 424 | 20 | 32 | my $lastWord = ''; # the one that didn't quite fit | ||||
| 425 | 20 | 36 | my $overflowed = 0; | ||||
| 426 | |||||||
| 427 | 20 | 54 | while (@txt) { | ||||
| 428 | # build up @line from @txt array until overfills line. | ||||||
| 429 | # need to remove SHYs (soft hyphens) at this point. | ||||||
| 430 | 119 | 189 | $lastWord = shift @txt; # preserve any SHYs in the word | ||||
| 431 | 119 | 244 | push @line, (_removeSHY($lastWord)); | ||||
| 432 | # one space between each element of line, like join(' ', @line) | ||||||
| 433 | 119 | 424 | $overflowed = $self->advancewidth("@line", %opts) > $width; | ||||
| 434 | 119 | 100 | 354 | last if $overflowed; | |||
| 435 | } | ||||||
| 436 | # if overflowed, and overflow not allowed, remove the last word added, | ||||||
| 437 | # unless single word in line and we're not going to attempt word splitting. | ||||||
| 438 | 20 | 100 | 66 | 82 | if ($overflowed && !$over) { | ||
| 439 | 13 | 50 | 33 | 101 | if ($hyphenate && @line == 1 || @line > 1) { | ||
| 33 | |||||||
| 440 | 13 | 21 | pop @line; # discard last (or only) word | ||||
| 441 | 13 | 36 | unshift @txt,$lastWord; # restore with SHYs intact | ||||
| 442 | } | ||||||
| 443 | # if not hyphenating (splitting words), just leave oversized | ||||||
| 444 | # single-word line. if hyphenating, could have empty @line. | ||||||
| 445 | } | ||||||
| 446 | |||||||
| 447 | 20 | 60 | my $Txt = "@txt"; # remaining text to put on next line | ||||
| 448 | 20 | 45 | my $Line = "@line"; # line that fits, but not yet with any split word | ||||
| 449 | # may be empty if first word in line overflows | ||||||
| 450 | |||||||
| 451 | # if we try to hyphenate, try splitting up that last word that | ||||||
| 452 | # broke the camel's back. otherwise, will return $Line and $Txt as is. | ||||||
| 453 | 20 | 50 | 33 | 56 | if ($hyphenate && $overflowed) { | ||
| 454 | 0 | 0 | my $space; | ||||
| 455 | # @line is current whole word list of line, does NOT overflow because | ||||||
| 456 | # $lastWord was removed. it may be empty if the first word tried was | ||||||
| 457 | # too long. @txt is whole word list of the remaining words to be output | ||||||
| 458 | # (includes $lastWord as its first word). | ||||||
| 459 | # | ||||||
| 460 | # we want to try splitting $lastWord into short enough left fragment | ||||||
| 461 | # (with right fragment remainder as first word of next line). if we | ||||||
| 462 | # fail to do so, just leave whole word as first word of next line, IF | ||||||
| 463 | # @line was not empty. if @line was empty, accept the overflow and | ||||||
| 464 | # output $lastWord as @line and remove it from @txt. | ||||||
| 465 | 0 | 0 | 0 | if (@line) { | |||
| 466 | # line not empty. $space is width for word fragment, not | ||||||
| 467 | # including blank after previous last word of @line. | ||||||
| 468 | 0 | 0 | $space = $width - $self->advancewidth("@line ", %opts); | ||||
| 469 | } else { | ||||||
| 470 | # line empty (first word too long, and we can try hyphenating). | ||||||
| 471 | # $space is entire $width available for left fragment. | ||||||
| 472 | 0 | 0 | $space = $width; | ||||
| 473 | } | ||||||
| 474 | |||||||
| 475 | 0 | 0 | 0 | if ($space > 0) { | |||
| 476 | 0 | 0 | my ($wordLeft, $wordRight); | ||||
| 477 | # @line is word(s) (if any) currently fitting within $width. | ||||||
| 478 | # @txt is remaining words unused in this line. $lastWord is first | ||||||
| 479 | # word of @txt. $space is width remaining to fill in line. | ||||||
| 480 | 0 | 0 | $wordLeft = ''; $wordRight = $lastWord; # fallbacks | ||||
| 0 | 0 | ||||||
| 481 | |||||||
| 482 | # if there is an error in Hyphenate_$lang, the message may be | ||||||
| 483 | # that the splitWord() function can't be found. debug errors by | ||||||
| 484 | # hard coding the require and splitWord() calls. | ||||||
| 485 | |||||||
| 486 | ## test that Hyphenate_$lang exists. if not, use Hyphenate_en | ||||||
| 487 | ## TBD: if Hyphenate_$lang is not found, should we fall back to | ||||||
| 488 | ## English (en) rules, or turn off hyphenation, or do limited | ||||||
| 489 | ## hyphenation (nothing language-specific)? | ||||||
| 490 | # only Hyphenate_basic. leave language support to other packages | ||||||
| 491 | 0 | 0 | require PDF::Builder::Content::Hyphenate_basic; | ||||
| 492 | #eval "require PDF::Builder::Content::Hyphenate_$lang"; | ||||||
| 493 | #if ($@) { | ||||||
| 494 | #print "something went wrong with require eval: $@\n"; | ||||||
| 495 | #$lang = 'en'; # perlmonks 27443 fall back to English | ||||||
| 496 | #require PDF::Builder::Content::Hyphenate_en; | ||||||
| 497 | #} | ||||||
| 498 | 0 | 0 | ($wordLeft,$wordRight) = PDF::Builder::Content::Hyphenate_basic::splitWord($self, $lastWord, $space, %opts); | ||||
| 499 | #eval '($wordLeft,$wordRight) = PDF::Builder::Content::Hyphenate_'.$lang.'::splitWord($self, "$lastWord", $space, %opts)'; | ||||||
| 500 | 0 | 0 | 0 | if ($@) { print "something went wrong with eval: $@\n"; } | |||
| 0 | 0 | ||||||
| 501 | |||||||
| 502 | # $wordLeft is left fragment of $lastWord that fits in $space. | ||||||
| 503 | # it might be empty '' if couldn't get a small enough piece. it | ||||||
| 504 | # includes a hyphen, but no leading space, and can be added to | ||||||
| 505 | # @line. | ||||||
| 506 | # $wordRight is the remainder of $lastWord (right fragment) that | ||||||
| 507 | # didn't fit. it might be the entire $lastWord. it shouldn't be | ||||||
| 508 | # empty, since the whole point of the exercise is that $lastWord | ||||||
| 509 | # didn't fit in the remaining space. it will replace the first | ||||||
| 510 | # element of @txt (there should be at least one). | ||||||
| 511 | |||||||
| 512 | # see if have a small enough left fragment of $lastWord to append | ||||||
| 513 | # to @line. neither left nor right Word should have full $lastWord, | ||||||
| 514 | # and both cannot be empty. it is highly unlikely that $wordLeft | ||||||
| 515 | # will be the full $lastWord, but quite possible that it is empty | ||||||
| 516 | # and $wordRight is $lastWord. | ||||||
| 517 | |||||||
| 518 | 0 | 0 | 0 | if (!@line) { | |||
| 519 | # special case of empty line. if $wordLeft is empty and | ||||||
| 520 | # $wordRight is presumably the entire $lastWord, use $wordRight | ||||||
| 521 | # for the line and remove it ($lastWord) from @txt. | ||||||
| 522 | 0 | 0 | 0 | if ($wordLeft eq '') { | |||
| 523 | 0 | 0 | @line = ($wordRight); # probably overflows $width. | ||||
| 524 | 0 | 0 | shift @txt; # remove $lastWord from @txt. | ||||
| 525 | } else { | ||||||
| 526 | # $wordLeft fragment fits $width. | ||||||
| 527 | 0 | 0 | @line = ($wordLeft); # should fit $width. | ||||
| 528 | 0 | 0 | shift @txt; # replace first element of @txt ($lastWord) | ||||
| 529 | 0 | 0 | unshift @txt, $wordRight; | ||||
| 530 | } | ||||||
| 531 | } else { | ||||||
| 532 | # usual case of some words already in @line. if $wordLeft is | ||||||
| 533 | # empty and $wordRight is entire $lastWord, we're done here. | ||||||
| 534 | # if $wordLeft has something, append it to line and replace | ||||||
| 535 | # first element of @txt with $wordRight (unless empty, which | ||||||
| 536 | # shouldn't happen). | ||||||
| 537 | 0 | 0 | 0 | if ($wordLeft eq '') { | |||
| 538 | # was unable to split $lastWord into short enough fragment. | ||||||
| 539 | # leave @line (already has words) and @txt alone. | ||||||
| 540 | } else { | ||||||
| 541 | 0 | 0 | push @line, ($wordLeft); # should fit $space. | ||||
| 542 | 0 | 0 | shift @txt; # replace first element of @txt (was $lastWord) | ||||
| 543 | 0 | 0 | 0 | unshift @txt, $wordRight if $wordRight ne ''; | |||
| 544 | } | ||||||
| 545 | } | ||||||
| 546 | |||||||
| 547 | # rebuild $Line and $Txt, in case they were altered. | ||||||
| 548 | 0 | 0 | $Txt = "@txt"; | ||||
| 549 | 0 | 0 | $Line = "@line"; | ||||
| 550 | } # there was $space available to try to fit a word fragment | ||||||
| 551 | } # we had an overflow to clean up, and hyphenation (word splitting) OK | ||||||
| 552 | 20 | 100 | return ($Line, $Txt); | ||||
| 553 | } | ||||||
| 554 | |||||||
| 555 | # remove soft hyphens (SHYs) from a word. assume is always #173 (good for | ||||||
| 556 | # Latin-1, CP-1252, UTF-8; might not work for some encodings) TBD | ||||||
| 557 | sub _removeSHY { | ||||||
| 558 | 119 | 119 | 212 | my ($word) = @_; | |||
| 559 | |||||||
| 560 | 119 | 284 | my @chars = split //, $word; | ||||
| 561 | 119 | 189 | my $out = ''; | ||||
| 562 | 119 | 198 | foreach (@chars) { | ||||
| 563 | 357 | 50 | 601 | next if ord($_) == 173; | |||
| 564 | 357 | 582 | $out .= $_; | ||||
| 565 | } | ||||||
| 566 | 119 | 283 | return $out; | ||||
| 567 | } | ||||||
| 568 | |||||||
| 569 | =over | ||||||
| 570 | |||||||
| 571 | =item ($width, $leftover) = $content->text_fill_left($string, $width, %opts) | ||||||
| 572 | |||||||
| 573 | Fill a line of 'width' with as much text as will fit, | ||||||
| 574 | and outputs it left justified. | ||||||
| 575 | The width actually used, and the leftover text (that didn't fit), | ||||||
| 576 | are B |
||||||
| 577 | |||||||
| 578 | =item ($width, $leftover) = $content->text_fill($string, $width, %opts) | ||||||
| 579 | |||||||
| 580 | Alias for text_fill_left(). | ||||||
| 581 | |||||||
| 582 | =back | ||||||
| 583 | |||||||
| 584 | =cut | ||||||
| 585 | |||||||
| 586 | sub text_fill_left { | ||||||
| 587 | 10 | 10 | 1 | 33 | my ($self, $text, $width, %opts) = @_; | ||
| 588 | # copy dashed option names to preferred undashed names | ||||||
| 589 | 10 | 50 | 33 | 51 | if (defined $opts{'-spillover'} && !defined $opts{'spillover'}) { $opts{'spillover'} = delete($opts{'-spillover'}); } | ||
| 10 | 24 | ||||||
| 590 | |||||||
| 591 | 10 | 33 | 40 | my $over = (not(defined($opts{'spillover'}) and $opts{'spillover'} == 0)); | |||
| 592 | 10 | 35 | my ($line, $ret) = $self->_text_fill_line($text, $width, $over, %opts); | ||||
| 593 | 10 | 42 | $width = $self->text($line, %opts); | ||||
| 594 | 10 | 35 | return ($width, $ret); | ||||
| 595 | } | ||||||
| 596 | |||||||
| 597 | sub text_fill { | ||||||
| 598 | 0 | 0 | 1 | 0 | my $self = shift; | ||
| 599 | 0 | 0 | return $self->text_fill_left(@_); | ||||
| 600 | } | ||||||
| 601 | |||||||
| 602 | =over | ||||||
| 603 | |||||||
| 604 | =item ($width, $leftover) = $content->text_fill_center($string, $width, %opts) | ||||||
| 605 | |||||||
| 606 | Fill a line of 'width' with as much text as will fit, | ||||||
| 607 | and outputs it centered. | ||||||
| 608 | The width actually used, and the leftover text (that didn't fit), | ||||||
| 609 | are B |
||||||
| 610 | |||||||
| 611 | =back | ||||||
| 612 | |||||||
| 613 | =cut | ||||||
| 614 | |||||||
| 615 | sub text_fill_center { | ||||||
| 616 | 2 | 2 | 1 | 9 | my ($self, $text, $width, %opts) = @_; | ||
| 617 | # copy dashed option names to preferred undashed names | ||||||
| 618 | 2 | 50 | 33 | 14 | if (defined $opts{'-spillover'} && !defined $opts{'spillover'}) { $opts{'spillover'} = delete($opts{'-spillover'}); } | ||
| 2 | 5 | ||||||
| 619 | |||||||
| 620 | 2 | 33 | 11 | my $over = (not(defined($opts{'spillover'}) and $opts{'spillover'} == 0)); | |||
| 621 | 2 | 11 | my ($line, $ret) = $self->_text_fill_line($text, $width, $over, %opts); | ||||
| 622 | 2 | 11 | $width = $self->text_center($line, %opts); | ||||
| 623 | 2 | 10 | return ($width, $ret); | ||||
| 624 | } | ||||||
| 625 | |||||||
| 626 | =over | ||||||
| 627 | |||||||
| 628 | =item ($width, $leftover) = $content->text_fill_right($string, $width, %opts) | ||||||
| 629 | |||||||
| 630 | Fill a line of 'width' with as much text as will fit, | ||||||
| 631 | and outputs it right justified. | ||||||
| 632 | The width actually used, and the leftover text (that didn't fit), | ||||||
| 633 | are B |
||||||
| 634 | |||||||
| 635 | =back | ||||||
| 636 | |||||||
| 637 | =cut | ||||||
| 638 | |||||||
| 639 | sub text_fill_right { | ||||||
| 640 | 2 | 2 | 1 | 10 | my ($self, $text, $width, %opts) = @_; | ||
| 641 | # copy dashed option names to preferred undashed names | ||||||
| 642 | 2 | 50 | 33 | 14 | if (defined $opts{'-spillover'} && !defined $opts{'spillover'}) { $opts{'spillover'} = delete($opts{'-spillover'}); } | ||
| 2 | 8 | ||||||
| 643 | |||||||
| 644 | 2 | 33 | 11 | my $over = (not(defined($opts{'spillover'}) and $opts{'spillover'} == 0)); | |||
| 645 | 2 | 11 | my ($line, $ret) = $self->_text_fill_line($text, $width, $over, %opts); | ||||
| 646 | 2 | 10 | $width = $self->text_right($line, %opts); | ||||
| 647 | 2 | 8 | return ($width, $ret); | ||||
| 648 | } | ||||||
| 649 | |||||||
| 650 | =over | ||||||
| 651 | |||||||
| 652 | =item ($width, $leftover) = $content->text_fill_justified($string, $width, %opts) | ||||||
| 653 | |||||||
| 654 | Fill a line of 'width' with as much text as will fit, | ||||||
| 655 | and outputs it fully justified (stretched or condensed). | ||||||
| 656 | The width actually used, and the leftover text (that didn't fit), | ||||||
| 657 | are B |
||||||
| 658 | |||||||
| 659 | Note that the entire line is fit to the available | ||||||
| 660 | width via a call to C |
||||||
| 661 | See C |
||||||
| 662 | The last line is unjustified (normal size) and left aligned by default, | ||||||
| 663 | although the option | ||||||
| 664 | |||||||
| 665 | B |
||||||
| 666 | |||||||
| 667 | =over | ||||||
| 668 | |||||||
| 669 | =item 'last_align' => place | ||||||
| 670 | |||||||
| 671 | where place is 'left' (default), 'center', or 'right' (may be shortened to | ||||||
| 672 | first letter) allows you to specify the alignment of the last line output. | ||||||
| 673 | |||||||
| 674 | =back | ||||||
| 675 | |||||||
| 676 | =back | ||||||
| 677 | |||||||
| 678 | =cut | ||||||
| 679 | |||||||
| 680 | sub text_fill_justified { | ||||||
| 681 | 6 | 6 | 1 | 23 | my ($self, $text, $width, %opts) = @_; | ||
| 682 | # copy dashed option names to preferred undashed names | ||||||
| 683 | 6 | 100 | 66 | 34 | if (defined $opts{'-last_align'} && !defined $opts{'last_align'}) { $opts{'last_align'} = delete($opts{'-last_align'}); } | ||
| 4 | 12 | ||||||
| 684 | 6 | 50 | 33 | 33 | if (defined $opts{'-spillover'} && !defined $opts{'spillover'}) { $opts{'spillover'} = delete($opts{'-spillover'}); } | ||
| 6 | 16 | ||||||
| 685 | |||||||
| 686 | 6 | 12 | my $align = 'l'; # default left align last line | ||||
| 687 | 6 | 100 | 15 | if (defined($opts{'last_align'})) { | |||
| 688 | 4 | 50 | 31 | if ($opts{'last_align'} =~ m/^l/i) { $align = 'l'; } | |||
| 0 | 100 | 0 | |||||
| 50 | |||||||
| 689 | 2 | 5 | elsif ($opts{'last_align'} =~ m/^c/i) { $align = 'c'; } | ||||
| 690 | 2 | 5 | elsif ($opts{'last_align'} =~ m/^r/i) { $align = 'r'; } | ||||
| 691 | 0 | 0 | else { warn "Unknown last_align for justified fill, 'left' used\n"; } | ||||
| 692 | } | ||||||
| 693 | |||||||
| 694 | 6 | 33 | 28 | my $over = (not(defined($opts{'spillover'}) and $opts{'spillover'} == 0)); | |||
| 695 | 6 | 31 | my ($line, $ret) = $self->_text_fill_line($text, $width, $over, %opts); | ||||
| 696 | # if last line, use $align (don't justify) | ||||||
| 697 | 6 | 100 | 23 | if ($ret eq '') { | |||
| 698 | 3 | 13 | my $lw = $self->advancewidth($line, %opts); | ||||
| 699 | 3 | 100 | 23 | if ($align eq 'l') { | |||
| 100 | |||||||
| 700 | 1 | 9 | $width = $self->text($line, %opts); | ||||
| 701 | } elsif ($align eq 'c') { | ||||||
| 702 | 1 | 6 | $width = $self->text($line, 'indent' => ($width-$lw)/2, %opts); | ||||
| 703 | } else { # 'r' | ||||||
| 704 | 1 | 6 | $width = $self->text($line, 'indent' => ($width-$lw), %opts); | ||||
| 705 | } | ||||||
| 706 | } else { | ||||||
| 707 | 3 | 18 | $width = $self->text_justified($line, $width, %opts); | ||||
| 708 | } | ||||||
| 709 | 6 | 26 | return ($width, $ret); | ||||
| 710 | } | ||||||
| 711 | |||||||
| 712 | =head2 Larger Text Segments | ||||||
| 713 | |||||||
| 714 | =over | ||||||
| 715 | |||||||
| 716 | =item ($overflow_text, $unused_height) = $txt->paragraph($text, $width,$height, $continue, %opts) | ||||||
| 717 | |||||||
| 718 | =item $overflow_text = $txt->paragraph($text, $width,$height, $continue, %opts) | ||||||
| 719 | |||||||
| 720 | Print a single string into a rectangular area on the page, of given width and | ||||||
| 721 | maximum height. The baseline of the first (top) line is at the current text | ||||||
| 722 | position. | ||||||
| 723 | |||||||
| 724 | Apply the text within the rectangle and B |
||||||
| 725 | not fit all of it within the rectangle). If called in an array context, the | ||||||
| 726 | unused height is also B |
||||||
| 727 | rectangle). | ||||||
| 728 | |||||||
| 729 | If C<$continue> is 1, the first line does B |
||||||
| 730 | indenting or outdenting, because we're printing the continuation of the | ||||||
| 731 | paragraph that was interrupted earlier. If it's 0, the first line may be | ||||||
| 732 | indented or outdented. | ||||||
| 733 | |||||||
| 734 | B |
||||||
| 735 | |||||||
| 736 | =over | ||||||
| 737 | |||||||
| 738 | =item 'pndnt' => $indent | ||||||
| 739 | |||||||
| 740 | Give the amount of indent (positive) or outdent (negative, for "hanging") | ||||||
| 741 | for paragraph first lines). This setting is ignored for centered text. | ||||||
| 742 | |||||||
| 743 | =item 'align' => $choice | ||||||
| 744 | |||||||
| 745 | C<$choice> is 'justified', 'right', 'center', 'left'; the default is 'left'. | ||||||
| 746 | See C |
||||||
| 747 | condensed if C<$choice> is 'justified'. C<$choice> may be shortened to the | ||||||
| 748 | first letter. | ||||||
| 749 | |||||||
| 750 | =item 'last_align' => place | ||||||
| 751 | |||||||
| 752 | where place is 'left' (default), 'center', or 'right' (may be shortened to | ||||||
| 753 | first letter) allows you to specify the alignment of the last line output, | ||||||
| 754 | but applies only when C |
||||||
| 755 | |||||||
| 756 | =item 'underline' => $distance | ||||||
| 757 | |||||||
| 758 | =item 'underline' => [ $distance, $thickness, ... ] | ||||||
| 759 | |||||||
| 760 | If a scalar, distance below baseline, | ||||||
| 761 | else array reference with pairs of distance and line thickness. | ||||||
| 762 | |||||||
| 763 | =item 'spillover' => $over | ||||||
| 764 | |||||||
| 765 | Controls if words in a line which exceed the given width should be | ||||||
| 766 | "spilled over" the bounds, or if a new line should be used for this word. | ||||||
| 767 | |||||||
| 768 | C<$over> is 1 or 0, with the default 1 (spills over the width). | ||||||
| 769 | |||||||
| 770 | =back | ||||||
| 771 | |||||||
| 772 | B |
||||||
| 773 | |||||||
| 774 | $txt->font($font,$fontsize); | ||||||
| 775 | $txt->leading($leading); | ||||||
| 776 | $txt->translate($x,$y); | ||||||
| 777 | $overflow = $txt->paragraph( 'long paragraph here ...', | ||||||
| 778 | $width, | ||||||
| 779 | $y+$leading-$bottom_margin ); | ||||||
| 780 | |||||||
| 781 | B |
||||||
| 782 | (B |
||||||
| 783 | plain text (all the same font, size, etc.) can be typeset with C |
||||||
| 784 | Also, there is currently very limited line splitting (hyphenation) to better | ||||||
| 785 | fit to a given width, and nothing is done for "widows and orphans". | ||||||
| 786 | |||||||
| 787 | =back | ||||||
| 788 | |||||||
| 789 | =cut | ||||||
| 790 | |||||||
| 791 | # TBD for LTR languages, does indenting on left make sense for right justified? | ||||||
| 792 | # TBD for bidi/RTL languages, should indenting be on right? | ||||||
| 793 | |||||||
| 794 | sub paragraph { | ||||||
| 795 | 12 | 12 | 1 | 112 | my ($self, $text, $width,$height, $continue, %opts) = @_; | ||
| 796 | # copy dashed option names to preferred undashed names | ||||||
| 797 | 12 | 100 | 66 | 59 | if (defined $opts{'-align'} && !defined $opts{'align'}) { $opts{'align'} = delete($opts{'-align'}); } | ||
| 5 | 20 | ||||||
| 798 | 12 | 50 | 33 | 42 | if (defined $opts{'-pndnt'} && !defined $opts{'pndnt'}) { $opts{'pndnt'} = delete($opts{'-pndnt'}); } | ||
| 0 | 0 | ||||||
| 799 | |||||||
| 800 | 12 | 29 | my @line = (); | ||||
| 801 | 12 | 23 | my $nwidth = 0; | ||||
| 802 | 12 | 34 | my $leading = $self->leading(); | ||||
| 803 | 12 | 27 | my $align = 'l'; # default left | ||||
| 804 | 12 | 100 | 38 | if (defined($opts{'align'})) { | |||
| 805 | 5 | 50 | 53 | if ($opts{'align'} =~ /^l/i) { $align = 'l'; } | |||
| 0 | 100 | 0 | |||||
| 100 | |||||||
| 50 | |||||||
| 806 | 1 | 4 | elsif ($opts{'align'} =~ /^c/i) { $align = 'c'; } | ||||
| 807 | 1 | 4 | elsif ($opts{'align'} =~ /^r/i) { $align = 'r'; } | ||||
| 808 | 3 | 10 | elsif ($opts{'align'} =~ /^j/i) { $align = 'j'; } | ||||
| 809 | 0 | 0 | else { warn "Unknown align value for paragraph(), 'left' used\n"; } | ||||
| 810 | } # default stays at 'l' | ||||||
| 811 | 12 | 50 | 38 | my $indent = defined($opts{'pndnt'})? $opts{'pndnt'}: 0; | |||
| 812 | 12 | 100 | 34 | if ($align eq 'c') { $indent = 0; } # indent/outdent makes no sense centered | |||
| 1 | 2 | ||||||
| 813 | 12 | 29 | my $first_line = !$continue; | ||||
| 814 | 12 | 18 | my $lw; | ||||
| 815 | 12 | 50 | my $em = $self->advancewidth('M'); | ||||
| 816 | |||||||
| 817 | 12 | 40 | while (length($text) > 0) { # more text to go... | ||||
| 818 | # indent == 0 (flush) all lines normal width | ||||||
| 819 | # indent (>0) first line moved in on left, subsequent normal width | ||||||
| 820 | # outdent (<0) first line is normal width, subsequent moved in on left | ||||||
| 821 | 20 | 36 | $lw = $width; | ||||
| 822 | 20 | 50 | 33 | 63 | if ($indent > 0 && $first_line) { $lw -= $indent*$em; } | ||
| 0 | 0 | ||||||
| 823 | 20 | 50 | 33 | 102 | if ($indent < 0 && !$first_line) { $lw += $indent*$em; } | ||
| 0 | 0 | ||||||
| 824 | # now, need to indent (move line start) right for 'l' and 'j' | ||||||
| 825 | 20 | 0 | 0 | 70 | if ($lw < $width && ($align eq 'l' || $align eq 'j')) { | ||
| 33 | |||||||
| 826 | 0 | 0 | $self->cr($leading); # go UP one line | ||||
| 827 | 0 | 0 | $self->nl(88*abs($indent)); # come down to right line and move right | ||||
| 828 | } | ||||||
| 829 | |||||||
| 830 | 20 | 100 | 76 | if ($align eq 'j') { | |||
| 100 | |||||||
| 100 | |||||||
| 831 | 6 | 34 | ($nwidth,$text) = $self->text_fill_justified($text, $lw, %opts); | ||||
| 832 | } elsif ($align eq 'r') { | ||||||
| 833 | 2 | 11 | ($nwidth,$text) = $self->text_fill_right($text, $lw, %opts); | ||||
| 834 | } elsif ($align eq 'c') { | ||||||
| 835 | 2 | 11 | ($nwidth,$text) = $self->text_fill_center($text, $lw, %opts); | ||||
| 836 | } else { # 'l' | ||||||
| 837 | 10 | 38 | ($nwidth,$text) = $self->text_fill_left($text, $lw, %opts); | ||||
| 838 | } | ||||||
| 839 | |||||||
| 840 | 20 | 85 | $self->nl(); | ||||
| 841 | 20 | 39 | $first_line = 0; | ||||
| 842 | |||||||
| 843 | # bail out and just return remaining $text if run out of vertical space | ||||||
| 844 | 20 | 100 | 77 | last if ($height -= $leading) < 0; | |||
| 845 | } | ||||||
| 846 | |||||||
| 847 | 12 | 100 | 58 | if (wantarray) { | |||
| 848 | # paragraph() called in the context of returning an array | ||||||
| 849 | 6 | 21 | return ($text, $height); | ||||
| 850 | } | ||||||
| 851 | 6 | 29 | return $text; | ||||
| 852 | } | ||||||
| 853 | |||||||
| 854 | =over | ||||||
| 855 | |||||||
| 856 | =item ($overflow_text, $continue, $unused_height) = $txt->section($text, $width,$height, $continue, %opts) | ||||||
| 857 | |||||||
| 858 | =item $overflow_text = $txt->section($text, $width,$height, $continue, %opts) | ||||||
| 859 | |||||||
| 860 | The C<$text> contains a string with one or more paragraphs C<$width> wide, | ||||||
| 861 | starting at the current text position, with a newline \n between each | ||||||
| 862 | paragraph. Each paragraph is output (see C |
||||||
| 863 | limit is met (a partial paragraph may be at the bottom). Whatever wasn't | ||||||
| 864 | output, will be B |
||||||
| 865 | If called in an array context, the | ||||||
| 866 | unused height and the paragraph "continue" flag are also B |
||||||
| 867 | |||||||
| 868 | C<$continue> is 0 for the first call of section(), and then use the value | ||||||
| 869 | returned from the previous call (1 if a paragraph was cut in the middle) to | ||||||
| 870 | prevent unwanted indenting or outdenting of the first line being printed. | ||||||
| 871 | |||||||
| 872 | For compatibility with recent changes to PDF::API2, B |
||||||
| 873 | as an I |
||||||
| 874 | |||||||
| 875 | B |
||||||
| 876 | |||||||
| 877 | =over | ||||||
| 878 | |||||||
| 879 | =item 'pvgap' => $vertical | ||||||
| 880 | |||||||
| 881 | Additional vertical space (unit: pt) between paragraphs (default 0). Note that this space | ||||||
| 882 | will also be added after the last paragraph printed. | ||||||
| 883 | |||||||
| 884 | =back | ||||||
| 885 | |||||||
| 886 | See C |
||||||
| 887 | |||||||
| 888 | =back | ||||||
| 889 | |||||||
| 890 | =cut | ||||||
| 891 | |||||||
| 892 | # alias for compatibility | ||||||
| 893 | sub paragraphs { | ||||||
| 894 | 1 | 1 | 0 | 16 | return section(@_); | ||
| 895 | } | ||||||
| 896 | |||||||
| 897 | sub section { | ||||||
| 898 | 2 | 2 | 1 | 16 | my ($self, $text, $width,$height, $continue, %opts) = @_; | ||
| 899 | # copy dashed option names to preferred undashed names | ||||||
| 900 | 2 | 50 | 33 | 10 | if (defined $opts{'-pvgap'} && !defined $opts{'pvgap'}) { $opts{'pvgap'} = delete($opts{'-pvgap'}); } | ||
| 0 | 0 | ||||||
| 901 | |||||||
| 902 | 2 | 5 | my $overflow = ''; # text to return if height fills up | ||||
| 903 | 2 | 50 | 7 | my $pvgap = defined($opts{'pvgap'})? $opts{'pvgap'}: 0; | |||
| 904 | # $continue =0 if fresh paragraph, or =1 if continuing one cut in middle | ||||||
| 905 | |||||||
| 906 | 2 | 13 | foreach my $para (split(/\n/, $text)) { | ||||
| 907 | # regardless of whether we've run out of space vertically, we will | ||||||
| 908 | # loop through all the paragraphs requested | ||||||
| 909 | |||||||
| 910 | # already seen inability to output more text? | ||||||
| 911 | # just put unused text back together into the string | ||||||
| 912 | # $continue should stay 1 | ||||||
| 913 | 6 | 50 | 18 | if (length($overflow) > 0) { | |||
| 914 | 0 | 0 | $overflow .= "\n" . $para; | ||||
| 915 | 0 | 0 | next; | ||||
| 916 | } | ||||||
| 917 | 6 | 23 | ($para, $height) = $self->paragraph($para, $width,$height, $continue, %opts); | ||||
| 918 | 6 | 13 | $continue = 0; | ||||
| 919 | 6 | 100 | 17 | if (length($para) > 0) { | |||
| 920 | # we cut a paragraph in half. set flag that continuation doesn't | ||||||
| 921 | # get indented/outdented | ||||||
| 922 | 2 | 6 | $overflow .= $para; | ||||
| 923 | 2 | 4 | $continue = 1; | ||||
| 924 | } | ||||||
| 925 | |||||||
| 926 | # inter-paragraph vertical space? | ||||||
| 927 | # note that the last paragraph will also get the extra space after it | ||||||
| 928 | 6 | 50 | 66 | 25 | if (length($para) == 0 && $pvgap != 0) { | ||
| 929 | 0 | 0 | $self->cr(-$pvgap); | ||||
| 930 | 0 | 0 | $height -= $pvgap; | ||||
| 931 | } | ||||||
| 932 | } | ||||||
| 933 | |||||||
| 934 | 2 | 50 | 8 | if (wantarray) { | |||
| 935 | # section() called in the context of returning an array | ||||||
| 936 | 0 | 0 | return ($overflow, $continue, $height); | ||||
| 937 | } | ||||||
| 938 | 2 | 9 | return $overflow; | ||||
| 939 | } | ||||||
| 940 | |||||||
| 941 | =over | ||||||
| 942 | |||||||
| 943 | =item $width = $txt->textlabel($x,$y, $font, $size, $text, %opts) | ||||||
| 944 | |||||||
| 945 | Place a line of text at an arbitrary C<[$x,$y]> on the page, with various text | ||||||
| 946 | settings (treatments) specified in the call. | ||||||
| 947 | |||||||
| 948 | =over | ||||||
| 949 | |||||||
| 950 | =item $font | ||||||
| 951 | |||||||
| 952 | A previously created font. | ||||||
| 953 | |||||||
| 954 | =item $size | ||||||
| 955 | |||||||
| 956 | The font size (points). | ||||||
| 957 | |||||||
| 958 | =item $text | ||||||
| 959 | |||||||
| 960 | The text to be printed (a single line). | ||||||
| 961 | |||||||
| 962 | =back | ||||||
| 963 | |||||||
| 964 | B |
||||||
| 965 | |||||||
| 966 | =over | ||||||
| 967 | |||||||
| 968 | =item 'rotate' => $deg | ||||||
| 969 | |||||||
| 970 | Rotate C<$deg> degrees counterclockwise from due East. | ||||||
| 971 | |||||||
| 972 | =item 'color' => $cspec | ||||||
| 973 | |||||||
| 974 | A color name or permitted spec, such as C<#CCE840>, for the character I |
||||||
| 975 | |||||||
| 976 | =item 'strokecolor' => $cspec | ||||||
| 977 | |||||||
| 978 | A color name or permitted spec, such as C<#CCE840>, for the character I |
||||||
| 979 | |||||||
| 980 | =item 'charspace' => $cdist | ||||||
| 981 | |||||||
| 982 | Additional distance between characters. | ||||||
| 983 | |||||||
| 984 | =item 'wordspace' => $wdist | ||||||
| 985 | |||||||
| 986 | Additional distance between words. | ||||||
| 987 | |||||||
| 988 | =item 'hscale' => $hfactor | ||||||
| 989 | |||||||
| 990 | Horizontal scaling mode (percentage of normal, default is 100). | ||||||
| 991 | |||||||
| 992 | =item 'render' => $mode | ||||||
| 993 | |||||||
| 994 | Character rendering mode (outline only, fill only, etc.). See C |
||||||
| 995 | |||||||
| 996 | =item 'left' => 1 | ||||||
| 997 | |||||||
| 998 | Left align on the given point. This is the default. | ||||||
| 999 | |||||||
| 1000 | =item 'center' => 1 | ||||||
| 1001 | |||||||
| 1002 | Center the text on the given point. | ||||||
| 1003 | |||||||
| 1004 | =item 'right' => 1 | ||||||
| 1005 | |||||||
| 1006 | Right align on the given point. | ||||||
| 1007 | |||||||
| 1008 | =item 'align' => $placement | ||||||
| 1009 | |||||||
| 1010 | Alternate to left, center, and right. C<$placement> is 'left' (default), | ||||||
| 1011 | 'center', or 'right'. | ||||||
| 1012 | |||||||
| 1013 | =back | ||||||
| 1014 | |||||||
| 1015 | Other options available to C |
||||||
| 1016 | |||||||
| 1017 | The width used (in points) is B |
||||||
| 1018 | |||||||
| 1019 | =back | ||||||
| 1020 | |||||||
| 1021 | B |
||||||
| 1022 | text operations. It is a standalone operation, and does I |
||||||
| 1023 | write" position (or any other setting) for another C |
||||||
| 1024 | following write will likely be at C<(0,0)>, and not at the expected location. | ||||||
| 1025 | |||||||
| 1026 | C |
||||||
| 1027 | lines of text, such as a label on some | ||||||
| 1028 | graphics, and not as part of putting down multiple pieces of text. It I |
||||||
| 1029 | possible to figure out the position of a following write (either C |
||||||
| 1030 | or C |
||||||
| 1031 | (assuming left-justified positioning). | ||||||
| 1032 | |||||||
| 1033 | =cut | ||||||
| 1034 | |||||||
| 1035 | sub textlabel { | ||||||
| 1036 | 0 | 0 | 1 | my ($self, $x,$y, $font, $size, $text, %opts) = @_; | |||
| 1037 | # copy dashed option names to preferred undashed names | ||||||
| 1038 | 0 | 0 | 0 | if (defined $opts{'-rotate'} && !defined $opts{'rotate'}) { $opts{'rotate'} = delete($opts{'-rotate'}); } | |||
| 0 | |||||||
| 1039 | 0 | 0 | 0 | if (defined $opts{'-color'} && !defined $opts{'color'}) { $opts{'color'} = delete($opts{'-color'}); } | |||
| 0 | |||||||
| 1040 | 0 | 0 | 0 | if (defined $opts{'-strokecolor'} && !defined $opts{'strokecolor'}) { $opts{'strokecolor'} = delete($opts{'-strokecolor'}); } | |||
| 0 | |||||||
| 1041 | 0 | 0 | 0 | if (defined $opts{'-charspace'} && !defined $opts{'charspace'}) { $opts{'charspace'} = delete($opts{'-charspace'}); } | |||
| 0 | |||||||
| 1042 | 0 | 0 | 0 | if (defined $opts{'-hscale'} && !defined $opts{'hscale'}) { $opts{'hscale'} = delete($opts{'-hscale'}); } | |||
| 0 | |||||||
| 1043 | 0 | 0 | 0 | if (defined $opts{'-wordspace'} && !defined $opts{'wordspace'}) { $opts{'wordspace'} = delete($opts{'-wordspace'}); } | |||
| 0 | |||||||
| 1044 | 0 | 0 | 0 | if (defined $opts{'-render'} && !defined $opts{'render'}) { $opts{'render'} = delete($opts{'-render'}); } | |||
| 0 | |||||||
| 1045 | 0 | 0 | 0 | if (defined $opts{'-right'} && !defined $opts{'right'}) { $opts{'right'} = delete($opts{'-right'}); } | |||
| 0 | |||||||
| 1046 | 0 | 0 | 0 | if (defined $opts{'-center'} && !defined $opts{'center'}) { $opts{'center'} = delete($opts{'-center'}); } | |||
| 0 | |||||||
| 1047 | 0 | 0 | 0 | if (defined $opts{'-left'} && !defined $opts{'left'}) { $opts{'left'} = delete($opts{'-left'}); } | |||
| 0 | |||||||
| 1048 | 0 | 0 | 0 | if (defined $opts{'-align'} && !defined $opts{'align'}) { $opts{'align'} = delete($opts{'-align'}); } | |||
| 0 | |||||||
| 1049 | 0 | my $wht; | |||||
| 1050 | |||||||
| 1051 | 0 | my %trans_opts = ( 'translate' => [$x,$y] ); | |||||
| 1052 | 0 | my %text_state = (); | |||||
| 1053 | 0 | 0 | $trans_opts{'rotate'} = $opts{'rotate'} if defined($opts{'rotate'}); | ||||
| 1054 | |||||||
| 1055 | 0 | my $wastext = $self->_in_text_object(); | |||||
| 1056 | 0 | 0 | if ($wastext) { | ||||
| 1057 | 0 | %text_state = $self->textstate(); | |||||
| 1058 | 0 | $self->textend(); | |||||
| 1059 | } | ||||||
| 1060 | 0 | $self->save(); | |||||
| 1061 | 0 | $self->textstart(); | |||||
| 1062 | |||||||
| 1063 | 0 | $self->transform(%trans_opts); | |||||
| 1064 | |||||||
| 1065 | 0 | 0 | $self->fillcolor(ref($opts{'color'}) ? @{$opts{'color'}} : $opts{'color'}) if defined($opts{'color'}); | ||||
| 0 | 0 | ||||||
| 1066 | 0 | 0 | $self->strokecolor(ref($opts{'strokecolor'}) ? @{$opts{'strokecolor'}} : $opts{'strokecolor'}) if defined($opts{'strokecolor'}); | ||||
| 0 | 0 | ||||||
| 1067 | |||||||
| 1068 | 0 | $self->font($font, $size); | |||||
| 1069 | |||||||
| 1070 | 0 | 0 | $self->charspace($opts{'charspace'}) if defined($opts{'charspace'}); | ||||
| 1071 | 0 | 0 | $self->hscale($opts{'hscale'}) if defined($opts{'hscale'}); | ||||
| 1072 | 0 | 0 | $self->wordspace($opts{'wordspace'}) if defined($opts{'wordspace'}); | ||||
| 1073 | 0 | 0 | $self->render($opts{'render'}) if defined($opts{'render'}); | ||||
| 1074 | |||||||
| 1075 | 0 | 0 | 0 | if (defined($opts{'right'}) && $opts{'right'} || | |||
| 0 | 0 | ||||||
| 0 | 0 | ||||||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 1076 | defined($opts{'align'}) && $opts{'align'} =~ /^r/i) { | ||||||
| 1077 | 0 | $wht = $self->text_right($text, %opts); | |||||
| 1078 | } elsif (defined($opts{'center'}) && $opts{'center'} || | ||||||
| 1079 | defined($opts{'align'}) && $opts{'align'} =~ /^c/i) { | ||||||
| 1080 | 0 | $wht = $self->text_center($text, %opts); | |||||
| 1081 | } elsif (defined($opts{'left'}) && $opts{'left'} || | ||||||
| 1082 | defined($opts{'align'}) && $opts{'align'} =~ /^l/i) { | ||||||
| 1083 | 0 | $wht = $self->text($text, %opts); # explicitly left aligned | |||||
| 1084 | } else { | ||||||
| 1085 | 0 | $wht = $self->text($text, %opts); # left aligned by default | |||||
| 1086 | } | ||||||
| 1087 | |||||||
| 1088 | 0 | $self->textend(); | |||||
| 1089 | 0 | $self->restore(); | |||||
| 1090 | |||||||
| 1091 | 0 | 0 | if ($wastext) { | ||||
| 1092 | 0 | $self->textstart(); | |||||
| 1093 | 0 | $self->textstate(%text_state); | |||||
| 1094 | } | ||||||
| 1095 | 0 | return $wht; | |||||
| 1096 | } | ||||||
| 1097 | |||||||
| 1098 | =head2 Complex Column Output with Markup | ||||||
| 1099 | |||||||
| 1100 | =over | ||||||
| 1101 | |||||||
| 1102 | =item ($rc, $next_y, $unused) = $text->column($page, $text, $grfx, $markup, $txt, %opts) | ||||||
| 1103 | |||||||
| 1104 | This method fills out a column of text on a page, returning any unused portion | ||||||
| 1105 | that could not be fit, and where it left off on the page. | ||||||
| 1106 | |||||||
| 1107 | Tag names, CSS entries, markup type, etc. are case-sensitive (usually | ||||||
| 1108 | lower-case letters only). For example, you cannot give a paragraph in |
||||||
| 1109 | HTML or a B selector in CSS styling. |
||||||
| 1110 | |||||||
| 1111 | B<$page> is the page context. Currently, its only use is for page annotations | ||||||
| 1112 | for links ('md1' []() and 'html' E |
||||||
| 1113 | you may pass anything such as C |
||||||
| 1114 | |||||||
| 1115 | B<$text> is the text context, so that various font and text-output operations | ||||||
| 1116 | may be performed. It is often, but not necessarily always, the same as the | ||||||
| 1117 | object containing the "column" method. | ||||||
| 1118 | |||||||
| 1119 | B<$grfx> is the graphics (gfx) context. It may be a dummy (e.g., undef) if | ||||||
| 1120 | I |
||||||
| 1121 | ('outline' option) and horizontal rule ( in HTML markup) use it. |
||||||
| 1122 | Currently, I |
||||||
| 1123 | 'html' C |
||||||
| 1124 | may in the future require a valid graphics context. Images (when implemented) | ||||||
| 1125 | will require a graphics context. | ||||||
| 1126 | |||||||
| 1127 | B<$markup> is information on what sort of I |
||||||
| 1128 | and lay out the column's text: | ||||||
| 1129 | |||||||
| 1130 | =over | ||||||
| 1131 | |||||||
| 1132 | =item 'pre' | ||||||
| 1133 | |||||||
| 1134 | The input material has already been processed and is already in the desired | ||||||
| 1135 | form. C<$txt> is an array reference to the list of hashes. This I |
||||||
| 1136 | when you are calling C |
||||||
| 1137 | time to output material left over from the first call. It may also be used when | ||||||
| 1138 | the caller application has already processed the text into the appropriate | ||||||
| 1139 | format, and other markup isn't being used. | ||||||
| 1140 | |||||||
| 1141 | =item 'none' | ||||||
| 1142 | |||||||
| 1143 | If I |
||||||
| 1144 | a new text array element specifies a new paragraph, and that's it. C<$txt> may | ||||||
| 1145 | be a single string, or an array (list) of strings. | ||||||
| 1146 | |||||||
| 1147 | The input B |
||||||
| 1148 | containing one or more paragraphs. A single string may also be given. An empty | ||||||
| 1149 | line between paragraphs may be used to separate the paragraphs. Paragraphs may | ||||||
| 1150 | not span array elements. | ||||||
| 1151 | |||||||
| 1152 | =item 'md1' | ||||||
| 1153 | |||||||
| 1154 | This specifies a certain flavor of Markdown compatible with Text::Markdown: | ||||||
| 1155 | |||||||
| 1156 | * or _ italics, ** bold, *** bold+italic; | ||||||
| 1157 | bulleted list *, numbered list 1. 2. etc.; | ||||||
| 1158 | #, ## etc. headings and subheadings; | ||||||
| 1159 | ---, ===, ___ horizontal rule; | ||||||
| 1160 | [label](URL) external links (to HTML page or within this document, see 'a') | ||||||
| 1161 | ` (backticks) enclose a "code" section | ||||||
| 1162 | |||||||
| 1163 | HTML (see below) may be mixed in as desired (although not within "code" blocks | ||||||
| 1164 | marked by backticks, where <, >, and & get turned into HTML entities, disabling | ||||||
| 1165 | the intended tags). | ||||||
| 1166 | Markdown will be converted into HTML, which will then be interpreted into PDF. | ||||||
| 1167 | I | ||||||
| 1168 | yet supported by HTML processing (see 'html' section below). Let us know if | ||||||
| 1169 | you need such a feature!> | ||||||
| 1170 | |||||||
| 1171 | The input B |
||||||
| 1172 | containing one or more paragraphs and other markup. A single string may also be | ||||||
| 1173 | given. Per Markdown formatting, an empty line between paragraphs may be used to | ||||||
| 1174 | separate the paragraphs. Separate array elements will first be glued together | ||||||
| 1175 | into a single string before processing, permitting paragraphs to span array | ||||||
| 1176 | elements if desired. | ||||||
| 1177 | |||||||
| 1178 | There are other flavors of Markdown, so other mdI |
||||||
| 1179 | in the future, such as POD from Perl code. | ||||||
| 1180 | |||||||
| 1181 | =item 'html' | ||||||
| 1182 | |||||||
| 1183 | This specifies that a subset of HTML markup is used, along with some attributes | ||||||
| 1184 | and CSS. Currently, HTML tags | ||||||
| 1185 | |||||||
| 1186 | 'i'/'em' (italic), 'b'/'strong' (bold), | ||||||
| 1187 | 'p' (paragraph), | ||||||
| 1188 | 'font' (font face->font-family, color, size->font-size), | ||||||
| 1189 | 'span' (needs style= attribute with CSS to do anything useful), | ||||||
| 1190 | 'ul', 'ol', 'li' (bulleted, numbered lists), | ||||||
| 1191 | 'img' (TBD, image, empty. hspace->margin-left/right, | ||||||
| 1192 | vspace->margin-top/bottom, width, height), | ||||||
| 1193 | 'a' (anchor/link, web page URL or this document target #p[-x-y[-z]]), | ||||||
| 1194 | 'pre', 'code' (TBD, preformatted and code blocks), | ||||||
| 1195 | 'h1' through 'h6' (headings) | ||||||
| 1196 | 'hr' (horizontal rule) | ||||||
| 1197 | 'br' (TBD, line break, empty) | ||||||
| 1198 | 'sup', 'sub' (TBD superscript and subscript) | ||||||
| 1199 | 's', 'strike', 'del' (line-through) | ||||||
| 1200 | 'u', 'ins' (underline) | ||||||
| 1201 | 'ovl' (TBD -- non-HTML, overline) | ||||||
| 1202 | 'k' (TBD -- non-HTML, kerning left/right shift) | ||||||
| 1203 | |||||||
| 1204 | are supported (fully or in part I |
||||||
| 1205 | color, font-size, font-family, etc. | ||||||
| 1206 | E |
||||||
| 1207 | within the E |
||||||
| 1208 | of the body and added (in order) on to the end of any style tag(s) defined in | ||||||
| 1209 | a head section. Multiple style tags will be condensed into a single collection | ||||||
| 1210 | (later definitions of equal precedence overriding earlier). These stylings will | ||||||
| 1211 | have global effect, as though they were defined in the head. As with normal CSS, | ||||||
| 1212 | the hierarchy of a given property (in decreasing precedence) is | ||||||
| 1213 | |||||||
| 1214 | appearance in a style= tag attribute | ||||||
| 1215 | appearance in a tag attribute (possibly a different name than the property) | ||||||
| 1216 | appearance in a #IDname selector in a , and an existing $style | ||||||
| 3516 | # hashref, update $style and return it | ||||||
| 3517 | sub _process_style_tag { | ||||||
| 3518 | 0 | 0 | my ($style, $text) = @_; | ||||
| 3519 | |||||||
| 3520 | # expect sets of selector { property: value; ... } | ||||||
| 3521 | # break up into selector => { property => value, ... } | ||||||
| 3522 | # replace or add to existing $style | ||||||
| 3523 | # note that a selector may be a tagName, a .className, or an #idName | ||||||
| 3524 | |||||||
| 3525 | 0 | $text =~ s/\n/ /sg; # replace end-of-lines with spaces | |||||
| 3526 | 0 | while ($text ne '') { | |||||
| 3527 | 0 | my $selector; | |||||
| 3528 | |||||||
| 3529 | 0 | 0 | if ($text =~ s/^\s+//) { # remove leading whitespace | ||||
| 3530 | 0 | 0 | if ($text eq '') { last; } | ||||
| 0 | |||||||
| 3531 | } | ||||||
| 3532 | 0 | 0 | if ($text =~ s/([^\s]+)//) { # extract selector | ||||
| 3533 | 0 | $selector = $1; | |||||
| 3534 | } | ||||||
| 3535 | 0 | 0 | if ($text =~ s/^\s*{//) { # remove whitespace up through { | ||||
| 3536 | 0 | 0 | if ($text eq '') { last; } | ||||
| 0 | |||||||
| 3537 | } | ||||||
| 3538 | # one or more property-name: value; sets (; might be missing on last) | ||||||
| 3539 | # go into %prop_val. we don't expect to see any } within a property | ||||||
| 3540 | # value string. | ||||||
| 3541 | 0 | 0 | if ($text =~ s/([^}]+)//) { | ||||
| 3542 | 0 | $style->{$selector} = _process_style_string({}, $1); | |||||
| 3543 | } | ||||||
| 3544 | 0 | 0 | if ($text =~ s/^}\s*//) { # remove closing } and whitespace | ||||
| 3545 | 0 | 0 | if ($text eq '') { last; } | ||||
| 0 | |||||||
| 3546 | } | ||||||
| 3547 | |||||||
| 3548 | } | ||||||
| 3549 | |||||||
| 3550 | 0 | return $style; | |||||
| 3551 | } # end of _process_style_tag() | ||||||
| 3552 | |||||||
| 3553 | # decompose a style string into property-value pairs. used for both |