Initial Commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
covergen
|
||||
!covergen/
|
||||
*.pdf
|
||||
3
Makefile
Normal file
3
Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
wasm:
|
||||
GOOS=js GOARCH=wasm go build -o assets/covergen.wasm ./cmd/wasm/main.go
|
||||
.PHONY: wasm
|
||||
BIN
assets/covergen.wasm
Executable file
BIN
assets/covergen.wasm
Executable file
Binary file not shown.
53
assets/index.html
Normal file
53
assets/index.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch("covergen.wasm"), go.importObject).then((result) => {
|
||||
go.run(result.instance);
|
||||
});
|
||||
|
||||
function makeCover(args) {
|
||||
const result = window.covergen(args);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
return new File([result], 'cover.pdf', {type: 'application/pdf'});
|
||||
}
|
||||
|
||||
function letsfuckinggo() {
|
||||
document.getElementById('cover').src = window.URL.createObjectURL(makeCover({
|
||||
customer: document.getElementById('customer').value,
|
||||
number: document.getElementById('number').value,
|
||||
}));
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
html, body, #cover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<label>
|
||||
Customer:
|
||||
<textarea id="customer"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Number:
|
||||
<input id="number" type="text">
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="letsfuckinggo()">Fuck it!</button>
|
||||
</div>
|
||||
<iframe id="cover"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
633
assets/wasm_exec.js
Normal file
633
assets/wasm_exec.js
Normal file
@@ -0,0 +1,633 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
// - Webpack
|
||||
|
||||
if (typeof global !== "undefined") {
|
||||
// global already exists
|
||||
} else if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
||||
}
|
||||
|
||||
if (!global.require && typeof require !== "undefined") {
|
||||
global.require = require;
|
||||
}
|
||||
|
||||
if (!global.fs && global.require) {
|
||||
const fs = require("fs");
|
||||
if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
|
||||
global.fs = fs;
|
||||
}
|
||||
}
|
||||
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!global.fs) {
|
||||
let outputBuf = "";
|
||||
global.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.process) {
|
||||
global.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!global.crypto && global.require) {
|
||||
const nodeCrypto = require("crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!global.crypto) {
|
||||
throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.TextEncoder && global.require) {
|
||||
global.TextEncoder = require("util").TextEncoder;
|
||||
}
|
||||
if (!global.TextEncoder) {
|
||||
throw new Error("global.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!global.TextDecoder && global.require) {
|
||||
global.TextDecoder = require("util").TextDecoder;
|
||||
}
|
||||
if (!global.TextDecoder) {
|
||||
throw new Error("global.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
go: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime1() (sec int64, nsec int32)
|
||||
"runtime.walltime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[global, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof module !== "undefined" &&
|
||||
global.require &&
|
||||
global.require.main === module &&
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
!global.process.versions.electron
|
||||
) {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
go.argv = process.argv.slice(2);
|
||||
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
|
||||
go.exit = process.exit;
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
process.on("exit", (code) => { // Node.js exits if no event handler is pending
|
||||
if (code === 0 && !go.exited) {
|
||||
// deadlock, make Go print error and stack traces
|
||||
go._pendingEvent = { id: 0 };
|
||||
go._resume();
|
||||
}
|
||||
});
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
87
cmd/covergen/main.go
Normal file
87
cmd/covergen/main.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"covergen/pkg/covergen"
|
||||
)
|
||||
|
||||
var colors = []string{"red", "blue", "yellow", "green"}
|
||||
var colorMap = map[string]covergen.Color{
|
||||
"red": {255, 85, 0},
|
||||
"blue": {59, 180, 255},
|
||||
"yellow": {255, 230, 0},
|
||||
"green": {76, 177, 110},
|
||||
}
|
||||
|
||||
var number = pflag.StringP("number", "n", "", "Invoice or quotation number")
|
||||
var customer = pflag.StringP("customer", "c", "", "Customer name for cover")
|
||||
var numberPrefix = pflag.String("number-prefix", "offerte", "Prefix to use for number")
|
||||
var color = pflag.String("color", "random", "Selects a color to use for the grid highlight. Valid choices: ['random', 'red', 'blue', 'yellow', 'green']")
|
||||
var output = pflag.StringP("output", "o", "cover.pdf", "File to output to")
|
||||
|
||||
func init() {
|
||||
log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger()
|
||||
pflag.Parse()
|
||||
|
||||
if *number == "" {
|
||||
log.Error().Msg("Missing (invoice) number flag")
|
||||
pflag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *customer == "" {
|
||||
log.Error().Msg("Missing customer name flag")
|
||||
pflag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *color != "random" {
|
||||
if _, ok := colorMap[*color]; !ok {
|
||||
log.Error().Msgf("Color %s is not defined", *color)
|
||||
pflag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if *output == "" {
|
||||
log.Error().Msg("Invalid output flag")
|
||||
pflag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
var chosenColor covergen.Color
|
||||
if *color == "random" {
|
||||
chosenColor = colorMap[colors[rand.Intn(len(colors))]]
|
||||
} else {
|
||||
chosenColor = colorMap[*color]
|
||||
}
|
||||
|
||||
*customer = strings.ReplaceAll(*customer, "\\n", "\n")
|
||||
|
||||
pdf, err := covergen.GenerateInvoice(covergen.CoverSettings{
|
||||
Number: *number,
|
||||
NumberPrefix: *numberPrefix,
|
||||
CustomerName: *customer,
|
||||
HLColor: chosenColor,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to render invoice")
|
||||
}
|
||||
|
||||
if err := pdf.OutputFileAndClose(*output); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to write invoice to disk")
|
||||
}
|
||||
}
|
||||
14
cmd/server/main.go
Normal file
14
cmd/server/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := http.ListenAndServe(":9090", http.FileServer(http.Dir("assets")))
|
||||
if err != nil {
|
||||
fmt.Println("Failed to start server", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
118
cmd/wasm/main.go
Normal file
118
cmd/wasm/main.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/thegrumpylion/jsref"
|
||||
|
||||
"covergen/pkg/covergen"
|
||||
)
|
||||
|
||||
type jsmap = map[string]interface{}
|
||||
|
||||
type covergenArgs struct {
|
||||
Customer string `jsref:"customer"`
|
||||
Number string `jsref:"number"`
|
||||
NumberPrefix string `jsref:"numberPrefix"`
|
||||
HLColor string `jsref:"hlColor"`
|
||||
}
|
||||
|
||||
func parseHexColor(s string) (c covergen.Color, err error) {
|
||||
switch len(s) {
|
||||
case 7:
|
||||
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
|
||||
case 4:
|
||||
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
|
||||
// Double the hex digits:
|
||||
c.R *= 17
|
||||
c.G *= 17
|
||||
c.B *= 17
|
||||
default:
|
||||
err = fmt.Errorf("invalid length, must be 7 or 4")
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func settingsFromValue(arg js.Value) (*covergen.CoverSettings, error) {
|
||||
settings := covergenArgs{
|
||||
NumberPrefix: "offerte",
|
||||
}
|
||||
err := jsref.Unmarshal(&settings, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if settings.Number == "" {
|
||||
return nil, errors.New("number: shouldn't be empty")
|
||||
}
|
||||
|
||||
if settings.Customer == "" {
|
||||
return nil, errors.New("customer: shouldn't be empty")
|
||||
}
|
||||
|
||||
color := covergen.Color{R: 255}
|
||||
if settings.HLColor != "" {
|
||||
color, err = parseHexColor(settings.HLColor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hlColor: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &covergen.CoverSettings{
|
||||
Number: settings.Number,
|
||||
NumberPrefix: settings.NumberPrefix,
|
||||
CustomerName: settings.Customer,
|
||||
HLColor: color,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateCover(this js.Value, args []js.Value) interface{} {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("recovered", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(args) != 1 {
|
||||
return jsmap{"error": "missing argument"}
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
if arg.Type() != js.TypeObject {
|
||||
return jsmap{"error": "expected object"}
|
||||
}
|
||||
|
||||
settings, err := settingsFromValue(arg)
|
||||
if err != nil {
|
||||
return jsmap{"error": err.Error()}
|
||||
}
|
||||
|
||||
pdf, err := covergen.GenerateInvoice(*settings)
|
||||
if err != nil {
|
||||
return jsmap{"error": err.Error()}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err = pdf.Output(&buf); err != nil {
|
||||
return jsmap{"error": err.Error()}
|
||||
}
|
||||
|
||||
s := buf.Bytes()
|
||||
sum := sha256.Sum256(s)
|
||||
fmt.Println("shasum", hex.EncodeToString(sum[:]))
|
||||
|
||||
ta := js.Global().Get("Uint8Array").New(len(s))
|
||||
js.CopyBytesToJS(ta, s)
|
||||
return ta
|
||||
}
|
||||
|
||||
func main() {
|
||||
js.Global().Set("covergen", js.FuncOf(generateCover))
|
||||
<-make(chan bool)
|
||||
}
|
||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module covergen
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/go-pdf/fpdf v0.5.0
|
||||
github.com/rs/zerolog v1.26.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thegrumpylion/jsref v0.0.0-20201010132516-cde94ac58b50 // indirect
|
||||
)
|
||||
53
go.sum
Normal file
53
go.sum
Normal file
@@ -0,0 +1,53 @@
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-pdf/fpdf v0.5.0 h1:GHpcYsiDV2hdo77VTOuTF9k1sN8F8IY7NjnCo9x+NPY=
|
||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
|
||||
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/thegrumpylion/jsref v0.0.0-20201010132516-cde94ac58b50 h1:+VI/5fDcWqq0+yXPIAR2y3FJuTT05VBgcev7P8jY1q0=
|
||||
github.com/thegrumpylion/jsref v0.0.0-20201010132516-cde94ac58b50/go.mod h1:SQhhmnL4j/8Axo6GXCMJkuWVpi90x42s7KNRfVjYH2E=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
21
pkg/covergen/color.go
Normal file
21
pkg/covergen/color.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package covergen
|
||||
|
||||
import "github.com/go-pdf/fpdf"
|
||||
|
||||
type Color struct {
|
||||
R uint8
|
||||
G uint8
|
||||
B uint8
|
||||
}
|
||||
|
||||
func (c Color) Text(pdf *fpdf.Fpdf) {
|
||||
pdf.SetTextColor(int(c.R), int(c.G), int(c.B))
|
||||
}
|
||||
|
||||
func (c Color) Draw(pdf *fpdf.Fpdf) {
|
||||
pdf.SetDrawColor(int(c.R), int(c.G), int(c.B))
|
||||
}
|
||||
|
||||
func (c Color) Fill(pdf *fpdf.Fpdf) {
|
||||
pdf.SetFillColor(int(c.R), int(c.G), int(c.B))
|
||||
}
|
||||
1
pkg/covergen/font_embed/Mark-Light.json
Normal file
1
pkg/covergen/font_embed/Mark-Light.json
Normal file
@@ -0,0 +1 @@
|
||||
{"Tp":"TrueType","Name":"Mark-Light","Desc":{"Ascent":790,"Descent":-210,"CapHeight":700,"Flags":32,"FontBBox":{"Xmin":-463,"Ymin":-239,"Xmax":1072,"Ymax":954},"ItalicAngle":0,"StemV":70,"MissingWidth":599},"Up":-309,"Ut":52,"Cw":[599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,265,260,507,728,700,956,690,351,400,400,433,600,244,418,260,492,600,600,600,600,600,600,600,600,600,600,260,244,600,600,600,424,990,703,700,764,801,671,644,819,783,271,576,684,602,930,785,843,666,843,671,672,608,755,693,1104,687,632,664,369,492,369,599,468,599,622,623,512,622,561,438,618,599,239,240,521,239,965,599,596,623,622,407,487,452,593,515,863,503,514,533,420,351,420,599,599,700,599,244,599,411,705,599,599,599,599,672,310,1130,599,664,599,599,244,244,411,411,437,601,813,599,674,487,310,991,599,533,632,265,260,599,700,599,700,599,655,599,949,599,499,599,418,440,599,384,599,599,599,599,599,599,375,599,599,599,499,599,599,599,424,703,703,703,703,703,703,1133,764,671,671,671,671,271,271,271,271,888,785,843,843,843,843,843,600,843,755,755,755,755,632,666,608,622,622,622,622,622,622,910,512,561,561,561,561,239,239,239,239,595,599,596,596,596,596,596,599,596,593,593,593,593,514,623,514],"Enc":"cp1252","Diff":"","File":"Mark-Light.z","Size1":0,"Size2":0,"OriginalSize":49024,"N":0,"DiffN":0}
|
||||
BIN
pkg/covergen/font_embed/Mark-Light.z
Normal file
BIN
pkg/covergen/font_embed/Mark-Light.z
Normal file
Binary file not shown.
1
pkg/covergen/font_embed/Mark-Medium.json
Normal file
1
pkg/covergen/font_embed/Mark-Medium.json
Normal file
@@ -0,0 +1 @@
|
||||
{"Tp":"TrueType","Name":"Mark-Medium","Desc":{"Ascent":790,"Descent":-210,"CapHeight":700,"Flags":32,"FontBBox":{"Xmin":-491,"Ymin":-237,"Xmax":1083,"Ymax":994},"ItalicAngle":0,"StemV":70,"MissingWidth":605},"Up":-368,"Ut":68,"Cw":[605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,270,265,556,740,700,992,727,371,425,425,475,600,253,423,265,504,600,600,600,600,600,600,600,600,600,600,265,253,600,600,600,445,986,724,702,766,794,682,652,820,794,303,575,712,615,944,796,847,662,847,686,676,604,765,705,1090,678,664,646,388,504,388,605,470,605,624,624,514,624,560,422,616,605,269,272,543,269,945,605,611,624,624,430,506,454,598,531,837,526,549,521,440,371,440,605,605,700,605,253,605,448,815,605,605,605,605,676,319,1127,605,646,605,605,253,253,448,448,423,589,807,605,790,506,319,968,605,521,664,270,265,605,700,605,700,605,654,605,956,605,531,605,423,509,605,421,605,605,605,605,605,605,328,605,605,605,531,605,605,605,445,724,724,724,724,724,724,1159,766,682,682,682,682,303,303,303,303,851,796,847,847,847,847,847,600,847,765,765,765,765,664,662,621,624,624,624,624,624,624,882,514,560,560,560,560,269,269,269,269,608,605,611,611,611,611,611,605,611,598,598,598,598,549,624,549],"Enc":"cp1252","Diff":"","File":"Mark-Medium.z","Size1":0,"Size2":0,"OriginalSize":47520,"N":0,"DiffN":0}
|
||||
BIN
pkg/covergen/font_embed/Mark-Medium.z
Normal file
BIN
pkg/covergen/font_embed/Mark-Medium.z
Normal file
Binary file not shown.
BIN
pkg/covergen/fonts/Mark-Light.ttf
Normal file
BIN
pkg/covergen/fonts/Mark-Light.ttf
Normal file
Binary file not shown.
BIN
pkg/covergen/fonts/Mark-Medium.ttf
Normal file
BIN
pkg/covergen/fonts/Mark-Medium.ttf
Normal file
Binary file not shown.
251
pkg/covergen/fonts/cp1252.map
Normal file
251
pkg/covergen/fonts/cp1252.map
Normal file
@@ -0,0 +1,251 @@
|
||||
!00 U+0000 .notdef
|
||||
!01 U+0001 .notdef
|
||||
!02 U+0002 .notdef
|
||||
!03 U+0003 .notdef
|
||||
!04 U+0004 .notdef
|
||||
!05 U+0005 .notdef
|
||||
!06 U+0006 .notdef
|
||||
!07 U+0007 .notdef
|
||||
!08 U+0008 .notdef
|
||||
!09 U+0009 .notdef
|
||||
!0A U+000A .notdef
|
||||
!0B U+000B .notdef
|
||||
!0C U+000C .notdef
|
||||
!0D U+000D .notdef
|
||||
!0E U+000E .notdef
|
||||
!0F U+000F .notdef
|
||||
!10 U+0010 .notdef
|
||||
!11 U+0011 .notdef
|
||||
!12 U+0012 .notdef
|
||||
!13 U+0013 .notdef
|
||||
!14 U+0014 .notdef
|
||||
!15 U+0015 .notdef
|
||||
!16 U+0016 .notdef
|
||||
!17 U+0017 .notdef
|
||||
!18 U+0018 .notdef
|
||||
!19 U+0019 .notdef
|
||||
!1A U+001A .notdef
|
||||
!1B U+001B .notdef
|
||||
!1C U+001C .notdef
|
||||
!1D U+001D .notdef
|
||||
!1E U+001E .notdef
|
||||
!1F U+001F .notdef
|
||||
!20 U+0020 space
|
||||
!21 U+0021 exclam
|
||||
!22 U+0022 quotedbl
|
||||
!23 U+0023 numbersign
|
||||
!24 U+0024 dollar
|
||||
!25 U+0025 percent
|
||||
!26 U+0026 ampersand
|
||||
!27 U+0027 quotesingle
|
||||
!28 U+0028 parenleft
|
||||
!29 U+0029 parenright
|
||||
!2A U+002A asterisk
|
||||
!2B U+002B plus
|
||||
!2C U+002C comma
|
||||
!2D U+002D hyphen
|
||||
!2E U+002E period
|
||||
!2F U+002F slash
|
||||
!30 U+0030 zero
|
||||
!31 U+0031 one
|
||||
!32 U+0032 two
|
||||
!33 U+0033 three
|
||||
!34 U+0034 four
|
||||
!35 U+0035 five
|
||||
!36 U+0036 six
|
||||
!37 U+0037 seven
|
||||
!38 U+0038 eight
|
||||
!39 U+0039 nine
|
||||
!3A U+003A colon
|
||||
!3B U+003B semicolon
|
||||
!3C U+003C less
|
||||
!3D U+003D equal
|
||||
!3E U+003E greater
|
||||
!3F U+003F question
|
||||
!40 U+0040 at
|
||||
!41 U+0041 A
|
||||
!42 U+0042 B
|
||||
!43 U+0043 C
|
||||
!44 U+0044 D
|
||||
!45 U+0045 E
|
||||
!46 U+0046 F
|
||||
!47 U+0047 G
|
||||
!48 U+0048 H
|
||||
!49 U+0049 I
|
||||
!4A U+004A J
|
||||
!4B U+004B K
|
||||
!4C U+004C L
|
||||
!4D U+004D M
|
||||
!4E U+004E N
|
||||
!4F U+004F O
|
||||
!50 U+0050 P
|
||||
!51 U+0051 Q
|
||||
!52 U+0052 R
|
||||
!53 U+0053 S
|
||||
!54 U+0054 T
|
||||
!55 U+0055 U
|
||||
!56 U+0056 V
|
||||
!57 U+0057 W
|
||||
!58 U+0058 X
|
||||
!59 U+0059 Y
|
||||
!5A U+005A Z
|
||||
!5B U+005B bracketleft
|
||||
!5C U+005C backslash
|
||||
!5D U+005D bracketright
|
||||
!5E U+005E asciicircum
|
||||
!5F U+005F underscore
|
||||
!60 U+0060 grave
|
||||
!61 U+0061 a
|
||||
!62 U+0062 b
|
||||
!63 U+0063 c
|
||||
!64 U+0064 d
|
||||
!65 U+0065 e
|
||||
!66 U+0066 f
|
||||
!67 U+0067 g
|
||||
!68 U+0068 h
|
||||
!69 U+0069 i
|
||||
!6A U+006A j
|
||||
!6B U+006B k
|
||||
!6C U+006C l
|
||||
!6D U+006D m
|
||||
!6E U+006E n
|
||||
!6F U+006F o
|
||||
!70 U+0070 p
|
||||
!71 U+0071 q
|
||||
!72 U+0072 r
|
||||
!73 U+0073 s
|
||||
!74 U+0074 t
|
||||
!75 U+0075 u
|
||||
!76 U+0076 v
|
||||
!77 U+0077 w
|
||||
!78 U+0078 x
|
||||
!79 U+0079 y
|
||||
!7A U+007A z
|
||||
!7B U+007B braceleft
|
||||
!7C U+007C bar
|
||||
!7D U+007D braceright
|
||||
!7E U+007E asciitilde
|
||||
!7F U+007F .notdef
|
||||
!80 U+20AC Euro
|
||||
!82 U+201A quotesinglbase
|
||||
!83 U+0192 florin
|
||||
!84 U+201E quotedblbase
|
||||
!85 U+2026 ellipsis
|
||||
!86 U+2020 dagger
|
||||
!87 U+2021 daggerdbl
|
||||
!88 U+02C6 circumflex
|
||||
!89 U+2030 perthousand
|
||||
!8A U+0160 Scaron
|
||||
!8B U+2039 guilsinglleft
|
||||
!8C U+0152 OE
|
||||
!8E U+017D Zcaron
|
||||
!91 U+2018 quoteleft
|
||||
!92 U+2019 quoteright
|
||||
!93 U+201C quotedblleft
|
||||
!94 U+201D quotedblright
|
||||
!95 U+2022 bullet
|
||||
!96 U+2013 endash
|
||||
!97 U+2014 emdash
|
||||
!98 U+02DC tilde
|
||||
!99 U+2122 trademark
|
||||
!9A U+0161 scaron
|
||||
!9B U+203A guilsinglright
|
||||
!9C U+0153 oe
|
||||
!9E U+017E zcaron
|
||||
!9F U+0178 Ydieresis
|
||||
!A0 U+00A0 space
|
||||
!A1 U+00A1 exclamdown
|
||||
!A2 U+00A2 cent
|
||||
!A3 U+00A3 sterling
|
||||
!A4 U+00A4 currency
|
||||
!A5 U+00A5 yen
|
||||
!A6 U+00A6 brokenbar
|
||||
!A7 U+00A7 section
|
||||
!A8 U+00A8 dieresis
|
||||
!A9 U+00A9 copyright
|
||||
!AA U+00AA ordfeminine
|
||||
!AB U+00AB guillemotleft
|
||||
!AC U+00AC logicalnot
|
||||
!AD U+00AD hyphen
|
||||
!AE U+00AE registered
|
||||
!AF U+00AF macron
|
||||
!B0 U+00B0 degree
|
||||
!B1 U+00B1 plusminus
|
||||
!B2 U+00B2 twosuperior
|
||||
!B3 U+00B3 threesuperior
|
||||
!B4 U+00B4 acute
|
||||
!B5 U+00B5 mu
|
||||
!B6 U+00B6 paragraph
|
||||
!B7 U+00B7 periodcentered
|
||||
!B8 U+00B8 cedilla
|
||||
!B9 U+00B9 onesuperior
|
||||
!BA U+00BA ordmasculine
|
||||
!BB U+00BB guillemotright
|
||||
!BC U+00BC onequarter
|
||||
!BD U+00BD onehalf
|
||||
!BE U+00BE threequarters
|
||||
!BF U+00BF questiondown
|
||||
!C0 U+00C0 Agrave
|
||||
!C1 U+00C1 Aacute
|
||||
!C2 U+00C2 Acircumflex
|
||||
!C3 U+00C3 Atilde
|
||||
!C4 U+00C4 Adieresis
|
||||
!C5 U+00C5 Aring
|
||||
!C6 U+00C6 AE
|
||||
!C7 U+00C7 Ccedilla
|
||||
!C8 U+00C8 Egrave
|
||||
!C9 U+00C9 Eacute
|
||||
!CA U+00CA Ecircumflex
|
||||
!CB U+00CB Edieresis
|
||||
!CC U+00CC Igrave
|
||||
!CD U+00CD Iacute
|
||||
!CE U+00CE Icircumflex
|
||||
!CF U+00CF Idieresis
|
||||
!D0 U+00D0 Eth
|
||||
!D1 U+00D1 Ntilde
|
||||
!D2 U+00D2 Ograve
|
||||
!D3 U+00D3 Oacute
|
||||
!D4 U+00D4 Ocircumflex
|
||||
!D5 U+00D5 Otilde
|
||||
!D6 U+00D6 Odieresis
|
||||
!D7 U+00D7 multiply
|
||||
!D8 U+00D8 Oslash
|
||||
!D9 U+00D9 Ugrave
|
||||
!DA U+00DA Uacute
|
||||
!DB U+00DB Ucircumflex
|
||||
!DC U+00DC Udieresis
|
||||
!DD U+00DD Yacute
|
||||
!DE U+00DE Thorn
|
||||
!DF U+00DF germandbls
|
||||
!E0 U+00E0 agrave
|
||||
!E1 U+00E1 aacute
|
||||
!E2 U+00E2 acircumflex
|
||||
!E3 U+00E3 atilde
|
||||
!E4 U+00E4 adieresis
|
||||
!E5 U+00E5 aring
|
||||
!E6 U+00E6 ae
|
||||
!E7 U+00E7 ccedilla
|
||||
!E8 U+00E8 egrave
|
||||
!E9 U+00E9 eacute
|
||||
!EA U+00EA ecircumflex
|
||||
!EB U+00EB edieresis
|
||||
!EC U+00EC igrave
|
||||
!ED U+00ED iacute
|
||||
!EE U+00EE icircumflex
|
||||
!EF U+00EF idieresis
|
||||
!F0 U+00F0 eth
|
||||
!F1 U+00F1 ntilde
|
||||
!F2 U+00F2 ograve
|
||||
!F3 U+00F3 oacute
|
||||
!F4 U+00F4 ocircumflex
|
||||
!F5 U+00F5 otilde
|
||||
!F6 U+00F6 odieresis
|
||||
!F7 U+00F7 divide
|
||||
!F8 U+00F8 oslash
|
||||
!F9 U+00F9 ugrave
|
||||
!FA U+00FA uacute
|
||||
!FB U+00FB ucircumflex
|
||||
!FC U+00FC udieresis
|
||||
!FD U+00FD yacute
|
||||
!FE U+00FE thorn
|
||||
!FF U+00FF ydieresis
|
||||
BIN
pkg/covergen/miwebb.white.png
Normal file
BIN
pkg/covergen/miwebb.white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
247
pkg/covergen/render.go
Normal file
247
pkg/covergen/render.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package covergen
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pdf/fpdf"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const margin = 20
|
||||
const miwebbLogoName = "miwebbLogo"
|
||||
const miwebbLogoPath = "miwebb.white.png"
|
||||
const logoScale = 0.6
|
||||
|
||||
const gridXTicks = 10
|
||||
const gridYTicks = 6
|
||||
const gridMargin = 5
|
||||
const lineHeight = 1.2
|
||||
const maxFontSize = 100
|
||||
|
||||
var bgColor = Color{25, 25, 25}
|
||||
var textColor = Color{255, 255, 255}
|
||||
var gridColor = Color{80, 80, 80}
|
||||
var gridNoduleColor = Color{255, 255, 255}
|
||||
|
||||
//go:embed miwebb.white.png font_embed
|
||||
var fs embed.FS
|
||||
|
||||
const fontsEmbedPrefix = "font_embed/"
|
||||
|
||||
// Copied from fPDF's source, can't get it dynamically
|
||||
// constant for mm is just to ensure you don't change it without noting the scale factor that depends on it
|
||||
const scaleUnit = "mm"
|
||||
const scaleFactor = 72.0 / 25.4
|
||||
|
||||
type CoverSettings struct {
|
||||
Number string
|
||||
NumberPrefix string
|
||||
|
||||
CustomerName string
|
||||
HLColor Color
|
||||
}
|
||||
|
||||
func GenerateInvoice(settings CoverSettings) (*fpdf.Fpdf, error) {
|
||||
pdf := fpdf.New("P", scaleUnit, "A4", "")
|
||||
|
||||
if err := addEmbeddedFont(pdf, "Mark Medium", "", "Mark-Medium"); err != nil {
|
||||
log.Fatal().Err(err).Msg("Error adding font")
|
||||
}
|
||||
if err := addEmbeddedFont(pdf, "Mark Light", "", "Mark-Light"); err != nil {
|
||||
log.Fatal().Err(err).Msg("Error adding font")
|
||||
}
|
||||
miwebbLogo, err := addEmbeddedLogo(pdf, miwebbLogoName, miwebbLogoPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error adding logo")
|
||||
}
|
||||
|
||||
pdf.SetMargins(margin, margin, margin)
|
||||
pageWidth, pageHeight := pdf.GetPageSize()
|
||||
pdf.SetAutoPageBreak(false, 0)
|
||||
|
||||
pdf.SetHeaderFunc(func() {
|
||||
bgColor.Fill(pdf)
|
||||
pdf.Rect(0, 0, pageWidth, pageHeight, "F")
|
||||
pdf.Image(miwebbLogoName, margin-5, margin, miwebbLogo.Width()*logoScale, miwebbLogo.Height()*logoScale, false, "", 0, "")
|
||||
})
|
||||
|
||||
pdf.AddPage()
|
||||
|
||||
drawCustomerName(pdf, settings)
|
||||
drawInvoiceNumber(pdf, settings)
|
||||
drawMiwebbLink(pdf)
|
||||
|
||||
pdf.AddPage()
|
||||
drawCustomerName(pdf, settings)
|
||||
|
||||
if pdf.Err() {
|
||||
return nil, pdf.Error()
|
||||
}
|
||||
|
||||
return pdf, nil
|
||||
}
|
||||
func drawInvoiceNumber(pdf *fpdf.Fpdf, settings CoverSettings) {
|
||||
_, pageHeight := pdf.GetPageSize()
|
||||
|
||||
pdf.MoveTo(margin, pageHeight-margin)
|
||||
pdf.SetFont("Mark Light", "", 9)
|
||||
textColor.Text(pdf)
|
||||
|
||||
pdf.TransformBegin()
|
||||
pdf.TransformRotate(90, margin, pageHeight-margin)
|
||||
invoiceText := strings.ToUpper(fmt.Sprintf("%s %s", settings.NumberPrefix, settings.Number))
|
||||
pdf.CellFormat(0, 1, invoiceText, "", 0, "LT", false, 0, "")
|
||||
pdf.TransformEnd()
|
||||
|
||||
_, h := pdf.GetFontSize()
|
||||
// X is 'margin + half text height'
|
||||
x := float64(margin) + h/2
|
||||
// Y is 'where the text ended' + some gap
|
||||
y := pageHeight - margin - pdf.GetStringWidth(invoiceText) - 5
|
||||
|
||||
textColor.Draw(pdf)
|
||||
pdf.SetLineWidth(0.01)
|
||||
pdf.Line(x, y, x, y-10)
|
||||
}
|
||||
|
||||
func drawMiwebbLink(pdf *fpdf.Fpdf) {
|
||||
pageWidth, pageHeight := pdf.GetPageSize()
|
||||
pdf.MoveTo(pageWidth-margin, pageHeight-margin)
|
||||
|
||||
pdf.SetFont("Mark Light", "", 9)
|
||||
textColor.Text(pdf)
|
||||
pdf.CellFormat(0, 0, "WWW.MIWEBB.COM", "", 0, "RB", false, 0, "https://www.miwebb.com")
|
||||
}
|
||||
|
||||
func drawCustomerName(pdf *fpdf.Fpdf, settings CoverSettings) {
|
||||
ml, _, mr, _ := pdf.GetMargins()
|
||||
pw, ph := pdf.GetPageSize()
|
||||
|
||||
width := pw - ml - mr
|
||||
height := (width/gridXTicks - 1) * gridYTicks
|
||||
text := strings.ToUpper(settings.CustomerName)
|
||||
|
||||
// Draw the grid with a weird gray color
|
||||
gridColor.Draw(pdf)
|
||||
pdf.SetLineWidth(0.01)
|
||||
gridX, gridY := ml, (ph/2)-(height/2)
|
||||
drawGrid(pdf, gridX, gridY, width, height, gridXTicks, gridYTicks)
|
||||
|
||||
// Draw the highlight on the right
|
||||
drawNoduleLine(
|
||||
pdf,
|
||||
gridX+width, gridY,
|
||||
gridX+width, gridY+height,
|
||||
1.2, gridNoduleColor,
|
||||
0.25, settings.HLColor)
|
||||
|
||||
pdf.SetFont("Mark Medium", "", 10)
|
||||
// Estimate font size, and calculate line height immediately after
|
||||
pdf.SetFontSize(estimateFontSize(pdf, text, width, height, gridMargin))
|
||||
_, sz := pdf.GetFontSize()
|
||||
sz *= lineHeight
|
||||
|
||||
_, textHeight := stringSize(pdf, text, sz, 0)
|
||||
// Centered vertically:
|
||||
// x = <gridX> + <margin>
|
||||
// y = <gridY> + <gridH>/2 - textHeight/2
|
||||
|
||||
pdf.MoveTo(ml+gridMargin, ph/2-(textHeight/2))
|
||||
textColor.Text(pdf)
|
||||
pdf.MultiCell(width-2*gridMargin, sz, text, "", "ML", false)
|
||||
}
|
||||
|
||||
func drawGrid(pdf *fpdf.Fpdf, x, y, w, h float64, xs, ys int) {
|
||||
// Horizontal lines
|
||||
for i := 0; i < ys; i++ {
|
||||
yOff := y + (h/float64(ys-1))*float64(i)
|
||||
pdf.Line(x, yOff, x+w, yOff)
|
||||
}
|
||||
|
||||
// Vertical line(pw/2)-(width/2)s
|
||||
for i := 0; i < xs; i++ {
|
||||
xOff := x + (w/float64(xs-1))*float64(i)
|
||||
pdf.Line(xOff, y, xOff, y+h)
|
||||
}
|
||||
}
|
||||
|
||||
func drawNoduleLine(pdf *fpdf.Fpdf, x1, y1, x2, y2, squareSize float64, squareColor Color, lineWidth float64, lineColor Color) {
|
||||
lineColor.Draw(pdf)
|
||||
pdf.SetLineWidth(lineWidth)
|
||||
pdf.Line(x1, y1, x2, y2)
|
||||
|
||||
squareColor.Fill(pdf)
|
||||
pdf.Rect(x1-(squareSize/2), y1-(squareSize/2), squareSize, squareSize, "F")
|
||||
pdf.Rect(x2-(squareSize/2), y2-(squareSize/2), squareSize, squareSize, "F")
|
||||
}
|
||||
|
||||
func estimateFontSize(pdf *fpdf.Fpdf, text string, containerWidth, containerHeight, margin float64) float64 {
|
||||
// Margin is on both sides haha, but I add an extra just for 'safety'
|
||||
maxWidth := containerWidth - 3*margin
|
||||
maxHeight := containerHeight - 3*margin
|
||||
|
||||
// Probably smallest we can start with
|
||||
for fontSize := 10.; fontSize <= maxFontSize; fontSize += 1 {
|
||||
width, height := stringSize(pdf, text, (fontSize/scaleFactor)*lineHeight, fontSize)
|
||||
if width >= maxWidth || height >= maxHeight {
|
||||
return fontSize - 1
|
||||
}
|
||||
}
|
||||
|
||||
return maxFontSize
|
||||
}
|
||||
|
||||
func stringSize(pdf *fpdf.Fpdf, text string, lineHeight, fontSizePt float64) (width, height float64) {
|
||||
if fontSizePt == 0 {
|
||||
fontSizePt, _ = pdf.GetFontSize()
|
||||
}
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
height = float64(len(lines)) * lineHeight
|
||||
for _, line := range lines {
|
||||
lineWidth := float64(pdf.GetStringSymbolWidth(line)) * fontSizePt / scaleFactor / 1000
|
||||
if lineWidth > width {
|
||||
width = lineWidth
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addEmbeddedFont(pdf *fpdf.Fpdf, family, style, path string) error {
|
||||
jsonData, err := fs.ReadFile(fontsEmbedPrefix + path + ".json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zData, err := fs.ReadFile(fontsEmbedPrefix + path + ".z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pdf.AddFontFromBytes(family, style, jsonData, zData)
|
||||
if pdf.Err() {
|
||||
return pdf.Error()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addEmbeddedLogo(pdf *fpdf.Fpdf, name, path string) (*fpdf.ImageInfoType, error) {
|
||||
reader, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handle := pdf.RegisterImageOptionsReader(name, fpdf.ImageOptions{
|
||||
ImageType: "PNG",
|
||||
ReadDpi: true,
|
||||
}, reader)
|
||||
|
||||
if pdf.Err() {
|
||||
return nil, pdf.Error()
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
Reference in New Issue
Block a user