File Coverage

blib/lib/Pod/Simple/RTF.pm
Criterion Covered Total %
statement 141 184 76.6
branch 52 100 52.0
condition 12 37 32.4
subroutine 24 24 100.0
pod 1 14 7.1
total 230 359 64.0


line stmt bran cond sub pod time code
1             package Pod::Simple::RTF;
2 3     3   329993 use strict;
  3         7  
  3         103  
3 3     3   12 use warnings;
  3         5  
  3         192  
4              
5             #sub DEBUG () {4};
6             #sub Pod::Simple::DEBUG () {4};
7             #sub Pod::Simple::PullParser::DEBUG () {4};
8              
9             our $VERSION = '3.47';
10 3     3   509 use Pod::Simple::PullParser ();
  3         6  
  3         96  
11             our @ISA;
12 3     3   85 BEGIN {@ISA = ('Pod::Simple::PullParser')}
13              
14 3     3   13 use Carp ();
  3         5  
  3         90  
15 3 50   3   10366 BEGIN { *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG }
16              
17             sub to_uni ($) { # Convert native code point to Unicode
18 270     270 0 348 my $x = shift;
19              
20             # Broken for early EBCDICs
21 270 50 50     774 $x = chr utf8::native_to_unicode(ord $x) if "$]" >= 5.007_003
22             && ord("A") != 65;
23 270         1058 return $x;
24             }
25              
26             # We escape out 'F' so that we can send RTF files thru the mail without the
27             # slightest worry that paragraphs beginning with "From" will get munged.
28             # We also escape '\', '{', '}', and '_'
29             my $map_to_self = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEGHIJKLMNOPQRSTUVWXYZ[]^`abcdefghijklmnopqrstuvwxyz|~';
30              
31             our $WRAP;
32             $WRAP = 1 unless defined $WRAP;
33             our %Escape = (
34              
35             # Start with every character mapping to its hex equivalent
36             map( (chr($_) => sprintf("\\'%02x", $_)), 0 .. 0xFF),
37              
38             # Override most ASCII printables with themselves (or on non-ASCII platforms,
39             # their ASCII values. This is because the output is UTF-16, which is always
40             # based on Unicode code points)
41             map( ( substr($map_to_self, $_, 1)
42             => to_uni(substr($map_to_self, $_, 1))), 0 .. length($map_to_self) - 1),
43              
44             # And some refinements:
45             "\r" => "\n",
46             "\cj" => "\n",
47             "\n" => "\n\\line ",
48              
49             "\t" => "\\tab ", # Tabs (altho theoretically raw \t's are okay)
50             "\f" => "\n\\page\n", # Formfeed
51             "-" => "\\_", # Turn plaintext '-' into a non-breaking hyphen
52             $Pod::Simple::nbsp => "\\~", # Latin-1 non-breaking space
53             $Pod::Simple::shy => "\\-", # Latin-1 soft (optional) hyphen
54              
55             # CRAZY HACKS:
56             "\n" => "\\line\n",
57             "\r" => "\n",
58             "\cb" => "{\n\\cs21\\lang1024\\noproof ", # \\cf1
59             "\cc" => "}",
60             );
61              
62             # Generate a string of all the characters in %Escape that don't map to
63             # themselves. First, one without the hyphen, then one with.
64             my $escaped_sans_hyphen = "";
65             $escaped_sans_hyphen .= $_ for grep { $_ ne $Escape{$_} && $_ ne '-' }
66             sort keys %Escape;
67             my $escaped = "-$escaped_sans_hyphen";
68              
69             # Then convert to patterns
70             $escaped_sans_hyphen = qr/[\Q$escaped_sans_hyphen \E]/;
71             $escaped= qr/[\Q$escaped\E]/;
72              
73             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74              
75             sub _openclose {
76 3     3   7 return map {;
77 75 50       165 m/^([-A-Za-z]+)=(\w[^\=]*)$/s or die "what's <$_>?";
78 75         315 ( $1, "{\\$2\n", "/$1", "}" );
79             } @_;
80             }
81              
82             my @_to_accept;
83              
84             our %Tagmap = (
85             # 'foo=bar' means ('foo' => '{\bar'."\n", '/foo' => '}')
86             _openclose(
87             'B=cs18\b',
88             'I=cs16\i',
89             'U=cs30\ul',
90             'C=cs19\f1\lang1024\noproof',
91             'F=cs17\i\lang1024\noproof',
92              
93             'VerbatimI=cs26\i',
94             'VerbatimB=cs27\b',
95             'VerbatimBI=cs28\b\i',
96              
97             map {; m/^([-a-z]+)/s && push @_to_accept, $1; $_ }
98             qw[
99             underline=ul smallcaps=scaps shadow=shad
100             superscript=super subscript=sub strikethrough=strike
101             outline=outl emboss=embo engrave=impr
102             dotted-underline=uld dash-underline=uldash
103             dot-dash-underline=uldashd dot-dot-dash-underline=uldashdd
104             double-underline=uldb thick-underline=ulth
105             word-underline=ulw wave-underline=ulwave
106             ]
107             # But no double-strikethrough, because MSWord can't agree with the
108             # RTF spec on whether it's supposed to be \strikedl or \striked1 (!!!)
109             ),
110              
111             # Bit of a hack here:
112             'L=pod' => '{\cs22\i'."\n",
113             'L=url' => '{\cs23\i'."\n",
114             'L=man' => '{\cs24\i'."\n",
115             '/L' => '}',
116              
117             'Data' => "\n",
118             '/Data' => "\n",
119              
120             'Verbatim' => "\n{\\pard\\li#rtfindent##rtfkeep#\\plain\\s20\\sa180\\f1\\fs18\\lang1024\\noproof\n",
121             '/Verbatim' => "\n\\par}\n",
122             'VerbatimFormatted' => "\n{\\pard\\li#rtfindent##rtfkeep#\\plain\\s20\\sa180\\f1\\fs18\\lang1024\\noproof\n",
123             '/VerbatimFormatted' => "\n\\par}\n",
124             'Para' => "\n{\\pard\\li#rtfindent#\\sa180\n",
125             '/Para' => "\n\\par}\n",
126             'head1' => "\n{\\pard\\li#rtfindent#\\s31\\keepn\\sb90\\sa180\\f2\\fs#head1_halfpoint_size#\\ul{\n",
127             '/head1' => "\n}\\par}\n",
128             'head2' => "\n{\\pard\\li#rtfindent#\\s32\\keepn\\sb90\\sa180\\f2\\fs#head2_halfpoint_size#\\ul{\n",
129             '/head2' => "\n}\\par}\n",
130             'head3' => "\n{\\pard\\li#rtfindent#\\s33\\keepn\\sb90\\sa180\\f2\\fs#head3_halfpoint_size#\\ul{\n",
131             '/head3' => "\n}\\par}\n",
132             'head4' => "\n{\\pard\\li#rtfindent#\\s34\\keepn\\sb90\\sa180\\f2\\fs#head4_halfpoint_size#\\ul{\n",
133             '/head4' => "\n}\\par}\n",
134             # wordpad borks on \tc\tcl1, or I'd put that in =head1 and =head2
135              
136             'item-bullet' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n",
137             '/item-bullet' => "\n\\par}\n",
138             'item-number' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n",
139             '/item-number' => "\n\\par}\n",
140             'item-text' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n",
141             '/item-text' => "\n\\par}\n",
142              
143             # we don't need any styles for over-* and /over-*
144             );
145              
146              
147             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
148             sub new {
149 3     3 1 1392 my $new = shift->SUPER::new(@_);
150 3         17 $new->nix_X_codes(1);
151 3         16 $new->nbsp_for_S(1);
152 3         14 $new->accept_targets( 'rtf', 'RTF' );
153              
154 3         105 $new->{'Tagmap'} = {%Tagmap};
155              
156 3         28 $new->accept_codes(@_to_accept);
157 3         9 $new->accept_codes('VerbatimFormatted');
158 3         5 DEBUG > 2 and print STDERR "To accept: ", join(' ',@_to_accept), "\n";
159             $new->doc_lang(
160             ( $ENV{'RTFDEFLANG'} || '') =~ m/^(\d{1,10})$/s ? $1
161             : ($ENV{'RTFDEFLANG'} || '') =~ m/^0?x([a-fA-F0-9]{1,10})$/s ? hex($1)
162             # yes, tolerate hex!
163 3 50 50     65 : ($ENV{'RTFDEFLANG'} || '') =~ m/^([a-fA-F0-9]{4})$/s ? hex($1)
    50 50        
    50 50        
164             # yes, tolerate even more hex!
165             : '1033'
166             );
167              
168 3         10 $new->head1_halfpoint_size(32);
169 3         9 $new->head2_halfpoint_size(28);
170 3         9 $new->head3_halfpoint_size(25);
171 3         9 $new->head4_halfpoint_size(22);
172 3         9 $new->codeblock_halfpoint_size(18);
173 3         9 $new->header_halfpoint_size(17);
174 3         9 $new->normal_halfpoint_size(25);
175              
176 3         48 return $new;
177             }
178              
179             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
180              
181             __PACKAGE__->_accessorize(
182             'doc_lang',
183             'head1_halfpoint_size',
184             'head2_halfpoint_size',
185             'head3_halfpoint_size',
186             'head4_halfpoint_size',
187             'codeblock_halfpoint_size',
188             'header_halfpoint_size',
189             'normal_halfpoint_size',
190             'no_proofing_exemptions',
191             );
192              
193              
194             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195             sub run {
196 3     3 0 7 my $self = $_[0];
197 3 50       27 return $self->do_middle if $self->bare_output;
198             return
199 3   33     10 $self->do_beginning && $self->do_middle && $self->do_end;
200             }
201              
202              
203             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204              
205             # Match something like an identifier. Prefer XID if available, then plain ID,
206             # then just ASCII
207             my $id_re = Pod::Simple::BlackBox::my_qr('[\'_\p{XIDS}][\'\p{XIDC}]+', "ab");
208             $id_re = Pod::Simple::BlackBox::my_qr('[\'_\p{IDS}][\'\p{IDC}]+', "ab")
209             unless $id_re;
210             $id_re = qr/['_a-zA-Z]['a-zA-Z0-9_]+/ unless $id_re;
211              
212             sub do_middle { # the main work
213 3     3 0 5 my $self = $_[0];
214 3         21 my $fh = $self->{'output_fh'};
215              
216 3         30 my($token, $type, $tagname, $scratch);
217 3         0 my @stack;
218 3         0 my @indent_stack;
219 3 50       13 $self->{'rtfindent'} = 0 unless defined $self->{'rtfindent'};
220              
221 3         10 while($token = $self->get_token) {
222              
223 63 100       105 if( ($type = $token->type) eq 'text' ) {
    100          
    50          
224 19 100       34 if( $self->{'rtfverbatim'} ) {
225 1         2 DEBUG > 1 and print STDERR " $type " , $token->text, " in verbatim!\n";
226 1         2 rtf_esc(0, $scratch = $token->text); # 0 => Don't escape hyphen
227 1         3 print $fh $scratch;
228 1         4 next;
229             }
230              
231 18         24 DEBUG > 1 and print STDERR " $type " , $token->text, "\n";
232              
233 18         32 $scratch = $token->text;
234 18         31 $scratch =~ tr/\t\cb\cc/ /d;
235              
236 18 50       772 $self->{'no_proofing_exemptions'} or $scratch =~
237             s/(?:
238             ^
239             |
240             (?<=[\r\n\t "\[\<\(])
241             ) # start on whitespace, sequence-start, or quote
242             ( # something looking like a Perl token:
243             (?:
244             [\$\@\:\<\*\\_]\S+ # either starting with a sigil, etc.
245             )
246             |
247             # or starting alpha, but containing anything strange:
248             (?:
249             ${id_re}[\$\@\:_<>\(\\\*]\S+
250             )
251             )
252             /\cb$1\cc/xsg
253             ;
254              
255 18         43 rtf_esc(1, $scratch); # 1 => escape hyphen
256 18 50       152 $scratch =~
257             s/(
258             [^\r\n]{65} # Snare 65 characters from a line
259             [^\r\n ]{0,50} # and finish any current word
260             )
261             (\ {1,10})(?![\r\n]) # capture some spaces not at line-end
262             /$1$2\n/gx # and put a NL before those spaces
263             if $WRAP;
264             # This may wrap at well past the 65th column, but not past the 120th.
265              
266 18         43 print $fh $scratch;
267              
268             } elsif( $type eq 'start' ) {
269             DEBUG > 1 and print STDERR " +$type ",$token->tagname,
270 22         25 " (", map("<$_> ", %{$token->attr_hash}), ")\n";
271              
272 22 100 66     41 if( ($tagname = $token->tagname) eq 'Verbatim'
    50          
    50          
    50          
    50          
273             or $tagname eq 'VerbatimFormatted'
274             ) {
275 1         2 ++$self->{'rtfverbatim'};
276 1         2 my $next = $self->get_token;
277 1 50       3 next unless defined $next;
278 1         2 my $line_count = 1;
279 1 50       3 if($next->type eq 'text') {
280 1         3 my $t = $next->text_r;
281 1         6 while( $$t =~ m/$/mg ) {
282 15 100       27 last if ++$line_count > 15; # no point in counting further
283             }
284 1         2 DEBUG > 3 and print STDERR " verbatim line count: $line_count\n";
285             }
286 1         4 $self->unget_token($next);
287 1 50       3 $self->{'rtfkeep'} = ($line_count > 15) ? '' : '\keepn' ;
288              
289             } elsif( $tagname =~ m/^item-/s ) {
290 0         0 my @to_unget;
291 0         0 my $text_count_here = 0;
292 0         0 $self->{'rtfitemkeepn'} = '';
293             # Some heuristics to stop item-*'s functioning as subheadings
294             # from getting split from the things they're subheadings for.
295             #
296             # It's not terribly pretty, but it really does make things pretty.
297             #
298 0         0 while(1) {
299 0         0 push @to_unget, $self->get_token;
300 0 0       0 pop(@to_unget), last unless defined $to_unget[-1];
301             # Erroneously used to be "unshift" instead of pop! Adds instead
302             # of removes, and operates on the beginning instead of the end!
303              
304 0 0 0     0 if($to_unget[-1]->type eq 'text') {
    0 0        
    0          
305 0 0       0 if( ($text_count_here += length ${$to_unget[-1]->text_r}) > 150 ){
  0         0  
306 0         0 DEBUG > 1 and print STDERR " item-* is too long to be keepn'd.\n";
307 0         0 last;
308             }
309             } elsif (@to_unget > 1 and
310             $to_unget[-2]->type eq 'end' and
311             $to_unget[-2]->tagname =~ m/^item-/s
312             ) {
313             # Bail out here, after setting rtfitemkeepn yea or nay.
314 0 0 0     0 $self->{'rtfitemkeepn'} = '\keepn' if
315             $to_unget[-1]->type eq 'start' and
316             $to_unget[-1]->tagname eq 'Para';
317              
318             DEBUG > 1 and printf STDERR " item-* before %s(%s) %s keepn'd.\n",
319             $to_unget[-1]->type,
320             $to_unget[-1]->can('tagname') ? $to_unget[-1]->tagname : '',
321 0         0 $self->{'rtfitemkeepn'} ? "gets" : "doesn't get";
322 0         0 last;
323             } elsif (@to_unget > 40) {
324 0         0 DEBUG > 1 and print STDERR " item-* now has too many tokens (",
325             scalar(@to_unget),
326             (DEBUG > 4) ? (q<: >, map($_->dump, @to_unget)) : (),
327             ") to be keepn'd.\n";
328 0         0 last; # give up
329             }
330             # else keep while'ing along
331             }
332             # Now put it aaaaall back...
333 0         0 $self->unget_token(@to_unget);
334              
335             } elsif( $tagname =~ m/^over-/s ) {
336 0         0 push @stack, $1;
337 0         0 push @indent_stack,
338             int($token->attr('indent') * 4 * $self->normal_halfpoint_size);
339 0         0 DEBUG and print STDERR "Indenting over $indent_stack[-1] twips.\n";
340 0         0 $self->{'rtfindent'} += $indent_stack[-1];
341              
342             } elsif ($tagname eq 'L') {
343 0   0     0 $tagname .= '=' . ($token->attr('type') || 'pod');
344              
345             } elsif ($tagname eq 'Data') {
346 0         0 my $next = $self->get_token;
347 0 0       0 next unless defined $next;
348 0 0       0 unless( $next->type eq 'text' ) {
349 0         0 $self->unget_token($next);
350 0         0 next;
351             }
352 0         0 DEBUG and print STDERR " raw text ", $next->text, "\n";
353 0         0 printf $fh "\n" . $next->text . "\n";
354 0         0 next;
355             }
356              
357 22 100       58 defined($scratch = $self->{'Tagmap'}{$tagname}) or next;
358 19         76 $scratch =~ s/\#([^\#]+)\#/${$self}{$1}/g; # interpolate
  26         86  
359 19         43 print $fh $scratch;
360              
361 19 50       54 if ($tagname eq 'item-number') {
    50          
362 0         0 print $fh $token->attr('number'), ". \n";
363             } elsif ($tagname eq 'item-bullet') {
364 0         0 print $fh "\\'", ord("_"), "\n";
365             #for funky testing: print $fh '', rtf_esc(1, "\x{4E4B}\x{9053}");
366             }
367              
368             } elsif( $type eq 'end' ) {
369 22         22 DEBUG > 1 and print STDERR " -$type ",$token->tagname,"\n";
370 22 50 66     46 if( ($tagname = $token->tagname) =~ m/^over-/s ) {
    100          
371 0         0 DEBUG and print STDERR "Indenting back $indent_stack[-1] twips.\n";
372 0         0 $self->{'rtfindent'} -= pop @indent_stack;
373 0         0 pop @stack;
374             } elsif( $tagname eq 'Verbatim' or $tagname eq 'VerbatimFormatted') {
375 1         1 --$self->{'rtfverbatim'};
376             }
377 22 100       65 defined($scratch = $self->{'Tagmap'}{"/$tagname"}) or next;
378 19         29 $scratch =~ s/\#([^\#]+)\#/${$self}{$1}/g; # interpolate
  0         0  
379 19         31 print $fh $scratch;
380             }
381             }
382 3         18 return 1;
383             }
384              
385             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
386             sub do_beginning {
387 3     3 0 6 my $self = $_[0];
388 3         7 my $fh = $self->{'output_fh'};
389 3         8 return print $fh join '',
390             $self->doc_init,
391             $self->font_table,
392             $self->stylesheet,
393             $self->color_table,
394             $self->doc_info,
395             $self->doc_start,
396             "\n"
397             ;
398             }
399              
400             sub do_end {
401 3     3 0 7 my $self = $_[0];
402 3         5 my $fh = $self->{'output_fh'};
403 3         8 return print $fh '}'; # that should do it
404             }
405              
406             ###########################################################################
407              
408             sub stylesheet {
409 3     3 0 16 return sprintf <<'END',
410             {\stylesheet
411             {\snext0 Normal;}
412             {\*\cs10 \additive Default Paragraph Font;}
413             {\*\cs16 \additive \i \sbasedon10 pod-I;}
414             {\*\cs17 \additive \i\lang1024\noproof \sbasedon10 pod-F;}
415             {\*\cs18 \additive \b \sbasedon10 pod-B;}
416             {\*\cs19 \additive \f1\lang1024\noproof\sbasedon10 pod-C;}
417             {\*\cs30 \additive \ul \sbasedon10 pod-U;}
418             {\s20\ql \li0\ri0\sa180\widctlpar\f1\fs%s\lang1024\noproof\sbasedon0 \snext0 pod-codeblock;}
419             {\*\cs21 \additive \lang1024\noproof \sbasedon10 pod-computerese;}
420             {\*\cs22 \additive \i\lang1024\noproof\sbasedon10 pod-L-pod;}
421             {\*\cs23 \additive \i\lang1024\noproof\sbasedon10 pod-L-url;}
422             {\*\cs24 \additive \i\lang1024\noproof\sbasedon10 pod-L-man;}
423              
424             {\*\cs25 \additive \f1\lang1024\noproof\sbasedon0 pod-codelbock-plain;}
425             {\*\cs26 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-ital;}
426             {\*\cs27 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-bold;}
427             {\*\cs28 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-bold-ital;}
428              
429             {\s31\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head1;}
430             {\s32\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head2;}
431             {\s33\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head3;}
432             {\s34\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head4;}
433             }
434              
435             END
436              
437             $_[0]->codeblock_halfpoint_size(),
438             $_[0]->head1_halfpoint_size(),
439             $_[0]->head2_halfpoint_size(),
440             $_[0]->head3_halfpoint_size(),
441             $_[0]->head4_halfpoint_size(),
442             ;
443             }
444              
445             ###########################################################################
446             # Override these as necessary for further customization
447              
448             sub font_table {
449 3     3 0 8 return <<'END'; # text font, code font, heading font
450             {\fonttbl
451             {\f0\froman Times New Roman;}
452             {\f1\fmodern Courier New;}
453             {\f2\fswiss Arial;}
454             }
455              
456             END
457             }
458              
459             sub doc_init {
460 3     3 0 10 return <<'END';
461             {\rtf1\ansi\deff0
462              
463             END
464             }
465              
466             sub color_table {
467 3     3 0 9 return <<'END';
468             {\colortbl;\red255\green0\blue0;\red0\green0\blue255;}
469             END
470             }
471              
472              
473             sub doc_info {
474 3     3 0 6 my $self = $_[0];
475              
476 3   33     27 my $class = ref($self) || $self;
477              
478 3         6 my $tag = __PACKAGE__ . ' ' . $VERSION;
479              
480 3 50       9 unless($class eq __PACKAGE__) {
481 0         0 $tag = " ($tag)";
482 0 0       0 $tag = " v" . $self->VERSION . $tag if defined $self->VERSION;
483 0         0 $tag = $class . $tag;
484             }
485              
486             return sprintf <<'END',
487             {\info{\doccomm
488             %s
489             using %s v%s
490             under Perl v%s at %s GMT}
491             {\author [see doc]}{\company [see doc]}{\operator [see doc]}
492             }
493              
494             END
495              
496             # None of the following things should need escaping, I dare say!
497             $tag,
498             $ISA[0], $ISA[0]->VERSION(),
499 3   33     82 $], scalar(gmtime($ENV{SOURCE_DATE_EPOCH} || time)),
500             ;
501             }
502              
503             sub doc_start {
504 3     3 0 6 my $self = $_[0];
505 3         13 my $title = $self->get_short_title();
506 3         5 DEBUG and print STDERR "Short Title: <$title>\n";
507 3 50       11 $title .= ' ' if length $title;
508              
509 3         25 $title =~ s/ *$/ /s;
510 3         8 $title =~ s/^ //s;
511 3         13 $title =~ s/ $/, /s;
512             # make sure it ends in a comma and a space, unless it's 0-length
513              
514 3         4 my $is_obviously_module_name;
515 3 50 33     14 $is_obviously_module_name = 1
516             if $title =~ m/^\S+$/s and $title =~ m/::/s;
517             # catches the most common case, at least
518              
519 3         5 DEBUG and print STDERR "Title0: <$title>\n";
520 3         8 $title = rtf_esc(1, $title); # 1 => escape hyphen
521 3         5 DEBUG and print STDERR "Title1: <$title>\n";
522 3 50       10 $title = '\lang1024\noproof ' . $title
523             if $is_obviously_module_name;
524              
525 3         11 return sprintf <<'END',
526             \deflang%s\plain\lang%s\widowctrl
527             {\header\pard\qr\plain\f2\fs%s
528             %s
529             p.\chpgn\par}
530             \fs%s
531              
532             END
533             ($self->doc_lang) x 2,
534             $self->header_halfpoint_size,
535             $title,
536             $self->normal_halfpoint_size,
537             ;
538             }
539              
540             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
541             #-------------------------------------------------------------------------
542              
543 3     3   25 use integer;
  3         5  
  3         20  
544              
545             my $question_mark_code_points =
546             Pod::Simple::BlackBox::my_qr('([^\x00-\x{D7FF}\x{E000}-\x{10FFFF}])',
547             "\x{110000}");
548             my $plane0 =
549             Pod::Simple::BlackBox::my_qr('([\x{100}-\x{FFFF}])', "\x{100}");
550             my $other_unicode =
551             Pod::Simple::BlackBox::my_qr('([\x{10000}-\x{10FFFF}])', "\x{10000}");
552              
553             sub esc_uni($) {
554 3     3   333 use if do { no integer; "$]" <= 5.006002 }, 'utf8';
  3     3   4  
  3         12  
  3         149  
  3         5  
  3         5  
  3         1359  
555              
556 22     22 0 43 my $x = shift;
557              
558             # The output is expected to be UTF-16. Surrogates and above-Unicode get
559             # mapped to '?'
560 22 50       89 $x =~ s/$question_mark_code_points/?/g if $question_mark_code_points;
561              
562             # Non-surrogate Plane 0 characters get mapped to their code points. But
563             # the standard calls for a 16bit SIGNED value.
564 22 100       115 $x =~ s/$plane0/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg
  69 50       154  
565             if $plane0;
566              
567             # Use surrogate pairs for the rest
568 22 50       78 $x =~ s/$other_unicode/'\\uc1\\u' . ((ord($1) >> 10) + 0xD7C0 - 65536) . '\\u' . (((ord$1) & 0x03FF) + 0xDC00 - 65536) . '?'/eg if $other_unicode;
  2         19  
569              
570 22         48 return $x;
571             }
572              
573             sub rtf_esc ($$) {
574             # The parameter is true if we should escape hyphens
575 22 100   22 0 44 my $escape_re = ((shift) ? $escaped : $escaped_sans_hyphen);
576              
577             # When false, it doesn't change "-" to hard-hyphen.
578             # We don't want to change the "-" to hard-hyphen, because we want to
579             # be able to paste this into a file and run it without there being
580             # dire screaming about the mysterious hard-hyphen character (which
581             # looks just like a normal dash character).
582             # XXX The comments used to claim that when false it didn't apply computerese
583             # style-smarts, but khw didn't see this actually
584              
585 22         21 my $x; # scratch
586 22 100       43 if(!defined wantarray) { # void context: alter in-place!
    50          
587 19         28 for(@_) {
588 19         336 s/($escape_re)/$Escape{$1}/g; # ESCAPER
589 19         36 $_ = esc_uni($_);
590             }
591 19         25 return;
592             } elsif(wantarray) { # return an array
593 0         0 return map {; ($x = $_) =~
  0         0  
594             s/($escape_re)/$Escape{$1}/g; # ESCAPER
595 0         0 $x = esc_uni($x);
596 0         0 $x;
597             } @_;
598             } else { # return a single scalar
599 3 50       122 ($x = ((@_ == 1) ? $_[0] : join '', @_)
600             ) =~ s/($escape_re)/$Escape{$1}/g; # ESCAPER
601             # Escape \, {, }, -, control chars, and 7f-ff.
602 3         13 $x = esc_uni($x);
603 3         7 return $x;
604             }
605             }
606              
607             1;
608              
609             __END__