1 ! Copyright (C) 2011-2012 John Benediktsson
2 ! See http://factorcode.org/license.txt for BSD license
4 USING: accessors assocs calendar combinators environment fonts
5 formatting fry io io.streams.string kernel literals locals make
6 math math.order math.ranges pdf.canvas pdf.values pdf.wrap
7 sequences sorting splitting ui.text xml.entities ;
8 FROM: assocs => change-at ;
9 FROM: sequences => change-nth ;
10 FROM: pdf.canvas => draw-text ;
18 ! margin-left, margin-right += inset-width
21 ! margin-left, margin-right -= inset-width
26 ! TUPLE: spacer width height ;
29 ! TUPLE: image < span ;
32 ! Outlines (add to catalog):
34 ! /PageMode /UseOutlines
40 ! FIXME: spacing oddities if run multiple times
41 ! FIXME: make sure highlights text "in order"
42 ! FIXME: don't modify layout objects in pdf-render
43 ! FIXME: make sure unicode "works"
44 ! FIXME: only set style differences to reduce size?
45 ! FIXME: gadget. to take a "screenshot" into a pdf?
46 ! FIXME: compress each pdf object to reduce file size?
49 GENERIC: pdf-render ( canvas obj -- remain/f )
51 M: f pdf-render 2drop f ;
53 GENERIC: pdf-width ( canvas obj -- n )
57 : (pdf-layout) ( page obj -- page )
59 dupd [ pdf-render ] with-string-writer
60 '[ _ append ] [ change-stream ] curry dip
61 [ [ , <canvas> ] when ] keep
66 : pdf-layout ( seq -- pages )
69 dup stream>> empty? [ drop ] [ , ] if
73 TUPLE: div items style ;
78 [ style>> set-style ] keep
79 swap '[ _ pdf-render drop ] each f ;
82 [ style>> set-style ] keep
83 items>> [ dupd pdf-width ] map nip supremum ;
88 : convert-string ( str -- str' )
92 } escape-string-by [ 256 < ] filter ;
97 TUPLE: p string style ;
99 : <p> ( string style -- p )
100 [ convert-string ] dip p boa ;
103 [ style>> set-style ] keep
106 over [ font>> ] [ avail-width ] bi visual-wrap
107 over avail-lines short cut
108 [ draw-text ] [ "" concat-as ] bi*
109 ] change-string dup string>> empty? [ drop f ] when ;
112 [ style>> set-style ] keep
113 [ font>> ] [ string>> ] bi* string-lines
114 [ dupd text-width ] map nip supremum ;
117 TUPLE: text string style ;
119 : <text> ( string style -- text )
120 [ convert-string ] dip text boa ;
123 [ style>> set-style ] keep
127 over [ font>> ] [ avail-width ] bi visual-wrap
128 unclip [ "" concat-as ] dip
129 ] [ over line-break f ] if
132 [ { } ] [ over [ font>> ] [ width ] bi visual-wrap ]
134 ] dip [ prefix ] when*
135 over avail-lines short cut
136 [ draw-text ] [ "" concat-as ] bi*
137 ] change-string dup string>> empty? [ drop f ] when ;
140 [ style>> set-style ] keep
141 [ font>> ] [ string>> ] bi* string-lines
142 [ dupd text-width ] map nip supremum ;
152 [ dup 0 > pick avail-lines 0 > and ] [
153 over avail-width over min [ - ] keep [
154 [ over ] dip [ draw-line ] [ inc-x ] 2bi
155 ] unless-zero dup 0 > [ over line-break ] when
157 ] change-width nip dup width>> 0 > [ drop f ] unless ;
169 over avail-lines 0 > [ drop ?break f ] [ nip ] if ;
177 : <pb> ( -- pb ) f pb boa ;
180 dup used?>> [ f >>used? drop f ] [ t >>used? ] if nip ;
187 CONSTANT: table-cell-padding 5
189 TUPLE: table-cell contents width ;
191 : <table-cell> ( contents -- table-cell )
194 M: table-cell pdf-render
196 [ width>> >>col-width 0 >>x drop ]
198 [ [ dupd pdf-render ] map nip ] change-contents
199 dup contents>> [ ] any? [ drop f ] unless
202 width>> table-cell-padding +
203 swap margin>> [ + ] change-left drop
207 TUPLE: table-row cells ;
209 C: <table-row> table-row
211 ! save y before rendering each cell
212 ! set y to max y after all renders
214 M: table-row pdf-render
225 over y>> max-y max max-y!
226 ] map swap max-y >>y drop
229 dup cells>> [ ] any? [ drop f ] unless
232 [ drop margin>> 54 >>left drop ]
234 drop dup width>> >>col-width
235 [ ?line-break ] [ table-cell-padding inc-y ] bi
239 : col-widths ( canvas cells -- widths )
243 [ [ dupd pdf-width ] [ 0 ] if* ] map supremum
248 : change-last ( seq quot -- )
249 [ drop length 1 - ] [ change-nth ] 2bi ; inline
251 :: max-col-widths ( canvas rows -- widths )
254 cells>> canvas swap col-widths
255 [ widths [ 0 or max ] change-at ] each-index
256 ] each widths >alist sort-keys values
258 ! make last cell larger
259 dup sum 400 swap [-] [ + ] curry dupd change-last
261 ! size down each column
262 dup sum dup 400 > [ 400 swap / [ * ] curry map ] [ drop ] if ;
264 : set-col-widths ( canvas rows -- )
265 [ max-col-widths ] keep [
267 [ swap >>width drop ] [ drop ] if*
271 TUPLE: table rows widths? ;
273 : <table> ( rows -- table )
279 dup widths?>> [ 2drop ] [
280 t >>widths? rows>> set-col-widths
285 dup rows>> empty? [ t ] [
286 [ rows>> first dupd pdf-render ] keep swap
288 ] [ [ rest ] change-rows ] until nip
289 dup rows>> [ drop f ] [ drop ] if-empty
294 2drop 400 ; ! FIXME: hardcoded max-width
297 : pdf-object ( str n -- str' )
298 "%d 0 obj\n" sprintf "\nendobj" surround ;
300 : pdf-stream ( str -- str' )
301 [ length 1 + "<<\n/Length %d\n>>" sprintf ]
302 [ "\nstream\n" "\nendstream" surround ] bi append ;
304 : pdf-catalog ( -- str )
312 : pdf-pages ( n -- str )
316 "/MediaBox [ 0 0 612 792 ]" ,
317 [ "/Count %d" sprintf , ]
320 [ "%d 0 R " sprintf ] map concat
321 "/Kids [ " "]" surround ,
324 ] { } make "\n" join ;
326 : pdf-page ( n -- page )
331 1 + "/Contents %d 0 R" sprintf ,
332 "/Resources << /Font <<" ,
333 "/F1 3 0 R /F2 4 0 R /F3 5 0 R" ,
334 "/F4 6 0 R /F5 7 0 R /F6 8 0 R" ,
335 "/F7 9 0 R /F8 10 0 R /F9 11 0 R" ,
336 "/F10 12 0 R /F11 13 0 R /F12 14 0 R" ,
339 ] { } make "\n" join ;
341 : pdf-trailer ( objects -- str )
344 dup length 1 + "0 %d" sprintf ,
345 "0000000000 65535 f" ,
347 over "%010X 00000 n" sprintf , length 1 + +
351 dup length 1 + "/Size %d" sprintf ,
356 [ length 1 + ] map-sum 9 + "%d" sprintf ,
358 ] { } make "\n" join ;
360 TUPLE: pdf-info title timestamp producer author creator ;
362 : <pdf-info> ( -- pdf-info )
366 "USER" os-env "unknown" or >>author
367 "created with Factor" >>creator ;
369 M: pdf-info pdf-value
372 [ timestamp>> [ "/CreationDate " write pdf-write nl ] when* ]
373 [ producer>> [ "/Producer " write pdf-write nl ] when* ]
374 [ author>> [ "/Author " write pdf-write nl ] when* ]
375 [ title>> [ "/Title " write pdf-write nl ] when* ]
376 [ creator>> [ "/Creator " write pdf-write nl ] when* ]
378 ] with-string-writer ;
381 TUPLE: pdf-ref object revision ;
386 [ object>> ] [ revision>> ] bi "%d %d R" sprintf ;
389 TUPLE: pdf info pages fonts ;
397 :: pages>objects ( pdf -- objects )
399 pdf info>> pdf-value ,
401 { $ sans-serif-font $ serif-font $ monospace-font } {
402 [ [ f >>bold? f >>italic? pdf-value , ] each ]
403 [ [ t >>bold? f >>italic? pdf-value , ] each ]
404 [ [ f >>bold? t >>italic? pdf-value , ] each ]
405 [ [ t >>bold? t >>italic? pdf-value , ] each ]
407 pdf pages>> length pdf-pages ,
409 dup length 16 swap 2 range boa zip
410 [ pdf-page , , ] assoc-each
412 dup length [1,b] zip [ first2 pdf-object ] map ;
414 : objects>pdf ( objects -- str )
415 [ "\n" join "\n" append "%PDF-1.4\n" ]
416 [ pdf-trailer ] bi surround ;
418 ! Rename to pdf>string, have it take a <pdf> object?
420 : pdf>string ( seq -- pdf )
421 <pdf> swap pdf-layout [
422 stream>> pdf-stream over pages>> push
423 ] each pages>objects objects>pdf ;
425 : write-pdf ( seq -- )