@coldtea/pr-lens-renderer 0.2.2 → 0.2.4

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.
@@ -0,0 +1,122 @@
1
+ import { assertNever } from "@coldtea/pr-lens-schema";
2
+ import type { Delta, FlowMessage, GraphEdge } from "@coldtea/pr-lens-schema";
3
+ import { MONO_STACK } from "../text.js";
4
+ import type { Palette } from "../theme.js";
5
+ import type { Attributes } from "./primitives.js";
6
+ import { toneColour, toneFor, type Tone } from "./document.js";
7
+
8
+ // GitHub's attachment viewer can ignore CSS, so appearance lives on the elements.
9
+ export const stylesFor = (palette: Palette) => ({
10
+ lane: { fill: palette.lane },
11
+ laneLabel: { "font-size": 10, "font-weight": 700, "letter-spacing": ".12em", fill: palette.muted },
12
+ title: { "font-weight": 600, fill: palette.foreground },
13
+ subtitle: { "font-size": 9.5, fill: palette.muted, "font-family": MONO_STACK },
14
+ chip: { fill: palette.chip },
15
+ glyph: { fill: palette.muted },
16
+ glyphStroke: { stroke: palette.muted, "stroke-width": 1.4, fill: "none" },
17
+ badgeText: { "font-size": 8.5, "font-weight": 700, "letter-spacing": ".06em" },
18
+ glow: { fill: "none", "stroke-width": 7, opacity: 0.14 },
19
+ pill: { fill: palette.pill, stroke: palette.pillBorder, "stroke-width": 1 },
20
+ pillText: { "font-size": 9.5, "font-weight": 600 },
21
+ lifeline: { stroke: palette.lifeline, "stroke-width": 1, "stroke-dasharray": "3 4" },
22
+ activation: { fill: palette.addedFill, stroke: palette.addedBorder },
23
+ });
24
+
25
+ export type SvgStyles = ReturnType<typeof stylesFor>;
26
+
27
+ const cardStroke = (delta: Delta, palette: Palette): Attributes => {
28
+ switch (delta) {
29
+ case "added":
30
+ return { stroke: palette.added, "stroke-opacity": 0.55 };
31
+ case "modified":
32
+ return { stroke: palette.modified, "stroke-opacity": 0.5 };
33
+ case "removed":
34
+ return { stroke: palette.removed, "stroke-dasharray": "4 3", "stroke-opacity": 0.6 };
35
+ case "unchanged":
36
+ return { stroke: palette.cardBorder };
37
+ default:
38
+ return assertNever(delta);
39
+ }
40
+ };
41
+
42
+ export const cardAttributes = (delta: Delta, palette: Palette): Attributes => ({
43
+ fill: palette.card,
44
+ "stroke-width": 1,
45
+ ...cardStroke(delta, palette),
46
+ });
47
+
48
+ const cardOpacity = (delta: Delta): number | undefined => {
49
+ switch (delta) {
50
+ case "removed":
51
+ return 0.55;
52
+ case "unchanged":
53
+ return 0.82;
54
+ case "added":
55
+ case "modified":
56
+ return undefined;
57
+ default:
58
+ return assertNever(delta);
59
+ }
60
+ };
61
+
62
+ export const cardGroupAttributes = (delta: Delta, palette: Palette): Attributes => ({
63
+ filter: `drop-shadow(0 1px 2px ${palette.shadow})`,
64
+ opacity: cardOpacity(delta),
65
+ });
66
+
67
+ export const badgeColours = (tone: Tone, palette: Palette) => {
68
+ switch (tone) {
69
+ case "added":
70
+ return { fill: palette.addedFill, stroke: palette.addedBorder, text: palette.addedText };
71
+ case "modified":
72
+ return { fill: palette.modifiedFill, stroke: palette.modifiedBorder, text: palette.modifiedText };
73
+ case "removed":
74
+ return { fill: palette.removedFill, stroke: palette.removedBorder, text: palette.removedText };
75
+ case "neutral":
76
+ return { fill: palette.neutralFill, stroke: palette.cardBorder, text: palette.muted };
77
+ default:
78
+ return assertNever(tone);
79
+ }
80
+ };
81
+
82
+ const strokeAttributes = (delta: Delta, palette: Palette): Attributes => ({
83
+ fill: "none",
84
+ stroke: toneColour(palette, toneFor(delta)),
85
+ "stroke-width": 1.5,
86
+ "stroke-dasharray": delta === "removed" ? "5 4" : undefined,
87
+ opacity: delta === "removed" ? 0.7 : undefined,
88
+ });
89
+
90
+ export const edgeAttributes = (edge: GraphEdge, palette: Palette): Attributes => {
91
+ const attributes = {
92
+ ...strokeAttributes(edge.delta, palette),
93
+ ...(edge.delta === "unchanged" ? { opacity: 0.82 } : {}),
94
+ };
95
+ switch (edge.emphasis) {
96
+ case "hero":
97
+ return { ...attributes, "stroke-width": 2.25 };
98
+ case "muted":
99
+ return { ...attributes, opacity: 0.45 };
100
+ case "normal":
101
+ return attributes;
102
+ default:
103
+ return assertNever(edge.emphasis);
104
+ }
105
+ };
106
+
107
+ export const messageAttributes = (message: FlowMessage, palette: Palette): Attributes => {
108
+ const attributes = {
109
+ ...strokeAttributes(message.delta, palette),
110
+ "stroke-width": message.delta === "added" ? 2.25 : 1.5,
111
+ };
112
+ switch (message.kind) {
113
+ case "return":
114
+ return { ...attributes, "stroke-dasharray": "4 3", opacity: 0.8 };
115
+ case "sync":
116
+ case "async":
117
+ case "self":
118
+ return attributes;
119
+ default:
120
+ return assertNever(message.kind);
121
+ }
122
+ };
package/src/text.ts CHANGED
@@ -28,9 +28,33 @@ const SANS_ADVANCE: Readonly<Record<string, number>> = {
28
28
  y: 500, z: 500, "{": 334, "|": 260, "}": 334, "~": 584,
29
29
  };
