-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsvg.ts
120 lines (108 loc) · 3.26 KB
/
svg.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
import { parseBuffer } from "./opentype.js";
interface Glyph {
index: number;
name: string;
unicode: number;
unicodes: number[];
advanceWidth?: number;
leftSideBearing: number;
}
const fontBuffer = await Deno.readFile("./Roboto-Regular.ttf");
const font = parseBuffer(fontBuffer.buffer, {});
export function write(
text: string,
options: { fontSize: number; decimalPlaces?: number; maxWidth?: number },
) {
const decimalPlaces = options.decimalPlaces || 2;
const fontSize = options.fontSize;
const fontScale = 1 / font.unitsPerEm * fontSize;
const maxWidth = options.maxWidth ?? Infinity;
const glyphs = font.stringToGlyphs(text) as Glyph[];
const chunks = [] as { width: number; index: number }[];
let width = 0;
glyphs.forEach((glyph, index) => {
const glyphWidth = (glyph.advanceWidth ?? 0) * fontScale;
if (width > 0 && width + glyphWidth > maxWidth) {
chunks.push({ width: +width.toFixed(decimalPlaces), index });
width = 0;
}
width += (width > 0
? font.getKerningValue(glyphs[index - 1], glyph) * fontScale
: 0) + glyphWidth;
});
chunks.push({ width: +width.toFixed(decimalPlaces), index: glyphs.length });
const ascender = font.ascender * fontScale;
const height = (font.ascender - font.descender) * fontScale;
return chunks.map((chunk, chunkIndex) => {
const startIndex = chunkIndex > 0 ? chunks[chunkIndex - 1].index : 0;
const line = text.slice(startIndex, chunk.index);
return {
h: height,
w: chunk.width,
d: font.getPath(line, 0, ascender, options.fontSize, {
kerning: true,
letterSpacing: false,
tracking: false,
}).toPathData() as string,
};
});
}
export function label(
messages: {
text: string;
fontSize?: number;
bgColor?: string;
textColor?: string;
px?: number;
py?: number;
}[],
): string {
if (messages.length === 0) {
return "";
}
const mMessages = messages.map((message) => {
const m = write(message.text, {
fontSize: message.fontSize ?? 12,
decimalPlaces: 2,
})[0];
return {
w: Math.ceil(m.w),
h: Math.ceil(m.h),
d: m.d,
px: message.px ?? 6,
py: message.py ?? 2.5,
bgColor: message.bgColor ?? "#555",
textColor: message.textColor ?? "#fff",
};
});
const width = mMessages.reduce((acc, m) => acc + m.w + m.px * 2, 0);
const height = Math.max(...mMessages.map((m) => m.h + m.py * 2));
let body =
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`;
body += `<g shape-rendering="crispEdges">`;
let rectX = 0;
mMessages.forEach((m) => {
body += `<rect x="${rectX}" width="${m.w +
m.px * 2}" height="${height}" fill="${m.bgColor}"/>`;
rectX += m.w + m.px * 2;
});
body += `</g>`;
body += `<g>`;
let textX = 0;
mMessages.forEach((m) => {
body += `<path transform="translate(${textX + m.px},${(height - m.h) /
2})" fill="${m.textColor}" d="${m.d}"/>`;
textX += m.w + m.px * 2;
});
body += `</g>`;
body += `</svg>`;
return body;
}
if (import.meta.main) {
// for test :-)
// $ deno run -A svg.ts > test.svg
console.log(label([
{ text: "2021-11-09 ~ 2021-11-20" },
{ text: "Day 2", bgColor: "#97ca00" },
]));
}