-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
158 lines (129 loc) · 4.07 KB
/
index.ts
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
import { encodeBase64, decodeBase64 } from "jsr:@std/[email protected]";
import { Buffer } from "jsr:@std/[email protected]";
import { assert } from "jsr:@std/[email protected]";
import {
ImageMagick,
IMagickImage,
initialize as initialise,
MagickFormat,
} from "https://deno.land/x/[email protected]/mod.ts";
const encoder = new TextEncoder();
const decoder = new TextDecoder("utf-8");
const toJpg = (data: Uint8Array, quality: number) =>
ImageMagick.read(data, (img: IMagickImage) => {
img.quality = quality;
return new Promise<Uint8Array>(res => img.write(MagickFormat.Jpeg, (data: Uint8Array) => res(data)));
});
const imgRegex =
/(?<before><\s*image\s[^>]*xlink:href\s*=\s*")data:image\/(?<format>png|jpeg|jpg);base64,(?<data>[A-Za-z0-9+\/=]+)(?<after>"[^>]*>)/;
type Parsed = {
before: string;
format: string;
data: string;
after: string;
};
const compare = (input: Uint8Array, compare: Uint8Array, fromIndex: number = 0) => {
for (let j = 0; j < compare.length; j++) if (input[fromIndex + j] !== compare[j]) return false;
return true;
};
Deno.test("compare", () => {
const input = encoder.encode("hello world");
const b = encoder.encode("world");
const i = compare(input, b, 6);
assert(i === true);
});
const findIndex = (input: Uint8Array, find: Uint8Array, fromindex: number = 0) => {
const len = find.length;
const endIndex = input.length - len + 1;
while (fromindex < endIndex) {
let found = true;
for (let j = 0; j < len; j++)
if (input[fromindex + j] !== find[j]) {
found = false;
break;
}
if (found) return fromindex;
fromindex++;
}
return -1;
};
Deno.test("findIndex", () => {
const input = encoder.encode("hello world");
const b = encoder.encode("world");
const i = findIndex(input, b, 0);
assert(i === 6);
});
const char = (char: string) => char.charCodeAt(0);
const OPEN = encoder.encode("<image");
const CLOSE = encoder.encode(">");
/** left here for the potential future */
// function parseAttrs(tag: string) {
// const attrs: { key: string; value: string }[] = [];
// const attrRegex = /(\w+|\w+:\w+)\s*=\s*"([^"]*?)"\s*/g;
// let match;
// while ((match = attrRegex.exec(tag)) !== null) {
// const [, attrName, attrValue] = match;
// attrs.push({ key: attrName, value: attrValue });
// }
// return attrs;
// }
export interface SvgcOptions {
/** Compression quality */
quality?: number;
/** Optimising PNGs will result in a loss of transparency and quality */
optimisePngs?: boolean;
// Future options
// /** Downscale images to fit within the width and height attributes if present */
// downscaleToFit?: boolean;
}
const defaultOptions: Required<SvgcOptions> = {
quality: 80,
optimisePngs: true,
// downscaleToFit: true,
};
export async function svgc(input: Uint8Array, options: SvgcOptions = defaultOptions) {
const opts = { ...defaultOptions, ...options };
const out = new Buffer();
await initialise();
let i = 0;
while (i < input.length) {
const c = input[i];
if (c !== char("<")) {
await out.write(new Uint8Array([c]));
i++;
continue;
}
const start = i;
// could the next characters be an opening <image tag?
const potential = input.subarray(i, i + OPEN.length);
i += OPEN.length;
// is potential an opening <image tag?
if (!compare(potential, OPEN)) {
await out.write(potential);
continue;
}
// find > after <image
const end = findIndex(input, CLOSE, start + OPEN.length);
// no closing > found, bail out
if (end < 0) {
await out.write(input.subarray(start, i));
continue;
}
i = end + 1;
const tag = input.subarray(start, i);
const match = decoder.decode(tag).match(imgRegex);
if (match) {
const { before, format, data, after } = match.groups as Parsed;
if (format === "png" && !opts.optimisePngs) {
await out.write(tag);
continue;
}
const buf = await toJpg(decodeBase64(data), opts.quality);
const jpg = "data:image/jpg;base64," + encodeBase64(buf);
const newTag = encoder.encode(`${before}${jpg}${after}`);
if (newTag.length < tag.length) await out.write(newTag);
else await out.write(tag);
} else await out.write(tag);
}
return out.bytes();
}