mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-23 23:10:17 +05:30
feat(monospace): wire PDF export to use bundled monospace font
Replaces -V monofont=Consolas with a generated xelatex/lualatex header that fontspec-loads the bundled JetBrains Mono or Fira Code TTF. Adds a cached settings reader with proper invalidation on store.set, and reorders fallback engines to prefer lualatex (fontspec-capable) before pdflatex.
This commit is contained in:
+45
-5
@@ -1,10 +1,13 @@
|
|||||||
const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron');
|
const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
const { execFile } = require('child_process');
|
const { execFile } = require('child_process');
|
||||||
const WordTemplateExporter = require('./wordTemplateExporter');
|
const WordTemplateExporter = require('./wordTemplateExporter');
|
||||||
const PDFOperations = require('./main/PDFOperations');
|
const PDFOperations = require('./main/PDFOperations');
|
||||||
const GitOperations = require('./main/GitOperations');
|
const GitOperations = require('./main/GitOperations');
|
||||||
|
const PdfFontHeader = require('./main/PdfFontHeader');
|
||||||
|
const MonospaceFontConfig = require('./main/MonospaceFontConfig');
|
||||||
|
|
||||||
// Add MiKTeX to PATH for LaTeX support
|
// Add MiKTeX to PATH for LaTeX support
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
@@ -296,9 +299,42 @@ const store = {
|
|||||||
} catch {}
|
} catch {}
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||||
|
_cachedSettings = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cached read of the on-disk settings.json with monospace defaults applied.
|
||||||
|
// Invalidated by store.set.
|
||||||
|
let _cachedSettings = null;
|
||||||
|
function readSettingsJsonCached() {
|
||||||
|
if (_cachedSettings) return _cachedSettings;
|
||||||
|
try {
|
||||||
|
_cachedSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
||||||
|
} catch {
|
||||||
|
_cachedSettings = {};
|
||||||
|
}
|
||||||
|
_cachedSettings.monospaceFont = _cachedSettings.monospaceFont || 'jetbrains-mono';
|
||||||
|
_cachedSettings.monospaceLigatures = _cachedSettings.monospaceLigatures === true;
|
||||||
|
return _cachedSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a monospace font header (.tex) for xelatex/lualatex from current settings.
|
||||||
|
// Returns null when the bundled font is unavailable — callers should fall back to defaults.
|
||||||
|
function buildMonospaceHeaderFile() {
|
||||||
|
const s = readSettingsJsonCached();
|
||||||
|
const familyKey = s.monospaceFont || 'jetbrains-mono';
|
||||||
|
const monoFontPath = MonospaceFontConfig.getMonoFontTtfPath(familyKey, 400);
|
||||||
|
const monoBoldPath = MonospaceFontConfig.getMonoFontTtfPath(familyKey, 700);
|
||||||
|
const tex = PdfFontHeader.build({
|
||||||
|
fontTtfPath: monoFontPath,
|
||||||
|
boldTtfPath: monoBoldPath,
|
||||||
|
ligatures: !!s.monospaceLigatures,
|
||||||
|
});
|
||||||
|
const headerFile = path.join(os.tmpdir(), `monospace-pdf-${Date.now()}-${process.pid}.tex`);
|
||||||
|
fs.writeFileSync(headerFile, tex, 'utf-8');
|
||||||
|
return headerFile;
|
||||||
|
}
|
||||||
|
|
||||||
// Plugin settings IPC handlers
|
// Plugin settings IPC handlers
|
||||||
ipcMain.handle('plugin-settings:get', (_event, key) => {
|
ipcMain.handle('plugin-settings:get', (_event, key) => {
|
||||||
return store.get(key);
|
return store.get(key);
|
||||||
@@ -516,6 +552,8 @@ function pandocSupportsEpubEmbedFont() {
|
|||||||
const v = getPandocVersion();
|
const v = getPandocVersion();
|
||||||
return v.major > 2 || (v.major === 2 && v.minor >= 11);
|
return v.major > 2 || (v.major === 2 && v.minor >= 11);
|
||||||
}
|
}
|
||||||
|
exports.pandocSupportsEpubEmbedFont = pandocSupportsEpubEmbedFont;
|
||||||
|
exports.getPandocVersion = getPandocVersion;
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
@@ -2635,8 +2673,9 @@ function performExportWithOptions(format, options) {
|
|||||||
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
pandocCmd += ` --pdf-engine="${pdfEngine}"`;
|
||||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
||||||
|
|
||||||
// Add monospace font settings for code blocks (ASCII art preservation)
|
// Embed bundled monospace font so ASCII columns align in the PDF.
|
||||||
pandocCmd += ' -V monofont="Consolas"';
|
const monoHeader = buildMonospaceHeaderFile();
|
||||||
|
pandocCmd += ` --include-in-header="${monoHeader}"`;
|
||||||
pandocCmd += ' --highlight-style=tango';
|
pandocCmd += ' --highlight-style=tango';
|
||||||
|
|
||||||
// Add header/footer if enabled
|
// Add header/footer if enabled
|
||||||
@@ -2683,7 +2722,7 @@ function performExportWithOptions(format, options) {
|
|||||||
runPandocCmd(pandocCmd, (error) => {
|
runPandocCmd(pandocCmd, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
// Try fallback engines if the specified one fails
|
// Try fallback engines if the specified one fails
|
||||||
const fallbackEngines = ['pdflatex', 'lualatex'];
|
const fallbackEngines = ['lualatex', 'pdflatex'];
|
||||||
tryPdfFallback(currentFile, outputFile, fallbackEngines, 0, options, error);
|
tryPdfFallback(currentFile, outputFile, fallbackEngines, 0, options, error);
|
||||||
} else {
|
} else {
|
||||||
showExportSuccess(outputFile);
|
showExportSuccess(outputFile);
|
||||||
@@ -2831,8 +2870,9 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, _lastErr
|
|||||||
const engine = engines[index];
|
const engine = engines[index];
|
||||||
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
|
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
|
||||||
|
|
||||||
// Add monospace font settings for code blocks (ASCII art preservation)
|
// Embed bundled monospace font so ASCII columns align in the PDF.
|
||||||
pandocCmd += ' -V monofont="Consolas"';
|
const monoHeader = buildMonospaceHeaderFile();
|
||||||
|
pandocCmd += ` --include-in-header="${monoHeader}"`;
|
||||||
pandocCmd += ' --highlight-style=tango';
|
pandocCmd += ' --highlight-style=tango';
|
||||||
|
|
||||||
// Add geometry if specified
|
// Add geometry if specified
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { app } = require('electron');
|
|
||||||
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' };
|
const WEIGHT_BY_KEY = { 300: 'Light', 400: 'Regular', 500: 'Medium', 600: 'SemiBold', 700: 'Bold' };
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ function build({ fontTtfPath, boldTtfPath, ligatures }) {
|
|||||||
return '% Monospace font path unavailable; TeX will use its default monospace.\n';
|
return '% Monospace font path unavailable; TeX will use its default monospace.\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `boldTtfPath` is accepted for API symmetry with future formats; the glob
|
||||||
|
// below resolves the bold file from the regular file's family prefix.
|
||||||
|
void boldTtfPath;
|
||||||
|
|
||||||
const ligValue = ligatures ? 'Ligatures=TeX' : 'Ligatures=NoCommon';
|
const ligValue = ligatures ? 'Ligatures=TeX' : 'Ligatures=NoCommon';
|
||||||
const prefix = escape(weightPrefix(fontTtfPath));
|
const prefix = escape(weightPrefix(fontTtfPath));
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ function buildFontFaceBlock(familyKey) {
|
|||||||
const data = fs.readFileSync(fontPath);
|
const data = fs.readFileSync(fontPath);
|
||||||
const dataUri = `data:font/woff2;base64,${data.toString('base64')}`;
|
const dataUri = `data:font/woff2;base64,${data.toString('base64')}`;
|
||||||
return `@font-face { font-family: '${family}'; font-weight: 400; font-style: normal; src: url('${dataUri}') format('woff2'); }`;
|
return `@font-face { font-family: '${family}'; font-weight: 400; font-style: normal; src: url('${dataUri}') format('woff2'); }`;
|
||||||
} catch (_) {
|
} 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);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user