File Coverage

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 If you have used some of these methods in PDF::Builder with a
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. Implemented for symmetry, for those who use a lot of
42             C and C, and desire a matching 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, but I on the current point.
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, but right-aligned to the current point.
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, but stretches text using C, C, and (as a
106             last resort) C, to fill the desired
107             (available) C<$width>. Note that if the desired width is I than the
108             natural width taken by the text, it will be I to fit, using the
109             same three routines.
110              
111             The unchanged C<$width> is B, unless there was some reason to
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 use any intercharacter
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 (interword) spacing values (explicit or default) are doubled if
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 is 1, double 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 is 1, force C to 0.
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 amount to add to
146             (each) interword spacing to further expand the line.
147             If C is 1, double 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 amount to add to (each)
153             intercharacter spacing to further expand the line.
154             If C is 1, force C to 0.
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 is 1, double 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 is 1, force C to 0.
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 to finish expanding or
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=>0 to disallow spillover.
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. Note that neither hyphenation nor simple
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), 1: do basic hyphenation. Always allows
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 split at a hard hyphen (x\2D), 1: I (B)
348              
349             =item 'spOP' => value
350              
351             0: do I split after most punctuation, 1: I (B)
352              
353             =item 'spDR' => value
354              
355             0: do I split after a run of one or more digits, 1: I (B)
356              
357             =item 'spLR' => value
358              
359             0: do I split after a run of one or more ASCII letters, 1: I (B)
360              
361             =item 'spCC' => value
362              
363             0: do I split in camelCase between a lowercase letter and an
364             uppercase letter, 1: I (B)
365              
366             =item 'spRB' => value
367              
368             0: do I split on a Required Blank ( ), is B.
369             1: I Try to avoid this; it is a desperation
370             move!
371              
372             =item 'spFS' => value
373              
374             0: do I split where it will I fit (middle of word!), is B.
375             1: I Try to avoid this; it is a
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 word split point (hyphenation point).
381             The B is 2.
382              
383             =item 'min_suffix' => value
384              
385             Minimum number of letters I word split point (hyphenation point).
386             The B is 3.
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 for options to control stretch and condense.
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 any leftover text (if could
725             not fit all of it within the rectangle). If called in an array context, the
726             unused height is also B (may be 0 or negative if it just filled the
727             rectangle).
728              
729             If C<$continue> is 1, the first line does B get special treatment for
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 call for options to control how a line is expanded or
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 is 'justified'.
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 if you need to change any text treatment I a paragraph
782             (B or I text, for instance), this can not handle it. Only
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) until the C<$height>
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 is accepted
873             as an I for C
.
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 for other C<%opts> you can use, such as C and 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 call.
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, such as underlining, can be used here.
1016              
1017             The width used (in points) is B.
1018              
1019             =back
1020              
1021             B that C was not designed to interoperate with other
1022             text operations. It is a standalone operation, and does I leave a "next
1023             write" position (or any other setting) for another C mode operation. A
1024             following write will likely be at C<(0,0)>, and not at the expected location.
1025              
1026             C is intended as an "all in one" convenience function for single
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) by adding the returned width to the original position's I value
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' EaE), so if you're not using those,
1113             you may pass anything such as C for C<$page> if you wish.
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 graphics are to be drawn, but graphical items such as the column outline
1121             ('outline' option) and horizontal rule (
in HTML markup) use it.
1122             Currently, I underline (default for links, 'md1' C<[]()> and
1123             'html' CaE>) or line-through or overline use the text context, but
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 is being used to format
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 be used
1136             when you are calling C a second (or later)
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 is specified, there is no markup in use. At most, a blank line or
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 is a list (anonymous array reference) of strings, each
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 is a list (anonymous array reference) of strings, each
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 flavors I be defined
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 "TBD"), along with limited CSS for
1205             color, font-size, font-family, etc.
1206             EstyleE tags may be placed in an optional EheadE section, or
1207             within the EbodyE. In the latter case, style tags will be pulled out
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