-
Notifications
You must be signed in to change notification settings - Fork 13
/
ghostscript.go
216 lines (190 loc) · 6.78 KB
/
ghostscript.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
// Package ghostscript provides simple, and idiomatic Go bindings for the
// Ghostscript Interpreter C API.
// For more information: http://www.ghostscript.com/doc/current/API.htm
package ghostscript
/*
#include <stdlib.h>
#include <ghostscript/gdevdsp.h>
#include <ghostscript/iapi.h>
#include <ghostscript/ierrors.h>
#cgo LDFLAGS: -lgs
#define e_Fatal gs_error_Fatal
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
)
const (
MAX_SUPPORTED_REVISION = 10010
MIN_SUPPORTED_REVISION = 910
GS_NO_ERRORS = 0
GS_FATAL_ERROR = C.e_Fatal
)
var (
instantiated bool
mu sync.Mutex
)
// Revision contains information about the Ghostscript interpreter.
type Revision struct {
Product string
Copyright string
Revision int
RevisionDate int
}
// Ghostscript contains a pointer to the global static instance of Ghostscript.
// It should not be initialised manually. i.e. Call NewInstance instead.
// Only one instance of Ghostscript may exist at any time.
type Ghostscript struct {
instance unsafe.Pointer
}
// CStrings converts a Go string array to a C array of char pointers,
// and returns a pointer to that array.
// It will allocate a new string array of the appropriate length, and should
// be garbage collected using FreeCStrings.
func CStrings(goStrings []string) **C.char {
// Kids, don't try this at home. Absolutely nasty.
var char C.char
length := len(goStrings)
charArray := C.calloc(C.size_t(unsafe.Sizeof(&char)), C.size_t(length))
tmp := (*[1 << 30]*C.char)(unsafe.Pointer(charArray))[:length:length]
for i, str := range goStrings {
tmp[i] = C.CString(str)
}
return (**C.char)(charArray)
}
// FreeCStrings frees memory used to allocate a C array of char pointers.
func FreeCStrings(cStrings **C.char, length int) {
// Yea, this is real nasty.
tmp := (*[1 << 30]*C.char)(unsafe.Pointer(cStrings))[:length:length]
for i, _ := range tmp {
C.free(unsafe.Pointer(tmp[i]))
}
C.free(unsafe.Pointer(cStrings))
}
func instantiate() {
mu.Lock()
defer mu.Unlock()
instantiated = true
}
func uninstantiate() {
mu.Lock()
defer mu.Unlock()
instantiated = false
}
// IsInstantiated returns true if a global static instance of Ghostscript
// already exists.
// This should be used to ensure that only one instance of Ghostscript may
// exist at any time. Ghostscript does not support multiple instances.
func IsInstantiated() bool {
return instantiated
}
// GetRevision is an implementation of gsapi_revision.
// It returns the version numbers, and strings of the Ghostscript interpreter.
// It is safe to call at any time, and it does not rely on an instance of
// Ghostscript.
// It should be called before any other interpreter library functions to ensure
// that the correct version of Ghostscript interpreter has been loaded.
func GetRevision() (Revision, error) {
revision := Revision{}
var gsapiRevision C.gsapi_revision_t
if err := C.gsapi_revision(&gsapiRevision, C.int(unsafe.Sizeof(gsapiRevision))); err != 0 {
return revision, fmt.Errorf("revision structure size is incorrect, expected: %+v", err)
}
revision.Product = C.GoString(gsapiRevision.product)
revision.Copyright = C.GoString(gsapiRevision.copyright)
revision.Revision = int(gsapiRevision.revision)
revision.RevisionDate = int(gsapiRevision.revisiondate)
return revision, nil
}
// NewInstance is an implementation of gsapi_new_instance.
// It returns a global static instance of Ghostscript (encapsulated in a
// struct).
// i.e. Do not call NewInstance more than once, otherwise an error will be
// returned.
func NewInstance() (*Ghostscript, error) {
if IsInstantiated() {
return nil, fmt.Errorf("unable to create a new instance of Ghostscript, an instance already exists")
}
rev, err := GetRevision()
if err != nil {
return nil, err
}
if rev.Revision < MIN_SUPPORTED_REVISION || rev.Revision > MAX_SUPPORTED_REVISION {
return nil, fmt.Errorf("Ghostscript interpreter version not supported: %d (must be >%d, and <%d)", rev.Revision, MIN_SUPPORTED_REVISION, MAX_SUPPORTED_REVISION)
}
var instance unsafe.Pointer
if err := C.gsapi_new_instance(&instance, nil); err < GS_NO_ERRORS {
return nil, fmt.Errorf("unable to create a new instance of Ghostscript: %+v", err)
}
defer instantiate()
return &Ghostscript{instance}, nil
}
// Destroy is an implementation of gsapi_delete_instance.
// It destroys a global static instance of Ghostscript.
// It should be called only after Exit has been called if Init has been called.
func (gs *Ghostscript) Destroy() {
defer uninstantiate()
C.gsapi_delete_instance(gs.instance)
}
// Init is an implementation of gsapi_init_with_args.
// It initialises the Ghostscript interpreter given a set of arguments.
// The first argument is ignored, and the arguments that will be used starts
// from index 1.
func (gs *Ghostscript) Init(args []string) error {
cArgs := CStrings(args)
defer FreeCStrings(cArgs, len(args))
err := C.gsapi_init_with_args(gs.instance, C.int(len(args)), cArgs)
if err <= GS_FATAL_ERROR {
_ = gs.Exit()
return fmt.Errorf("unable to initialise Ghostscript interpreter due to a fatal error, exiting: %+v", err)
}
if err < GS_NO_ERRORS {
return fmt.Errorf("unable to initialise Ghostscript interpreter: %+v", err)
}
return nil
}
// RunOnString is an implementation of gsapi_run_string_with_length.
// It runs the Ghostscript interpreter against a document in the form of a
// fixed length string.
func (gs *Ghostscript) RunOnString(strDoc string) error {
gsStrDoc := C.CString(strDoc)
defer C.free(unsafe.Pointer(gsStrDoc))
var exitCode C.int
err := C.gsapi_run_string_with_length(gs.instance, gsStrDoc, C.uint(len(strDoc)), C.int(0), &exitCode)
if err <= GS_FATAL_ERROR {
_ = gs.Exit()
return fmt.Errorf("unable to run Ghostscript interpreter due to a fatal error, exiting: %+v", err)
}
if err < GS_NO_ERRORS {
return fmt.Errorf("unable to run Ghostscript interpreter on string: %+v", err)
}
return nil
}
// RunOnFile is an implementation of gsapi_run_file.
// It runs the Ghostscript interpreter against an existing file, given its
// name / path.
func (gs *Ghostscript) RunOnFile(fnDoc string) error {
gsFnDoc := C.CString(fnDoc)
defer C.free(unsafe.Pointer(gsFnDoc))
var exitCode C.int
err := C.gsapi_run_file(gs.instance, gsFnDoc, C.int(0), &exitCode)
if err <= GS_FATAL_ERROR {
_ = gs.Exit()
return fmt.Errorf("unable to run Ghostscript interpreter due to a fatal error, exiting: %+v", err)
}
if err < GS_NO_ERRORS {
return fmt.Errorf("unable to run Ghostscript interpreter on file name: %+v", err)
}
return nil
}
// Exit is an implementation of gsapi_exit.
// It exits the Ghostscript interpreter.
// It must be called if Init has been called, and just before Destroy.
func (gs *Ghostscript) Exit() error {
if err := C.gsapi_exit(gs.instance); err < GS_NO_ERRORS {
return fmt.Errorf("unable to exit Ghostscript interpreter: %+v", err)
}
return nil
}