RTF-Writer-1.11/0040711000177300017730000000000007751705660012734 5ustar sburkesburkeRTF-Writer-1.11/t/0040711000177300017730000000000007751705660013177 5ustar sburkesburkeRTF-Writer-1.11/t/image2.t0100644000177300017730000000267207743132551014536 0ustar sburkesburke require 5; # Time-stamp: "2003-10-14 18:48:09 ADT" use strict; use Test; BEGIN { plan tests => 10 } use RTF::Writer 1.10; ok 1; sub isbal ($) { (my $x = $_[0]) =~ tr/\{\}//cd; while($x =~ s/\{\}//g){;}; length($x) ? 0 : 1 } foreach my $i (qw(png jpg)) { my $filename = "hypnocat2_$i.rtf"; use File::Spec; $filename = File::Spec::->catfile( File::Spec::->curdir(), $filename); print "# Writing to $filename...\n"; my $rtf = RTF::Writer->new_to_file($filename); $rtf->prolog( 'title' => "Greetings, $i hyoomon" ); $rtf->number_pages; $rtf->paragraph( \'\fs40\b\i', # 20pt, bold, italic "Hi there!" ); my $imagepath; ok( -e( $imagepath = File::Spec::->catfile( File::Spec::->curdir(), 't', "hypnocat.$i") ) or -e( $imagepath = File::Spec::->catfile( File::Spec::->curdir(), "hypnocat.$i") ) or 0 ); $rtf->paragraph( "Blah blah blah ", $rtf->image('filename' => $imagepath), " blah blah -- it's a $i!", ); $rtf->paragraph("Here's a subsequent paragraph."); $rtf->close; ok 1; my $errorcount; undef $rtf; { print "# Now checking $filename...\n"; open IN, $filename or die "Can't read-open $filename: $!"; local $/; my $rtf = ; close(IN); ok $rtf, '/\\\\pict/' # simple sanity or ++$errorcount; ok isbal($rtf), 1, "$filename 's is unbalanced" or ++$errorcount; } $errorcount or unlink $filename; } print "# Byebye\n"; ok 1; RTF-Writer-1.11/t/align_test1.t0100644000177300017730000000342007743125454015600 0ustar sburkesburke require 5; # Time-stamp: "2003-10-14 18:04:28 ADT" use strict; use Test; BEGIN { plan tests => 14 } use RTF::Writer; ok 1; sub isbal ($) { (my $x = $_[0]) =~ tr/\{\}//cd; while($x =~ s/\{\}//g){;}; length($x) ? 0 : 1 } my $filename = "aligny1.rtf"; use File::Spec; $filename = File::Spec::->catfile( File::Spec::->curdir(), $filename); my $doc = RTF::Writer->new_to_file($filename); $doc->prolog; my $t = RTF::Writer::TableRowDecl->new( borders => 'none', align => ['ne', 'w', '', 's' ], ); my @them = ([\'\fs50', "It's like this"], 'aa', 'ba', 'ca'); my $x = 'This module is for generating documents in Rich Text Format.'; foreach my $align ( [qw], [qw], [qw] ) { $doc->row(RTF::Writer::TableRowDecl->new('align' => $align) => @them); for(@them) { ref($_) or ++$_ } } $doc->close; undef $doc; print "# Now checking $filename...\n"; open IN, $filename or die "Can't read-open $filename: $!"; local $/; my $rtf = ; close(IN); ok 1; my $errorcount; ok scalar( grep 1, $rtf =~ m/(\\qc)\b/g), 3 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\qr)\b/g), 6 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\ql)\b/g), 3 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clvertalc)\b/g), 6 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clvertalt)\b/g), 3 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clvertalb)\b/g), 3 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clbrdrl)\b/g), 12 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clbrdrr)\b/g), 12 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clbrdrt)\b/g), 12 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\clbrdrb)\b/g), 12 or ++$errorcount; ok isbal($rtf), 1, "Unbalanced: $rtf"; $errorcount or unlink $filename; print "# Bye\n"; ok 1; RTF-Writer-1.11/t/00about.t0100644000177300017730000000422507743107366014646 0ustar sburkesburke require 5; # Time-stamp: "2003-10-14 16:04:06 ADT" # Summary of, well, things. use Test; BEGIN {plan tests => 2}; ok 1; #chdir "t" if -e "t"; use File::Spec; use RTF::Writer; { my @out; push @out, "\n\nPerl v", defined($^V) ? sprintf('%vd', $^V) : $], " under $^O ", (defined(&Win32::BuildNumber) and defined &Win32::BuildNumber()) ? ("(Win32::BuildNumber ", &Win32::BuildNumber(), ")") : (), (defined $MacPerl::Version) ? ("(MacPerl version $MacPerl::Version)") : (), "\n" ; # Ugly code to walk the symbol tables: my %v; my @stack = (''); # start out in %:: my $this; my $count = 0; my $pref; while(@stack) { $this = shift @stack; die "Too many packages?" if ++$count > 1000; next if exists $v{$this}; next if $this eq 'main'; # %main:: is %:: #print "Peeking at $this => ${$this . '::VERSION'}\n"; if(defined ${$this . '::VERSION'} ) { $v{$this} = ${$this . '::VERSION'} } elsif( defined *{$this . '::ISA'} or defined &{$this . '::import'} or ($this ne '' and grep defined *{$_}{'CODE'}, values %{$this . "::"}) # If it has an ISA, an import, or any subs... ) { # It's a class/module with no version. $v{$this} = undef; } else { # It's probably an unpopulated package. ## $v{$this} = '...'; } $pref = length($this) ? "$this\::" : ''; push @stack, map m/^(.+)::$/ ? "$pref$1" : (), keys %{$this . '::'}; #print "Stack: @stack\n"; } push @out, " Modules in memory:\n"; delete @v{'', '[none]'}; foreach my $p (sort {lc($a) cmp lc($b)} keys %v) { $indent = ' ' x (2 + ($p =~ tr/:/:/)); push @out, ' ', $indent, $p, defined($v{$p}) ? " v$v{$p};\n" : ";\n"; } push @out, sprintf "[at %s (local) / %s (GMT)]\n", scalar(gmtime), scalar(localtime); my $x = join '', @out; $x =~ s/^/#/mg; print $x; } print "# Running", (chr(65) eq 'A') ? " in an ASCII world.\n" : " in a non-ASCII world.\n", "#\n", ; print "# \@INC:\n", map("# [$_]\n", @INC), "#\n#\n"; print "# \%INC:\n"; foreach my $x (sort {lc($a) cmp lc($b)} keys %INC) { print "# [$x] = [", $INC{$x} || '', "]\n"; } ok 1; RTF-Writer-1.11/t/hypnocat.png0100644000177300017730000002753407727331740015547 0ustar sburkesburkePNG  IHDR\|\:4ll\Ctldd|tm,,%<=ļ$$tld{nDDDTI3̸lUD>,t\LLTL<,ĴlLDldV|u,,ܤL\VLD=<<6w\d[̄||Ĵƌ<4$\\D{\lm|d<4l$t\Dwllg||s,4*V9"f'$ɉM ۴qoS"iKb[猪SKouź~H2s.m`kd|ΘVteU]h\`VBGȋ9.S4/eUM3;@2ombF LJVYlERòSLAH?`|f)3F$# <3GfAGgqћfյB%ʻ&?є5~XO^DJtR̀3>{Z'ѹ?mEnA>ɋFE~%zg/@M"ƽAϠVy^\fEYrc\gVBBcyBM$(./0>+ `#""Vk\9zO[Uo19m_8ڱ5d dJq=v2/1X:ufQ]yAF|>g0w"B 4d3!M^.I$B 6|%8.b7B^ VE!G#_Ip=~RU^EGg!H|ɨtHc|F8e|xy T`1qRRדXt%;4Y J@nF=/ N|֋Z9s mCqlf\$VZObm|JȼBydOO ;3L[4*J" x4&s$Q30 l rr];xEtixڀ- iguijS7[lBQ)h#%)De^r17tƪ69[.jCB)v ›ZmMqb͢Pm; BCG@.@G+Snt8Xz(AO *%>YCO3;O5:teH骔8%jQ#`)[F[&H{wy>,7 (6BbkOn>m=r%@GuDic4KF-`A೤HFsIqJ`dדqݡQUMhGw+}A)&״(prhG.G##)&x*#1g(Cmt+-`Mh8N:ڢKf^v+r}9n;+|0JTcc}[ЍTP댽;X`:g갼AKY7e+djiif/T7YBmn[klnؗѨ^82~ [L 1 q{Q@\x bb}y3B*jboM!䓩2!@+Vnn12ﱘQ>&ލzy~Z-PPAbwӛbMaWxzF:nf=A.'_B#wSͲCsE_w8Cĵ Q2kJ5"2BՏDK|dK3DB4 7E~?j Hfjݮ鰷ԼxbEJ BdsLs`Cݔ݊M 0QYyM^! x1@mf|s/5A]XP1p\T&kn͖%dN{ooY0JP&J O:|76D^6r7 q? 3ÇNs{g`Зu45X<$3vJJΎj5mBJM OfuMRX'kZ_̀Gd{BG jȯ~gtŬ.{w6RBsFy\g4imGnqj1?;TH|ZR&}f&ClI 7 5(%;Fy@xΝG6t͗\k8q5S}'$Wbx%y'-})Jic+:CGX? _JR?=z[yZv$Rz:XnD h1gpQ[4ivj@rZ@qF-mRCn+.3%%ya哴'[WR^aKUڭ[oG EedΧ׵ r&*\N%̺Pk(r6X`ߏŀ7֠dzV[~`nܩH !OYddO3{dY?᥅]"79#79fTY|Ŵ~>ĢZ@x54"XS@ONb2 "u$ ie,z>4wHAzXGb󦰞lu*#Uz]Ʒ\ͤ>J,/= }7?.[F-Ɨ,8Q%ORqy~##AG>S>Y W\/,ر?h5z\ͤOo&5tt;nތ0ap!)e$Cj[&GF`s3">iXO|6JY`Ym c:? Bߞf~ >@V$B? 0868,`@+Tۛv.'j KHKF42<؄ܛu/I~)C흹'1{z<χlMiyK$՚UV*kDnZHHX"xH)ӯJ/D1QMyy+UKH ^ro| ZaZ(~sA Qt^\b,U5ɚͱh(VZzx8CSXj/\)_rӎK\ . ;`b6oZuIJD'eP?g O+搟}ao AJb4M HrctwtnTf#Mn<_7E|+ѬMeMեot\#ejU{%ecxw`3sXL5~e.[+2V {7肺D~ijVZ헩)=K!b& Q=.`Zmh~ϡTM5 r(1 rtbahQKwN| [VAX@Y I~[ n&tA^m/ Q-`BA3:~SܯdCh\faf)m|Md޽{gN{P^ |5DY}29*f'۽:'Ue[GqgJZQdЉEsy{f%ЋZ ހ%,0XS6j QQqcQ ~D{{9*@ea}:agkm8QBj|;Ag+@}nWsџ`>+CgYjԤ&Ntu2UOl5n88q>a(gr6nX3 /.wmWVG./zټ9#}-:j { /tvh~5)!M$"P~‘3[ Μ&Nk7,ݻ2nC̆>sdoi?Y;o:zhua_ UKCLb3Oa LKO4tDޚ:?rHZ3GfvbBDK@ T#7On3q1Kc ~A]AWخuquvm3){ ڛJZhu©g!2% *_*n?x:^ ˲Z~!A z%nR 0[}8jGGztœ˨v9&.2|>RIVY.;N]A vmEg| u+ǏF\Fs%i(!淟C^n:qw6q4}ƀǃ;7995k8ڮ3 6 Dl 2"v_%AK*Bh!LB!2O!Kh.1.M.ؗKu{/Ǒ\ʹoy[Wٿݏ\uzsvIþQߋjEEZa ;l=. .L8XҰ[:+oAV`ZˀS$fE3Ƕo߽ؖ[ߺ֞=}SݜھPd_Fj]=`4/1.:x`[ MG>r?RUL ZX.<~'%2lDK?2iBEcږ{uQƖ9Po= }ta8$r!f擿OץGnz7_p`Sk,;fH%>7p%Kzc_I^<|aζ9]9w?=w?{bp6,Zڰs~鱥x'﫧Ir=oqJ_B Eq9"u( eSF=ȢcFehhˤ-A?y:.+g۶7^?3ϻѕvgRmcgBKcRNbciiNȝǩ~Rz>1uO@.4uE:N.(KKME6gIPGsI}EWkUDYpQζu~].ծ[W[77OUcTewP6T|Slpu)Voqia ϡ} -Co ʞI=-g(kZm'> ЗQq9rN?0|Rf~Ox]rj.CCq">ebfs J'}P*?gS\3l\2 @fnE˲QIEirָ3zˏ~Xp>nl~_yĶg7#r2*'ml2"ZG~ߧwnV-hzfHPS1 %xSكe8o4)]J uΩ궝\[ BN\yɮg>s[NתUKQ{[aO/GecfIr DjVDs촌b5]S`8x& emJ%oT%6c@l IHx P5#-)+}Ovz[<˨QQ@r8]Z߾:l(XyGBnS\.%KрKv~H^>=;] U&S<>fcK)=Ԫú]ٳk~FoP8\gd)gjodk Ўzer ꔕೱQbI.}9GMtj_]#pRt6IVsE E%7 6jrQ(!v1ʐ]x 5M@*jŋŋ 0L Cm7H$JRTC.+]YWpz=7n,@e8S+&HEwnX0gݮg]|0bR]iq\l 5Y@%rSֽt{TQ_PµkBP ^fbr*+Z*ή voΝh]v;z42^UuYcI&& x(һ+ST$j_)gС< ss0FÉ5uńO0u6s8Cɼe3@ k/{zyD݇cqK=;Ɩ6B9wq 2XZ_ۛ~??blW>-ԙ6hq>0e^;RY_=h7;ϗͪZe`ɢs}q:do:~(qCJd熯οW`Gwp$k ҵFR ܶ`WG:!t% )n/0ư4'mIW:諨 9L-%3/V_ȿƗ!owUWW&M(ky~y0'$ }Ŧ .5G+Sa/?MM=% OO±NMMQI\?l=\_#=Wa+:R=y򍏐 .Upz6fӤ^ [x9#՗J SE>_pUI8a68(S +WЀTg}v+mG?7pQb*GZ.uYc{9y{2^LTq}!'1@TѺӰnaG5&uVt%)BR a nO}uY[pFĿꚚunl.& )Y,Dj?3*Ȫ>RS9 ^.xu2cޛA*Wdi.().+1bE Ed|hjDX?ϳ]Vg@ PUP5&px6=e˖MG Ϯg?gGtzKuu#՟UOVׁX<;w.kӦ,̬Lz!YiJ:43I/\Sj&2+5s*kQv}kk55[k Mur\s{ms՛sW[ʫ7s||\6]sG'ρ#55·(@j]=h/,l (2"|T\c.km>2y~=.[堑~M EFtn^l,)8IPQ'q$ |lr2SVu糧 s2+2m cp];WCЎL2H׮uWrF.jk ?~,6-0S‡Ç?Os9<s \?+x;ÇBцӡ oqtH -~Oٞ *7Vo )Kvxs#w#u*1o`|ff;@(?Go!>}^?o݄38,+ڈS% ;1IENDB`RTF-Writer-1.11/t/image1.t0100644000177300017730000000265707743132540014536 0ustar sburkesburke require 5; # Time-stamp: "2003-10-14 18:48:00 ADT" use strict; use Test; BEGIN { plan tests => 10 } use RTF::Writer 1.10; ok 1; sub isbal ($) { (my $x = $_[0]) =~ tr/\{\}//cd; while($x =~ s/\{\}//g){;}; length($x) ? 0 : 1 } foreach my $i (qw(png jpg)) { my $errorcount; my $filename = "hypnocat1_$i.rtf"; use File::Spec; $filename = File::Spec::->catfile( File::Spec::->curdir(), $filename); my $rtf = RTF::Writer->new_to_file($filename); $rtf->prolog( 'title' => "Greetings, $i hyoomon" ); $rtf->number_pages; $rtf->paragraph( \'\fs40\b\i', # 20pt, bold, italic "Hi there!" ); my $imagepath; ok( -e( $imagepath = File::Spec::->catfile( File::Spec::->curdir(), 't', "hypnocat.$i") ) or -e( $imagepath = File::Spec::->catfile( File::Spec::->curdir(), "hypnocat.$i") ) or 0 ); $rtf->image_paragraph('filename' => $imagepath, scaley => 200, scalex => 200, wgoal => 1300, hgoal => 700, cropl => 200, ); $rtf->paragraph("Here's a subsequent paragraph."); $rtf->close; ok 1; undef $rtf; { print "# Now checking $filename...\n"; open IN, $filename or die "Can't read-open $filename: $!"; local $/; my $rtf = ; close(IN); ok $rtf, '/\\\\pict/' or ++$errorcount; # simple sanity ok isbal($rtf), 1, "$filename 's RTF is unbalanced" or ++$errorcount; } $errorcount or unlink $filename; } print "# Byebye\n"; ok 1; RTF-Writer-1.11/t/hypnocat.jpg0100644000177300017730000000642007727331762015536 0ustar sburkesburkeJFIFC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((\"<!1AQaq"B#%2R345CSr!1AQ ? $tVæ=[7ۻ`1j:b뽢k[([gfdwFHE95 ZDZGJRŒəzvU-Fjt5Y\'BcVm_qѫ>ʬ_T"3LP4JJ x^r~ d'X 5#JugFbB[{ڑ\~8+kóNf&AW>?tb;]D☛8[db;Igr)u ]M"ݗ6mReX:m΁,2BrF|~ 3XǶSrPHq}!<5#_e( w-dTx4XWrL +oLFT񬦉anR&!%+=;&SknY #>93[qɤ^0H@Zc=fIhP?6*y$#V6Uetu$ #"{jۙ tO^CӀs﮳#Vnl@3]A'KПYcAW?fdY"IXqûbc*9+S]FJp+fV,Xlyr-]IYq[cAխ9 #^]m!{Ku`~hj[;$c4-/,hWKSk}`OOh^ .3Sɣk{htHK>[SQ]2ܰ&:Rk)V$FI9&iW;b,nFhȏ#YVKI80}պ8$u5*'LnV01)@Uτ35b2q5f1e'; {^vc \Af;ǀQ W1IO6jVN Ӵ>h٩!!jZJD"ijfXS ~u㗏Sn5؋$w,qRD7jKrR\Pʡ2u.T?NBRGxB9Gp=-`ϺHnM $}9q"6M<$&GZ/kzd)@Ll,˷+mԫ'g(h?/%I*nT* 1lyfi $*u{:ҙhWijr崨D]Z[mFGL~o d,s_L\PI)䚛[/致v/6 );tZݒTR =쐰D>4oQJHH=k*Mڈ$Bu.8Ga 'kѭ{ O^evgiSKKh`Tn`'<~ ;N7 %K26doNS- Fy6TS_wmn0c9Cn-H=0="Toqfyɘ4ݫJZNxi)QP8ay5ǘZC;d@;ұ#)61i* ?:*ش.S;gP[RXZmaPBXGI۶zc VB6< Seo`mLztkMAU; 1P4*3G\1e2te3!fYOERTF-Writer-1.11/t/border_test.t0100644000177300017730000000210207743124051015666 0ustar sburkesburke require 5; # Time-stamp: "2003-10-14 17:51:37 ADT" use strict; use Test; BEGIN { plan tests => 8 } sub isbal ($) { (my $x = $_[0]) =~ tr/\{\}//cd; while($x =~ s/\{\}//g){;}; length($x) ? 0 : 1 } use RTF::Writer; ok 1; my $f = "bordery.rtf"; use File::Spec; $f = File::Spec::->catfile( File::Spec::->curdir(), $f); my $doc = RTF::Writer->new_to_file($f); $doc->prolog; my $t = RTF::Writer::TableRowDecl->new( borders => 't-20 l-33-wavy', #['n s e', 'n-wavy s-wavy'], ); my $x = 'This module is for generating documents in Rich Text Format.'; $doc->row($t, $x, $x, "$x $x", $x); $doc->row($t, $x, $x, $x, $x); $doc->close; undef $doc; my $errorcount = 0; ok 1; { my $rtf; open IN, $f or die $!; local $/; $rtf = ; close(IN); ok $rtf, '/\\\\brdrs\\b/' or ++$errorcount; ok $rtf, '/\\\\brdrwavy\\b/' or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\brdrs)\b/g), 8 or ++$errorcount; ok scalar( grep 1, $rtf =~ m/(\\brdrwavy)\b/g), 8 or ++$errorcount;; ok isbal($rtf), 1, "Unbalanced: $rtf"; } $errorcount or unlink $f; print "# Bye...\n"; ok 1; RTF-Writer-1.11/t/rtf_test.t0100644000177300017730000000237207743124535015224 0ustar sburkesburke require 5.005; # we need m/...\z/ # Time-stamp: "2003-10-14 17:56:45 ADT" # Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) use Test; BEGIN { plan tests => 11 } END {print "not ok 1\n" unless $loaded;} use RTF::Writer ('rtfesc'); sub isbal ($) { (my $x = $_[0]) =~ tr/\{\}//cd; while($x =~ s/\{\}//g){;}; length($x) ? 0 : 1 } $loaded = 1; ok 1; print "# RTF::Writer version: $RTF::Writer::VERSION\n", "# Perl version: $] on OS \"$^O\"\n"; # First let's make sure out isbal works ok isbal('{aoeaoe}aoe'); ok isbal('aoeaoe'); ok isbal('a{o{e}aoe}'); ok isbal('a{o{e{}a}{oe}}'); ok isbal('a{o{e}aoe}'); ok!isbal('a}oe{aoe'); ok!isbal('a}oe{ao}e'); # Pretty elementary test. my $x = rtfesc("foo{\\\n"); ok $x, "foo\\'7b\\'5c\n\\line "; $x = ''; my $r = RTF::Writer->new_to_string(\$x); $r->print("foo{\\\n"); $r->close; ok $x, "foo\\'7b\\'5c\n\\line "; ok isbal($x), 1, "Unbalanced: $x"; ########################################################################### $| = 1; RTF-Writer-1.11/MANIFEST0100644000177300017730000000056607751705647014106 0ustar sburkesburkeChangeLog examples/demo_writer.pl examples/la_dame.rtf lib/RTF/Cookbook.pod lib/RTF/Writer.pm lib/RTF/Writer.pod lib/RTF/Writer/TableRowDecl.pm Makefile.PL MANIFEST MANIFEST.SKIP README t/00about.t t/align_test1.t t/border_test.t t/hypnocat.jpg t/hypnocat.png t/image1.t t/image2.t t/rtf_test.t META.yml Module meta-data (added by MakeMaker) RTF-Writer-1.11/lib/0040711000177300017730000000000007751705660013502 5ustar sburkesburkeRTF-Writer-1.11/lib/RTF/0040711000177300017730000000000007751705660014135 5ustar sburkesburkeRTF-Writer-1.11/lib/RTF/Cookbook.pod0100644000177300017730000006066107734225534016423 0ustar sburkesburke=head1 NAME The_RTF_Cookbook -- RTF overview and quick reference =head1 SYNOPSIS # Time-stamp: "2003-09-23 21:27:56 ADT" # This document is in Perl POD format, but you can read it # with just an ASCII text viewer, if you want. =head1 DESCRIPTION RTF is a nearly ubiquitous text formatting language devised by Microsoft. Microsoft's I is widely available, but it's usable mainly just as a reference for the language's entire command set. This short document, however, is meant as a quick reference and overview. It is meant for people interested in writing programs that generate a minimal subset of RTF. B> : I've mostly superceded this document with my book I, which is much longer and more comprehensive -- see L =head1 INTRODUCTION RTF code consists of plaintext, commands, escapes, and groups: Plaintext contains seven bit (US ASCII) characters except for \, {, and }. Returns and linefeeds can be present but are ignored, and are harmless (as long as they are not in the middle of an RTF command). Space (ASCII 0x20) characters I significant -- five spaces means five spaces. (The only exception is a space that ends an RTF command; such a space is ignored.) Example of plaintext: "I like pie". An RTF command consists of a backslash, then some characters a-z, and then an optional positive or negative integer argument. The command is then terminated either by a space, return, or linefeed (all of which are ignored), or by some character (like brace or backslash, etc.) that couldn't possibly be a continuation of this command. A simple rule of thumb for emitting RTF is that every command should be immediately followed either by a space, return, or linefeed, (as in "\foo bar"), or by another command (as in "\foo\bar"). Examples of RTF commands: "\page" (command "page" with no parameter), "\f3" (command "f" with parameter 3), "\li-320" (command "li" with parameter -320). An RTF escape consists of a backslash followed by something other than a letter. There are few of these in the language, and the basic ones to know now are the two-byte long escape sequences: \{, \}, \\ (to convey a literal openbrace, closebrace, or backslash), and the only four-byte-long escape sequence, \'xx, where xx is two hexadecimal digits. This is used for expressing any byte value in a document. For example, \x'BB expresses character value BB (decimal 187), which for Latin-1/ANSI is close-angle-quote (which looks like a little ">>"). An RTF group consists of an openbrace "{", any amount of RTF code (possibly including other groups), and then a closebrace "}". Roughly speaking, you can treat an RTF group as the conceptual equivalent of an SGML element. Effectively, a group limits the scope of commands in that group. So if you're in a group and you turn on italics, then that can apply only as far as the end of the group -- regardless of whether you do this at the start of the group, as in {\i I like pie}, or the middle, as in {I like \i pie}. Note that you must emit just as many openbraces as closebraces -- otherwise your document is syntactally invalid, and RTF readers will not tolerate that. This is an example of a paragraph using plaintext, escapes, commands, and groups: {\pard\fs32\b NOTES\par} {\pard\fs26 Recently I skimmed {\i Structure and Interpretation of Computer Programs}, by Sussman and Abelson, and I think there should have been more pictures. \line I like pictures. Is that so na\'efve? \par} (\'ef makes an i-dieresis, in the Latin-1/ANSI character set.) Note that "foo[newline]bar" isn't the same as "foo bar", it's the same as "foobar", because the newline is I. So if you mean "foo bar", and want to work in a newline, you should consider "foo[newline] bar", or "foo [newline]bar", or even things like "fo[newline]o bar". Note that newlines aren't needed in your output file at all, and there's no reason to get your RTF code to be wrapped at 72 columns or anywhere else; but it's very useful to be able to open a RTF file in a plaintext editor and see something other than a giant sea of unbroken text. So at the very least, I emit a newline before every paragraph. (Note that if you I ambitiously trying to wrap your RTF code by inserting newlines, consider that just about the only really I places to insert a newline are in the middle of a command or an escape -- because "\pa[newline]ge" doesn't mean the same as "\page", it means the same as "\pa ge" (i.e., a \pa command, and then two text characters "ge"); and "\'f[newline]8" is not good RTF. So I suggest making wrapping algorithms insert a newline only after a space character -- a guaranteed safe spot.) =head1 Twips Measurements in RTF are generally in twips. A twip is a twentieth of a point, i.e., a 1440th of an inch. That leads to some large numbers sometimes (like \li2160, to set the left indent to an inch and a half), but this table should be useful for conversions: inches twips points centimeters ------ ------- ------ ----------- 20 tw 1 pt 40 tw 2 pt ~57 tw .1cm 60 tw 3 pt 80 tw 4 pt 1/16" 90 tw 4.5 pts 100 tw 5 pts ~113 tw .2cm 1/9" 160 tw 8 pts ~170 tw .3cm 1/8" 180 tw 9 pts 1/7" ~206 tw ~10.3 pts ~227 tw .4cm 1/6" 240 tw 12 pts 3/16" 270 tw 13.5 pts ~283 tw .5cm 1/5" 288 tw 14.4 pts ~340 tw .6cm 1/4" 360 tw 18 pts ~397 tw .7cm ~454 tw .8cm 1/3" 480 tw 24 pts ~510 tw .9cm ~567 tw 1cm 1/2" 720 tw 36 pts ~850 tw 1.5cm 3/4 1080 tw 54 pts ~1134 tw 2cm 1" 1440 tw 72 pts ~1701 tw 3cm 1.5" 2160 tw 108 pts ~2268 tw 4cm ~2835 tw 5cm 2" 2880 tw 144 pts ~3402 tw 6cm 2.5" 3600 tw 180 pts 3" 4320 tw 216 pts ~5669 tw 10cm 4" 5760 tw 288 pts 5" 7200 tw 360 pts (Conversions between centimeters and anything else are approximate, so figures with a preceding "~" have been rounded.) =head1 RTF Document Structure An RTF document consists of: =over =item * a prolog, which starts with a "{", and then has essential information for the document =item * then some optional document-formatting commands =item * then any amount of commands, groups, plaintext, and escapes =item * then a closing "}" (which closes the group opened by the start of the prolog). =back =head2 RTF Prolog A minimal RTF prolog looks like this: {\rtf1\ansi\deff0{\fonttbl{\f0 Times New Roman;}} This declares this file as being in RTF version 1 (the only version there is at time of writing), that the charset in use in ANSI (Latin-1), the default font is the 0th entry in the font table (to follow). And then this declares a font table consisting only of one entry, number 0, for Times New Roman. A font table with three fonts might look like: {\rtf1\ansi\deff0{\fonttbl {\f0 Georgia;} {\f1 Braille Kiama;} {\f2 Courier New;} } Note that each font-name has to be followed by a semicolon. Recall that the newlines here ignored, and so are useful here only for readability. But unlike most computer languages, you can't indent this (or any other part of an RTF document) with space characters or tabs -- those would I be ignored! A generally optional but sometimes necessary part of the prolog is a color table. This is a color table: {\colortbl;\red255\green0\blue0;\red0\green0\blue255;} That declares three colors: a 0th entry, with null (default) color (expressed by the zero-length string between "\colortbl" and ";"), then a 1st entry, which is 100% red, and then a 2nd entry, which is 100% blue. (Note that each entry, whether null or \redN\greenN\blueN, must be followed by a semicolon.) A color table is necessary only if you need to change text foreground or background colors -- in that case, you need to refer to entries in the color table. For more information, see the RTF spec. If you aren't changing text color in the document, you needn't bother with a color table. (Some graphics options might involve the color table, but graphics is not discussed in this document.) =head2 RTF Document Formatting Commands Then following the prolog, there are document-formatting codes -- i.e., codes that apply to the document as a whole. This section is often left empty, but a commonly useful string is: \deflang1033\widowctrl That declares that the default language in the document is US English (for purposes of spellchecking and possibly hyphenation), and also turns on widow-and-orphan control. A useful bit of code that's worth emitting right after the document-formatting commands, is this: {\header\pard\qr\plain\f0\chpgn\par} That turns on page numbering. Strictly speaking, it is not a document-formatting command, but a section-formatting command. However, as "section" is not a concept addressed in this document, you can just think of that code as just something to be emitted before you start your document content, and after any real document-formatting commands. =head2 RTF Document Content Document content is basically text (plaintext, commands, and escapes) in paragraphs (in the broad sense of "paragraph", including things like headers). Although it's not necessary to put braces ("{...}") around each paragraph, it is often useful to do so, to keep character formatting codes from bleeding into the next paragraph. Taking that approach, a basic paragraph looks like this: {\pard ...stuff... \par} Here, \pard signals that this paragraph shouldn't inherit paragraph-formatting attributes from the previous paragraph. (You're free to try experimenting with leaving out the \pard in order to inherit attributes, but I advise against it, as I find it leads to abstruse RTF and hard-to-find bugs.) And \par signals the end of this paragraph. Paragraph-formatting attributes include justification (like \qj for full justification), \liN, \riN, and \fiN for (respectively) paragraph indenting on the left, paragraph indenting on the right, and left indenting for just the first line. For example: {\pard \li1440\ri1440\fi480\qj A resource can be anything that has identity. Familiar examples include an electronic document, an image, a service (e.g., "today's weather report for Los Angeles"), and a collection of other resources. Not all resources are network "retrievable"; e.g., human beings, corporations, and bound books in a library can also be considered resources. \par} This sets left and right margins of 1 inch, and an additional first-line indenting of a third of an inch (480 twips), and full justification. Formatted, it'll look like this: A resource can be anything that has identity. Familiar examples include an electronic document, an image, a service (e.g., "today's weather report for Los Angeles"), and a collection of other resources. Not all resources are network "retrievable"; e.g., human beings, corporations, and bound You can have a negative figure for first-line indenting, to give the effect of a "hanging paragraph": {\pard \li500\fi-300\ri200\ql {\b\f2\fs30 resource:} [jargon] {\i n.} anything that has identity. Familiar examples include an electronic document, an image, a service (e.g., "today's weather report for Los Angeles"), and a collection of other resources. \par } This says that the paragraph's left margin is 500 twips; the first line indent is -300 twips -- meaning 300 twips I from the paragraph's left end (i.e., 200 twips from the page's left margin!); the right indent is 200 twips; and the paragraph is to be left-justified (i.e., with a ragged right edge). Formatted, it'll look like this: resource: [jargon] n. anything that has anything that has identity. Familiar examples include an electronic document, an image, a service (e.g., "today's weather report for Los Angeles"), and a collection of other resources. If you want a newline in the middle of a paragraph (like a "
" in HTML), use the \line command: {\par\pard\sb300\sa300 my $x = "<<<><<<>><>><<>><>>>";\line print "[$x] initially\'5cn";\line print "[$x]\'5cn"\line while $x =~ s/<>//;\line print "[$x] finally\'5cn";\line \par} That paragraph expresses a block of Perl code, with 300 twips of vertical space before it all, and 300 twips of vertical space after it all. Even headings are typically expressed as paragraphs: {\pard \qc \b\f3\fs40 Section 1: The Larch \par} This uses the \qc code, for paragraph-centering, which is useful generally only in headings. The character-formatting codes used here are: \b for bold, \f3 to change to whatever is entry 3 in the font table, and \fs40 to change the current point size to 20-point. The integer parameter for the \fsN command is in half-points, so that's why 40 means 20-point type. \fsN is is one of the few commands in RTF (and the only one that I mention here) that expresses size/distance in units other than twips. Character formatting codes are given further below, and they are relatively straightforward. =head2 RTF Conclusion To conclude your document, you should emit a "}", and then close the file. That closebrace closes the group that the first character of the file opened. Presumably that's all the groups you have to close. =head1 FORMATTING CODES The following are references for: 1) Character Formatting, 2) Paragraph and Block-Level Formatting, 3) Document Formatting, and 4) Characters, Escapes, and Character Commands =head2 Character Formatting \plain -- turn off all formatting \ul -- underline \b -- bold \i -- italic \sub -- subscript \super -- superscript \fN -- change to font #N (where that's the number of a font declared in the fonttable in the prolog) \fsN -- set current font size to N half-points e.g., \fs30 = 15 point \scaps -- smallcaps \strike -- strikethru \v -- hidden text (For comments?) \langN -- language N US English: 1033 MX Spanish: 2058 French: 1036 Turkish: 1055 No language: 1024 \noproof -- disable spellchecking here (not known to older RTF readers) {\super\chftn}{\footnote\pard\plain\chftn. Foo!} -- set an autonumbered footnote of "Foo!" hanging off of the current point in the text {\field{\*\fldinst{HYPERLINK "http://www.suck.com/"}}{\fldrslt{\ul stuff }}} -- make "stuff" a link to http://www.suck.com/ \cfN -- select foreground color #N (from color table) \cbN -- select background color #N (from color table) =head2 Paragraph and Block-Level Formatting \page -- pagebreak \line -- newline (not a real paragraph break) \tab -- tab (better than using a literal tab character) \par -- End this paragraph, and start a new one, inheriting paragraph properties from the previous one. (Note that \par means END-paragraph, but if you treat it as it if means start-paragraph, you won't break too many things.) \par\pard -- End previous paragraph, and start a new one that doesn't inherit paragraph properties from the previous one. (The \par ends the previous paragraph, the \pard resets the new paragraph's formatting attributes.) \ql -- left (i.e., no) justification \qr -- right-justify (i.e., align right) \qj -- full-justify (i.e., smooth margins on both sides) \qc -- center \liN -- left-indent this whole paragraph by N twips (default: 0) \riN -- right-indent this whole paragraph by N twips (default: 0) \fiN -- first-line left-indent this paragraph by N twips (default: 0) (Note that this indenting is relative to the margin set by \liN!) (Note also that the above can have negative values. E.g., \fi-120 backdents (to the left!) the first line by 120 twips from the paragraph's left margin.) \sbN -- N twips of extra (vertical) space before this paragraph (default: 0) \saN -- N twips of extra (vertical) space after this paragraph (default: 0) \pagebb -- pagebreak before this paragraph \keep -- don't split up this paragraph (i.e., across pages) \keepn -- don't split up this paragraph from the next one \widctlpar -- widow-and-orphans control for this paragraph (antonym: \nowidctlpar) {\header\pard\qr\plain\f0\chpgn\par} -- turn on page numbering. Lasts until next \sect\sectd. \colsN -- N newspaper-columns per page. Lasts until next \sect\sectd. \linebetcol -- show lines between columns. \sect\sectd -- new section. (Resets header and columnation.) =head2 Document Formatting If you emit these, do it right after the prolog: \ftnbj\ftnrestart -- initialize footnote numbering \deflangN -- set the document's default language to N. (See \languageN, above) \widowctrl -- turn on widows-and-orphans control for the document \hyphcaps -- allow hyphenation of capitalized words (\hyphcaps0 turns off) \hyphauto -- automatic hyphenation (\hyphauto0 turns off) \pgnstartN -- for page numbering, set first page to N \marglN -- set left page-margin to N twips (default: 1800) \margrN -- set right page-margin to N twips (default: 1800) \margtN -- set top page-margin to N twips (default: 1440) \margbN -- set top page-margin to N twips (default: 1440) \landscape -- document is in landscape format (i.e., it's sideways on the page) =head2 Characters, Escapes, and Character Commands [return] -- ignored (unless preceded by an escaping backslash) [linefeed] -- ignored (unless preceded by an escaping backslash) [space] -- a space (yes, space and tabs ARE significant whitespace) [tab] -- a tab to the next tab stop. Use the command \tab instead, or consider expanding tabs to spaces. (Setting tab stops is not covered in this document.) \'XX -- character with hex code XX (e.g., \'BB is character 187) \\ -- a backslash (same as \'5c) \{ -- an open-brace (same as \'7b) \} -- a close-brace (same as \'7d) \~ -- non-breaking space \- -- optional hyphen (!) \_ -- non-breaking hyphen Remember that all of the following, unlike the above, are commands; and so you can't say, for example, "\bulleta" to get a bullet and then an "a". Instead, you'd need to have: "\bullet a". Or "\bullet", a newline, and "a". \bullet -- bullet character (same as Latin-1 character 149) \endash -- n-width dash \emdash -- m-width dash \enspace -- n-width non-breaking space \emspace -- m-width non-breaking space \lquote -- single openquote (6) \rquote -- single closequote (9) \ldblquote -- double openquote (66) \rdblquote -- double closequote (66) =head1 SAMPLE COMPLETE RTF DOCUMENT {\rtf1\ansi\deff0 {\fonttbl {\f0 Times New Roman;} } \deflang1033\widowctrl {\header\pard\qr\plain\f0{\noproof\i La dame} p.\chpgn\par} \lang1036\fs36 {\pard\qc\f1\fs60\b\i La dame\par} {\pard\sb300\li900 Toc toc Il a ferm\'e9 la porte\line Les lys du jardin sont fl\'e9tris\line Quel est donc ce mort qu'on emporte \par} {\pard\sb300\li900 Tu viens de toquer \'e0 sa porte\line Et trotte trotte\line Trotte la petite souris \par} {\pard\sb900\li900\scaps\fs44 \endash Guillaume Apollinaire, {\i Alcools}\par} \page\lang1033\fs32 {\pard\b\ul\fs40 Vocabulary\par} {\pard\li300\fi-150{\noproof\b toc }{\i(n.m.)} \endash tap; knock\par} {\pard\li300\fi-150{\noproof\b lys }{\i(n.m.)} \endash lily\par} {\pard\li300\fi-150{\noproof\b fl\'e9trir } {\i(v.itr.)} \endash to wilt; for a flower or beauty to fade; for a plant to wither\par} {\pard\li300\fi-150{\noproof\b mort } {\i(adj., here used as a masc. noun)} \endash dead\par} {\pard\li300\fi-150{\noproof\b emporter } {\i(v.tr.)} \endash to take a person or thing [somewhere]; to take\~[out/away/etc.] or carry\~[away] a thing\par} {\pard\li300\fi-150{\noproof\b toquer } {\i(v.itr.)} \endash to tap; to knock\par} {\pard\li300\fi-150{\noproof\b trotter } {\i(v.itr.)} \endash to trot; to scurry\par} {\pard\li300\fi-150{\noproof\b souris }{\i(n.f.)} \endash mouse\par} {\pard\sb200\b\ul\fs40 Free Translation\par} {\pard Click click He closed the door / Garden lilies faded / Which body is today // You just tapped on the door / And tip toe / Taps the little mouse \line \_Translation Sean M. Burke, 2001 \par} } =head1 NOTES * This document does not discuss the RTF commands specific to the MSWin .HLP compiler. * There may be slight differences in how the same RTF is interpreted by different word processors. For example, wordpad understands only a subset of RTF. Also, the last version of WordPerfect that I dealt with (8.0) would occasionally apply margin-changing codes to more paragraphs than the RTF actually said to. The most "reliable" rendering of RTF is generally what you get from MSWord, since RTF is after all (as far as I can tell), a direct recapitulation of the MSWord's internal representation of documents. * In this document, I don't cover embedding graphics in RTF, because it's a big old mess. * A hint: if you're writing a program that generates RTF, and it runs happily, but the generated document crashes your word processor, then make sure you've got as many {'s as }'s. If you represent literal openbrace and closebrace as \'7b and \'7d, then all "{"s in your RTF code will be group-openers, all "}"s in RTF code will be group-closers, and there should be equal numbers of them. (They should also nest properly, but most errors are detectable thru unequal numbers of braces.) This bit of Perl code can be used to check a given file for matching {'s and }'s: # Assuming all that literal {'s are encoded as \'7b # that all literal }'s are encoded as \'7d $/ = '}'; use strict; my $stack = ''; my $open_count = my $close_count = 0; while(<>) { tr/{}//cd; $open_count += tr/{//; $close_count += tr/}//; while( s/{}//g ) {1}; $stack .= $_; } while( $stack =~ s/{}//g ) {1} print "$open_count x {\n$close_count x }\n", length($stack) ? "Unbalanced.\n" : "Balanced.\n"; Altho note that this fails to trap the case where you close the document's top-level group and then open another (which you shouldn't do). * I don't cover making tables here, because they're rather hard to do; because they're not really isomorphic with HTML tables or TeX tables (just to name two models of tables that people know, and that are rather more sane than RTF); and also because messing up the code for tables (as you are prone to do while experimenting with a writer-program) sometimes crashes MSWord! However, if you need to make tables, you can mull over this code while referring to the murky explanation of tables in the RTF Spec: {\pard Hmmm \par} {\pard \trowd\trgaph300\trleft400\cellx1500\cellx3000\cellx4500 \pard\intbl Wun. Doo wah ditty ditty dum ditty do \cell \pard\intbl Too. Doo wah ditty ditty dum ditty do \cell \pard\intbl Chree. Doo wah ditty ditty dum ditty do \cell \row \trowd\trgaph300\trleft400\cellx1500\cellx3000\cellx4500 \pard\intbl Foh. Doo wah ditty ditty dum ditty do \cell \pard\intbl Fahv. Doo wah ditty ditty dum ditty do \cell \pard\intbl Saxe. Doo wah ditty ditty dum ditty do \cell \row \trowd\trgaph300\trleft400\cellx1500\cellx3500 \pard\intbl Saven. Doo wah ditty ditty dum ditty do \cell \pard\intbl Ight. Doo wah ditty ditty dum ditty do \cell \row } {\pard I LIKE PIE} =head1 COPYRIGHT AND DISCLAIMER Copyright 2001,2,3 Sean M. Burke. This document is not in the public domain, but you can redistribute this document and/or modify it under the same terms as Perl itself, as explained in the Perl Artistic License. This document is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of accuracy, merchantability, or fitness for a particular purpose. The author of this document is not affiliated with the Microsoft corporation. Product and company names mentioned in this document may be the trademarks or service marks of their respective owners. Trademarks and service marks are not identified, although this must not be construed as the author's expression of validity or invalidity of each trademark or service mark. =head1 AUTHOR Sean M. Burke, sburke@cpan.org =cut # End document RTF-Writer-1.11/lib/RTF/Writer.pm0100644000177300017730000006024407751704704015760 0ustar sburkesburke require 5.005; # we need m/...\z/ package RTF::Writer; use strict; # Time-stamp: "2003-11-04 02:13:08 AST" BEGIN { eval {require utf8}; $INC{"utf8.pm"} = "dummy_value" if $@ } # hack to allow "use utf8" under old Perls use utf8; die sprintf "%s can't work (yet) in a non-ASCII world", __PACKAGE__ unless chr(65) eq 'A'; use vars qw($VERSION @ISA @EXPORT_OK $AUTOLOAD $AUTO_NL $WRAP @Escape); $AUTO_NL = 1 unless defined $AUTO_NL; # TODO: document $WRAP = 1 unless defined $WRAP; # TODO: document require Exporter; @ISA = ('Exporter'); $VERSION = '1.11'; @EXPORT_OK = qw( inch inches in point points pt cm rtfesc ); sub DEBUG () {0} use Carp (); use RTF::Writer::TableRowDecl (); #************************************************************************** sub CHARSET_LATIN1 { $Escape[0xA0] = "\\~"; $Escape[0xAD] = "\\-"; return; } sub CHARSET_UNICODE { $Escape[0xA0] = "\\~"; $Escape[0xAD] = "\\-"; return; } sub CHARSET_OTHER { $Escape[0xA0] = "\\'a0"; $Escape[0xAD] = "\\'ad"; return; } #-------------------------------------------------------------------------- # Init: # Using an array for this avoids some problems with nasty UTF8 bugs in # hash lookup algorithms. @Escape = map sprintf("\\'%02x", $_), 0x00 .. 0xFF; foreach my $i ( 0x20 .. 0x7E ) { $Escape[$i] = chr($i) } { my @refinements = ( "\\" => "\\'5c", "{" => "\\'7b", "}" => "\\'7d", "\cm" => '', "\cj" => '', "\n" => "\n\\line ", # This bit of voodoo means that whichever of \cm | \cj isn't synonymous # with \n, is aliased to empty-string, and whichever of them IS "\n", # turns into the "\n\\line ". "\t" => "\\tab ", # Tabs (altho theoretically raw \t's might be okay) "\f" => "\n\\page\n", # Formfeed "-" => "\\_", # Turn plaintext '-' into a non-breaking hyphen # I /think/ that's for the best. "\xA0" => "\\~", # \xA0 is Latin-1/Unicode non-breaking space "\xAD" => "\\-", # \xAD is Latin-1/Unicode soft (optional) hyphen '.' => "\\'2e", 'F' => "\\'46", ); my($char, $esc); while(@refinements) { ($char, $esc) = splice @refinements,0,2; $Escape[ord $char] = $esc; } } #-------------------------------------------------------------------------- # The conversion functions, for export: sub inch { int(.5 + $_[0] * 1440) } sub inches { int(.5 + $_[0] * 1440) } sub in { int(.5 + $_[0] * 1440) } sub points { int(.5 + $_[0] * 20) } sub point { int(.5 + $_[0] * 20) } sub pt { int(.5 + $_[0] * 20) } sub cm { int(.5 + $_[0] * (1440 / 2.54) ) } # approx 567 sub rtfesc { # Note that this doesn't apply our wrapping algorithm, because # I don't forsee this being used for many-line things. shift if @_ and ref($_[0] || '') and UNIVERSAL::isa($_[0], __PACKAGE__); # that's so we can double as a method my $x; # scratch if(!defined wantarray) { # void context: alter in-place! for(@_) { s/([F\.\x00-\x1F\-\\\{\}\x7F-\xFF])/$Escape[ord$1]/g; # ESCAPER s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; # We escape F and . because when they're line-initial (or alone # on a line), some mailers eat them or freak out. } return; } elsif(wantarray) { # return an array return map {; ($x = $_) =~ s/([F\.\x00-\x1F\-\\\{\}\x7F-\xFF])/$Escape[ord$1]/g; # ESCAPER $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; $x; } @_; } else { # return a single scalar ($x = ((@_ == 1) ? $_[0] : join '', @_) ) =~ s/([F\.\x00-\x1F\-\\\{\}\x7F-\xFF])/$Escape[ord$1]/g; # ESCAPER # Escape \, {, }, -, control chars, and 7f-ff. $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; return $x; } } #************************************************************************** sub new_to_file { # just a wrapper around new_to_fh my $class = shift; defined $_[0] or Carp::croak "undef isn't a good filename for new_to_file"; length $_[0] or Carp::croak "\"\" isn't a good filename for new_to_file"; local(*FH); open(FH, ">$_[0]") or Carp::croak "Can't write-open $_[0]: $!"; DEBUG and print "Opened-file $_[0] -> ", *FH{IO}, "\n"; my $new = $class->new_to_fh(*FH{IO}); return $new; } sub new_to_filehandle { shift->new_to_handle(@_) } sub new_to_handle { shift->new_to_fh( @_) } sub new_to_fh { # legacy Carp::croak "Open to what filehandle?" unless defined $_[1] and length $_[1]; my $fh = $_[1]; DEBUG and print "Opened-fh $fh\n"; my $class = shift; my $last_was_command = 0; my $new = bless [ _make_emitter_closure($fh), '', # things to be printed, on closing $fh, ], ref($class) || $class; return $new; } sub new_to_string { Carp::croak "Open to what scalar-ref?" unless defined $_[1] and ref($_[1]) eq 'SCALAR'; my($class, $sr) = @_; DEBUG and print "Opened-sr $sr\n"; my $new = bless [ _make_emitter_closure(undef,$sr), '', # things to be printed, on closing undef, ], ref($class) || $class; return $new; } #************************************************************************** # Think twice before outright overriding this method: sub print { ref $_[0] or Carp::croak(__PACKAGE__ . "'s print(...) is supposed to be an object method!"); DEBUG > 1 and print "Calling $_[0][0]\n"; goto &{ $_[0][0] || # call the closure Carp::croak("That " . __PACKAGE__ . " object has been closed!?") }; } #************************************************************************** sub printf { ref $_[0] or Carp::croak(__PACKAGE__ . "'s printf(...) is supposed to be an object method!"); my($it,$format) = splice(@_,0,2); $format = '' unless defined $format; if(ref($format) ne 'SCALAR') { # Example: $it->printf("%04d: %s\n", @stuff) DEBUG and print "Nonescaped format <$format> on <@_>\n"; my $x = sprintf($format, @_); DEBUG and print "Formatted (not yet esc): $x\n"; $it->print( $x ); # And, in escaping, this will be wrapped. } else { # Example: $it->printf(\'{\f30\b %s:} {\i %d}', @stuff) DEBUG and print "Escaped format <", $$format, "> on <@_>\n"; my $str; # scratch # Escape anything non-numeric: for(my $i = 0; $i < @_; ++$i) { next if !defined($_[$i]) or !length($_[$i]) or $_[$i] =~ m/^[+-]?(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee](?:[+-]?\d+))?\z/s; ($str = $_[$i]) =~ s/([F\.\x00-\x1F\-\\\{\}\x7F-\xFF])/$Escape[ord$1]/g; # ESCAPER $str =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; # Don't bother applying wrapping, I guess. DEBUG > 2 and print "Escaping <$_[$i]> to <$str>\n"; splice @_, $i, 1, $str; # MAGIC! makes it so we don't alter the original. } my $x = sprintf $$format, @_; DEBUG and print "Formatted (esc): $x\n"; $it->print( \$x ); # No wrapping applied. # We mustn't escape things that we might intend, in the sprintf # format, to treat as numbers, since escaping would turn '-' # to '\_', and that would turn something numeric like "-14" # or "1.5E-9" into something non-numeric like "\_14" # or "1.5E\_9". So we use this regexp. # The solution here /could/ fail to apply the escaping of # "-" -> "\_", to number-seeming things we were really going # to use as strings, but that seems relatively harmless. # The only completely correct way to do that would be to # completely reimplement sprintf in pure Perl, or at least # enough of it that we parse the format -- so not only could we # tell what items from @_ were to be treated as numbers and # which as strings, but also so we could take the output of # formatting numbers, and /then/ apply the '-' -> '\_' # escaping. # However, the /only/ benefit of this would be to get the # '-' -> '\_' escaping to apply. And in practice, this could # be a problem only in two cases: a leading minus-sign, as # in '-53.3', which presumably won't occur in a context # where a word-processor would hyphenate; and after an "E", # as in "1.5E-9". While it's more likely that a word-precessor # might hyphenate there, I that think scientific-notation # numbers are in practive relatively rare. So there. } } #-------------------------------------------------------------------------- sub AUTOLOAD { DEBUG and print "**** $_[0] hits autoload for $AUTOLOAD\n"; if(ref($_[0]) and $AUTOLOAD =~ m<::([A-Z][a-z]*(?:_?[0-9]+)?)$>s) { my $cmd = "\\" . lc($1); $cmd =~ tr<_><->; # So: $x->fi_180 -> $x->print(\'\fs-180') my $it = shift; if(@_) { return $it->print(\'{', \$cmd, @_, \'}'); # So: $it->Lang1234(...) -> $it->print([\'\lang123', ... ]); # (Well, the { ... } is just an incidental optimization.) } else { return $it->print(\$cmd); # So: $it->Lang1234() -> $it->print(\'\lang123'); } } else { Carp::croak "Can't locate object method \"$AUTOLOAD\" via package \"" . (ref($_[0]) || $_[0]) . '"'; } } #-------------------------------------------------------------------------- sub close { return unless $_[0][0]; # Already closed?! DEBUG > 1 and print "Closing $_[0]\n"; $_[0]->print(\$_[0][1]) if length $_[0][1]; undef $_[0][0]; # ...presumably clausing any FH to close and destroy. $_[0][1] = ''; return; } #-------------------------------------------------------------------------- sub DESTROY { # just a rudimentary version of $fh->close() $_[0]->print(\$_[0][1]) if $_[0][0] and $_[0][1]; } #************************************************************************** use UNIVERSAL (); sub table { # Wrapper around row(). my $it = shift; Carp::croak "table isn't a class method" unless ref $it; my $decl = shift if @_ and defined $_[0] and ref($_[0]) and UNIVERSAL::isa($_[0], __PACKAGE__ . '::TableRowDecl'); # Remaining items are row-arrayrefs. push @_, [''] unless @_; # avoid table with no rows! $decl ||= RTF::Writer::TableRowDecl->new_auto_for_rows(@_); $it->print(\'\par\pard'); # Because ill things happen unless the paragraph # that the table starts in, is virgin. foreach my $row_content (@_) { Carp::croak "table's row-parameters have to be arrayrefs" unless ref($row_content || '') eq 'ARRAY'; $it->row($decl, @$row_content); } return scalar @_; } #-------------------------------------------------------------------------- sub row { # Generate a table row. my $it = shift; Carp::croak "row isn't a class method" unless ref $it; Carp::croak "row's first parameter has to be a table row declaration" unless @_ and defined $_[0] and ref($_[0]) and UNIVERSAL::isa($_[0], __PACKAGE__ . '::TableRowDecl'); my $decl = shift; # Pad with blank cells, if need be: push @_, (\'') x scalar(@{$decl->[0]} - @_) if @{$decl->[0]} > @_; # We have to avoid having a cell-less row: push @_, \'' unless @_; my $cell_count = @_; my @inits = $decl->cell_content_init; unshift @_, \( '\pard\intbl' . ( shift(@inits) || '' ) ); for(my $i = 1; $i < @_; $i += 2) { if(defined($_) and ref($_) eq '' and -1 != index($_[$i], "\f")) { # The one case where we need to mess with things: if there's a # formfeed in this plaintext. my $x = $_[$i]; $x =~ tr/\f/\n/; splice @_, $i, 1, $x; # Swap in the copy, not touching the original. } splice(@_, $i + 1, 0, \( '\cell\pard\intbl' . (shift(@inits) || '') )); } $_[-1] = \'\cell\row\pard'; $it->print( \'{', $decl->decl_code($cell_count), @_, \'}', ); return $cell_count; # Might as well return somehting. } #-------------------------------------------------------------------------- sub number_pages { my $r = shift; $r->print( \"\n{\\header \\pard\\qr\\plain\\f0", @_, \"\\chpgn\\par}\n\n" ); # This is actually a section attribute. To reset, \'\sect\sectd' # to start a new section. } #************************************************************************** sub paragraph { my $r = shift; $r->print(\"{\\pard\n", @_, \"\n\\par}\n\n"); } #************************************************************************** sub image_paragraph { my $r = shift; my($filename, $declcode) = $r->_image_params(@_); return unless $r->print( \"{\\pard\\qc\n{\\pict\n", \$declcode); $r->_image_data($filename) or return; $r->print( \"}\n\\par}\n\n" ); } sub paragraph_image { shift->image_paragraph(@_) } sub paragraph_picture { shift->image_paragraph(@_) } sub picture_paragraph { shift->image_paragraph(@_) } sub pict { shift->image(@_) } sub image { Carp::croak "Don't call \$rtf->image(...) in void context!" unless defined wantarray; my $r = shift; my($filename, $declcode) = $r->_image_params(@_); my $out = "{\\pict\n$declcode"; $r->_image_data($filename, \$out ); $out .= "}\n"; return \$out; } #-------------------------------------------------------------------------- sub _image_params { my $self = shift; my %o = @_; my $decl; my $filespec = $o{'filename'} || Carp::croak "What filename?"; Carp::croak "No such file as $filespec" unless $filespec and -e $filespec; if(defined $o{'picspecs'}) { $decl = $o{'picspecs'}; $decl = $$decl if ref $decl; } else { require Image::Size; my($h,$w, $type) = Image::Size::imgsize( $filespec ); Carp::croak "$filespec - $type" unless $h and $w; my $tag = ($type eq 'PNG') ? '\pngblip' : ($type eq 'JPG') ? '\jpegblip' : Carp::croak("I can't handle images of type $type like $filespec"); ; $decl = "$tag\\picw$w\\pich$h\n"; # Now glom on any extra parameters specified: $decl .= join '', map sprintf("\\pic%s%s", $_, int $o{$_}), grep defined($o{$_}), qw ; } $decl .= "\n"; # So it doesn't run together with the image data. return( $filespec, $decl ); } sub _image_data { my($r, $filename, $to) = @_; my $buffer; my $in; { local(*IMAGE); open(IMAGE, $filename) or Carp::croak "Can't read-open $filename: $!"; $in = *IMAGE; } binmode($in); while( read($in, $buffer, 32) ) { if($to) { $$to .= unpack("H*", $buffer) . "\n" ; } else { $r->print( \( unpack("H*", $buffer) . "\n" ) ) or return 0; } # Turn 32 bytes into 64 hex characters, and then add a newline. # (If the last chunk of data is under 32 bytes, then the unpack() # does the right thing.) } CORE::close($in); return 1; } #************************************************************************** # two tolerated variant forms: sub prologue { shift->prolog(@_) } sub premable { shift->prolog(@_) } sub prolog { # Emit prolog with given parameters DEBUG and print "Prolog args: <@_>\n"; my($it, %h) = (@_); my $x; #scratch # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $h{'revtim' } = time unless exists $h{'revtim'}; $h{'creatim'} = time unless exists $h{'creatim'}; $h{'doccomm'} = escape_broadly(sprintf 'written by %s [Perl %s v%s]', $0, ref($it), $it->VERSION()) unless exists $h{'doccomm'}; # So you can set each to undef if you want it suppressed. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $fonts = $h{'fonts'} || $h{'font_table'} || $h{'fonttable'} || []; $fonts = [$fonts] unless ref $fonts; push @$fonts, \'\froman Times New Roman' if ref($fonts) eq 'ARRAY' and ! @$fonts; # avoid having an empty font table my $font_count = -1; $fonts = \join '', # '{' \fonttbl ( | ('{' '}'))+ '}' "{\\fonttbl\n", map( ref($_) ? ("{\\f", ++$font_count, ' ', $$_, ";}\n") : ("{\\f", ++$font_count, '\fnil ', escape_broadly($_), ";}\n"), @$fonts # # ? ? ? ? ? ? # ? ';' ), "}\n" if ref $fonts eq 'ARRAY' ; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $info = join '', # And the info group: "\n{\\info \n", # \version? & \vern? & \edmins? & \nofpages? & \nofwords? \nofchars? # & \id? # & ? & <subject>? & <author>? & <manager>? & <company>? # & <operator>? & <category>? & <keywords>? & <comment>? & <doccomm>? # Time things, all optional: map( (!defined($x = $h{$_})) ? () : ( "{\\$_ ", ( ref($x) eq 'SCALAR' ? $$x : ref($x) eq 'ARRAY' ? _time_to_rtf(@$x) : $x =~ m<^\d+$> ? _time_to_rtf( $x) : $x, # dubious, but let it thru ), "}\n" ), qw(creatim revtim printim buptim) ), map( # Optional integer things: (!defined($x = $h{$_})) ? () : $x =~ m<^[0-9]+$> ? "\\$_$x\n" : Carp::croak("value for \"$_\" must be an integer, not \"$_\""), qw(version vern edmins nofpages nofwords nofchars nofcharsws id) ), # Optional non-time non-integer things: map( (!defined($x = $h{$_})) ? () : ( "{\\$_ ", (ref($x) eq 'SCALAR') ? $$x : $x, "}\n" ), qw(title subject author manager company operator category keywords comment doccomm hlinkbase) ), ref( $h{'more_info'} || '' ) eq 'SCALAR' ? ${ $h{'more_info'} } : ( $h{'more_info'} || '' ), "}\n\n", # end of info group ; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Cook up the color table. # # Note that you might want to feed this a null 0th entry: # as in: [ undef, [255,0,0], [0,0,255], ... ] my $color_table = ($h{'colors'} || $h{'color_table'} || $h{'colortable'} || $h{'colortbl'} || ''); if(ref($color_table) eq 'ARRAY') { #print "R ", ref($color_table), "<", @$color_table, "> =$color_table\n"; $color_table = \join '', '{\colortbl ', map( (ref($_ || '') eq 'ARRAY' ) ? sprintf('\red%d\green%d\blue%d;', $_->[0] || 0, $_->[1] || 0, $_->[2] || 0, ) : (ref($_ || '') eq 'SCALAR') ? ( ($$_ =~ m/;[\cm\cj\n]*\z/s) ? $$_ : ($$_ . ';') ) # Make sure it ends with a semicolon : ';', # null entry @$color_table ), '}' ; } elsif(ref($color_table) eq 'SCALAR') { # pass it thru } else { $color_table = \'{\colortbl;\red255\green0\blue0;\red0\green0\blue255;}'; } $h{'colortbl'} = $color_table; #print "Color table: <", ${$h{'colortbl'}}, ">\n"; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Now emit the table: # # \rtf <charset> \deff? <fonttbl> <filetbl>? <colortbl>? <stylesheet>? # <listtables>? <revtbl>? $it->print( \join '', '{\rtf' , defined($h{'rtf_version'}) ? $h{'rtf_version'} : '1', "\\" . ($h{'charset'} || 'ansi'), "\\deff" . int($h{'deff'} || 0), (!defined($x = $h{'more_default'})) ? '' # place to sneak in more stuff : ref($x) eq 'SCALAR' ? $$x : $x, $$fonts, map( ref( $h{$_} || '' ) eq 'SCALAR' ? ${ $h{$_} } : ( $h{$_} || '' ), qw( filetbl colortbl stylesheet listtables revtbl ) ), $info, ); #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $it->[1] .= '}'; DEBUG > 2 and print "Setting $it\'s out-buffer to <$it->[1]>\n"; # to close the group that this document opened in its first char return 1; } # Two subs used in the "prolog" method: sub escape_broadly { # Non-destructively quote anything fishy. my $scratch = $_[0]; $scratch =~ s/([F\.\x00-\x1F\\\{\}\x7F-\xFF])/"\\'".(unpack("H2",$1))/eg; # ESCAPER $scratch =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; return $scratch; } sub _time_to_rtf { # accepts no-params (meaning now), an epoch time, or a timelist push @_, time() unless @_; if(@_ == 1) { # normal case @_ = (localtime(shift @_))[5,4,3,2,1,0]; $_[0] += 1900; # RTF counts 2023 as 2023, not 123. $_[1]++; # RTF counts January as 1, not 0. } return sprintf '\yr%d\mo%d\dy%d\hr%d\min%d\sec%d', @_; } #************************************************************************** # # The following makes the scary scary emitter-closure: # my $counter = 0; # for debug purposes sub _make_emitter_closure { my($fh, $sr) = @_; # sr should either be undef, or a scalar-ref my $scratch; # A closure on $fh or $sr, for printing to it. sub { my $this = shift; DEBUG > 1 and print "Writing (@_) to ", $sr ? "S_$sr\n" : "F_$fh\n"; foreach my $x (@_) { next unless defined $x; if(ref($x) eq 'ARRAY') { next if @$x == 0; $sr ? ( $$sr .= '{' ) : print $fh '{'; DEBUG > 1 and print " $counter: wrote {\n"; $this->[0]->($this, @$x); # recurse! $sr ? ( $$sr .= '}' ) : print $fh '}'; DEBUG > 2 and print " wrote }\n"; } elsif(ref($x) eq 'SCALAR') { if(!defined($$x) or !length($$x)) { # no-op DEBUG > 2 and print " $counter: skipping null sr\n"; } elsif( not( $AUTO_NL and $$x =~ m<[a-zA-Z0-9]\z>s )) { $sr ? ( $$sr .= $$x ) : print $fh $$x; DEBUG > 2 and print " $counter: wrote sr $$x\n"; } else { # $AUTO_NL is true, and $$x's last char is in [a-zA-Z0-9] $sr ? ( $$sr .= $$x . "\n" ) : print $fh $$x, "\n"; DEBUG > 2 and print " $counter: wrote sr $$x +nl\n"; # Why emit a newline? Because that string might end in a # command, and we want to do the Right Thing in the case of: # $r->print(\'\i', 'donuts') # i.e., printing "\i[newline]donuts", not "\idonuts" # # And why not emit "\i[space]donuts"? because we if we emit a # space and the thing we emitted WASN'T a control word, then # we did a bad thing! Spaces are tricky -- sometimes they're # meaningless, and sometimes they mean a literal space. # But newlines are always ignored -- well, unless preceded # by an escaping backslash, but to get that, the user would # have to have the previous group end in an unmatched backslash, # as in $h->print(\"\\foo\\", ...) So don't do that! } } elsif(length $x) { # It's plaintext ($scratch = $x) =~ s/([F\.\x00-\x1F\-\\\{\}\x7F-\xFF])/$Escape[ord$1]/eg; # ESCAPER $scratch =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; # Escape \, {, }, -, control chars, and 7f-ff, and Unicode. # And now: a not terribly clever algorithm for inserting newlines # at a guaranteed harmless place: after a block of whitespace # after the 65th column. # Why not before the block of whitespace? Consider: # q<\foo bar> If we break that into q<\foo>+NL+q< bar>, then # suddenly the space after the newline is significant, instead # of just being the dummy space that ends the \foo command token. $scratch =~ s/( [^\cm\cj\n]{65} # Snare 65 characters from a line [^\cm\cj\n\x20]{0,50} # and finish any current word ) (\x20{1,10})(?![\cm\cj\n]) # capture some spaces not at line-end /$1$2\n/gx # and put a NL after those spaces if $WRAP; # This may wrap at well past the 65th column, but not past the 120th. $sr ? ( $$sr .= $scratch ) : print $fh $scratch; DEBUG > 2 and print " $counter: wrote scalar <$scratch>\n"; $scratch = ''; } # otherwise it's 0-length plaintext, so ignore. } DEBUG > 3 and print $fh "{\\v $^T/", ++$counter, "}\n"; return 1; }; } #-------------------------------------------------------------------------- 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/lib/RTF/Writer/���������������������������������������������������������������������0040711�0001773�0001773�00000000000�07751705660�015411� 5����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/lib/RTF/Writer/TableRowDecl.pm������������������������������������������������������0100644�0001773�0001773�00000022756�07734225420�020267� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 5; package RTF::Writer::TableRowDecl; use strict; # Time-stamp: "2003-09-23 21:26:40 ADT" use Carp (); BEGIN { if(defined &DEBUG) { } # nil elsif(defined &RTF::Writer::DEBUG) { *DEBUG = \&RTF::Writer::DEBUG } else { *DEBUG = sub(){0} } } #-------------------------------------------------------------------------- use vars qw($DEFAULT_BORDER_WIDTH %Directions %Align_Directions); $DEFAULT_BORDER_WIDTH ||= 15; unless(keys %Directions) { @Directions{qw(N S E W)} = (0 .. 3); @Directions{qw(n s e w)} = (0 .. 3); @Directions{qw(T B R L)} = (0 .. 3); @Directions{qw(t b r l)} = (0 .. 3); } # N S E W my(@tabledirs) = qw( t b r l ); unless(keys %Align_Directions) { for my $d (\%Align_Directions) { # First char is vertical, second is horiz @$d{qw(NW N NE)} = qw(tl tc tr); @$d{qw(W C E)} = qw(cl cc cr); @$d{qw(SW S SE)} = qw(bl bc br); @$d{qw(WN EN)} = qw(tl tr); @$d{qw(WS ES)} = qw(bl br); @$d{qw(TL T TR)} = qw(tl tc tr); @$d{qw(L C R)} = qw(cl cc cr); @$d{qw(BL B BR)} = qw(bl bc br); @$d{qw(LT RT)} = qw(tl tr); @$d{qw(LB RB)} = qw(bl br); @$d{map lc($_), keys %$d} = values %$d; }} #-------------------------------------------------------------------------- # INSIDES: # 0: the right-ends table # 1: the left-margin setting # 2: the inbetween setting # 3: a list of border settings # 4: a list of valign settings # 5: a list of halign settings # 6: the cached decl string sub new { my($it, %h) = @_; my $new; my(@reaches); if(ref $it) { # clone $new = $it->clone(); } else { $new = bless [ \@reaches, int( $h{'left_start'}||0 ) || 0, int( $h{'inbetween' }||0 ) || 120, # 6 points, 1/12th-inch, about 2mm ]; } my $x; # scratch if($x = $h{'widths'}) { Carp::croak("'widths' value has to be an arrayref") unless ref($x) eq 'ARRAY'; my $start = $new->[1]; foreach my $w (map int($_), @$x) { push @reaches, ($start += ($w < 1 ) ? 1 : $w); } } elsif($x = $h{'reaches'}) { Carp::croak("'reaches' value has to be an arrayref") unless ref($h{'reaches'}) eq 'ARRAY'; @reaches = sort {$a <=> $b} map int($_), @$x; } $new->make_border_decl( defined($h{'borders'}) ? $h{'borders'} : $h{'border'} ); $new->make_alignment_decl( defined($h{'align'}) ? $h{'align'} : $h{'alignment'} ); return $new; } #-------------------------------------------------------------------------- sub clone { # sufficient to our task, I think bless [ map {; (!defined $_) ? undef : (ref($_) eq 'ARRAY') ? [@$_] : (ref($_) eq 'HASH' ) ? {%$_} : $_ } @{$_[0]} ], ref $_[0]; } #-------------------------------------------------------------------------- sub make_border_decl { my($it, @params) = @_; my @borders; $it->[3] = \@borders; unless( @params and grep defined($_), @params ) { @params = ('1'); } @params = @{$params[0]} if @params == 1 and ref $params[0]; # I.e., if they passed border => [...] @params = "all-$DEFAULT_BORDER_WIDTH-s" if @params == 1 and $params[0] eq '1'; # if they passed just border => 1 foreach my $spec (@params) { push @borders, $it->_borderspec2bordercode($spec); } return; } #-------------------------------------------------------------------------- sub make_alignment_decl { my($it,@alignments) = @_; my(@valign, @halign); $it->[4] = \@valign; $it->[5] = \@halign; unless(@alignments and grep defined($_), @alignments) { # most common case: nothing push @valign, ''; push @halign, ''; return; } if( @alignments != 1) { # Pass thru (altho normally impossible) } elsif( ref $alignments[0] ) { @alignments = @{$alignments[0]} # I.e., they passed align => [...] } else { @alignments = grep length($_), split m/(?:\s*,\s*)|\s+/, $alignments[0]; # I.e., they passed in align => 'sw c c t' or 'sw, c, t' or whatever. } my($x, $v, $h); foreach my $spec (@alignments) { unless(defined $spec and length $spec) { push @valign, ''; push @halign, ''; DEBUG and printf " - => valign - halign -\n"; next; } $x = $Align_Directions{$spec}; unless($x) { require Carp; Carp::croak "Unintelligible alignment spec \"$spec\""; } die "WHAAAAA? [$x]" unless 2 == length $x; # sanity my($v,$h) = split '', $x; push @valign, "\\clvertal$v"; push @halign, "\\q$h"; DEBUG and printf "% 2s => valign %s halign %s\n", $spec, $valign[-1], $halign[-1]; } return; } #-------------------------------------------------------------------------- sub _borderspec2bordercode { my($it, $spec) = @_; $spec = 'all' unless defined $spec and length $spec; return '' if lc($spec) eq 'none'; $spec = "all-$spec-s" if $spec =~ m/^\d+$/s; my @widths = (undef, undef, undef, undef); my @styles = (undef, undef, undef, undef); my($dir, $width, $style); my @specs = split m/(?:,|\s+)/, $spec; foreach my $it (@specs) { next unless $it; unless( ($dir, $width, $style) = $it =~ m/ ^\s* (all|[nsewNSEWtbrlTBRL]) (?:-(\d+))? (?:-([a-z]+))? \s* $ /xs ) { require Carp; Carp::croak "Unintelligible cell-border spec \"$spec\""; } $width = $DEFAULT_BORDER_WIDTH unless defined $width and length $width; #print " $it => [$dir] [$width] [$style]\n"; $style ||= 's'; if($dir eq 'all') { @widths = ($width) x 4; @styles = ($style) x 4; } else { $dir = $Directions{$dir}; $widths[$dir] = $width; $styles[$dir] = $style; } } my @out; foreach my $i (0 .. 3) { next unless $styles[$i]; push @out, sprintf '\clbrdr%s\brdrw%s\brdr%s', $tabledirs[$i], $widths[$i], $styles[$i], ; } return join "\n", @out; } #-------------------------------------------------------------------------- sub new_auto_for_rows { my $class = shift; my $max_cols = 1; foreach my $r (@_) { next unless defined $r and ref $r eq 'ARRAY'; $max_cols = @$r if @$r > $max_cols; } return $class->new( 'width' => [ ((6.5 * 1440) / $max_cols) x scalar(@_) ] ); } #-------------------------------------------------------------------------- sub row_count { return scalar @{ $_[0][0] } } # How many rows we were declared to handle #-------------------------------------------------------------------------- sub decl_code { my $it = shift; return $it->[6] if defined $it->[6]; my $reaches = $it->[0]; my $cell_count = int($_[0] || 0) || scalar @$reaches; if($cell_count > @$reaches) { # Uncommon case -- we need to ad-hoc pad this decl. $reaches = [@$reaches]; # so we won't mutate the original while(@$reaches < $cell_count) { if(@$reaches == 0) { push @$reaches, $it->[1] + 1440; # sane and noticeable default width, I think: 1 inch, 2.54cm } elsif(@$reaches == 1) { push @$reaches, 2 * $reaches->[0] - $it->[1]; # The left-margin setting } else { push @$reaches, 2 * $reaches->[-1] - $reaches->[-2]; # i.e., last + (last - one_before) # DEBUG and printf "Improvised the width %d based on %d,%d\n", # $reaches->[-1], $reaches->[-3], $reaches->[-2]; } } } my @borders = @{ $it->[3] || [] }; push @borders, ($borders[-1]) x ($cell_count - @borders) if @borders > 0 and @borders < $cell_count; my @valign = @{ $it->[4] || [] }; push @valign , ($valign[-1] ) x ($cell_count - @valign ) if @valign > 0 and @valign < $cell_count; # Or should I have it default to a lack of any alignment code? $it->[6] = \join '', # Cache it for next time (and there usually are many next-times) sprintf("\\trowd\\trleft%d\\trgaph%d\n", $it->[1], int($it->[2] / 2) ), map( sprintf("%s%s\\cellx%d\n", (shift(@borders) ||''), (shift(@valign ) ||''), $_, ), @$reaches ), ; DEBUG and print "Init code:\n", ${ $it->[6] }, "\n\n"; return $it->[6]; } #-------------------------------------------------------------------------- sub cell_content_init { return @{ $_[0][5] || [] }; } #-------------------------------------------------------------------------- 1; __END__ =head1 NAME RTF::Writer::TableRowDecl - class for RTF table settings =head1 SYNOPSIS # see RTF::Writer =head1 DESCRIPTION See L<RTF::Writer|RTF::Writer>. =head1 AUTHOR Sean M. Burke, E<lt>sburke@cpan.orgE<gt> =cut # zing! # s : Single-thickness border # th : Double-thickness border # sh : Shadowed border # db : Double border # dot : Dotted border # dash : Dashed border # hair : Hairline border # inset : Inset border # dashsm : Dashed border (small) # dashd : Dot-dashed border # dashdd : Dot-dot-dashed border # outset : Outset border # triple : Triple border # tnthsg : Thick-thin border (small) # thtnsg : Thin-thick border (small) # tnthtnsg : Thin-thick thin border (small) # tnthmg : Thick-thin border (medium) # thtnmg : Thin-thick border (medium) # tnthtnmg : Thin-thick thin border (medium) # tnthlg : Thick-thin border (large) # thtnlg : Thin-thick border (large) # tnthtnlg : Thin-thick-thin border (large) # wavy : Wavy border # wavydb : Double wavy border # dashdotstr : Striped border # emboss : Embossed border # engrave : Engraved border # frame : Border resembles a "Frame" ������������������RTF-Writer-1.11/lib/RTF/Writer.pod������������������������������������������������������������������0100644�0001773�0001773�00000074037�07751705205�016130� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� #Time-stamp: "2003-11-04 02:16:21 AST" =head1 NAME RTF::Writer - for generating documents in Rich Text Format =head1 SYNOPSIS use RTF::Writer; my $rtf = RTF::Writer->new_to_file("greetings.rtf"); $rtf->prolog( 'title' => "Greetings, hyoomon" ); $rtf->number_pages; $rtf->paragraph( \'\fs40\b\i', # 20pt, bold, italic "Hi there!" ); $rtf->close; =head1 DESCRIPTION This module is for generating documents in Rich Text Format. This module is a class; an object belonging to this class acts like an output filehandle, and calling methods on it causes RTF text to be written. Incidentally, this module also exports a few useful functions, upon request. The following documentation assumes some familiarity with the RTF Specification. Users not already intimately familiar with RTF should look at L<RTF::Cookbook|RTF::Cookbook> and/or my book I<RTF Pocket Guide> from O'Reilly, L<http://www.oreilly.com/catalog/rtfpg/> =head1 METHODS =over =item $h = RTF::Writer->new_to_file($filename); This creates a new RTF output stream object, such that sending text to this object will write to the filespec given. This is basically a wrapper around new_to_handle. If opening a write-handle to $filename fails (or if $filename is undef or zero-length), then a fatal error results. =item $h = RTF::Writer->new_to_handle(*FILEHANDLE); This creates a new RTF output stream object, such that sending text to this object will write to the filehandle given. The filehandle can be a glob (*FH) or a filehandle object (*FH{IO} or the value from C<IO::File-E<gt>new(...)>). =item $h = RTF::Writer->new_to_string(\$string); This creates a new RTF output stream object, such that sending text to this object will append to the string that you've passed a reference to. =item $h->print(...); This is the basic method for writing text to an RTF stream. This takes a list of items. Each item is either: =over =item a plain string, like "foo\n" In this case, the value is imputed to be a plaintext string, and an rtf-escaped version of it is written. For example C<"Stuff\n\t\tUmmm\n"> causes C<'Stuff\line \tab \tab Umm\line '> to be written. See C<rtfesc(x)> for further details of escaping. =item a scalar-reference, like \'\ul' In this case, the value is imputed to be a reference to I<already escaped> text. This is the basic way to emit RTF codes. Text passed this way will be written without any additional escaping. Unless $RTF::Writer::AUTO_NL (normally on) has been turned off, the item written will be followed with a (presumably harmless) newline character to delimit any code in there from any following text, I<if> the last character of this string is a digit or a lowercase letter. This is so that C<(\'\i', "foo!")> emits C<\i[newline]foo!'> (which does what you expected), instead of C<'\ifoo!'>, which looks like an RTF command "ifoo" followed by a plaintext "!". =item an array-reference, like [ \'\ul', 'foo' ] This emits an open-brace "{", as RTF uses for opening "groups" (generally for delimiting the effects of character-formatting commands like '\ul', or a few formatting commands like '\footnote'); then it emits the items in the referred-to array; and then emits a closing "}". I intend this to be useful is making sure that you don't emit more open-braces than close-braces, since that usually makes RTF readers immediately reject such a file. You can nest these array-references, like: $h->print( \'\col2', [ \'\pard', "It is now ", [ \'\f1', scalar(localtime), " local, or ", scalar(gmtime), " GMT.", ], " -- if you're ", [ \'\i', "keeping track.", ], ], \'\par\page', ); =back The return value of the print() method is currently always the value 1, although this may change. =item $h->prolog(...); This writes an RTF prolog to $h. You are free to make your own prolog using just $h->print(\'...your own code...'), but I find in easier to automate this task, particularly with some sane defaults. Since emitting a prolog opens a "{"-group, calling $h->prolog(...) sets a flag in $h so that when you call $h->close(), a closing "}" will automatically be written before the stream object is actually closed. The options to the prolog() method are passed as a list of keys and values, for controlling the contents of the prolog written. The options are listed below, roughly with the most important options first. (Be careful with the spelling of these options. Some are rather odd, because they are (mostly) based on the name of the relevent RTF command, and a systematic naming scheme for commands is one thing you won't find in RTF!) =over =item 'fonts' => [ "Courier New", "Georgia", "Whatever"...], This value is for the font table section of the prolog. If the value is an arrayref, then it should be a reference to an array whose items should be either plain text strings, like "Times Roman", which are the (unescaped) names of fonts; or the items in the array can be scalar-refs, for expressing RTF control words along with the (escaped) font name, as in C<\'\froman Times New Roman'>. If the value of the "fonts" parameters is a scalar ref, then it is taken to be a reference to code of your own that expresses the whole font table. If you don't specify a value for the "font" option, then you get a font table with one entry, "Times New Roman". You should be sure to declare all fonts that you switch to in your document (as with \'\f3', to change the current font to what's declared in entry 3 (counting from 0) in the font table). =item 'deff' => INTEGER, This is for expressing, in the prolog, the font-table number of the default font for this document. The default is 0, which is an often useful value. =item 'colors' => [ undef, [0,142,252], [200,32,0], ...], This value is for expressing the document's (generally optional) color table. If you stipulate an arrayref value, then each item of the array should be either an RGB triplet expressed as an arrayref like [200,32,0], or undef, for a null color-entry. If you stipulate a scalar-ref value for 'colors', then it is taken to be a reference to code of your own that expresses the whole font table. If you don't stipulate any value for 'colors', then you get a table consisting of three colors: null/default (undef), 100% red ([2550,0,0]), and 100% blue ([0,0,255]). You can freely ignore concerns of color tables if you don't use color-changing codes in your document (like \'\cf2', to switch the text foreground color to what's declared at entry 2 (starting from 0) in the color table). =item 'stylesheet' => STRING, =item 'filetbl' => STRING, =item 'listtables' => STRING, =item 'revtbl' => STRING, These are for expressing, in the prolog, code constituting the document's style sheet, table-of-files, table-of-lists, and table-of-revisions, respectively. The default value of each of these is empty-string. None of these are needed by a typical RTF document. =item 'more_default' => STRING, This is for inserting any additional code just after the '\deffN' in the start of the prolog, before the font table. A common useful value here is \'\deflang1033', to express the default language (1033 = RTFese for US English) for the document, although my reading of the RTF spec leads me to believe that this doesn't need to be in the prolog here (where many writers put it, as apparently accepted by many RTF readers), but should (instead?) go just after the prolog, with other "document formatting" commands described in the "Document Formatting Properties" section of the RTF Specification. =item 'doccomm' => STRING, This value is for the "document comment" metainformation item in the prolog, which appears as the "Comment" field in the "File Properties" panel in MSWord, or as the "Abstract" field in the "File Properties" window in WordPerfect. If no value is specified, then RTF::Writer puts a string noting the value of C<$0> (typically the filespec to the current Perl program), and the version of RTF::Writer used. =item 'title' => STRING, =item 'subject' => STRING, =item 'author' => STRING, =item 'manager' => STRING, =item 'company' => STRING, =item 'operator' => STRING, =item 'category' => STRING, =item 'keywords' => STRING, =item 'hlinkbase' => STRING, =item 'comment' => STRING, These are for stipulating the string values of these various optional document metainformation items. 'operator' is for the name of the person who last made changes to the document; 'hlinkbase' is which is the URL or path that is used for for resolving any all relative hyperlinks in the document; 'comment' is reportedly just ignored (cf. the 'doccomm' attribute, which is I<not> ignored); and you can guess the rest. The meanings of all of these are explained in greater detail in the RTF spec. =item 'revtim' => EPOCH_NUMBER, This value is for the document metainformation section of the prolog. It signifies the last-modified time of the document. EPOCH_NUMBER is the number of seconds since the epoch, such as one gets from C<(stat($thing)[9])> or C<time()>; or you may pass a reference a timelist, like [localtime($whatever)]. If no defined value for revtime is stipulated in the call to prolog(...) then the current value of time() is used. Explicitly pass a value of undef to suppress emitting any 'creatim' value. =item 'creatim' => EPOCH_NUMBER, This value is for the document metainformation section of the prolog. It signifies the last-modified time of the document. If no defined value for 'creatim' is stipulated in the call to prolog(...) then the current value of time() is used. Explicitly pass a value of undef to suppress emitting any 'creatim' value. =item 'printim' => EPOCH_NUMBER, This value is for the document metainformation section of the prolog. It signifies the time when this document was last printed. If you don't stipulate a defined value here, no 'printim' metainformation is written. =item 'buptim' => EPOCH_NUMBER, This value is for the document metainformation section of the prolog. It signifies the "backup time" of this document. If you don't stipulate a defined value here, no 'buptim' metainformation is written. =item 'version' => INTEGER, =item 'vern' => INTEGER, =item 'edmins' => INTEGER, =item 'nofpages' => INTEGER, =item 'nofwords' => INTEGER, =item 'nofchars' => INTEGER, =item 'nofcharsws' => INTEGER, =item 'id' => INTEGER, These are for stipulating the integer values of these various optional (and not terribly useful, for most purposes!) document metainformation items. The meanings of all of these are explained in the RTF spec. =item 'charset' => STRING, This is for expressing, in the prolog, RTF codename for the character set being used in this document. The default is "ansi", and don't stipulate anything else (like "mac", "pc", or "pca") unless you know what you're doing. =item 'rtf_version' => INTEGER, This is for expressing, in the prolog, what major version of RTF is being used in this document. The default is 1, and don't use anything else unless you really know what you're doing. =back =item $h->printf('format', ...items...); This is just short for $h->print(sprintf('format', ...items...) =item $h->printf(\'format', ...items...); In this case, 'format' is assumed to contain already-escaped RTF code. The items in ...items... are escaped as necessary, and then interpolated. I.e., this is rather like: $h->print(\sprintf 'format', map rtfesc($_), ...items...)) except that numeric items don't get escaped (and don't need to be). Example: $h->printf( \'{\i "%s"} was found in %2.2f percent of matches\par', $word, 100 * $count / $total ); =item $h->number_pages(); =item $h->number_pages(...); This is just a handy wrapper for some code that turns on page numbering. If you call this method, you should call it right after you emit a prolog. The page numbering consists of just putting the page number at the top-right of each page. If you provide items in the list (...), then that is pre-pended to the page number. Example: $h->number_pages("Lexicon, p."); Or: $h->number_pages(\'\b\fs30\f2', "page "); =item $trdecl = RTF::Writer::TableRowDecl->new( ...options... ) This constructs an object representing a declaration for a table row. You can have to use it in calls to $h->row($tabldecl,...), and can reuse it on subsequent calls. This object is for declaring the dimensions of table rows. The work that a declaration has to do, is best explained in this diagram of a bordered three-cell table (first cell containing "Foo ya!"), placed near a left margin (shown as the line of colons). The things in brackets are not on the page, but just for our reference: : [..w1...] : [......w2.......] : [...w3....] [.A..] [.B.] [.B.] : : +-------+---------------+---------+ : | Foo | Bar baz | Yee! | : | ya! | quuxi quuxo | | : | | quaqua. | | : +-------+---------------+---------+ : [.A..] [.B.] [.B.] [..r1........] [.....r2.....................] [........r3............................] Here the horizontal dimensions of the three-celled table are expressed in terms of: A, the distance from the current left margin; B, the minimum distance between the content of the cells (or you can think of this as twice the internal left or right borders in each cell); and then EITHER [w1, w2, w3], expressing the width of each cell, OR [r1, r2, r3], expressing each cell's right end's distance from the current left margin. All distances are, of course, in twips. Options to RTF::Writer::TableRowDecl->new( ...options... ) are: =over =item left_start => TWIPS, This declares the distance between the left margin, and the left end of the table. Default is 0. =item inbetween => TWIPS, This declares the distance labelled "B", above. Default is 120, which is 6 points, 1/12th-inch, about 2mm. =item widths => [TWIPS, TWIPS, TWIPS, ... ], This expresses the widths of each of the cells in this row, starting from the leftmost. =item reaches => [TWIPS, TWIPS, TWIPS, ... ], This expresses the rightmost extreme of each of the cells in this row. =item align => I<alignmentspecs>, This is explained in detail in the L<section "Cell Alignment Syntax", below.|/"Cell Alignment Syntax"> =item borders => I<borderspecs>, This is explained in detail in the L<section "Cell Border Syntax", below.|/"Cell Border Syntax"> =back =item $h->paragraph(...); This makes the items in the list (...) into a paragraph. Basically just a wrapper for $h->print([ \'{\par', ..., \'\pard}', ]) =item $h->row($trdecl, ...items...); This emits a table row, with dimensions as stipulated by the $trdecl object, and with row content from the items given. You must provide a value for $trdecl, or a fatal error results. If you provide I<fewer> items than $trdecl declares cells, then you get empty cells to fill out the row. If you provide I<more> items than $trdecl declares cells, then the width of the last declared row is used in figuring the width of the additional cells for this row. Example: my $decl = RTF::Writer::TableRowDecl->new('widths' => [1500,1900]); $h->row($decl, "Stuff", "Hmmm"); $h->row($decl, [\'\ul', 'Foo'], 'Bar', \'\bullet'); $h->row($decl, "Hooboy."); This creates a table resembing: +-------------+-------------------+ | Stuff | Hmm | +-------------+-------------------+-------------------+ | _Foo_ | Bar | * | +-------------+-------------------+-------------------+ | "Hooboy." | | +-------------+-------------------+ Note that you I<MUST NOT> use '\par' commands in any items you emit in row cells! The $h->row(...) method is a wrapper for producing elementary tables in RTF, with the minimum of parameters; the myriad other options that tables can have (for example, changing borders) are not supported. If you really need to generate tables fancier than what $h->row(...) can produce, start off reading the RTF spec, reading the source for row() (and the RTF::Writer::TableRowDecl class), and progress from there. Note that MSWord has been known to crash when given malformed RTF table code. =item $h->table($trdecl, [...row1 items...], [...row2 items...], ... ); =item $h->table([...row1 items...], [...row2 items...], ... ); This is a wrapper around $h->row. It takes a list of arrayrefs, which are fed to calls to h->row($tr_decl, @$each_arrayref). You should provide a $trdecl, but if you don't, then one is I<crudely> guessed at, based on the maximum number of columns in all rows. =item $h->image( I<image_parameters> ) This returns a scalar-reference to RTF-code representing the given image with given parameters. For example: $h->paragraph( "See here: ", $h->image( 'filename' => "foo.png", ), ); The legal options are explained below: =over =item filename => FILENAME, This should be the path to a readable filename. You have to specify this. If you don't specify this, or if the value isn't a readable file, then a fatal error results. Currently, only JPEGs and PNGs are allowed; specifying any other kind of file causes a fatal error. (The C<filename> option above is required, but the following options are all generally optional -- altho some RTF processors may be finicky if you set some of the following but not others, for no apparent reason. When in doubt, test.) =item wgoal => TWIPS, The desired width of the image =item hgoal => TWIPS, The desired height of the image =item scalex => PERCENT, =item scaley => PERCENT, Respectively, the horizontal (X) or vertical (Y) scaling value. The argument is an integer representing a percentage. (The default is 100 percent) =item cropt => TWIPS, =item cropb => TWIPS, =item cropl => TWIPS, =item cropr => TWIPS, These specify the top, bottom, left, and right cropping values. A positive value crops I<toward> the center of the image. A negative value crops I<away> from the center, adding a padding space around the image. (The default is to do neither, as you'd get from a cropping value of 0.) =item picspecs => \SCALARVALUE, This overrides generation of the normal image values based the image and the above parameters, and instead uses whatever value you pass a reference to. You normally shouldn't need to use this. =back =item $h->image_paragraph( I<image_parameters> ); This take the same options as C<< $h->image(...) >>, but has three differences: First, it is a shortcut for this: $h->paragraph( \'\qc', $h->image( ...params...), ); Secondly, whereas C<< $h->image(...) >> returns the image data (as an RTF scalarref), C<< $h->image_paragraph(...) >> doesn't return much of anything. Thirdly, C<< $h->image_paragraph(...) >> is often much more memory-efficient, since it can write the image data to a file as it's RTF-ified, instead of building it all up in memory. =item $h->close(); This completes writing to the stream denoted by the object in C<$h>; this generally (assuming you'd called $h->prolog) involves just writing a final close-brace to $h, and then closing whatever filehandle or file $h writes to (unless we're writing to a string, in which case we just discard $h's reference to it). After you call C<$h-E<gt>close>, you should not call any other methods with C<$h>! Note that you don't I<have> to explicitly call C<$h-E<gt>close> -- when an unclosed RTF::Writer object goes out of scope (or, more precisely speaking, when if its refcount hits zero), then something equivalent to calling C<$h-E<gt>close> is done automatically for you. =back =head1 AUTOMETHODS In addition to any of the above methods, you can use any RTF command (and optional integer arguments) as a valid method name, by just capitalizing its first letter, as shown below: =over =item $h->Foo(); The same as $h->print( \'\foo' ); For example, $h-E<gt>Page() is the same as $h-E<gt>print(\'\page') =item $h->Foo(...); (Where "..." is a non-empty list.) The same as $h->print( [ \'\foo', ... ] ); For example: C<$h-E<gt>I('stuff')> is the same as $h-E<gt>print([\'\i', 'stuff']) =item $h->Foo123(); The same as $h->print( \'\foo123' ). I.e., command word "\foo" with an integer argument of 123. For example: $h-E<gt>Cols2() is the same as $h-E<gt>print(\'\cols2') =item $h->Foo123(...); (Where "..." is a non-empty list.) The same as $h->print( [ \'\foo123', ... ] ); For example: $h->F2('stuff') is the same as $h->print([\'\f2', 'stuff']). =item $h->Foo_123(); The same as $h->print( \'\foo-123' );, i.e., command word "\foo" with an integer argument of negative 123. (You can't have a "-" in a method name, so I use an underscore instead.) For example: $h->Li_1440() is the same as $h->print([\'\li-1440', 'stuff']) =item $h->Foo_123(...); (Where "..." is a non-empty list.) The same as $h->print( [ \'\foo-123', ... ] ); =back =head1 FUNCTIONS None of these functions are exported by default, but they can be exported on request, as in: use RTF::Writer qw(inches cm rtfesc); =over =item inch($x), inches($x), in($x) These synonymous functions all construe the numeric value in $x as inches, and return the equivalent number of twips. For example, C<inches(1.5)> returns 2160, because an inch and a half is exactly 2160 twips. The return value of these functions is always an integer, as fractions of twips are not used in RTF. =item point($x), points($x), pt($x) These synonymous functions all construe the numeric value in $x as points, and return the equivalent number of twips. For example, C<points(54)> returns 1080, because fifty-four points is exactly 1080 twips. The return value of these functions is always an integer, as fractions of twips are not used in RTF. =item cm(x) This function construes the numeric value in $x as centimeters, and returns the equivalent number of twips. For example, C<cm(1.5)> returns 850, because 1.5cm is approximately 850 twips (i.e., it's 850, when rounded to the nearest whole number). Since twips and points are both are defined in terms of inches (1440 twips = 72 points = 1 inch), conversion between cm and these other units is approximate. The return value of C<cm($x)> is always an integer, as fractions of twips are not used in RTF. =item rtfesc($text); # void context =item rtfesc($x, $y); # void context =item rtfesc(@z); # void context =item $escaped = rtfesc($x); =item @escaped = rtfesc($x, $y, ...); This escapes some plaintext so it's good RTF. E.g., it turns "Foo\nBar\\" into "Foo\n\\line Bar\\'5c" (since a plaintext backslash needs to be escaped in RTF, and a "\n"'s RTF equivalent is the '\line' command). In void context (i.e., where you aren't capturing the return value), this in-place alters the values you pass it. In scalar or list context, doesn't alter the original(s), but returns an escaped copy of what you pass in. =back =head2 Cell Alignment Syntax To control alignment of cells, specify C<< align => "I<direction direction direction...>" >>, where each direction is one of these alphametic strings for the given directions (based on the abbreviated English names for map directions and canvas directions): NW N NE TL T TR \ | / \ | / W - C - E L - C - R / | \ / | \ SW S SE BL B BR For example, C<< align => "nw c" >> means that the first cell will be aligned to the B<I<n>>orthB<I<w>>est (a.k.a. the B<I<t>>op-B<I<l>>eft), and that the second cell (and any cells thereafter) will be aligned to the B<I<c>>enter. An acceptable alternate syntax is to C<< align => ['nw', 'c'] >> -- i.e., to pass a reference to an array of I<'direction'> items, instead of just passing a single scalar of whitespace-padded directions. (Note that alignment syntax and cell border syntax, may look a bit alike, but are really very different; try not to mix them up.) =head2 Cell Border Syntax To specify what borders occur on cells, use one of the following syntaxes: =over =item C<< borders => 1, >> or C<< borders => 'all', >> to turn on a simple border for all sides of all cells. B<This is the default --> so if you don't specify a C<< borders => I<something> >> option, it will be as if you specified C<< borders => 1 >>. =item C<< borders => 0, >> or C<< borders => 'none', >> to turn off all borders for all cells. B<In previous versions of RTF::Writer, this I<was> the default.> =back or use this complex syntax for finer control: =over =item C<< borders => [ I<cellborders, cellborders, ...> ], >> =back ...where each C<cellborders> is a string in the form "I<border border border>", where, in turn, each I<border> is a substring in the form I<"direction-thickness-type",> I<"direction-type"> I<"direction-thickness",> or I<"direction".> Alternately, C<cellborders> can be one of these shorter values: =over =item the value "none" -- meaning no borders in any direction =item an integer between 2 and 75 -- meaning a simple border that's that many twips thick, on all sides. (So specifying "22" is synonymous with "all-22-s" in the longer syntax.) =item empty-string or undef -- meaning a simple border of the default thickness on all sides. (So specifying "" is synonymous with "all", which is in turn synonymous with "nsew-15-s".) =back I<direction> is either "all", or a combination of some of the uppercase or lowercase letters N, S, E, W, T, B, R, L. (Of course, the first four are synonymous with the other four, respectively.) I<thickness> (by default, 15) is an integer between 1 and 75, specifying the thickness of the border, in twips. And I<type> (by default, "s") is one of these, as specified in the RTF spec: s : Single-thickness border th : Double-thickness border sh : Shadowed border db : Double border dot : Dotted border hair : Hairline border dash : Dashed border inset : Inset border dashsm : Dashed border (small) dashd : Dot-dashed border dashdd : Dot-dot-dashed border outset : Outset border triple : Triple border tnthsg : Thick-thin border (small) thtnsg : Thin-thick border (small) tnthtnsg : Thin-thick thin border (small) tnthmg : Thick-thin border (medium) thtnmg : Thin-thick border (medium) tnthtnmg : Thin-thick thin border (medium) tnthlg : Thick-thin border (large) thtnlg : Thin-thick border (large) tnthtnlg : Thin-thick-thin border (large) wavy : Wavy border wavydb : Double wavy border dashdotstr : Striped border emboss : Embossed border engrave : Engraved border frame : Border resembles a "frame" Not all of the above are supported by all RTF readers. If you're concerned about portability, consider sticking to the core set of just the first six listed above. Also, the syntax C<< borders => cellspec >> is accepted as a synonym for C<< borders => [cellspec] >>, for when you're specifying just a single cellspec, for use the the first and all subsequent cells. Cell border syntax is best shown by example: borders=> [ "ns-30-db w-25", "all-10-wavy", "none", 13 ], That means to that the first cell should have a 30-twip-thick double border on the top and bottom (north and south) and a 25-twip-thick single border on the west (and no border on the east side); the second cell should have a 10-twip-thick wavy border on all sides; the third cell should have no borders on any sides; and the fourth (and any additional) cells should have a 13-twip-thick single border on all sides. Incidentally, when a particular I<cellspec> contains apparently contradictory declarations, the last one is the one that has an effect. For example, consider S<C<"all-20-db w-10-s">> -- the first part turns on 20-twip double borders on all sides, and the second part turns on a 10-twip single border on the west side. Since the second part is last, that's the one that has an effect -- so just the north, south, and east sides actually get a 20-twip double border, and the west side gets the 10-twip single border. (This means that if you say S<C<"w-10-s all-20-db">>, the first part will have no effect, because the second part will override the west-side declaration.) =head2 Cell Border Syntax, Formally If you'd prefer a more formal grammar for this all, this should help: borderdec := 'borders' => '0' # no borders at all | '1' # same as ["all-15-s"] | [ cellspec, cellspec, ... ] | cellspec # default for one-cell form of the above cellspec := "" | undef # same as "all-15-s" | int # same as "all-INT-s" (note: 2 <= int <= 75) | "none" # no borders on this cell | (border ( ', ' . border )* ) # a list of border expressions separated by # a comma (and/or whitespace, in fact) border := direction-thickness-type # For example, "nse-15-s" | direction-type # same as "DIR-15-TYPE" | direction-thickness # same as "DIR-THICK-s" | direction # same as "DIR-15-s" direction := "all" | qr/^[nsewtblrNSEWTBLR]+$/ # Note that "nw" doesn't mean the direction northwest, but # simultaneously the north and west sides. thickness := integer in the range 1 - 75 type := "s" | "th" | "sh" | "db" | "dot" | "hair" | (etc) =head1 SEE ALSO L<RTF::Cookbook|RTF::Cookbook> The book I<RTF Pocket Guide> from O'Reilly. L<http://www.oreilly.com/catalog/rtfpg/> =head1 COPYRIGHT AND DISCLAIMER Copyright 2001,2,3 Sean M. Burke. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. The author of this document is not affiliated with the Microsoft corporation. Product and company names mentioned in this document may be the trademarks or service marks of their respective owners. Trademarks and service marks are not identified, although this must not be construed as the author's expression of validity or invalidity of each trademark or service mark. =head1 AUTHOR Sean M. Burke, E<lt>sburke@cpan.orgE<gt> =cut # So there. # �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/Makefile.PL�������������������������������������������������������������������������0100644�0001773�0001773�00000001532�07743130266�014707� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # Time-stamp: "2003-10-14 18:28:06 ADT" # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. require 5.005; # we need m/...\z/ use strict; use ExtUtils::MakeMaker; WriteMakefile( 'NAME' => 'RTF::Writer', 'VERSION_FROM' => 'lib/RTF/Writer.pm', # finds $VERSION 'ABSTRACT_FROM' => 'lib/RTF/Writer.pod', 'PREREQ_PM' => { 'Exporter' => 0, 'Carp' => 0, 'File::Path' => 0, # the tests use it #'utf8' => 0, 'Image::Size' => 0, # I /think/ any version'll do 'strict' => 0, 'UNIVERSAL' => 0, }, 'dist' => { COMPRESS => 'gzip -6f', SUFFIX => 'gz', }, ); package MY; sub libscan { # Determine things that should *not* be installed my($self, $path) = @_; return '' if $path =~ m/~/; $path; } __END__ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/ChangeLog���������������������������������������������������������������������������0100644�0001773�0001773�00000006426�07751705542�014522� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Revision history for Perl extension RTF::Writer Time-stamp: "2003-11-04 02:20:02 AST" 2003-11-04 Sean M. Burke sburke@cpan.org * Release 1.11 * Minor bugfix version * Vitor Hugo reports a problem where ->image and ->image_paragraph when called with a picspecs parameter would forget to put whitespace before the start of the picture data. Fixed! * A few little doc typoes fixed. 2003-10-14 Sean M. Burke sburke@cpan.org * Release 1.10 * Loc Bourgeois catches a nasty error with $rtf->image -- it generates one more } than {'s, unbalancing the whole document! (No problem with image_paragraph, though.) Fixed! * Tests rewritten to use File::Spec instead of chdir'ing. * Added tests for well-formedness ({}-matching). 2003-09-26 Sean M. Burke sburke@cpan.org * Release 1.09 * Just fixed the Makefile.PL to declare the Image::Size dependency. 2003-09-23 Sean M. Burke sburke@cpan.org * Release 1.08 * Now supports borders and alignment in table cells. * Now supports embedding images, thanks to some pointers from David Thielen. * CHANGE: previously tables by default had no borders. Now they have a thin solid-line border, by default. (The original behavior was an accident on my part, by the way.) * More tests. * No longer requires utf8, thanks to a hack adapted from Slaven Rezic: BEGIN { eval {require utf8}; $INC{"utf8.pm"} = "dummy_value" if $@ } use utf8; 2002-12-07 Sean M. Burke sburke@cpan.org * Release 1.07 Bugfix: changed internal object %Escape to @Escape, to get around some utf8 bugs in hash lookups on utf8-strings. Bugfix: Now handles Unicode characters. But now requires the utf8 module. Probably won't work under versions before 5.6 -- altho you could always create a utf8.pm consisting of just "1;". Bugfix: minor change to the code that inserts newlines in the output RTF. Previous versions could erroneously change "\foo \bar" to "\foo[NL] \bar". Now correctly changes to "\foo [NL]\bar". Bugfix: the pod documented a new_to_handle, but the routine was previously called new_to_fh. Now there's a new new_to_handle aliases to new_to_handle. Added compile-time assertion that module is running in an ASCII world. It never worked right under EBCDIC (etc) anyway. Full Unicode support doesn't really turn on unless you're under Perl 5.7 or later. Altho you can force it by turning on $RTF::Writer::Unicode. I do this to get around some segfaulty errors under 5.6. 2001-07-27 Sean M. Burke sburke@cpan.org * Release 1.06 -- bugfix: TableRowDecl would emit pointless warnings because of int()ing an undefined value. Fixed. 2001-06-21 Sean M. Burke sburke@cpan.org * Release 1.05 -- bugfix: on failure, new_to_file stupidly reported $1 instead of $!. Ironic, huh? Fixed. 2001-05-27 Sean M. Burke sburke@cpan.org * Release 1.04 -- fixing some doc typos caught by Matt Olson <matto@advanis.ca> and "David D. Kilzer" <ddkilzer@theracingworld.com> 2001-05-16 Sean M. Burke sburke@cpan.org * Release 1.03 -- Just fixing minor typos in the docs. 2001-04-26 Sean M. Burke sburke@cpan.org * Release 1.02 -- no substantive changes; just fixing the horribly screwed up makefile. 2001-04-24 Sean M. Burke sburke@cpan.org * Release 1.01 -- first public release ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/examples/���������������������������������������������������������������������������0040711�0001773�0001773�00000000000�07751705660�014552� 5����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/examples/la_dame.rtf����������������������������������������������������������������0100644�0001773�0001773�00000003070�07734222161�016645� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������{\rtf1\ansi\deff0 {\fonttbl {\f0 Times New Roman;} } \deflang1033\widowctrl {\header\pard\qr\plain\f0{\noproof\i La dame} p.\chpgn\par} \lang1036\fs36 {\pard\qc\f1\fs60\b\i La dame\par} {\pard\sb300\li900 Toc toc Il a ferm\'e9 la porte\line Les lys du jardin sont fl\'e9tris\line Quel est donc ce mort qu'on emporte \par} {\pard\sb300\li900 Tu viens de toquer \'e0 sa porte\line Et trotte trotte\line Trotte la petite souris \par} {\pard\sb900\li900\scaps\fs44 \endash Guillaume Apollinaire, {\i Alcools}\par} \page\lang1033\fs32 {\pard\b\ul\fs40 Vocabulary\par} {\pard\li300\fi-150{\noproof\b toc }{\i(n.m.)} \endash tap; knock\par} {\pard\li300\fi-150{\noproof\b lys }{\i(n.m.)} \endash lily\par} {\pard\li300\fi-150{\noproof\b fl\'e9trir } {\i(v.itr.)} \endash to wilt; for a flower or beauty to fade; for a plant to wither\par} {\pard\li300\fi-150{\noproof\b mort } {\i(adj., here used as a masc. noun)} \endash dead\par} {\pard\li300\fi-150{\noproof\b emporter } {\i(v.tr.)} \endash to take a person or thing [somewhere]; to take\~[out/away/etc.] or carry\~[away] a thing\par} {\pard\li300\fi-150{\noproof\b toquer } {\i(v.itr.)} \endash to tap; to knock\par} {\pard\li300\fi-150{\noproof\b trotter } {\i(v.itr.)} \endash to trot; to scurry\par} {\pard\li300\fi-150{\noproof\b souris }{\i(n.f.)} \endash mouse\par} {\pard\sb200\b\ul\fs40 Free Translation\par} {\pard Click click He closed the door / Garden lilies faded / Which body is today // You just tapped on the door / And tip toe / Taps the little mouse \line \_Translation Sean M. Burke, 2001 \par} } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/examples/demo_writer.pl�������������������������������������������������������������0100644�0001773�0001773�00000006777�07734222161�017444� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 5.005; use strict; # Time-stamp: "2001-04-24 01:50:43 MDT" use RTF::Writer 1.01; =head1 NAME demo_writer.pl -- a lame sample program for outputting RTF =head1 SYNOPSIS Read the source of this program. It's ejumucational. =head1 DESCRIPTION One of my many superpowers is writing Perl programs that read lexical databases, chew them up, and spit out a tidily formatted dictionary, in RTF. Why RTF? Just convenience -- I happened to know a bit about RTF, and I didn't know TeX. (And XML+XSL didn't exist at the time.) For ages I wrote (and rewrote, anew each time) completely ad-hoc code to spit out RTF. But after a few years of having to consult the icky I<RTF Specification> to remember how to turn on page numbering, or emit a useful prolog, I decided to write RTF::Writer, to simplify these tasks. This program, demo_writer.pl, is just an example program that uses RTF::Writer to emit RTF. The RTF it happens to emit, is a miniature lexicon file, based an a miniature lexical database as input. See also L<RTF::Writer|RTF::Writer> and L<RTF::Cookbook|RTF::Cookbook>. =head1 AUTHOR Sean M. Burke, sburke@cpan.org =cut my $nl = " "; my @records = split("\n\n", q{ \hw toc \en tap \en knock \pos n.m. \hw lys \pos n.m. \en lily \en knock \hw fltrir \pos v.itr. \en to wilt \en for a flower or beauty to fade \en for a plant to wither \hw mort \pos adj. \en dead \xref mourir \hw emporter \pos v.tr. \en to take a person or thing [somewhere] \en to take [out/away/etc.] or carry [away] a thing \hw toquer \pos v.itr. \en to tap \en to knock \hw trotter \pos v.itr. \en to trot \en to scurry \hw souris \pos n.f. \en mouse \semantic_field animals }); #------------------------------------------------------------------------ { # A private class for lexicon entries: package _SMB::Lexicon::Entry; sub get ($) { my $x = $_[0]->{$_[1]} || []; return @$x if wantarray; return join '', @$x; } } #------------------------------------------------------------------------ # Init records: foreach my $r (@records) { my %hash; foreach my $l (grep m/\S/, split "\n", $r) { if($l =~ m/^\\(\w+)\s+(.+)/s) { # print "<$1> <$2>\n"; push @{$hash{$1} ||= []}, $2; } else { die "Line <$l> is bonkers"; } } $r = bless(\%hash, '_SMB::Lexicon::Entry') if scalar keys %hash; } { my $nil = ['']; @records = # Actually we'd really want to use Sort::ArbBiLex instead sort { # Sort according to the (first) headword lc( ($a->{'hw'} || $nil )->[0] ) cmp lc( ($b->{'hw'} || $nil )->[0] ) } grep ref($_), @records; } my $rtf = RTF::Writer->new_to_file('lex_out.rtf'); $rtf->prolog; $rtf->number_pages("Lexicon, p."); $rtf->paragraph(\'\sa400\fs44\scaps', "Sample Lexicon",); #------------------------------------------------------------------------ # Write out each record: foreach my $r (@records) { next unless ref $r; # use Data::Dumper; print Dumper($r), "\n\n"; my @stuff; $rtf->printf( \'{\pard\li300\fi-150\plain\fs24' ); my $hw = $r->get('hw'); my $pos = $r->get('pos'); if($pos) { $rtf->printf( \'{\fs30\b\lang1036\noproof %s}{\i (%s)} \endash ', $hw || '??', $pos ); } else { $rtf->printf( \'{\fs30\b\lang1036 %s} \emdash ', $hw || '??' ); } $rtf->print(join '; ', $r->get('en')); $rtf->printf( \"\\par}\n\n" ); } $rtf->paragraph(\'\sb400', sprintf("--\n%d entries. Generated %s by \xAB%s\xBB", scalar(@records), scalar(localtime), $0 || '??', ) ); exit; �RTF-Writer-1.11/MANIFEST.SKIP�����������������������������������������������������������������������0100644�0001773�0001773�00000000151�07743130560�014624� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������^MANIFEST\.bak$ ^[-_a-zA-Z0-9]+[0-9]+\.[0-9]+(?:_[0-9]+)?$ Makefile(\.old)?$ t/.*.rtf$ \.rej$ CVS blib ~ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/README������������������������������������������������������������������������������0100644�0001773�0001773�00000006561�07734222161�013620� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������README for RTF::Writer Time-stamp: "2001-04-26 17:12:12 MDT" [Excerpted from the POD...] NAME RTF::Writer - for generating documents in Rich Text Format SYNOPSIS use RTF::Writer; my $rtf = RTF::Writer->new_to_file("greetings.rtf"); $rtf->prolog( 'title' => "Greetings, hyoomon" ); $rtf->number_pages; $rtf->paragraph( \'\fs40\b\i', # 20pt, bold, italic "Hi there!" ); $rtf->close; DESCRIPTION This module is for generating documents in Rich Text Format. This module is a class; an object belonging to this class acts like an output filehandle, and calling methods on it causes RTF text to be written. Incidentally, this module also exports a few useful functions, upon request. The following documentation assumes some familiarity with the RTF Specification. Users not already intimately familiar with RTF should look at RTF::Cookbook. [... RTF::Cookbook is included in this dist ...] COPYRIGHT AND DISCLAIMER Copyright 2001 Sean M. Burke. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. The author of this document is not affiliated with the Microsoft corporation. Product and company names mentioned in this document may be the trademarks or service marks of their respective owners. Trademarks and service marks are not identified, although this must not be construed as the author's expression of validity or invalidity of each trademark or service mark. [...end pod excerpt] PREREQUISITES This suite requires perl 5.005 to run. If you really need to run it under perl 5.004, email me and I'll suggest how to get it to work. INSTALLATION You install RTF::Writer et al, as you would install any Perl module library, by running these commands: perl Makefile.PL make make test make install If you want to install a private copy of RTF::Writer in your home directory, then you should try to produce the initial Makefile with something like this command: perl Makefile.PL PREFIX=~/perl See perldoc perlmodinstall for more information and advice. !!!!!!!!!!SPECIAL NOTE!!!!!!!!!! The distribution archive contains two files of some interest, in the examples/ directory. They do not get installed anywhere by the "make install" command, but you should look at them: demo_writer.pl -- sample program using RTF::Writer to generate output. la_dame.rtf -- the "sample complete RTF file" from RTF::Cookbook. DOCUMENTATION POD-format documentation is included in RTF/Writer.pod and RTF/Cookbook.pod. POD is readable with the 'perldoc' utility. See ChangeLog for recent changes. MACPERL INSTALLATION NOTES Don't bother with the makefiles. Just move the lib/RTF directory into your MacPerl site_lib or lib directory. SUPPORT Questions, bug reports, useful code bits, and suggestions for RTF::Writer should just be sent to me at sburke@cpan.org AVAILABILITY The latest version of this suite is available from the Comprehensive Perl Archive Network (CPAN). Visit <http://www.perl.com/CPAN/> to find a CPAN site near you. �����������������������������������������������������������������������������������������������������������������������������������������������RTF-Writer-1.11/META.yml����������������������������������������������������������������������������0100644�0001773�0001773�00000001016�07751705657�014216� 0����������������������������������������������������������������������������������������������������ustar �sburke��������������������������sburke�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: RTF-Writer version: 1.11 version_from: lib/RTF/Writer.pm installdirs: site requires: Carp: 0 Exporter: 0 File::Path: 0 Image::Size: 0 strict: 0 UNIVERSAL: 0 distribution_type: module generated_by: ExtUtils::MakeMaker version 6.17 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������