[REBOL] A solution to printing in REBOL
From: g::santilli::tiscalinet::it at: 16-Jun-2001 19:59
Hi all!
I see there has been some discussion on the list lately about the
need of a (possibly multiplatform) solution to the problem of
printing. One of the possible solution, useful when one has a
relatively simple text document and does not need precise
positioning of text in the page, is using HTML files.
But if I need to print a report on a given template with
precise text positioning? A better control on the page layout and
size? In this case, a useful solution would be to create a PDF
file. I see this solution has been proposed on the list, but I
didn't see (maybe I missed them?) any taker wrt writing a dialect
to generate PDF files.
So I decided to write it myself. I downloaded the specification
from www.adobe.com yesterday afternoon, and I was able to write a
PDF generator in REBOL in about a day of work.
WARNING! This is ALPHA unfinished buggy code! WARNING!
I thought that this script might be useful. Creating a PDF file is
as easy as writing:
write/binary %pdf-file.pdf layout-pdf [
[ textbox [
"Hello world! This is a simple PDF file."
]
]
]
Please run the provided pdf-maker-doc.r script to create the
documentation, in PDF format of course. :-)
I'm looking forward to get comments, and possibly help.
Have fun!
Gabriele.
--
Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer
Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/
-- Attached file included as plaintext by Listar --
-- File: pdf-maker-doc.r
REBOL []
do %pdf-maker.r
write/binary %pdf-maker-doc.pdf layout-pdf [
[ ; page 1
textbox [
font Helvetica-Bold 36 offset 50 0 "PDF-Maker!"
font Helvetica 12 offset 78 0 "(first alpha release)"
offset -123 -20
{Welcome to pdf-maker.r preliminary documentation. This is my first attempt at a PDF
generator as}
newline
offset -5 0
{a multiplatform solution to printing in }
font Helvetica-Bold 12 {REBOL} font Helvetica 12 {. As you can see, it is pretty limited
currently. Anyway, it
should work, and can surely be improved in the future. Current limitations are:} newline
offset 20 -5
"^(97) Text only, no graphics at the moment." newline
"^(97) No colors, etc. You have to cope with just black text." newline
"^(97) Only the 14 PostScript standard fonts are supported." newline
"^(97) No justification, right alignement, etc." newline
offset -20 -5
{Graphics would not be very difficult to add, so would colors. Custom fonts would definitely
require
work, and justification and right alignement would require A LOT of work, because you'd
have to
process font metrics and so on. These two are not likely to be added by me soon.} newline
offset 0 -5
{Anyway, let's go on explaining how to create a PDF file. You'll need just one function,
}
font Courier 12 "layout-pdf" font Helvetica 12 {,
which in turn just needs an argument of type } font Courier 12 "block!" font Helvetica
12 {:} newline
offset 20 -5
font Courier 12
{layout-pdf [
; page definitions here
]} newline
offset -20 -5
font Helvetica 12
{This block contains a block for each page in the document, so that a document with two
pages will
look like:} newline
offset 20 -5
font Courier 12
{layout-pdf [
[ ; first page
; ...
] [ ; second page
; ...
]
]} newline
offset -20 -5
font Helvetica 12
{Each page definition might include and optional page size and rotation specification;
the page size
is expressed in millimeters (as any other position or size in the dialect). For example,
to create a page
150 millimeters wide and 140 high, you can write:} newline
offset 20 -5
font Courier 12
{[ page size 150 140
; ...
]} newline
offset -20 -5
font Helvetica 12
{while to create a page of default size (ISO A4, 211x297 millimeters) but rotated 270
degrees
clockwise:} newline
offset 20 -5
font Courier 12
{[ page rotation 270 ; has to be multiple of 90
; ...
]}
]
] [ ; page 2
textbox [
{Of course the two can be specified together, in any order:} newline
offset 20 -5
font Courier 12
{[ page rotation 90 size 180.5 280
; ...
]} newline
offset -20 -5
font Helvetica 12
{A page can contain any number of } font Times-Italic 12 "text boxes"
font Helvetica 12 {; a text box is used to render text inside a rectangular
area of the page with proper clipping. A text box can be created with:} newline
offset 20 -5
font Courier 12
{[ textbox 10 17 191 263 [
; text...
]
]} newline
offset -20 -5
font Helvetica 12
{The numbers after } font Courier 12 "textbox" font Helvetica 12 { are x position, y
position, width and height (the numbers shown above
are a reasonable default for A4 pages used if you don't specify any). The position is
referred to
the LOWER LEFT corner of the box; also notice that the y axis is swapped if you compare
it to
REBOL/View's faces y axis.} newline
offset 0 -5
{A simple text box can just contain a string of text. The lines of text will simply be
rendered left
justified inside the text box with the default font (Helvetica 12pt). However, it can
also contain some
commands to specify the font to use, where to position the text, and so on. The currently
available
commands are:} newline
offset 20 -5
font Courier 10
{font <font-name> <font-size> ; font-size is in points
offset <x-offset> <y-offset> ; in millimeters, relative to start of line
newline ; just begins a new line
line height <line-height> ; sets the line height} newline
offset -20 -5
font Helvetica 12
{To see an example of these commands, look at the REBOL script that created this PDF
file. Notice
that the font name should be one of:} newline
offset 20 -5
font Courier 10
{Helvetica Courier Times-Roman Symbol
Helvetica-Bold Courier-Bold Times-Bold ZapfDingbats
Helvetica-Oblique Courier-Oblique Times-Italic
Helvetica-BoldOblique Courier-BoldOblique Times-BoldItalic} newline
offset -20 -5
font Helvetica 12
"Enjoy!" newline
offset 10 0 font Times-Italic 12 "Gabriele."
]
]
]
-- Attached file included as plaintext by Listar --
-- File: pdf-maker.r
REBOL []
context [
pdf-start: "%PDF-1.3^/"
pdf-end: "%%EOF"
pdf-string-valid: complement charset "()\"
pdf-form: func ["REBOL to PDF" value /only /local result mrk1 mrk2] [
result: make string! 256
if block? :value [
if only [insert result "["]
foreach element value [
insert insert tail result pdf-form/only element #" "
]
either only [change back tail result "]"] [remove back tail result]
return result
]
if char? :value [
return head insert result reduce [
#"("
either find pdf-string-valid value [""] [#"\"] value
#")"
]
]
if string? :value [
insert result "("
parse/all value [
some [
mrk1: some pdf-string-valid mrk2: (
insert/part tail result mrk1 mrk2
)
| mrk1: skip (
insert insert tail result #"\" mrk1/1
)
]
]
insert tail result ")"
return result
]
if issue? :value [return form value]
mold :value
]
xref: []
contents: #{}
pdf-words: context [
obj: func [id data] [
insert tail xref reduce [id index? tail contents]
insert tail contents reduce [
id " 0 obj^/" pdf-form data "^/endobj^/"
]
]
stream: func [id data] [
insert tail xref reduce [id index? tail contents]
if block? data [data: pdf-form data]
insert tail contents reduce [
id " 0 obj^/"
pdf-form compose [
#<< /Length (length? data) #>>
]
"^/stream^/"
data
"^/endstream^/endobj^/"
]
]
]
zero-padded: func [val n] [
val: form val
head insert insert/dup make string! n #"0" n - length? val val
]
make-xref: has [pos] [
pos: tail contents
insert pos "xref^/0 1^/0000000000 65535 f ^/"
foreach [id ofs] xref [
insert tail pos reduce [
id " 1^/"
zero-padded ofs 10 " 00000 n ^/"
]
]
insert tail pos reduce [
newline
"trailer^/"
pdf-form compose [
#<< /Size (1 + divide length? xref 2)
/Root 1 0 R ; this assumes root will always be 1
#>>
]
"^/startxref^/"
index? pos newline
]
]
set 'make-pdf func [spec [block!]] [
clear xref
clear contents
insert contents pdf-start
do bind spec in pdf-words 'self
make-xref
copy head insert tail contents pdf-end
]
pages: []
used-fonts: []
font-resources: []
pdf-spec: []
default-page: context [
size: [211 297] ; mm. (ISO A4)
rotation: 0
contents: []
]
default-textbox: context [
bbox: [10 17 191 263]
text: []
]
make-docroot: does [
insert tail pdf-spec [
obj 1 [
#<< /Type /Catalog
/Outlines 2 0 R
/Pages 100 0 R
#>>
]
obj 2 [
#<< /Type /Outlines
/Count 0
#>>
]
obj 3 [ ; ProcSet to use in pages
[/PDF /Text]
]
]
]
new: val1: val2: bb: txtb: none
txtb-emit: func [data] [
insert tail txtb/text reduce data
]
use-font: func [name size] [
used-fonts: union used-fonts reduce [name]
txtb-emit [to-refinement name size 'Tf size / 10 + size 'TL]
]
font-def: ['font set val1 word! set val2 number! (use-font val1 val2)]
offset-text: ['offset set val1 number! set val2 number! (txtb-emit [mm2pt val1 mm2pt
val2 'Td])]
set-lead: ['line 'height set val1 number! (txtb-emit [val1 'TL])]
process-text: func [text] [
text: parse/all text "^/"
if pick text 1 [txtb-emit [pick text 1 'Tj]]
foreach line next text [
txtb-emit ['T* line 'Tj]
]
]
draw-text: [set val1 string! (process-text val1)]
textbox-rule: [
[font-def | none (use-font 'Helvetica 12)]
(txtb-emit [mm2pt txtb/bbox/1 mm2pt txtb/bbox/2 + txtb/bbox/4 'Td 'T*])
some [
font-def
| offset-text
| 'newline (insert tail txtb/text 'T*)
| set-lead
| draw-text
]
]
page-rule: [
(insert tail pages new: make default-page [])
opt ['page any [
'size set val1 number! set val2 number! (new/size: reduce [val1 val2])
| 'rotation set val1 integer! (new/rotation: val1)
]]
any [
'textbox (insert tail new/contents txtb: make default-textbox [])
opt [(bb: clear []) 4 [set val1 number! (append bb val1)] (change txtb/bbox bb)]
into textbox-rule
]
]
parse-spec: func [spec] [
parse spec [some [into page-rule]]
]
make-fonts: has [i] [
i: 4
clear font-resources
foreach font used-fonts [
insert tail font-resources reduce [to-refinement font i 0 'R]
insert tail pdf-spec compose/deep [
obj (i) [
#<< /Type /Font
/Subtype /Type1
/BaseFont (to-refinement font)
/Encoding /WinAnsiEncoding
#>>
]
]
i: i + 1
]
]
mm2pt: func [mm] [mm * 72 / 25.4]
make-pages: has [i kids mediabox stream] [
i: 101
kids: clear []
foreach page pages [
insert tail kids reduce [i 0 'R]
mediabox: reduce [0 0 mm2pt page/size/1 mm2pt page/size/2]
stream: clear []
foreach textbox page/contents [
insert tail stream compose [
q
(mm2pt textbox/bbox/1) (mm2pt textbox/bbox/2)
(mm2pt textbox/bbox/3) (mm2pt textbox/bbox/4) re W n
BT (textbox/text) ET
Q
]
]
insert tail pdf-spec compose/deep [
obj (i) [
#<< /Type /Page
/Parent 100 0 R
/MediaBox [(mediabox)]
/Rotate (page/rotation)
/Contents (i + 1) 0 R
/Resources #<<
/ProcSet 3 0 R
/Font #<< (font-resources) #>>
#>>
#>>
]
stream (i + 1) [
(stream)
]
]
i: i + 2
]
insert tail pdf-spec compose/deep [
obj 100 [
#<< /Type /Pages
/Kids [(kids)]
/Count (length? pages)
#>>
]
]
]
set 'layout-pdf func [spec [block!]] [
clear pages
clear used-fonts
make-docroot
parse-spec spec
make-fonts
make-pages
make-pdf pdf-spec
]
]