diff --git a/src/main/MonospaceFontConfig.js b/src/main/MonospaceFontConfig.js new file mode 100644 index 0000000..f92c477 --- /dev/null +++ b/src/main/MonospaceFontConfig.js @@ -0,0 +1,44 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { app } = require('electron'); +const { getActiveMonoFont, isLigaturesEnabled, FAMILY_BY_KEY } = require('./settings/monospaceSettings'); + +const WEIGHT_BY_KEY = { 300: 'Light', 400: 'Regular', 500: 'Medium', 600: 'SemiBold', 700: 'Bold' }; + +function getAppRoot() { + if (process.resourcesPath && fs.existsSync(path.join(process.resourcesPath, 'app.asar.unpacked'))) { + return process.resourcesPath; + } + return path.resolve(__dirname, '..', '..'); +} + +function getCandidatePaths(family, weight) { + const familyDir = family === 'Fira Code' ? 'FiraCode' : 'JetBrainsMono'; + const weightName = WEIGHT_BY_KEY[weight] || 'Regular'; + const filename = `${familyDir}-${weightName}.ttf`; + + const candidates = []; + candidates.push(path.resolve(getAppRoot(), 'assets', 'fonts', filename)); + const packagedRoot = process.resourcesPath || getAppRoot(); + candidates.push(path.join(packagedRoot, 'app.asar.unpacked', 'assets', 'fonts', filename)); + return candidates; +} + +function getMonoFontTtfPath(familyKey, weight = 400) { + const family = FAMILY_BY_KEY[familyKey] || 'JetBrains Mono'; + const candidates = getCandidatePaths(family, weight); + for (const p of candidates) { + if (fs.existsSync(p)) return p; + } + const filename = path.basename(candidates[candidates.length - 1]); + console.warn(`[MonospaceFontConfig] bundled font missing: ${filename}; falling back to system monospace`); + return null; +} + +function ligaturesEnabled(settings) { return isLigaturesEnabled(settings); } + +function getActiveFamily(settings) { return getActiveMonoFont(settings); } + +module.exports = { getMonoFontTtfPath, ligaturesEnabled, getActiveFamily }; diff --git a/tests/monospace-font-config.test.js b/tests/monospace-font-config.test.js new file mode 100644 index 0000000..5af8bc3 --- /dev/null +++ b/tests/monospace-font-config.test.js @@ -0,0 +1,45 @@ +jest.mock('electron', () => ({ + app: { getPath: () => '/fake/userData' }, +})); +jest.mock('fs', () => ({ existsSync: jest.fn(), statSync: jest.fn() })); + +const fs = require('fs'); +const MonospaceFontConfig = require('../src/main/MonospaceFontConfig'); + +describe('MonospaceFontConfig', () => { + afterEach(() => { jest.clearAllMocks(); }); + + test('returns null when no file exists', () => { + fs.existsSync.mockReturnValue(false); + const p = MonospaceFontConfig.getMonoFontTtfPath('jetbrains-mono', 400); + expect(p).toBeNull(); + }); + + test('returns dev repo path when dev file exists', () => { + fs.existsSync.mockImplementation((p) => !p.includes('app.asar.unpacked') && p.endsWith('JetBrainsMono-Regular.ttf')); + const p = MonospaceFontConfig.getMonoFontTtfPath('jetbrains-mono', 400); + expect(p).toMatch(/assets\/fonts\/JetBrainsMono-Regular\.ttf$/); + }); + + test('returns packaged asar.unpacked path when present and file exists', () => { + fs.existsSync.mockImplementation((p) => p.includes('app.asar.unpacked') && p.endsWith('FiraCode-Regular.ttf')); + const p = MonospaceFontConfig.getMonoFontTtfPath('fira-code', 400); + expect(p).toContain('app.asar.unpacked'); + expect(p).toContain('FiraCode-Regular.ttf'); + }); + + test('returns null and warns when file is missing', () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + fs.existsSync.mockReturnValue(false); + const p = MonospaceFontConfig.getMonoFontTtfPath('fira-code', 700); + expect(p).toBeNull(); + expect(warn).toHaveBeenCalledWith(expect.stringMatching(/FiraCode-Bold\.ttf/)); + warn.mockRestore(); + }); + + test('ligaturesEnabled maps from settings', () => { + expect(MonospaceFontConfig.ligaturesEnabled({ monospaceLigatures: true })).toBe(true); + expect(MonospaceFontConfig.ligaturesEnabled({ monospaceLigatures: false })).toBe(false); + expect(MonospaceFontConfig.ligaturesEnabled({})).toBe(false); + }); +});