Public: anyone can read this; the crew can change it.

A cart nobody gave a cover still has one: Jay Moran's src/arcade/cover.ts and cover-template.png are on main and tested #9283

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

torque/forge:torque/9281-a-cart-nobody-gave-a-cover-still-has-one → main 1 commit fork

  • Merged. torque/9281-a-cart-nobody-gave-a-cover-still-has-one was merged into main as f8b6d4e.

Review

Sign in to review this change.
3 files changed +92 −0
src/arcade/cover-template.png added
Binary file not shown.
src/arcade/cover.ts added +68
@@ -0,0 +1,68 @@
1 /**
2 * The house cover, drawn in the browser: the page's picture inset in a
3 * black frame, the cart's name in orange top left, its number big top
4 * right, and the handle and green cube taken from the template cover
5 * (`cover-template.png`). The same drawing as browser-arcade's
6 * `scripts/cover.ts`, so a cart nobody gave a cover still looks like one.
7 *
8 * A string of browser code, spliced into the panel's and the Arcade's
9 * scripts. `drawCover(canvas, art, template, name, version)` draws on a
10 * 512×512 canvas; `coverTitle` splits a name and version into its lines.
11 */
12 export const COVER_JS = `
13 function coverTitle(name, version) {
14 var parts = String(name).split('-');
15 var lines = parts.length < 2 ? [String(name)] : [parts.slice(0, -1).join('-'), parts[parts.length - 1]];
16 var v = String(version || '');
17 var tail = /(\\d+(?:\\.\\d+)?)$/.exec(v);
18 var number = v.length <= 4 ? v : tail ? tail[1] : '';
19 return { lines: lines, number: number };
20 }
21 function drawCover(c, art, tpl, name, version) {
22 c.width = c.height = 512;
23 var x = c.getContext('2d');
24 x.fillStyle = '#000'; x.fillRect(0, 0, 512, 512);
25 if (art) {
26 var s = Math.min(art.naturalWidth, art.naturalHeight);
27 x.imageSmoothingEnabled = true; x.imageSmoothingQuality = 'high';
28 x.drawImage(art, (art.naturalWidth - s) / 2, (art.naturalHeight - s) / 2, s, s, 60, 60, 396, 396);
29 }
30 var t = coverTitle(name, version);
31 var font = '"Ubuntu", "Arial Black", "Helvetica Neue", sans-serif';
32 x.font = '700 44px ' + font; x.textBaseline = 'alphabetic'; x.lineJoin = 'round';
33 t.lines.forEach(function (line, k) {
34 var y = 104 + k * 46;
35 x.lineWidth = 6; x.strokeStyle = 'rgba(0,0,0,.65)'; x.strokeText(line, 66, y);
36 x.fillStyle = '#ffa300'; x.fillText(line, 66, y);
37 });
38 if (t.number) {
39 x.font = '700 76px ' + font; x.textAlign = 'right';
40 x.lineWidth = 8; x.strokeStyle = 'rgba(0,0,0,.65)'; x.strokeText(t.number, 446, 128);
41 x.fillStyle = '#ffa300'; x.fillText(t.number, 446, 128);
42 x.textAlign = 'left';
43 }
44 if (!tpl) return;
45 x.drawImage(tpl, 200, 466, 220, 34, 200, 466, 220, 34);
46 var L = document.createElement('canvas'); L.width = L.height = 112;
47 var lx = L.getContext('2d');
48 lx.drawImage(tpl, 400, 400, 112, 112, 0, 0, 112, 112);
49 var d = lx.getImageData(0, 0, 112, 112);
50 for (var i = 0; i < d.data.length; i += 4) {
51 var r = d.data[i], g = d.data[i + 1], b = d.data[i + 2];
52 if (!(g > 40 && g > r * 1.15 && g > b * 1.2)) d.data[i + 3] = 0;
53 }
54 lx.putImageData(d, 0, 0);
55 x.save();
56 x.filter = 'drop-shadow(2px 0 0 #000) drop-shadow(-2px 0 0 #000) drop-shadow(0 2px 0 #000) drop-shadow(0 -2px 0 #000)';
57 x.drawImage(L, 400, 400); x.restore();
58 }
59 function loadImage(src) {
60 return new Promise(function (done) {
61 if (!src) return done(null);
62 var i = new Image();
63 i.onload = function () { done(i); };
64 i.onerror = function () { done(null); };
65 i.src = src;
66 });
67 }
68 `;
test/arcade-cover.test.ts added +24
@@ -0,0 +1,24 @@
1 import { expect, test } from "bun:test";
2 import { COVER_JS } from "../src/arcade/cover.ts";
3
4 // Evaluate only coverTitle from the browser string, with no DOM.
5 const coverTitle = new Function(`${COVER_JS}; return coverTitle;`)() as (
6 name: string,
7 version: string,
8 ) => { lines: string[]; number: string };
9
10 test("splits a hyphenated name into its lines and takes the trailing number", () => {
11 expect(coverTitle("space-pong", "0.12.3")).toEqual({ lines: ["space", "pong"], number: "12.3" });
12 });
13
14 test("a version of four characters or fewer is used whole", () => {
15 expect(coverTitle("space-pong", "v12").number).toBe("v12");
16 });
17
18 test("a one-word name gives one line", () => {
19 expect(coverTitle("pong", "1").lines).toEqual(["pong"]);
20 });
21
22 test("a long version with no trailing number gives an empty number", () => {
23 expect(coverTitle("space-pong", "release-candidate").number).toBe("");
24 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs