mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 10:16:03 +00:00
fix(simulation): render screenshot symbol glyphs (#37691)
This commit is contained in:
@@ -900,6 +900,9 @@
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@fontsource/commit-mono": "5.2.5",
|
||||
"@fontsource/noto-sans-math": "5.2.5",
|
||||
"@fontsource/noto-sans-symbols": "5.2.5",
|
||||
"@fontsource/noto-sans-symbols-2": "5.2.5",
|
||||
"@napi-rs/canvas": "1.0.2",
|
||||
"@opencode-ai/ai": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -1795,6 +1798,12 @@
|
||||
|
||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||
|
||||
"@fontsource/noto-sans-math": ["@fontsource/noto-sans-math@5.2.5", "", {}, "sha512-1bxEvVlF51Vfgpju32mRZzI/CHvsfqjXjI2+sAuEyHYvXABUAIyj+93sCO3QZIoMG5drWyrzgoCqRQRaL6wQ8Q=="],
|
||||
|
||||
"@fontsource/noto-sans-symbols": ["@fontsource/noto-sans-symbols@5.2.5", "", {}, "sha512-mxoIRstsmZpZFzd/SRWiD+l6T7TGhpgCrGs7TEnnuGSQIfjVMrQT9Zej2enh9pkfmPNAFyeaGJkHkszJ1hH++w=="],
|
||||
|
||||
"@fontsource/noto-sans-symbols-2": ["@fontsource/noto-sans-symbols-2@5.2.5", "", {}, "sha512-F4O9WLifwoZS1quNzY1ebjMNo2cQPe/UP68Dmud0ONi2lOxaR6xp6fFPO2gG17MI7DwAnfMyQFl64A2tAd28hg=="],
|
||||
|
||||
"@fuma-translate/react": ["@fuma-translate/react@1.0.2", "", { "peerDependencies": { "@types/react": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "optionalPeers": ["@types/react"] }, "sha512-uOiOtBx3nRXR8Nu1GzBf1tApgF1FErDBTHxRIAQeyQdyOoZbrNRN6H4kDCWObY4qyGeGbHydG0DHzgeUgFDMIw=="],
|
||||
|
||||
"@fumadocs/tailwind": ["@fumadocs/tailwind@0.1.0", "", { "peerDependencies": { "tailwindcss": "^4.0.0" }, "optionalPeers": ["tailwindcss"] }, "sha512-nF/DCAwOR21HZ4AkjIOv3Iqwyqywzb6pdyeMcoa+aZzirXj5ntvNZbe3jJ0v3ehhtrRfYYeXBezvjn8ZmV+fuQ=="],
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/commit-mono": "5.2.5",
|
||||
"@fontsource/noto-sans-math": "5.2.5",
|
||||
"@fontsource/noto-sans-symbols": "5.2.5",
|
||||
"@fontsource/noto-sans-symbols-2": "5.2.5",
|
||||
"@napi-rs/canvas": "1.0.2",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/ai": "workspace:*",
|
||||
|
||||
@@ -5,16 +5,29 @@ import regularFont from "@fontsource/commit-mono/files/commit-mono-latin-400-nor
|
||||
import boldFont from "@fontsource/commit-mono/files/commit-mono-latin-700-normal.woff2" with { type: "file" }
|
||||
import italicFont from "@fontsource/commit-mono/files/commit-mono-latin-400-italic.woff2" with { type: "file" }
|
||||
import boldItalicFont from "@fontsource/commit-mono/files/commit-mono-latin-700-italic.woff2" with { type: "file" }
|
||||
import symbolFont from "@fontsource/noto-sans-symbols/files/noto-sans-symbols-symbols-400-normal.woff2" with { type: "file" }
|
||||
import symbolFont2 from "@fontsource/noto-sans-symbols-2/files/noto-sans-symbols-2-symbols-400-normal.woff2" with { type: "file" }
|
||||
import brailleFont from "@fontsource/noto-sans-symbols-2/files/noto-sans-symbols-2-braille-400-normal.woff2" with { type: "file" }
|
||||
import mathFont from "@fontsource/noto-sans-math/files/noto-sans-math-math-400-normal.woff2" with { type: "file" }
|
||||
|
||||
const CellWidth = 10
|
||||
const CellHeight = 20
|
||||
const FontSize = 16
|
||||
const FontFamily = "OpenCode Mono"
|
||||
const SymbolFontFamily = "OpenCode Symbols"
|
||||
const SymbolFontFamily2 = "OpenCode Symbols 2"
|
||||
const MathFontFamily = "OpenCode Math"
|
||||
const FontStack = `"${FontFamily}", "${SymbolFontFamily}", "${SymbolFontFamily2}", "${MathFontFamily}"`
|
||||
|
||||
for (const file of [regularFont, boldFont, italicFont, boldItalicFont]) {
|
||||
for (const [file, family] of [
|
||||
...[regularFont, boldFont, italicFont, boldItalicFont].map((file) => [file, FontFamily] as const),
|
||||
[symbolFont, SymbolFontFamily],
|
||||
[symbolFont2, SymbolFontFamily2],
|
||||
[brailleFont, SymbolFontFamily2],
|
||||
[mathFont, MathFontFamily],
|
||||
] as const) {
|
||||
const font = Buffer.from(await Bun.file(file).arrayBuffer())
|
||||
if (!GlobalFonts.register(font, FontFamily))
|
||||
throw new Error(`Failed to register screenshot font: ${file}`)
|
||||
if (!GlobalFonts.register(font, family)) throw new Error(`Failed to register screenshot font: ${file}`)
|
||||
}
|
||||
|
||||
export function screenshot(renderer: CliRenderer) {
|
||||
@@ -55,7 +68,7 @@ export function screenshotFrame(frame: CapturedFrame) {
|
||||
const x = column * CellWidth
|
||||
const y = row * CellHeight
|
||||
if (!drawBlockElement(context, char, x, y, cells)) {
|
||||
context.font = `${attributes & TextAttributes.ITALIC ? "italic " : ""}${attributes & TextAttributes.BOLD ? "bold " : ""}${FontSize}px "${FontFamily}"`
|
||||
context.font = `${attributes & TextAttributes.ITALIC ? "italic " : ""}${attributes & TextAttributes.BOLD ? "bold " : ""}${FontSize}px ${FontStack}`
|
||||
context.fillText(char, x, y + 1)
|
||||
}
|
||||
if (attributes & TextAttributes.UNDERLINE) {
|
||||
|
||||
@@ -29,6 +29,38 @@ test("renders captured frames with bundled fonts", () => {
|
||||
expect(image.data.subarray(1, 4).toString()).toBe("PNG")
|
||||
})
|
||||
|
||||
test("renders OpenCode symbols instead of missing-glyph boxes", async () => {
|
||||
const pixels = async (text: string) => {
|
||||
const image = SimulationPng.screenshotFrame({
|
||||
cols: 1,
|
||||
rows: 1,
|
||||
cursor: [0, 0],
|
||||
lines: [
|
||||
{
|
||||
spans: [
|
||||
{
|
||||
text,
|
||||
width: 1,
|
||||
fg: RGBA.fromInts(255, 255, 255),
|
||||
bg: RGBA.fromInts(0, 0, 0),
|
||||
attributes: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
const canvas = createCanvas(image.width, image.height)
|
||||
const context = canvas.getContext("2d")
|
||||
context.drawImage(await loadImage(image.data), 0, 0)
|
||||
return [...context.getImageData(0, 0, image.width, image.height).data]
|
||||
}
|
||||
|
||||
const missingGlyph = await pixels("\u{10ffff}")
|
||||
for (const symbol of ["△", "✱", "⇆", "⠹"]) {
|
||||
expect(await pixels(symbol)).not.toEqual(missingGlyph)
|
||||
}
|
||||
})
|
||||
|
||||
test("fills adjacent block elements without glyph gaps", async () => {
|
||||
const image = SimulationPng.screenshotFrame({
|
||||
cols: 2,
|
||||
@@ -97,7 +129,5 @@ test("draws heavy vertical box elements on cell boundaries", async () => {
|
||||
expect([...context.getImageData(4, 0, 2, 30).data]).toEqual(
|
||||
Array.from({ length: 60 }, () => [255, 255, 255, 255]).flat(),
|
||||
)
|
||||
expect([...context.getImageData(4, 30, 2, 10).data]).toEqual(
|
||||
Array.from({ length: 20 }, () => [0, 0, 0, 255]).flat(),
|
||||
)
|
||||
expect([...context.getImageData(4, 30, 2, 10).data]).toEqual(Array.from({ length: 20 }, () => [0, 0, 0, 255]).flat())
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user