char/iocgn-mono

iosevka variant inspired by Simple Köln-Bonn

git clone https://git.t4t.associates/char/iocgn-mono

Charlotte Sominitial commitb757355

main
7.6 KiB152 linesraw
1import { ContourSetGeometry, TransformedGeometry } from '@iosevka/geometry';
2import { Point } from '@iosevka/geometry/point';
3
4// Two coordinates describe an on-curve point; six describe a cubic segment.
5export function outline(contours) {
6    const points = contours.map(contour => contour.flatMap(segment => {
7        if (segment.length === 2) return [Point.corner(...segment)];
8        return [new Point(Point.Type.CubicStart, segment[0], segment[1]),
9                new Point(Point.Type.CubicEnd, segment[2], segment[3]),
10                Point.corner(segment[4], segment[5])];
11    }));
12    return {
13        applyToGlyph(glyph) {
14            glyph.includeGeometry(TransformedGeometry.create(glyph.gizmo, new ContourSetGeometry(points)));
15        },
16    };
17}
18
19export function arc(cx, cy, radius, from, to) {
20    const count = Math.ceil(Math.abs(to - from) / (Math.PI / 2));
21    const segments = [];
22    for (let i = 0; i < count; i++) {
23        const a = from + (to - from) * i / count;
24        const b = from + (to - from) * (i + 1) / count;
25        const k = 4 / 3 * Math.tan((b - a) / 4);
26        segments.push([
27            cx + radius * (Math.cos(a) - k * Math.sin(a)),
28            cy + radius * (Math.sin(a) + k * Math.cos(a)),
29            cx + radius * (Math.cos(b) + k * Math.sin(b)),
30            cy + radius * (Math.sin(b) - k * Math.cos(b)),
31            cx + radius * Math.cos(b), cy + radius * Math.sin(b),
32        ]);
33    }
34    return segments;
35}
36
37// An annular arc gives a constant-width hook even when the terminal stops
38// near the bottom of the turn. The upstream hook interpolator assumes a
39// longer, vertical terminal and folds over on these shorter cuts.
40export function roundHook(right, stemEnd, bottom, radius, stroke, degrees, inverted = false) {
41    radius = Math.max(radius, stroke * 1.2);
42    const cx = right - radius, cy = bottom + radius;
43    const angle = -degrees * Math.PI / 180;
44    const inner = radius - stroke;
45    let contour = [
46        [right, stemEnd], [right, cy],
47        ...arc(cx, cy, radius, 0, angle),
48        [cx + inner * Math.cos(angle), cy + inner * Math.sin(angle)],
49        ...arc(cx, cy, inner, angle, 0),
50        [right - stroke, stemEnd],
51    ];
52    if (inverted) contour = contour.map(segment => segment.map(v => -v));
53    return outline([contour]);
54}
55
56export function capsule(l, r, b, t, radius, reverse = false) {
57    const contour = [[l, b + radius], [l, t - radius],
58        ...arc(l + radius, t - radius, radius, Math.PI, Math.PI / 2),
59        [r - radius, t], ...arc(r - radius, t - radius, radius, Math.PI / 2, 0),
60        [r, b + radius], ...arc(r - radius, b + radius, radius, 0, -Math.PI / 2),
61        [l + radius, b], ...arc(l + radius, b + radius, radius, -Math.PI / 2, -Math.PI)];
62    return reverse ? reverseContour(contour) : contour;
63}
64
65export function reverseContour(contour) {
66    const reversed = [contour.at(-1).slice(-2)];
67    for (let i = contour.length - 1; i > 0; i--) {
68        const s = contour[i], end = contour[i - 1].slice(-2);
69        reversed.push(s.length === 2 ? end : [s[2], s[3], s[0], s[1], ...end]);
70    }
71    return reversed;
72}
73
74export function lowerWidth(advance, stroke) {
75    return (319 + .56 * stroke) * advance / 660;
76}
77
78export function lowerBowl(middle, advance, height, stroke, side = 0, stemTop = height, stemBottom = 0) {
79    const span = lowerWidth(advance, stroke), l = middle - span / 2, r = middle + span / 2;
80    const b = -(14.5 - .04 * stroke), t = height + 10, radius = (span - 20 * advance / 660) / 2;
81    const contours = [capsule(l, r, b, t, radius),
82        capsule(l + stroke, r - stroke, b + stroke, t - stroke, radius - stroke, true)];
83    if (side) {
84        const x = side < 0 ? l : r - stroke;
85        contours.push([[x, stemBottom], [x, stemTop], [x + stroke, stemTop], [x + stroke, stemBottom]]);
86    }
87    return outline(contours);
88}
89
90export function shoulder(middle, advance, height, stemTop, stroke, open = false) {
91    const span = lowerWidth(advance, stroke);
92    const l = middle - (open ? (200 + 1.12 * stroke) * advance / 660 : span) / 2, r = l + span;
93    const radius = open ? (143 + .54 * stroke) * advance / 660 : (span - 20 * advance / 660) / 2;
94    const t = height + 10, cy = t - radius, cx = l + radius, inner = radius - stroke;
95    const end = Math.max(45, Math.min(65, 67.6 - .158 * stroke)) * Math.PI / 180;
96    const arch = [[l, 0], [l, cy], ...arc(cx, cy, radius, Math.PI, Math.PI / 2)];
97    if (open) {
98        // Extend the shoulder without widening the stem or stretching its curved terminal.
99        const reach = 40 * advance / 660;
100        arch.push([cx + reach, t], ...arc(cx + reach, cy, radius, Math.PI / 2, end),
101            [cx + reach + inner * Math.cos(end), cy + inner * Math.sin(end)],
102            ...arc(cx + reach, cy, inner, end, Math.PI / 2), [cx, t - stroke],
103            ...arc(cx, cy, inner, Math.PI / 2, Math.PI));
104    } else {
105        arch.push([r - radius, t], ...arc(r - radius, cy, radius, Math.PI / 2, 0),
106            [r, 0], [r - stroke, 0], [r - stroke, cy],
107            ...arc(r - radius, cy, inner, 0, Math.PI / 2), [cx, t - stroke],
108            ...arc(cx, cy, inner, Math.PI / 2, Math.PI));
109    }
110    arch.push([l + stroke, 0]);
111    return outline([arch, [[l, 0], [l, stemTop], [l + stroke, stemTop], [l + stroke, 0]]]);
112}
113
114export function sixShape(middle, advance, top, stroke) {
115    const span = (368 + .5 * stroke) * advance / 660, l = middle - span / 2, r = middle + span / 2;
116    const radius = (span - 20 * advance / 660) / 2, inner = radius - stroke;
117    const b = -(14.5 - .04 * stroke), t = top - b;
118    const bowlBottom = top * (281.3 / 735) - .38 * stroke;
119    const cy = b + radius, cx = r - radius, cxLeft = cx - 10 * advance / 660;
120    const contours = [capsule(l, r, bowlBottom, t, radius),
121        capsule(l + stroke, r - stroke, bowlBottom + stroke, t - stroke, inner, true),
122        [[r, t - radius], [r, cy], ...arc(cx, cy, radius, 0, -Math.PI / 2),
123            [cxLeft, b], ...arc(cxLeft, cy, radius, -Math.PI / 2, -Math.PI),
124            [cxLeft - inner, cy], ...arc(cxLeft, cy, inner, -Math.PI, -Math.PI / 2),
125            [cx, b + stroke], ...arc(cx, cy, inner, -Math.PI / 2, 0), [r - stroke, t - radius]]];
126    return outline(contours.map(c => c.map(s => s.map((v, i) => i % 2 ? top - v : 2 * middle - v))));
127}
128
129export function doubleStoreyA(middle, width, height, stroke) {
130    const s = stroke, span = (351 + .76 * s) * width / 660;
131    const l = middle - span / 2, r = l + lowerWidth(width, s), w = r - l;
132    const b = -(14.5 - .04 * s), t = height + 10;
133    const headLeft = l + 11, radius = (r - headLeft - 21) / 2;
134    const cy = t - radius, cl = headLeft + radius, cr = r - radius, inner = radius - s;
135    const counterTop = height * .652 - .92 * s, floor = b + s;
136    const weight = Math.max(0, Math.min(1, (s - 67) / 38));
137    const terminal = cy - 15 * weight;
138    const outer = [[headLeft, terminal], [headLeft, cy], ...arc(cl, cy, radius, Math.PI, Math.PI / 2),
139        [cr, t], ...arc(cr, cy, radius, Math.PI / 2, 0),
140        [r, s + 12], [r, s, r + 8, s, r + 12, s], [l + span, s], [l + span, 0],
141        [r, 0, r - .4 * s, 0, r - .8 * s, 33],
142        [r - .40 * w, b, l + .5 * w, b, l + .42 * w, b],
143        [l + .15 * w, b, l, 40, l, 132], [l, 146],
144        [l, 305 + 20 * weight, r - s, 296 + 20 * weight, r - s, cy],
145        ...arc(cr, cy, inner, 0, Math.PI / 2), [cl, t - s],
146        ...arc(cl, cy, inner, Math.PI / 2, Math.PI), [headLeft + s, terminal]];
147    const counter = [[r - s, counterTop],
148        [r - s - .27 * w, counterTop - 35, l + s, 228, l + s, 146], [l + s, 132],
149        [l + s, floor + 20, l + .33 * w, floor, l + .44 * w, floor],
150        [l + .65 * w, floor, r - s, 116, r - s, 164]];
151    return outline([outer, counter]);
152}