feat(monospace): print-preview iframe uses bundled monospace font

Inlines @font-face as base64 data URI so the iframe srcdoc can render
JetBrains Mono / Fira Code without depending on the parent window's
loaded @font-face sets. Reads family + ligature state from the
renderer-wide cache populated by applyMonospaceClasses().

Amit Haridas
This commit is contained in:
2026-08-23 19:31:33 +05:30
parent 228ee04b09
commit 151be60b03
3 changed files with 1698 additions and 4 deletions
File diff suppressed because it is too large Load Diff
+63 -2
View File
@@ -1,11 +1,55 @@
const fs = require('fs');
const path = require('path');
function getBundledFontWoff2Path(familyKey, weight) {
// Renderer can read directly from disk because nodeIntegration is on.
// Try repo-relative first, then packaged app.asar mirror.
const familyDir = familyKey === 'fira-code' ? 'FiraCode' : 'JetBrainsMono';
const weightName = weight >= 700 ? 'Bold' : 'Regular';
const filename = `${familyDir}-${weightName}.woff2`;
const repoPath = path.resolve(__dirname, '..', 'assets', 'fonts', filename);
if (fs.existsSync(repoPath)) return repoPath;
// Packaged: under <resourcesPath>/assets/fonts/
if (process.resourcesPath) {
const packaged = path.join(process.resourcesPath, 'assets', 'fonts', filename);
if (fs.existsSync(packaged)) return packaged;
}
return null;
}
function buildFontFaceBlock(familyKey) {
const family = familyKey === 'fira-code' ? 'Fira Code' : 'JetBrains Mono';
const fontPath = getBundledFontWoff2Path(familyKey, 400);
if (!fontPath) return '';
try {
const data = fs.readFileSync(fontPath);
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'); }`;
} catch (_) {
return '';
}
}
class PrintPreview { class PrintPreview {
constructor() { constructor(monospaceSettings = {}) {
this.overlay = document.getElementById('print-preview-overlay'); this.overlay = document.getElementById('print-preview-overlay');
this.modal = window.modals?.printPreviewModal; this.modal = window.modals?.printPreviewModal;
this._lastContent = ''; this._lastContent = '';
this._monospaceSettings = {
monospaceFont: monospaceSettings.monospaceFont || 'jetbrains-mono',
monospaceLigatures: monospaceSettings.monospaceLigatures === true,
};
this.setupEventListeners(); this.setupEventListeners();
} }
setMonospaceSettings(settings) {
this._monospaceSettings = {
monospaceFont: (settings && settings.monospaceFont) || 'jetbrains-mono',
monospaceLigatures: !!(settings && settings.monospaceLigatures === true),
};
this.refreshPreview();
}
open(htmlContent) { open(htmlContent) {
this._lastContent = htmlContent; this._lastContent = htmlContent;
if (this.modal) { if (this.modal) {
@@ -79,11 +123,17 @@ class PrintPreview {
const width = orientation === 'landscape' ? size.height : size.width; const width = orientation === 'landscape' ? size.height : size.width;
const height = orientation === 'landscape' ? size.width : size.height; const height = orientation === 'landscape' ? size.width : size.height;
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);
const previewHtml = ` const previewHtml = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<style> <style>
${fontFaceBlock}
body { body {
margin: 20px; margin: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -91,7 +141,18 @@ class PrintPreview {
line-height: 1.6; line-height: 1.6;
} }
@page { size: ${width} ${height}; } @page { size: ${width} ${height}; }
pre { background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; } pre, code, kbd, samp {
font-family: '${family}', monospace;
font-feature-settings: ${featureSettings};
}
pre {
background: #f5f5f5;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
white-space: pre;
tab-size: 4;
}
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; } code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre code { background: none; padding: 0; } pre code { background: none; padding: 0; }
table { border-collapse: collapse; width: 100%; } table { border-collapse: collapse; width: 100%; }
+18 -2
View File
@@ -15,10 +15,21 @@ const { undo, redo } = require('@codemirror/commands');
/** /**
* Toggle body classes that drive the monospace font + ligatures CSS tokens. * Toggle body classes that drive the monospace font + ligatures CSS tokens.
* Single source of truth for the live preview/editor font behaviour. * Single source of truth for the live preview/editor font behaviour.
* Also pushes the settings into the renderer-wide cache so PrintPreview can read them.
* @param {{monospaceFont?: string, monospaceLigatures?: boolean} | null | undefined} settings * @param {{monospaceFont?: string, monospaceLigatures?: boolean} | null | undefined} settings
*/ */
function applyMonospaceClasses(settings) { function applyMonospaceClasses(settings) {
const s = settings || {}; const s = settings || {};
// Cache for downstream consumers (PrintPreview, future settings UI, etc.)
if (typeof window !== 'undefined') {
window.__monospaceSettings = {
monospaceFont: s.monospaceFont || 'jetbrains-mono',
monospaceLigatures: s.monospaceLigatures === true,
};
if (window.__printPreview && typeof window.__printPreview.setMonospaceSettings === 'function') {
window.__printPreview.setMonospaceSettings(window.__monospaceSettings);
}
}
const body = document.body; const body = document.body;
if (!body) return; if (!body) return;
const isFira = s.monospaceFont === 'fira-code'; const isFira = s.monospaceFont === 'fira-code';
@@ -1951,7 +1962,10 @@ document.addEventListener('DOMContentLoaded', async () => {
// Initialize print preview // Initialize print preview
const PrintPreview = getPrintPreview(); const PrintPreview = getPrintPreview();
const printPreview = new PrintPreview(); const printPreview = new PrintPreview(
window.__monospaceSettings || { monospaceFont: 'jetbrains-mono', monospaceLigatures: false }
);
window.__printPreview = printPreview;
// Register commands // Register commands
commandPalette.register('New File', 'Ctrl+N', () => tabManager.createNewTab()); commandPalette.register('New File', 'Ctrl+N', () => tabManager.createNewTab());
@@ -2270,7 +2284,9 @@ function openPrintPreviewDialog() {
return; return;
} }
const PrintPreviewClass = getPrintPreview(); const PrintPreviewClass = getPrintPreview();
const printPreviewInstance = new PrintPreviewClass(); const printPreviewInstance = new PrintPreviewClass(
window.__monospaceSettings || { monospaceFont: 'jetbrains-mono', monospaceLigatures: false }
);
printPreviewInstance.open(previewContent.innerHTML); printPreviewInstance.open(previewContent.innerHTML);
} }