-
-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathobject.go
435 lines (366 loc) · 15.2 KB
/
object.go
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
package pdf
/*
#cgo LDFLAGS: -lwkhtmltox
#include <stdlib.h>
#include <wkhtmltox/pdf.h>
*/
import "C"
import (
"errors"
"fmt"
"io"
"os"
"strings"
"unsafe"
)
// ErrorAction defines actions to take in case of object load failure.
type ErrorAction string
// Error action values.
const (
ActionAbort ErrorAction = "abort"
ActionIgnore ErrorAction = "ignore"
ActionSkip ErrorAction = "skip"
)
// TOC contains settings related to the table of contents of an object.
type TOC struct {
// Specifies whether dotted lines should be used for the line of items
// of the TOC.
UseDottedLines bool `json:"useDottedLines" yaml:"useDottedLines"`
// The title used for the table of contents.
// E.g.: "Table of Contents".
Title string `json:"title" yaml:"title"`
// Specifies whether the TOC items should contain links to the content.
GenerateForwardLinks bool `json:"generateForwardLinks" yaml:"generateForwardLinks"`
// Specifies whether the content should contain links to the TOC.
GenerateBackLinks bool `json:"generateBackLinks" yaml:"generateBackLinks"`
// The indentation used for the TOC nesting levels.
// E.g.: "1em".
Indentation string `json:"indentation" yaml:"indentation"`
// Scaling factor for each nesting level of the TOC.
// E.g.: 1.
FontScale float64 `json:"fontScale" yaml:"fontScale"`
}
// Header contains settings related to the headers and footers of an object.
type Header struct {
// The system font name to use for headers/footers.
// E.g.: "Arial".
Font string `json:"font" yaml:"font"`
// The font size to use for headers/footers.
// E.g.: 12.
FontSize uint64 `json:"fontSize" yaml:"fontSize"`
// Content to print on each of the available regions of the header/footer.
// Substitution variables that can be used in the content fields:
// - [page] The number of the current page.
// - [frompage] The number of the first page.
// - [topage] The number of the last page.
// - [webpage] The URL of the source page.
// - [section] The name of the current section.
// - [subsection] The name of the current subsection.
// - [date] The current date in system local format.
// - [isodate] The current date in ISO 8601 extended format.
// - [time] The current time in system local format.
// - [title] The title of the of the current page object.
// - [doctitle] The title of the output document.
// - [sitepage] The number of the page in the currently converted site.
// - [sitepages] The number of pages in the current site being converted.
// e.g.: object.Footer.ContentRight = "[page]"
ContentLeft string `json:"contentLeft" yaml:"contentLeft"`
ContentCenter string `json:"contentCenter" yaml:"contentCenter"`
ContentRight string `json:"contentRight" yaml:"contentRight"`
// Specifies whether a line separator should be printed for headers/footers.
DisplaySeparator bool `json:"displaySeparator" yaml:"displaySeparator"`
// The amount of space between the header/footer and the content.
// E.g.: 0.
Spacing float64 `json:"spacing" yaml:"spacing"`
// Location of a user defined HTML document to be used as the header/footer.
CustomLocation string `json:"customLocation" yaml:"customLocation"`
}
// ObjectOpts defines a set of options to be used in the conversion process.
type ObjectOpts struct {
// Specifies the location of the HTML document. Can be a file path or a URL.
Location string `json:"location" yaml:"location"`
// Specifies whether external links in the HTML document should be converted
// to external PDF links.
UseExternalLinks bool `json:"useExternalLinks" yaml:"useExternalLinks"`
// Specifies whether internal links in the HTML document should be converted
// into PDF references.
UseLocalLinks bool `json:"useLocalLinks" yaml:"useLocalLinks"`
// Specifies whether HTML forms should be converted into PDF forms.
ProduceForms bool `json:"produceForms" yaml:"produceForms"`
// Specifies whether the sections from the HTML document are included in
// outlines and TOCs.
IncludeInOutline bool `json:"includeInOutline" yaml:"includeInOutline"`
// Specifies whether the page count of the HTML document participates in
// the counter used for tables of contents, headers and footers.
CountPages bool `json:"countPages" yaml:"countPages"`
// Contains settings for the TOC of the object.
TOC TOC `json:"toc" yaml:"toc"`
// Contains settings for the header of the object.
Header Header `json:"header" yaml:"header"`
// Contains settings for the footer of the object.
Footer Header `json:"footer" yaml:"footer"`
// The username to use when logging in to a website.
Username string `json:"username" yaml:"username"`
// The password to use when logging in to a website.
Password string `json:"password" yaml:"password"`
// The amount of milliseconds to wait after page load, before
// executing JS scripts.
// E.g.: 300.
JavascriptDelay uint64 `json:"javascriptDelay" yaml:"javascriptDelay"`
// Specifies the `window.status` value to wait for, before
// rendering the page.
// E.g.: "ready".
WindowStatus string `json:"windowStatus" yaml:"windowStatus"`
// Zoom factor to use for the document content.
// E.g.: 1.
Zoom float64 `json:"zoom" yaml:"zoom"`
// Specifies whether local file access is blocked.
BlockLocalFileAccess bool `json:"blockLocalFileAccess" yaml:"blockLocalFileAccess"`
// Specifies whether slow JS scripts should be stopped.
StopSlowScripts bool `json:"stopSlowScripts" yaml:"stopSlowScripts"`
// Specifies a course of action when an HTML document fails to load.
// E.g.: ActionAbort.
ErrorAction ErrorAction `json:"errorAction" yaml:"errorAction"`
// The name of a proxy to use when loading the HTML document.
Proxy string `json:"proxy" yaml:"proxy"`
// Specifies whether the background of the HTML document is preserved.
PrintBackground bool `json:"printBackground" yaml:"printBackground"`
// Specifies whether the images in the HTML document are loaded.
LoadImages bool `json:"loadImages" yaml:"loadImages"`
// Specifies whether Javascript should be executed.
EnableJavascript bool `json:"enableJavascript" yaml:"enableJavascript"`
// Specifies whether to use intelligent shrinkng in order to fit more
// content on a page.
UseSmartShrinking bool `json:"useSmartShrinking" yaml:"useSmartShrinking"`
// The minimum font size allowed for rendering content.
MinFontSize uint64 `json:"minFontSize" yaml:"minFontSize"`
// The text encoding to use if the HTML document does not specify one.
// E.g.: "utf-8".
DefaultEncoding string `json:"defaultEncoding" yaml:"defaultEncoding"`
// Specifies whether the content should be rendered using the print media
// type instead of the screen media type.
UsePrintMediaType bool `json:"usePrintMediaType" yaml:"usePrintMediaType"`
// The location of a user defined stylesheet to use when converting
// the HTML document.
UserStylesheetLocation string `json:"userStylesheetLocation" yaml:"userStylesheetLocation"`
// Specifies whether NS plugins should be enabled.
EnablePlugins bool `json:"enablePlugins" yaml:"enablePlugins"`
}
// NewObjectOpts returns a new instance of object options, configured
// using sensible defaults.
//
// Defaults options:
//
// UseExternalLinks: true
// UseLocalLinks: true
// IncludeInOutline: true
// CountPages: true
// JavascriptDelay: 300
// Zoom: 1
// StopSlowScripts: true
// ErrorAction: ActionAbort
// PrintBackground: true
// LoadImages: true
// EnableJavascript: true
// UseSmartShrinking: true
// DefaultEncoding: "utf-8"
// TOC:
// UseDottedLines: true
// Title: "Table of Contents"
// GenerateForwardLinks: true
// GenerateBackLinks: true
// Indentation: "1em"
// FontScale: 1
// Header:
// Font: "Arial"
// FontSize: 12
// Footer:
// Font: "Arial"
// FontSize: 12
func NewObjectOpts() *ObjectOpts {
return &ObjectOpts{
UseExternalLinks: true,
UseLocalLinks: true,
ProduceForms: true,
IncludeInOutline: true,
CountPages: true,
JavascriptDelay: 300,
Zoom: 1,
StopSlowScripts: true,
ErrorAction: ActionAbort,
PrintBackground: true,
LoadImages: true,
EnableJavascript: true,
UseSmartShrinking: true,
DefaultEncoding: "utf-8",
TOC: TOC{
UseDottedLines: true,
Title: "Table of Contents",
GenerateForwardLinks: true,
GenerateBackLinks: true,
Indentation: "1em",
FontScale: 1,
},
Header: Header{
Font: "Arial",
FontSize: 12,
},
Footer: Header{
Font: "Arial",
FontSize: 12,
},
}
}
// Object represents an HTML document. The contained options are applied only
// to the current object.
type Object struct {
*ObjectOpts
settings *C.wkhtmltopdf_object_settings
temporary bool
}
// NewObject returns a new object instance from the document at the specified
// location. The location can be a file path or a URL. The object is configured
// using sensible defaults. See NewObjectOpts for the default options.
func NewObject(location string) (*Object, error) {
return newObject(location, false, nil)
}
// NewObjectWithOpts returns a new object instance from the document at the
// specified location. The location can be a file path or a URL. The object is
// configured using the specified options. If no options are provided, sensible
// defaults are used. See NewObjectOpts for the default options.
func NewObjectWithOpts(opts *ObjectOpts) (*Object, error) {
return newObject("", false, opts)
}
// NewObjectFromReader creates a new object from the specified reader.
// The object is configured using sensible defaults. See NewObjectOpts for
// the default options.
func NewObjectFromReader(r io.Reader) (*Object, error) {
file, err := os.CreateTemp("", "pdf-")
if err != nil {
return nil, err
}
if _, err := io.Copy(file, r); err != nil {
return nil, err
}
tempLocation := file.Name()
if err := file.Close(); err != nil {
return nil, err
}
location := fmt.Sprintf("%s.html", tempLocation)
if err := os.Rename(tempLocation, location); err != nil {
return nil, err
}
return newObject(location, true, nil)
}
func newObject(location string, temp bool, opts *ObjectOpts) (*Object, error) {
if opts == nil {
opts = NewObjectOpts()
}
if location != "" {
opts.Location = location
}
if opts.Location == "" {
return nil, errors.New("must provide HTML document location")
}
settings := C.wkhtmltopdf_create_object_settings()
if settings == nil {
return nil, errors.New("could not create object settings")
}
return &Object{
ObjectOpts: opts,
settings: settings,
temporary: temp,
}, nil
}
// Destroy releases all resources used by the object.
func (o *Object) Destroy() {
// Remove temporary file.
if o.temporary && o.Location != "" {
os.Remove(o.Location)
o.Location, o.temporary = "", false
}
// Destroy settings.
if o.settings != nil {
C.wkhtmltopdf_destroy_object_settings(o.settings)
o.settings = nil
}
}
func (o *Object) setOption(name, value string) error {
if name = strings.TrimSpace(name); name == "" {
return errors.New("object option name cannot be empty")
}
n := C.CString(name)
defer C.free(unsafe.Pointer(n))
v := C.CString(value)
defer C.free(unsafe.Pointer(v))
if C.wkhtmltopdf_set_object_setting(o.settings, n, v) != 1 {
return fmt.Errorf("could not set object option: %s", name)
}
return nil
}
func (o *Object) setOptions() error {
if o.settings == nil {
return errors.New("cannot use uninitialized or destroyed object")
}
setter := o.setOption
opts := []*setOp{
// General options.
newSetOp("page", o.Location, optTypeString, setter, true),
newSetOp("useExternalLinks", o.UseExternalLinks, optTypeBool, setter, true),
newSetOp("useLocalLinks", o.UseLocalLinks, optTypeBool, setter, true),
newSetOp("produceForms", o.ProduceForms, optTypeBool, setter, true),
newSetOp("includeInOutline", o.IncludeInOutline, optTypeBool, setter, true),
newSetOp("pagesCount", o.CountPages, optTypeBool, setter, true),
// TOC options.
newSetOp("toc.useDottedLines", o.TOC.UseDottedLines, optTypeBool, setter, true),
newSetOp("toc.captionText", o.TOC.Title, optTypeString, setter, true),
newSetOp("toc.forwardLinks", o.TOC.GenerateForwardLinks, optTypeBool, setter, true),
newSetOp("toc.backLinks", o.TOC.GenerateBackLinks, optTypeBool, setter, true),
newSetOp("toc.indentation", o.TOC.Indentation, optTypeString, setter, false),
newSetOp("toc.fontScale", o.TOC.FontScale, optTypeFloat, setter, false),
// Header options.
newSetOp("header.fontName", o.Header.Font, optTypeString, setter, false),
newSetOp("header.fontSize", o.Header.FontSize, optTypeUint, setter, false),
newSetOp("header.left", o.Header.ContentLeft, optTypeString, setter, true),
newSetOp("header.center", o.Header.ContentCenter, optTypeString, setter, true),
newSetOp("header.right", o.Header.ContentRight, optTypeString, setter, true),
newSetOp("header.line", o.Header.DisplaySeparator, optTypeBool, setter, true),
newSetOp("header.spacing", o.Header.Spacing, optTypeFloat, setter, true),
newSetOp("header.htmlUrl", o.Header.CustomLocation, optTypeString, setter, true),
// Footer options.
newSetOp("footer.fontName", o.Footer.Font, optTypeString, setter, false),
newSetOp("footer.fontSize", o.Footer.FontSize, optTypeUint, setter, false),
newSetOp("footer.left", o.Footer.ContentLeft, optTypeString, setter, true),
newSetOp("footer.center", o.Footer.ContentCenter, optTypeString, setter, true),
newSetOp("footer.right", o.Footer.ContentRight, optTypeString, setter, true),
newSetOp("footer.line", o.Footer.DisplaySeparator, optTypeBool, setter, true),
newSetOp("footer.spacing", o.Footer.Spacing, optTypeFloat, setter, true),
newSetOp("footer.htmlUrl", o.Footer.CustomLocation, optTypeString, setter, true),
// Load options.
newSetOp("load.username", o.Username, optTypeString, setter, false),
newSetOp("load.password", o.Password, optTypeString, setter, false),
newSetOp("load.jsdelay", o.JavascriptDelay, optTypeUint, setter, false),
newSetOp("load.windowStatus", o.WindowStatus, optTypeString, setter, false),
newSetOp("load.zoomFactor", o.Zoom, optTypeFloat, setter, false),
newSetOp("load.blockLocalFileAccess", o.BlockLocalFileAccess, optTypeBool, setter, true),
newSetOp("load.stopSlowScripts", o.StopSlowScripts, optTypeBool, setter, true),
newSetOp("load.loadErrorHandling", string(o.ErrorAction), optTypeString, setter, false),
newSetOp("load.proxy", o.Proxy, optTypeString, setter, false),
// Web options.
newSetOp("web.background", o.PrintBackground, optTypeBool, setter, true),
newSetOp("web.loadImages", o.LoadImages, optTypeBool, setter, true),
newSetOp("web.enableJavascript", o.EnableJavascript, optTypeBool, setter, true),
newSetOp("web.enableIntelligentShrinking", o.UseSmartShrinking, optTypeBool, setter, true),
newSetOp("web.minimumFontSize", o.MinFontSize, optTypeUint, setter, false),
newSetOp("web.defaultEncoding", o.DefaultEncoding, optTypeString, setter, false),
newSetOp("web.printMediaType", o.UsePrintMediaType, optTypeBool, setter, true),
newSetOp("web.userStyleSheet", o.UserStylesheetLocation, optTypeString, setter, true),
newSetOp("web.enablePlugins", o.EnablePlugins, optTypeBool, setter, true),
}
for _, opt := range opts {
if err := opt.execute(); err != nil {
return err
}
}
return nil
}