aboutsummaryrefslogtreecommitdiff
path: root/lpg/lpg.lua
blob: caf7b3e417e732715264c7f6db5d4657551374b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/usr/bin/env lpg
local project_url = "https://git.janouch.name/p/pdf-simple-sign"

function h1 (title)
	return lpg.VBox {fontsize=18., fontweight=600,
		title, lpg.HLine {2}, lpg.Filler {-1, 6}}
end
function h2 (title)
	return lpg.VBox {fontsize=16., fontweight=600,
		lpg.Filler {-1, 8}, title, lpg.HLine {1}, lpg.Filler {-1, 6}}
end
function h3 (title)
	return lpg.VBox {fontsize=14., fontweight=600,
		lpg.Filler {-1, 8}, title, lpg.HLine {.25}, lpg.Filler {-1, 6}}
end
function p (...)
	return lpg.VBox {..., lpg.Filler {-1, 6}}
end
function code (...)
	return lpg.VBox {
		lpg.Filler {-1, 4},
		lpg.HBox {
			lpg.Filler {12},
			lpg.VBox {"<tt>" .. table.concat {...} .. "</tt>"},
			lpg.Filler {},
		},
		lpg.Filler {-1, 6},
	}
end
function define (name, ...)
	return lpg.VBox {
		lpg.Filler {-1, 2},
		lpg.Text {fontweight=600, name}, lpg.Filler {-1, 2},
		lpg.HBox {lpg.Filler {12}, lpg.VBox {...}, lpg.Filler {}},
		lpg.Filler {-1, 2},
	}
end
function pad (widget)
	return lpg.VBox {
		lpg.Filler {-1, 2},
		lpg.HBox {lpg.Filler {4}, widget, lpg.Filler {}, lpg.Filler {4}},
		lpg.Filler {-1, 2},
	}
end

local page1 = lpg.VBox {fontfamily="sans serif", fontsize=12.,
	h1("lpg User Manual"),
	p("<b>lpg</b> is a Lua-based PDF document generator, exposing a trivial " ..
		"layouting engine on top of the Cairo graphics library, " ..
		"with manual paging."),
	p("The author has primarily been using this system to typeset invoices."),

	h2("Synopsis"),
	p("<b>lpg</b> <i>program.lua</i> [<i>args...</i>]"),

	h2("API"),
	p("The Lua program receives <b>lpg</b>'s and its own path joined " ..
		"as <tt>arg[0]</tt>.  Any remaining sequential elements " ..
		"of this table represent the passed <i>args</i>."),

	h3("Utilities"),

	define("lpg.cm (centimeters)",
		p("Returns how many document points are needed " ..
			"for the given physical length.")),

	define("lpg.ntoa {number [, precision=…]\n" ..
		"\t[, thousands_sep=…] [, decimal_point=…] [, grouping=…]}",
		p("Formats a number using the C++ localization " ..
			"and I/O libraries.  " ..
			"For example, the following call results in “3 141,59”:"),
		code("ntoa {3141.592, precision=2,\n" ..
			"  thousands_sep=\" \", decimal_point=\",\", " ..
			"grouping=\"\\003\"}")),

	define("lpg.escape (values...)",
		p("Interprets all values as strings, " ..
			"and escapes them to be used as literal text—" ..
			"all text within <b>lpg</b> is parsed as Pango markup, " ..
			"which is a subset of XML.")),

	h3("PDF documents"),

	define("lpg.Document (filename, width, height [, margin])",
		p("Returns a new <i>Document</i> object, whose pages are all " ..
			"the same size in 72 DPI points, as specified by <b>width</b> " ..
			"and <b>height</b>.  The <b>margin</b> is used by <b>show</b> " ..
			"on all sides of pages."),
		p("The file is finalized when the object is garbage collected.")),

	define("<i>Document</i>.title, author, subject, keywords, " ..
		"creator, create_date, mod_date",
		p("Write-only PDF <i>Info</i> dictionary metadata strings.")),

	define("<i>Document</i>:show ([widget...])",
		p("Starts a new document page, and renders <i>Widget</i> trees over " ..
		"the whole print area.")),

	lpg.Filler {},
}

