snowkit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +142 -2
- package/bin/snowkit.js +19 -0
- package/package.json +45 -4
- package/src/cli.js +177 -0
- package/src/engine/colorkit.js +338 -0
- package/src/engine/cron.js +148 -0
- package/src/engine/devtools-kit.js +1964 -0
- package/src/engine/docpdf-layout.js +1188 -0
- package/src/engine/docpdf-render.js +106 -0
- package/src/engine/html.js +59 -0
- package/src/engine/jsonkit.js +1241 -0
- package/src/engine/md-render.js +596 -0
- package/src/engine/mdpdf.js +113 -0
- package/src/engine/qrkit.js +1205 -0
- package/src/engine/readability.js +100 -0
- package/src/engine/sqlformat.js +425 -0
- package/src/engine/texttools-kit.js +623 -0
- package/src/files.js +132 -0
- package/src/pdf.js +297 -0
- package/src/qr.js +100 -0
- package/src/server.js +50 -0
- package/src/text.js +189 -0
- package/src/tools.js +223 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { layoutDocument } from './docpdf-layout.js';
|
|
2
|
+
|
|
3
|
+
const FACES = ['regular', 'bold', 'italic', 'bolditalic'];
|
|
4
|
+
|
|
5
|
+
function link(L, doc, page, o) {
|
|
6
|
+
const ctx = doc.context;
|
|
7
|
+
let uri = o.url;
|
|
8
|
+
try {
|
|
9
|
+
uri = encodeURI(decodeURI(o.url));
|
|
10
|
+
} catch (e) {}
|
|
11
|
+
const ref = ctx.register(ctx.obj({ Type: 'Annot', Subtype: 'Link', Rect: [o.x, o.y, o.x + o.w, o.y + o.h], Border: [0, 0, 0], F: 4, A: { Type: 'Action', S: 'URI', URI: L.PDFString.of(uri) } }));
|
|
12
|
+
const annots = page.node.lookup(L.PDFName.of('Annots'));
|
|
13
|
+
if (annots instanceof L.PDFArray) annots.push(ref);
|
|
14
|
+
else page.node.set(L.PDFName.of('Annots'), ctx.obj([ref]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function buildDocPdf(L, blocks, opts = {}) {
|
|
18
|
+
const doc = await L.PDFDocument.create();
|
|
19
|
+
const fonts = {};
|
|
20
|
+
if (opts.fontkit && opts.fontBytes && opts.fontBytes.regular) {
|
|
21
|
+
doc.registerFontkit(opts.fontkit);
|
|
22
|
+
for (const f of FACES) fonts[f] = await doc.embedFont(opts.fontBytes[f] || opts.fontBytes.regular, { subset: true });
|
|
23
|
+
} else {
|
|
24
|
+
const std = { regular: L.StandardFonts.Helvetica, bold: L.StandardFonts.HelveticaBold, italic: L.StandardFonts.HelveticaOblique, bolditalic: L.StandardFonts.HelveticaBoldOblique };
|
|
25
|
+
for (const f of FACES) fonts[f] = await doc.embedFont(std[f]);
|
|
26
|
+
}
|
|
27
|
+
fonts.mono = await doc.embedFont(L.StandardFonts.Courier);
|
|
28
|
+
fonts.monobold = await doc.embedFont(L.StandardFonts.CourierBold);
|
|
29
|
+
const custom = !!(opts.fontkit && opts.fontBytes && opts.fontBytes.regular);
|
|
30
|
+
const encodable = new Map();
|
|
31
|
+
const canEncode = (f, text) => {
|
|
32
|
+
if (custom && !f.startsWith('mono')) return true;
|
|
33
|
+
const key = f + '\u0000' + text;
|
|
34
|
+
if (encodable.has(key)) return encodable.get(key);
|
|
35
|
+
let ok = true;
|
|
36
|
+
try {
|
|
37
|
+
fonts[f].encodeText(text);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
ok = false;
|
|
40
|
+
}
|
|
41
|
+
if (encodable.size < 20000) encodable.set(key, ok);
|
|
42
|
+
return ok;
|
|
43
|
+
};
|
|
44
|
+
const measure = (text, f, size) => fonts[f].widthOfTextAtSize(text, size);
|
|
45
|
+
const images = opts.images || new Map();
|
|
46
|
+
const layout = layoutDocument(blocks, { page: opts.page, margin: opts.margin, base: opts.base, numbers: opts.numbers, measure, canEncode, image: (src) => (images.get(src) ? { w: images.get(src).w, h: images.get(src).h } : null) });
|
|
47
|
+
const embedded = new Map();
|
|
48
|
+
const color = (c) => L.rgb(c[0], c[1], c[2]);
|
|
49
|
+
for (const p of layout.pages) {
|
|
50
|
+
const page = doc.addPage([layout.width, layout.height]);
|
|
51
|
+
for (const o of p.ops) {
|
|
52
|
+
if (o.op === 'text') {
|
|
53
|
+
const t = { x: o.x, y: o.y, size: o.size, font: fonts[o.font] || fonts.regular, color: color(o.color) };
|
|
54
|
+
try {
|
|
55
|
+
page.drawText(o.text, t);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
page.drawText(o.text.replace(/[^\x20-\x7e\u00a0-\u00ff]/g, '?'), t);
|
|
58
|
+
}
|
|
59
|
+
} else if (o.op === 'rect') {
|
|
60
|
+
const r = { x: o.x, y: o.y, width: o.w, height: o.h };
|
|
61
|
+
if (o.fill) r.color = color(o.fill);
|
|
62
|
+
if (o.stroke) {
|
|
63
|
+
r.borderColor = color(o.stroke);
|
|
64
|
+
r.borderWidth = o.sw || 1;
|
|
65
|
+
}
|
|
66
|
+
page.drawRectangle(r);
|
|
67
|
+
} else if (o.op === 'line') {
|
|
68
|
+
page.drawLine({ start: { x: o.x1, y: o.y1 }, end: { x: o.x2, y: o.y2 }, thickness: o.w, color: color(o.color) });
|
|
69
|
+
} else if (o.op === 'circle') {
|
|
70
|
+
const c = { x: o.x, y: o.y, size: o.r };
|
|
71
|
+
if (o.fill) c.color = color(o.fill);
|
|
72
|
+
if (o.stroke) {
|
|
73
|
+
c.borderColor = color(o.stroke);
|
|
74
|
+
c.borderWidth = o.sw || 1;
|
|
75
|
+
}
|
|
76
|
+
page.drawCircle(c);
|
|
77
|
+
} else if (o.op === 'path') {
|
|
78
|
+
for (let i = 1; i < o.points.length; i++) page.drawLine({ start: { x: o.points[i - 1][0], y: o.points[i - 1][1] }, end: { x: o.points[i][0], y: o.points[i][1] }, thickness: o.w, color: color(o.color), lineCap: L.LineCapStyle.Round });
|
|
79
|
+
} else if (o.op === 'image') {
|
|
80
|
+
const src = images.get(o.src);
|
|
81
|
+
if (!src) continue;
|
|
82
|
+
let img = embedded.get(o.src);
|
|
83
|
+
if (!img) {
|
|
84
|
+
img = src.mime === 'image/jpeg' ? await doc.embedJpg(src.bytes) : await doc.embedPng(src.bytes);
|
|
85
|
+
embedded.set(o.src, img);
|
|
86
|
+
}
|
|
87
|
+
page.drawImage(img, { x: o.x, y: o.y, width: o.w, height: o.h });
|
|
88
|
+
} else if (o.op === 'link') {
|
|
89
|
+
link(L, doc, page, o);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (opts.title) doc.setTitle(opts.title);
|
|
94
|
+
doc.setCreator('Snowkit');
|
|
95
|
+
doc.setProducer('Snowkit');
|
|
96
|
+
const bytes = await doc.save();
|
|
97
|
+
return { bytes, pages: layout.pages.length, skipped: layout.skipped, links: layout.links };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function imageSources(blocks, out = new Set()) {
|
|
101
|
+
for (const b of blocks) {
|
|
102
|
+
if (b.type === 'image' && b.src) out.add(b.src);
|
|
103
|
+
if (b.type === 'table') for (const r of b.rows) for (const c of r.cells) imageSources(c.blocks, out);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const VOID = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
2
|
+
const RAW = new Set(["script", "style", "textarea", "title"]);
|
|
3
|
+
const AUTO_CLOSE = { p: /^(p|div|ul|ol|table|h[1-6]|pre|blockquote|section|article|header|footer|hr|dl)$/, li: /^li$/, dt: /^(dt|dd)$/, dd: /^(dt|dd)$/, tr: /^tr$/, td: /^(td|th|tr)$/, th: /^(td|th|tr)$/, option: /^option$/ };
|
|
4
|
+
const ENT = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\u00a0", copy: "\u00a9", reg: "\u00ae", hellip: "\u2026", mdash: "\u2014", ndash: "\u2013", lsquo: "\u2018", rsquo: "\u2019", ldquo: "\u201c", rdquo: "\u201d", bull: "\u2022", middot: "\u00b7", trade: "\u2122", euro: "\u20ac", pound: "\u00a3" };
|
|
5
|
+
|
|
6
|
+
export function decode(s) {
|
|
7
|
+
return s.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (m, e) => {
|
|
8
|
+
if (e[0] === "#") {
|
|
9
|
+
const code = e[1] === "x" || e[1] === "X" ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10);
|
|
10
|
+
return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : m;
|
|
11
|
+
}
|
|
12
|
+
return ENT[e.toLowerCase()] ?? m;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function el(name, attrs, parent) {
|
|
17
|
+
return { nodeType: 1, tagName: name, attrs, childNodes: [], parentNode: parent };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseHTML(src) {
|
|
21
|
+
const root = el("#root", {}, null);
|
|
22
|
+
let cur = root;
|
|
23
|
+
const re = /<!--[\s\S]*?-->|<!doctype[^>]*>|<\/([a-zA-Z][\w:-]*)\s*>|<([a-zA-Z][\w:-]*)((?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*(\/?)>/gi;
|
|
24
|
+
let at = 0;
|
|
25
|
+
let m;
|
|
26
|
+
const text = (s) => {
|
|
27
|
+
if (s) cur.childNodes.push({ nodeType: 3, nodeValue: decode(s), parentNode: cur });
|
|
28
|
+
};
|
|
29
|
+
while ((m = re.exec(src))) {
|
|
30
|
+
text(src.slice(at, m.index));
|
|
31
|
+
at = re.lastIndex;
|
|
32
|
+
if (m[0].startsWith("<!")) continue;
|
|
33
|
+
if (m[1]) {
|
|
34
|
+
const name = m[1].toLowerCase();
|
|
35
|
+
let n = cur;
|
|
36
|
+
while (n && n.tagName !== name) n = n.parentNode;
|
|
37
|
+
if (n && n !== root) cur = n.parentNode;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = m[2].toLowerCase();
|
|
41
|
+
const attrs = {};
|
|
42
|
+
for (const a of m[3].matchAll(/([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g)) attrs[a[1].toLowerCase()] = decode(a[2] ?? a[3] ?? a[4] ?? "");
|
|
43
|
+
while (AUTO_CLOSE[cur.tagName] && AUTO_CLOSE[cur.tagName].test(name)) cur = cur.parentNode;
|
|
44
|
+
const node = el(name, attrs, cur);
|
|
45
|
+
cur.childNodes.push(node);
|
|
46
|
+
if (VOID.has(name) || m[4]) continue;
|
|
47
|
+
if (RAW.has(name)) {
|
|
48
|
+
const end = src.toLowerCase().indexOf("</" + name, at);
|
|
49
|
+
const stop = end < 0 ? src.length : end;
|
|
50
|
+
node.childNodes.push({ nodeType: 3, nodeValue: name === "textarea" || name === "title" ? decode(src.slice(at, stop)) : src.slice(at, stop), parentNode: node });
|
|
51
|
+
const close = src.indexOf(">", stop);
|
|
52
|
+
at = re.lastIndex = close < 0 ? src.length : close + 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
cur = node;
|
|
56
|
+
}
|
|
57
|
+
text(src.slice(at));
|
|
58
|
+
return root;
|
|
59
|
+
}
|