diff --git a/src/ascii-generator.html b/src/ascii-generator.html index d8ca42a..942aa1b 100644 --- a/src/ascii-generator.html +++ b/src/ascii-generator.html @@ -142,7 +142,10 @@ line-height: 1.3; color: #00ff00; white-space: pre; - font-feature-settings: 'liga' 0, 'calt' 0, 'dlig' 0; + font-feature-settings: + 'liga' 0, + 'calt' 0, + 'dlig' 0; } .template-grid { diff --git a/src/main.js b/src/main.js index 35868e3..563bfcf 100644 --- a/src/main.js +++ b/src/main.js @@ -575,7 +575,9 @@ function checkPandocAvailability() { pandocAvailable = !error; if (!error) { const m = String(stdout).match(/pandoc\s+(\d+)\.(\d+)/); - pandocVersionCache = m ? { major: Number(m[1]), minor: Number(m[2]) } : { major: 0, minor: 0 }; + pandocVersionCache = m + ? { major: Number(m[1]), minor: Number(m[2]) } + : { major: 0, minor: 0 }; } else { pandocVersionCache = { major: 0, minor: 0 }; } @@ -2786,7 +2788,8 @@ function performExportWithOptions(format, options) { if (bold) fonts.push({ path: bold, family, weight: 700 }); if (fonts.length) await DocxFontEmbedder.embed(outputFile, fonts); } catch (embedErr) { - if (typeof console !== 'undefined') console.warn('[docx] font embed failed:', embedErr.message); + if (typeof console !== 'undefined') + console.warn('[docx] font embed failed:', embedErr.message); } if (tempInputFile) { try { @@ -2903,17 +2906,24 @@ function performExportWithOptions(format, options) { const familyKey = readSettingsJsonCached().monospaceFont || 'jetbrains-mono'; const regular = MonospaceFontConfig.getMonoFontTtfPath(familyKey, 400); const fonts = []; - if (regular) fonts.push({ path: regular, family: familyKey === 'fira-code' ? 'Fira Code' : 'JetBrains Mono', weight: 400 }); + if (regular) + fonts.push({ + path: regular, + family: familyKey === 'fira-code' ? 'Fira Code' : 'JetBrains Mono', + weight: 400, + }); if (fonts.length) { const patched = await EpubFontEmbedder.patchManifest(outputFile, fonts); try { fs.renameSync(patched, outputFile); } catch (renameErr) { - if (typeof console !== 'undefined') console.warn('[epub] could not overwrite with patched EPUB:', renameErr.message); + if (typeof console !== 'undefined') + console.warn('[epub] could not overwrite with patched EPUB:', renameErr.message); } } } catch (patchErr) { - if (typeof console !== 'undefined') console.warn('[epub] manifest patch failed:', patchErr.message); + if (typeof console !== 'undefined') + console.warn('[epub] manifest patch failed:', patchErr.message); } showExportSuccess(outputFile); }); @@ -3619,13 +3629,16 @@ ipcMain.on('export-with-options', (event, { format, options }) => { }); // Handle batch conversion -ipcMain.on('batch-convert', (event, { inputFolder, outputFolder, format, options, includeSubfolders }) => { - if (!conversionLimiter()) { - mainWindow.webContents.send('conversion-status', 'Please wait before converting again...'); - return; +ipcMain.on( + 'batch-convert', + (event, { inputFolder, outputFolder, format, options, includeSubfolders }) => { + if (!conversionLimiter()) { + mainWindow.webContents.send('conversion-status', 'Please wait before converting again...'); + return; + } + performBatchConversion(inputFolder, outputFolder, format, options, includeSubfolders); } - performBatchConversion(inputFolder, outputFolder, format, options, includeSubfolders); -}); +); // Handle folder selection for batch conversion ipcMain.on('select-folder', (event, type) => { @@ -3734,7 +3747,13 @@ function extractTablesFromMarkdown(markdown) { } return tables; } -async function performBatchConversion(inputFolder, outputFolder, format, options, includeSubfolders = true) { +async function performBatchConversion( + inputFolder, + outputFolder, + format, + options, + includeSubfolders = true +) { if (!fs.existsSync(inputFolder)) { dialog.showErrorBox('Error', 'Input folder does not exist'); return; @@ -4093,7 +4112,11 @@ async function performBatchConversion(inputFolder, outputFolder, format, options } if (error) { - console.error(`Batch: Failed to convert ${path.basename(inputFile)}:`, error.message, stderr); + console.error( + `Batch: Failed to convert ${path.basename(inputFile)}:`, + error.message, + stderr + ); } else { // Add headers/footers to DOCX if enabled if (format === 'docx' && headerFooterSettings.enabled) { diff --git a/src/main/DocxFontEmbedder.js b/src/main/DocxFontEmbedder.js index 9458c08..52fe820 100644 --- a/src/main/DocxFontEmbedder.js +++ b/src/main/DocxFontEmbedder.js @@ -38,8 +38,9 @@ async function patchZipWithFonts(inputPath, fonts) { `\n` + `\n` + fonts - .map((f, i) => - ` ` + .map( + (f, i) => + ` ` ) .join('\n') + `\n\n`; @@ -59,11 +60,17 @@ async function patchZipWithFonts(inputPath, fonts) { const ttfCt = 'application/x-font-ttf'; const filePart = `/word/fonts/${path.basename(f.path)}`; if (!ct.includes(`PartName="${filePart}"`)) { - ct = ct.replace('', ``); + ct = ct.replace( + '', + `` + ); } } if (!ct.includes('Default Extension="ttf"')) { - ct = ct.replace('', ''); + ct = ct.replace( + '', + '' + ); } zip.file(ctPath, ct); @@ -116,4 +123,4 @@ async function embed(docxPath, fonts) { return patchZipWithFonts(docxPath, fonts); } -module.exports = { embed }; \ No newline at end of file +module.exports = { embed }; diff --git a/src/main/EpubFontEmbedder.js b/src/main/EpubFontEmbedder.js index 8895420..bf1eb62 100644 --- a/src/main/EpubFontEmbedder.js +++ b/src/main/EpubFontEmbedder.js @@ -41,4 +41,4 @@ async function patchManifest(epubPath, fonts) { return tmp; } -module.exports = { patchManifest }; \ No newline at end of file +module.exports = { patchManifest }; diff --git a/src/main/MonospaceFontConfig.js b/src/main/MonospaceFontConfig.js index f9fc2ff..e70d1bd 100644 --- a/src/main/MonospaceFontConfig.js +++ b/src/main/MonospaceFontConfig.js @@ -2,12 +2,19 @@ const fs = require('fs'); const path = require('path'); -const { getActiveMonoFont, isLigaturesEnabled, FAMILY_BY_KEY } = require('./settings/monospaceSettings'); +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'))) { + if ( + process.resourcesPath && + fs.existsSync(path.join(process.resourcesPath, 'app.asar.unpacked')) + ) { return process.resourcesPath; } return path.resolve(__dirname, '..', '..'); @@ -32,12 +39,18 @@ function getMonoFontTtfPath(familyKey, weight = 400) { 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`); + console.warn( + `[MonospaceFontConfig] bundled font missing: ${filename}; falling back to system monospace` + ); return null; } -function ligaturesEnabled(settings) { return isLigaturesEnabled(settings); } +function ligaturesEnabled(settings) { + return isLigaturesEnabled(settings); +} -function getActiveFamily(settings) { return getActiveMonoFont(settings); } +function getActiveFamily(settings) { + return getActiveMonoFont(settings); +} module.exports = { getMonoFontTtfPath, ligaturesEnabled, getActiveFamily }; diff --git a/src/main/PdfFontHeader.js b/src/main/PdfFontHeader.js index dfcd076..283696b 100644 --- a/src/main/PdfFontHeader.js +++ b/src/main/PdfFontHeader.js @@ -44,4 +44,4 @@ function build({ fontTtfPath, boldTtfPath, ligatures }) { `; } -module.exports = { build }; \ No newline at end of file +module.exports = { build }; diff --git a/src/print-preview.js b/src/print-preview.js index 6b50da4..78e4b87 100644 --- a/src/print-preview.js +++ b/src/print-preview.js @@ -27,7 +27,8 @@ function buildFontFaceBlock(familyKey) { return `@font-face { font-family: '${family}'; font-weight: 400; font-style: normal; src: url('${dataUri}') format('woff2'); }`; } catch (err) { // Non-fatal: fall back to the system monospace stack declared in styles-modern.css. - if (typeof console !== 'undefined') console.warn('[print-preview] font embed failed:', err.message); + if (typeof console !== 'undefined') + console.warn('[print-preview] font embed failed:', err.message); return ''; } } @@ -125,7 +126,8 @@ class PrintPreview { const width = orientation === 'landscape' ? size.height : size.width; const height = orientation === 'landscape' ? size.width : size.height; - const family = this._monospaceSettings.monospaceFont === 'fira-code' ? 'Fira Code' : 'JetBrains Mono'; + const family = + this._monospaceSettings.monospaceFont === 'fira-code' ? 'Fira Code' : 'JetBrains Mono'; const ligaturesOn = this._monospaceSettings.monospaceLigatures === true; const featureSettings = ligaturesOn ? 'normal' : "'liga' 0, 'calt' 0, 'dlig' 0"; const fontFaceBlock = buildFontFaceBlock(this._monospaceSettings.monospaceFont); diff --git a/src/wordTemplateExporter.js b/src/wordTemplateExporter.js index 50df038..5ea857b 100644 Binary files a/src/wordTemplateExporter.js and b/src/wordTemplateExporter.js differ diff --git a/tests/docx-font-embedder.test.js b/tests/docx-font-embedder.test.js index 308511f..af55eef 100644 --- a/tests/docx-font-embedder.test.js +++ b/tests/docx-font-embedder.test.js @@ -12,10 +12,22 @@ describe('DocxFontEmbedder.embed', () => { fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(fontPath, 'fake-ttf-data'); const zip = new JSZip(); - zip.file('[Content_Types].xml', ''); - zip.file('_rels/.rels', ''); - zip.file('word/document.xml', ''); - zip.file('word/styles.xml', ''); + zip.file( + '[Content_Types].xml', + '' + ); + zip.file( + '_rels/.rels', + '' + ); + zip.file( + 'word/document.xml', + '' + ); + zip.file( + 'word/styles.xml', + '' + ); fs.writeFileSync(docxPath, await zip.generateAsync({ type: 'nodebuffer' })); }); @@ -27,7 +39,9 @@ describe('DocxFontEmbedder.embed', () => { ]); const zip = await JSZip.loadAsync(fs.readFileSync(out)); expect(Object.keys(zip.files).some((f) => f.startsWith('word/fonts/'))).toBe(true); - const fontTable = zip.file('word/fontTable.xml') ? await zip.file('word/fontTable.xml').async('string') : ''; + const fontTable = zip.file('word/fontTable.xml') + ? await zip.file('word/fontTable.xml').async('string') + : ''; expect(fontTable).toContain('JetBrains Mono'); expect(fontTable).toMatch(/ { }); test('is idempotent: running twice does not double-embed', async () => { - const once = await DocxFontEmbedder.embed(docxPath, [{ path: fontPath, family: 'JetBrains Mono', weight: 400 }]); - const twice = await DocxFontEmbedder.embed(once, [{ path: fontPath, family: 'JetBrains Mono', weight: 400 }]); + const once = await DocxFontEmbedder.embed(docxPath, [ + { path: fontPath, family: 'JetBrains Mono', weight: 400 }, + ]); + const twice = await DocxFontEmbedder.embed(once, [ + { path: fontPath, family: 'JetBrains Mono', weight: 400 }, + ]); const zip = await JSZip.loadAsync(fs.readFileSync(twice)); const fontEntries = Object.keys(zip.files).filter((f) => /^word\/fonts\/[^/]+\.ttf$/.test(f)); expect(fontEntries.length).toBe(1); }); -}); \ No newline at end of file +}); diff --git a/tests/epub-font-embedder.test.js b/tests/epub-font-embedder.test.js index b02d078..273c79c 100644 --- a/tests/epub-font-embedder.test.js +++ b/tests/epub-font-embedder.test.js @@ -12,7 +12,10 @@ describe('EpubFontEmbedder.patchManifest', () => { fs.mkdirSync(fixturesDir, { recursive: true }); fs.writeFileSync(fontPath, 'fake-ttf-binary'); const zip = new JSZip(); - zip.file('OEBPS/content.opf', ''); + zip.file( + 'OEBPS/content.opf', + '' + ); fs.writeFileSync(epubPath, await zip.generateAsync({ type: 'nodebuffer' })); }); @@ -28,4 +31,4 @@ describe('EpubFontEmbedder.patchManifest', () => { expect(opf).toMatch(/]*href="OEBPS\/fonts\/fake\.ttf"/); expect(opf).toMatch(/]*media-type="application\/x-font-ttf"/); }); -}); \ No newline at end of file +}); diff --git a/tests/export-css.test.js b/tests/export-css.test.js index 2809512..a74e8f0 100644 --- a/tests/export-css.test.js +++ b/tests/export-css.test.js @@ -13,14 +13,24 @@ describe('ExportCss.build', () => { afterAll(() => fs.rmSync(path.dirname(fakeFontPath), { recursive: true, force: true })); test('emits a self-contained CSS with embedded @font-face', () => { - const css = ExportCss.build({ activeFontPath: fakeFontPath, family: 'JetBrains Mono', weight: 400, ligatures: false }); + const css = ExportCss.build({ + activeFontPath: fakeFontPath, + family: 'JetBrains Mono', + weight: 400, + ligatures: false, + }); expect(css).toMatch(/@font-face\s*\{[^}]*src:\s*url\('data:font\/woff2;base64,/); expect(css).toContain("font-family: 'JetBrains Mono'"); expect(css).toMatch(/font-feature-settings:[^;]*liga[^;]*0/); }); test('falls back to family-only CSS when font path is missing', () => { - const css = ExportCss.build({ activeFontPath: null, family: 'Fira Code', weight: 700, ligatures: true }); + const css = ExportCss.build({ + activeFontPath: null, + family: 'Fira Code', + weight: 700, + ligatures: true, + }); expect(css).not.toContain('data:font/woff2;'); expect(css).toContain("font-family: 'Fira Code'"); }); diff --git a/tests/monospace-font-config.test.js b/tests/monospace-font-config.test.js index 5af8bc3..398d682 100644 --- a/tests/monospace-font-config.test.js +++ b/tests/monospace-font-config.test.js @@ -7,7 +7,9 @@ const fs = require('fs'); const MonospaceFontConfig = require('../src/main/MonospaceFontConfig'); describe('MonospaceFontConfig', () => { - afterEach(() => { jest.clearAllMocks(); }); + afterEach(() => { + jest.clearAllMocks(); + }); test('returns null when no file exists', () => { fs.existsSync.mockReturnValue(false); @@ -16,13 +18,17 @@ describe('MonospaceFontConfig', () => { }); test('returns dev repo path when dev file exists', () => { - fs.existsSync.mockImplementation((p) => !p.includes('app.asar.unpacked') && p.endsWith('JetBrainsMono-Regular.ttf')); + 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')); + 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'); diff --git a/tests/monospace-settings.test.js b/tests/monospace-settings.test.js index 489694b..c2ff0c8 100644 --- a/tests/monospace-settings.test.js +++ b/tests/monospace-settings.test.js @@ -1,4 +1,8 @@ -const { getDefaults, getActiveMonoFont, isLigaturesEnabled } = require('../src/main/settings/monospaceSettings'); +const { + getDefaults, + getActiveMonoFont, + isLigaturesEnabled, +} = require('../src/main/settings/monospaceSettings'); describe('monospaceSettings', () => { test('getDefaults returns sane defaults', () => { diff --git a/tests/pdf-font-header.test.js b/tests/pdf-font-header.test.js index 109e247..0c3303c 100644 --- a/tests/pdf-font-header.test.js +++ b/tests/pdf-font-header.test.js @@ -52,4 +52,4 @@ describe('PdfFontHeader.build', () => { }); expect(tex).toMatch(/\{FiraCode\}/); }); -}); \ No newline at end of file +});