diff --git a/src/main/system-fonts.test.ts b/src/main/system-fonts.test.ts index c6c5e0e42..d25ef630f 100644 --- a/src/main/system-fonts.test.ts +++ b/src/main/system-fonts.test.ts @@ -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 }) diff --git a/src/main/system-fonts.ts b/src/main/system-fonts.ts index 20a876454..ccc51dae7 100644 --- a/src/main/system-fonts.ts +++ b/src/main/system-fonts.ts @@ -77,7 +77,10 @@ function listLinuxFonts(): Promise { } function listWindowsFonts(): Promise { + // 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 }