The command line interface
The command line interface (cli) makes it possible to create documents without a Go installation.
It is built on the risor language, a small and simple language, yet powerful enough to handle all necessary tasks.
Installation
When you have a Go installation, just use go install github.com/boxesandglue/cli/bag@latest
to get the binary bag
.
You can also download the latest binaries at https://github.com/boxesandglue/cli/releases/latest. The ZIP files contain only a single binary, enough for using boxes and glue.
Sample document
Start with bag myfile.rsr
, where the file myfile.rsr
looks like this:
now := time.now()
func setup_fonts(f) {
ff := f.new_fontfamily("text")
fs := frontend.new_fontsource({
location: filepath.join("fonts","CrimsonPro-Regular.ttf"),
features: ["kern","liga"],
})
ff.add_member({source: fs, weight: 400, style: "normal"})
return ff
}
str := `The quick brown fox jumps
over the lazy dog with a very long line that should be wrapped
at some point. This is a test to see how the text is formatted
when it is too long to fit on one line. The quick brown fox jumps
over the lazy dog with a very long line that should be wrapped`
str = strings.join(strings.fields(str)," ")
f := frontend.new('out.pdf')
backend_doc := f.doc
backend_doc.language = frontend.get_language("en")
backend_doc.title = "A test document"
ff := setup_fonts(f)
p := f.doc.new_page()
para := frontend.new_text()
para.items = [str]
vlist := f.format_paragraph({
text: para,
width: bag.sp("225pt"),
leading: bag.sp("14pt"),
font_size: bag.sp("12pt"),
family: ff,
})
p.output_at(bag.sp("1cm"), bag.sp("10cm"), vlist)
p.shipout()
f.doc.finish()
printf("finished in %.2fms\n",time.since(now) * 1000)
Attachments
a:= f.doc.attachments.append({
filename: "img/ocean.pdf",
mimetype: "application/pdf",
description: "Test attachment",
})
Viewer preferences
f.doc.viewer_preferences = {NumCopies: "2", PrintScaling: "/None"}
Image inclusion
imgfile := f.doc.load_imagefile("img/ocean.pdf")
imgNode := f.doc.create_image_node_from_imagefile(imgfile, 1, "/MediaBox")
imgNode.width = bag.sp("100pt")
imgNode.height = bag.sp("100pt")
vl := node.vpack(imgNode)
p.output_at(bag.sp("1cm"), bag.sp("290mm"), vl)
f.doc.format = "PDF/A-3b"
Where format is one of "", "PDF/A-3b", "PDF/X-3", "PDF/X-4" or "PDF/UA".
Set bleed / show cropmarks
f.doc.bleed = bag.sp("3mm")
f.doc.show_cutmarks = true
Page height and width
// the default:
f.doc.default_page_width = bag.sp("210mm")
f.doc.default_page_height = bag.sp("297mm")
Set creation date
f.doc.creation_date = time.parse(time.RFC3339, "2023-08-01T12:00:00-04:00")
Debugging output
f.doc.dump_output = true
...
f.doc.finish()
f.doc.output_xml_dump("out.xml")
The XML structure in out.xml represents the document.
Reference
The backend document
Get with
f := frontend.new('out.pdf')
doc := f.doc
Name |
R/W |
Parameters |
Description |
attachments |
r |
- |
A list with attachments |
author |
w |
string |
Set the author of the document |
bleed |
w |
scaled point |
The amount of extra space on each side of the page |
compresslevel |
w |
int (0-9) |
The zlib compression level. |
create_image_node_from_imagefile() |
|
? |
Turns an image object into a node |
creation_date |
w |
time |
The creation date of the document |
creator |
w |
string |
The document creator software |
default_page_height |
w |
scaled point |
The default page height |
default_page_width |
w |
scaled point |
The default page width |
dump_output |
w |
bool |
Collect info for later XML output. |
filename |
r |
- |
The file name of the PDF |
finish() |
|
- |
Write all objects to the PDF file |
format |
w |
string |
One of "", "PDF/A-3b", "PDF/X-3", "PDF/X-4", "PDF/UA" |
keywords |
w |
string |
Comma separated list |
language |
w |
string or language object |
Set the document's default language for hyphenation |
load_imagefile() |
|
? |
Load an image object (PNG, PDF, JPEG) |
new_page() |
|
? |
Create a new page |
output_xml_dump() |
|
? |
Creates an XML dump from the document (might be large!) |
show_cutmarks |
w |
bool |
Show cropmarks |
show_hyperlinks |
w |
bool |
Toggle hyperlink borders |
subject |
w |
string |
The document's subject |
suppressinfo |
w |
bool |
Set to true if you need a fixed document id and date |
title |
w |
string |
The document's title |
viewer_preferences |
w |
Map |
A key value-map from section 8.1 of the PDF 1.7 spec |
Page
Name |
R/W |
Parameters |
Description |
height |
r/w |
scaled point |
Set the height of the page |
width |
r/w |
scaled point |
Set the width of the page |
output_at() |
|
? |
Place object on page |
shipout() |
|
scaled point, scaled point and vlist |
Place page in PDF |
Nodes
There are several types of nodes: node.disc, node.glue, node.glyph, node.hlist, node.image, node.kern, node.lang, node.penalty, node.rule, node.startstop and node.vlist.
You can create a new node with node.new()
. The expected argument is a string, one of disc
, glue
, glyph
, hlist
, image
, kern
, lang
, penalty
, rule
, startstop
or vlist
.
Package functions
Name |
Parameters |
Description |
node.new() |
string |
Create (and return ) a new node. |
node.insert_after() |
node (head), node (cur), node (new) |
Insert the new node after cur in a list starting with head. The head is returned. |
node.insert_before() |
node (head), node (cur), node (new) |
Insert the new node before cur in a list starting with head. The (perhaps new) head is returned. |
node.copy_list() |
node (head) |
Create a deep copy of a list starting at head. |
Disc node
Field |
R/W |
Parameters |
Description |
pre |
w |
node |
The node before the disc |
post |
w |
node |
The node after the disc |
replace |
w |
node |
The replacement |
penalty |
w |
int |
The penalty for breaking at this point |
Glue node
Field |
R/W |
Parameters |
Description |
subtype |
w |
string |
The subtype of the glue |
width |
w |
scaled point |
The natural width of the glue |
stretch |
w |
scaled point |
The stretchability of the glue |
shrink |
w |
scaled point |
The shrinkability of the glue |
stretchorder |
w |
int |
The order of infinity of stretching |
shrinkorder |
w |
int |
The order of infinity of shrinking |
Glyph node
Field |
R/W |
Parameters |
Description |
font |
w |
font object |
The font of the glyph |
codepoint |
w |
int |
The codepoint of the glyph |
components |
w |
string |
The components of the glyph |
width |
w |
scaled point |
The width of the glyph |
height |
w |
scaled point |
The height of the glyph |
depth |
w |
scaled point |
The depth of the glyph |
yoffset |
w |
scaled point |
The vertical offset of the glyph |
hyphenate |
w |
bool |
Set the hyphenation flag for the glyph |
Hlist node
Field |
R/W |
Parameters |
Description |
width |
w |
scaled point |
The width of the hlist |
height |
w |
scaled point |
The height of the hlist |
depth |
w |
scaled point |
The depth of the hlist |
badness |
w |
int |
The badness of the hlist |
glueset |
w |
float |
The ratio of the glue. Positive means stretching, negative shrinking. |
gluesign |
w |
int |
0 = normal, 1 = stretching, 2 = shrinking |
glueorder |
w |
int |
The level of infinity |
shift |
w |
scaled point |
The displacement perpendicular to the progressing direction. Not used. |
valign |
w |
int |
The vertical alignment of the hlist |
list |
w |
node |
The list itself |
Image node
Field |
R/W |
Parameters |
Description |
width |
w |
scaled point |
Set the resulting width of the image |
height |
w |
scaled point |
Set the resulting height of the image |
image |
w |
image object |
The image object |
page |
w |
int |
The page number of the image |
used |
w |
bool |
Set to true if the image is written to the page |
Kern node
Field |
R/W |
Parameters |
Description |
kern |
w |
scaled point |
The width of the kern |
Lang node
Field |
R/W |
Parameters |
Description |
lang |
w |
Lang |
The language of the node |
Penalty node
Field |
R/W |
Parameters |
Description |
penalty |
w |
int |
Set the penalty when breaking at this point |
width |
w |
scaled point |
Set the width of the penalty |
Rule node
Field |
R/W |
Parameters |
Description |
width |
w |
scaled point |
The width of the rule |
height |
w |
scaled point |
The height of the rule |
depth |
w |
scaled point |
The depth of the rule |
pre |
w |
string |
The pdf instructions before the rule |
post |
w |
string |
The pdf instructions after the rule |
hide |
w |
bool |
Set to true if the rule is hidden |
StartStop node
Field |
R/W |
Parameters |
Description |
action |
w |
ActionType |
The action type |
startnode |
w |
StartStop |
The start node |
position |
w |
PDFDataOutput |
The position of the node |
shipout_callback |
w |
StartStopFunc |
The shipout callback |
value |
w |
any |
The value of the node |
Vlist node
Field |
R/W |
Parameters |
Description |
width |
w |
scaled point |
The width of the vlist |
height |
w |
scaled point |
The height of the vlist |
depth |
w |
scaled point |
The depth of the vlist |
glueset |
w |
float |
The ratio of the glue. Positive means stretching, negative shrinking. |
gluesign |
w |
int |
0 = normal, 1 = stretching, 2 = shrinking |
shift_x |
w |
scaled point |
The displacement perpendicular to the progressing direction. |
list |
w |
node |
The list itself |
Logging
The logging is based on Go' standard library slog. It allows several log levels,
a main message and a set of key/value pairs, where each key is a string. You
have to provide an even number of arguments for the key/value pairs.
The commands for logging are bag.debug()
, bag.info()
, bag.warn()
and bag.error()
. Each of these follow the same schema:
bag.info("something happens", "key1", value1, "key2", value2, ...)
for example:
filename := "somefile.png"
bag.info("File loaded", "filename", filename)