local page2 = lpg.VBox {fontfamily="sans serif", fontsize=12.,
	h3("Widgets"),
	p("The layouting system makes heavy use of composition, " ..
		"and thus stays simple."),
	p("For convenience, anywhere a <i>Widget</i> is expected but another " ..
		"kind of value is received, <b>lpg.Text</b> widget will be invoked " ..
		"on that value."),
	p("Once a <i>Widget</i> is included in another <i>Widget</i>, " ..
		"the original Lua object can no longer be used, " ..
		"as its reference has been consumed."),
	p("<i>Widgets</i> can be indexed by strings to get or set " ..
		"their <i>attributes</i>.  All <i>Widget</i> constructor tables " ..
		"also accept attributes, for convenience.  Attributes can be " ..
		"either strings or numbers, mostly only act " ..
		"on specific <i>Widget</i> kinds, and are hereditary.  " ..
		"Prefix their names with an underscore to set them ‘privately’."),
	p("<i>Widget</i> sizes can be set negative, which signals to their " ..
		"container that they should take any remaining space, " ..
		"after all their siblings’ requests have been satisfied.  " ..
		"When multiple widgets make this request, that space is distributed " ..
		"in proportion to these negative values."),

	define("lpg.Filler {[width] [, height]}",
		p("Returns a new blank widget with the given dimensions, " ..
			"which default to -1, -1.")),
	define("lpg.HLine {[thickness]}",
		p("Returns a new widget that draws a simple horizontal line " ..
			"of the given <b>thickness</b>.")),
	define("lpg.VLine {[thickness]}",
		p("Returns a new widget that draws a simple vertical line " ..
			"of the given <b>thickness</b>.")),
	define("lpg.Text {[value...]}",
		p("Returns a new text widget that renders the concatenation of all " ..
			"passed values filtered through Lua’s <b>tostring</b> " ..
			"function.  Non-strings will additionally be escaped."),
		define("<i>Text</i>.fontfamily, fontsize, fontweight, lineheight",
			p("Various font properties, similar to their CSS counterparts."))),
	define("lpg.Frame {widget}",
		p("Returns a special container widget that can override " ..
			"a few interesting properties."),
		define("<i>Frame</i>.color",
			p("Text and line colour, for example <tt>0xff0000</tt> for red.")),
		define("<i>Frame</i>.w_override",
			p("Forcefully changes the child <i>Widget</i>’s " ..
				"requested width, such as to negative values.")),
		define("<i>Frame</i>.h_override",
			p("Forcefully changes the child <i>Widget</i>’s " ..
				"requested height, such as to negative values."))),

	lpg.Filler {},
}

local page3 = lpg.VBox {fontfamily="sans serif", fontsize=12.,
	define("lpg.Link {target, widget}",
		p("Returns a new hyperlink widget pointing to the <b>target</b>, " ..
			"which is a URL.  The hyperlink applies " ..
			"to the entire area of the child widget.  " ..
			"It has no special appearance.")),
	define("lpg.HBox {[widget...]}",
		p("Returns a new container widget that places children " ..
			"horizontally, from left to right."),
		p("If any space remains after satisfying the children widgets’ " ..
			"requisitions, it is distributed equally amongst all of them.  " ..
			"Also see the note about negative sizes.")),
	define("lpg.VBox {[widget...]}",
		p("Returns a new container widget that places children " ..
			"vertically, from top to bottom.")),
	define("lpg.Picture {filename}",
		p("Returns a new picture widget, showing the given <b>filename</b>, " ..
			"which currently must be in the PNG format.  " ..
			"Pictures are rescaled to fit, but keep their aspect ratio.")),
	define("lpg.QR {contents, module}",
		p("Returns a new QR code widget, encoding the <b>contents</b> " ..
			"string using the given <b>module</b> size.  " ..
			"The QR code version is chosen automatically.")),
	
	h2("Examples"),
	p("See the source code of this user manual " ..
		"for the general structure of scripts."),

	h3("Size distribution and composition"),
	lpg.VBox {
		lpg.HLine {},
		lpg.HBox {
			lpg.VLine {}, lpg.Frame {_w_override=lpg.cm(3), pad "3cm"},
			lpg.VLine {}, lpg.Frame {pad "Measured"},
			lpg.VLine {}, lpg.Frame {_w_override=-1, pad "-1"},
			lpg.VLine {}, lpg.Frame {_w_override=-2, pad "-2"},
			lpg.VLine {},
		},
		lpg.HLine {},
	},
	lpg.Filler {-1, 6},
	code([[
<small><b>function</b> pad (widget)
  <b>local function</b> f (...) <b>return</b> lpg.Filler {...} <b>end</b>
  <b>return</b> lpg.VBox {f(-1, 2), lpg.HBox {f(4), w, f(), f(4)}, f(-1, 2)}
<b>end</b>

lpg.VBox {lpg.HLine {}, lpg.HBox {
  lpg.VLine {}, lpg.Frame {_w_override=lpg.cm(3), pad "3cm"},
  lpg.VLine {}, lpg.Frame {pad "Measured"},
  lpg.VLine {}, lpg.Frame {_w_override=-1, pad "-1"},
  lpg.VLine {}, lpg.Frame {_w_override=-2, pad "-2"},
  lpg.VLine {},
}, lpg.HLine {}}</small>]]),

	h3("Clickable QR code link"),
	lpg.HBox {
		lpg.VBox {
			p("Go here to report bugs, request features, " ..
				"or submit pull requests:"),
			code(([[
url = "%s"
lpg.Link {url, lpg.QR {url, 2.5}}]]):format(project_url)),
		},
		lpg.Filler {},
		lpg.Link {project_url, lpg.QR {project_url, 2.5}},
	},

	lpg.Filler {},
}

if #arg < 1 then
	io.stderr:write("Usage: " .. arg[0] .. " OUTPUT-PDF..." .. "\n")
	os.exit(false)
end
local width, height, margin = lpg.cm(21), lpg.cm(29.7), lpg.cm(2.0)
for i = 1, #arg do
	local pdf = lpg.Document(arg[i], width, height, margin)
	pdf.title = "lpg User Manual"
	pdf.subject = "lpg User Manual"
	pdf.author = "Přemysl Eric Janouch"
	pdf.creator = ("lpg (%s)"):format(project_url)

	pdf:show(page1)
	pdf:show(page2)
	pdf:show(page3)
end