30
30
 
31
- /** What a character outside the table is assumed to cost, in the same units. */
31
+ /** What a narrow character outside the table is assumed to cost, in the same units. */
32
32
  const FALLBACK_ADVANCE = 600;
33
33
 
34
+ /**
35
+ * East Asian Wide and Fullwidth glyphs occupy a full em. Measuring them at
36
+ * the narrow fallback puts every CJK label about 38% short of its real width,
37
+ * which is enough for an edge label's plate to stop covering its own text.
38
+ */
39
+ const isWide = (character: string): boolean => {
40
+ const point = character.codePointAt(0) ?? 0;
41
+ return (
42
+ (point >= 0x1100 && point <= 0x115f) ||
43
+ (point >= 0x2e80 && point <= 0xa4cf) ||
44
+ (point >= 0xac00 && point <= 0xd7a3) ||
45
+ (point >= 0xf900 && point <= 0xfaff) ||
46
+ (point >= 0xfe30 && point <= 0xfe6f) ||
47
+ (point >= 0xff00 && point <= 0xff60) ||
48
+ (point >= 0xffe0 && point <= 0xffe6) ||
49
+ (point >= 0x20000 && point <= 0x3fffd)
50
+ );
51
+ };
52
+
53
+ const FULL_WIDTH_ADVANCE = 1000;
54
+
55
+ const fallbackAdvance = (character: string): number =>
56
+ isWide(character) ? FULL_WIDTH_ADVANCE : FALLBACK_ADVANCE;
57
+
34
58
  /**
35
59
  * The bold face of the same family runs a little wider at every weight step.
36
60
  * One factor over the regular table is close enough for boxes that already
@@ -43,9 +67,9 @@ const MONO_ADVANCE = 600;
43
67
  const advanceFor = (face: Face, character: string): number => {
44
68
  switch (face) {
45
69
  case "sans":
46
- return SANS_ADVANCE[character] ?? FALLBACK_ADVANCE;
70
+ return SANS_ADVANCE[character] ?? fallbackAdvance(character);
47
71
  case "sans-bold":
48
- return (SANS_ADVANCE[character] ?? FALLBACK_ADVANCE) * BOLD_WIDENING;
72
+ return (SANS_ADVANCE[character] ?? fallbackAdvance(character)) * BOLD_WIDENING;
49
73
  case "mono":
50
74
  return MONO_ADVANCE;
51
75
  default:
package/src/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  */
7
7
  export const RENDERER_NAME = "@coldtea/pr-lens-renderer";
8
8
 
9
- export const RENDERER_VERSION = "0.2.2";
9
+ export const RENDERER_VERSION = "0.2.4";