fix(settings): emit Windows font family names as UTF-8 (#12602)

* fix(settings): emit Windows font family names as UTF-8

Windows PowerShell 5.1 can write localized font names with the console
code page while Node always decodes stdout as UTF-8, which garbles
Korean and other non-ASCII family names in the font picker. Force
UTF-8 OutputEncoding before enumerating InstalledFontCollection (#12590).

* test(settings): assert UTF-8 pin precedes Windows font enumeration

Lock script order so OutputEncoding is set before InstalledFontCollection
enumeration, preventing a silent regression of the mojibake fix.

* refactor(settings): cut the Windows font UTF-8 pin to the standard shape

`$OutputEncoding` only governs bytes piped to a native executable's stdin;
this script pipes to ForEach-Object, so it was inert. Drop it, and drop the
script-builder export whose only consumer was a test — the one-shot
`-Command` shape now matches windows-foreground-process-rows and ssh-browse,
while the BOM-less `UTF8Encoding::new($false)` spelling matches
powershell-osc133-bootstrap and antigravity/hook-service.

The test reaches the script through the public listSystemFontFamilies path
and pins the assignment as the script's first statement, so it fails on
removal, on a stdout write above it, and on a swapped encoding.

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
This commit is contained in:
BingZ 2026-08-10 04:31:22 +08:00 committed by GitHub
parent 3ec48a74d5
commit 371cc26ca1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 0 deletions

View File

@ -67,6 +67,25 @@ describe('listSystemFontFamilies', () => {
killMock.mockReset()
})
it('sets UTF-8 stdout encoding as the first statement of the Windows font script', async () => {
await withPlatform('win32', async () => {
execFileMock.mockImplementation((_cmd, _args, _opts, cb) => {
cb(null, 'Consolas\n')
return { kill: killMock }
})
const { listSystemFontFamilies } = await import('./system-fonts')
await listSystemFontFamilies()
const args = (execFileMock.mock.calls[0]?.[1] ?? []) as string[]
const script = args[args.indexOf('-Command') + 1] ?? ''
// Why: match the whole statement, not a substring — anything emitted above it
// still leaves in the OEM code page, and a swapped encoding must not slip by.
expect(script.trim().split(/\r?\n/)[0]).toBe(
'[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)'
)
})
})
it('falls back when the platform font command never exits', async () => {
vi.useFakeTimers()
execFileMock.mockReturnValue({ kill: killMock })

View File

@ -77,7 +77,10 @@ function listLinuxFonts(): Promise<string[]> {
}
function listWindowsFonts(): Promise<string[]> {
// Why: PowerShell 5.1 emits redirected stdout in the OEM code page; pin UTF-8
// before the first name is written or localized families arrive as mojibake (#12590).
const script = `
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
Add-Type -AssemblyName System.Drawing
$fonts = New-Object System.Drawing.Text.InstalledFontCollection
$fonts.Families | ForEach-Object { $_.Name }