feat: merge local document font pickers into main · PDFMathTranslate/PDFMathReader@175a04e · GitHub
Skip to content

Commit 175a04e

Browse files
committed
feat: merge local document font pickers into main
Brings in the codex/local-pdf-fonts branch: choose installed fonts for original and translated documents in Appearance settings, including separate CJK fonts. Conflicts in canvas rendering keep main's stale-draw guard (currentDraw) alongside the per-document font substitution.
2 parents e7c9464 + 1c13247 commit 175a04e

24 files changed

Lines changed: 1661 additions & 12 deletions

‎electron/main/ipc/preferences-ipc.mjs‎

Lines changed: 14 additions & 0 deletions

‎electron/main/services/preferences.mjs‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
isValidCustomServices,
77
} from '../../../shared/translation/custom-services.mjs';
88
import { validShortcutOverrides } from '../../../shared/commands/shortcuts.mjs';
9-
import { isValidTranslationFont } from '../../../shared/reader/translation-font.mjs';
9+
import {
10+
isValidTranslationFont,
11+
MAX_RECENT_DOCUMENT_FONTS,
12+
} from '../../../shared/reader/translation-font.mjs';
1013
import {
1114
cloneTranslationServiceHistory,
1215
isValidTranslationServiceHistory,
@@ -43,6 +46,19 @@ const DEFAULT_PREFERENCES = Object.freeze({
4346
epubEnabled: true,
4447
ultraFastSerif: false,
4548
ultraFastFont: '',
49+
originalSansFont: '',
50+
originalSerifFont: '',
51+
originalMonoFont: '',
52+
translatedSansFont: '',
53+
translatedSerifFont: '',
54+
translatedMonoFont: '',
55+
originalCjkSansFont: '',
56+
originalCjkSerifFont: '',
57+
originalCjkMonoFont: '',
58+
translatedCjkSansFont: '',
59+
translatedCjkSerifFont: '',
60+
translatedCjkMonoFont: '',
61+
recentDocumentFonts: [],
4662
translationErrorDismissals: [],
4763
reduceResourceUsage: true,
4864
reduceBackgroundFrameRate: true,
@@ -272,6 +288,22 @@ const VALIDATORS = {
272288
epubEnabled: (value) => typeof value === 'boolean',
273289
ultraFastSerif: (value) => typeof value === 'boolean',
274290
ultraFastFont: isValidTranslationFont,
291+
originalSansFont: isValidTranslationFont,
292+
originalSerifFont: isValidTranslationFont,
293+
originalMonoFont: isValidTranslationFont,
294+
translatedSansFont: isValidTranslationFont,
295+
translatedSerifFont: isValidTranslationFont,
296+
translatedMonoFont: isValidTranslationFont,
297+
originalCjkSansFont: isValidTranslationFont,
298+
originalCjkSerifFont: isValidTranslationFont,
299+
originalCjkMonoFont: isValidTranslationFont,
300+
translatedCjkSansFont: isValidTranslationFont,
301+
translatedCjkSerifFont: isValidTranslationFont,
302+
translatedCjkMonoFont: isValidTranslationFont,
303+
recentDocumentFonts: (value) =>
304+
Array.isArray(value) &&
305+
value.length <= MAX_RECENT_DOCUMENT_FONTS &&
306+
value.every((name) => name && isValidTranslationFont(name)),
275307
translationErrorDismissals: (value) =>
276308
Array.isArray(value) && value.every((id) => typeof id === 'string' && id.length <= 1024),
277309
emphasizeTopicSentences: (value) => typeof value === 'boolean',
@@ -338,6 +370,19 @@ const VALIDATION_MESSAGES = {
338370
epubEnabled: 'Invalid EPUB support preference',
339371
ultraFastSerif: 'Invalid Ultra fast serif font preference',
340372
ultraFastFont: 'Invalid Ultra fast font preference',
373+
originalSansFont: 'Invalid original sans-serif document font preference',
374+
originalSerifFont: 'Invalid original serif document font preference',
375+
originalMonoFont: 'Invalid original monospace document font preference',
376+
translatedSansFont: 'Invalid translated sans-serif document font preference',
377+
translatedSerifFont: 'Invalid translated serif document font preference',
378+
translatedMonoFont: 'Invalid translated monospace document font preference',
379+
originalCjkSansFont: 'Invalid original CJK sans-serif document font preference',
380+
originalCjkSerifFont: 'Invalid original CJK serif document font preference',
381+
originalCjkMonoFont: 'Invalid original CJK monospace document font preference',
382+
translatedCjkSansFont: 'Invalid translated CJK sans-serif document font preference',
383+
translatedCjkSerifFont: 'Invalid translated CJK serif document font preference',
384+
translatedCjkMonoFont: 'Invalid translated CJK monospace document font preference',
385+
recentDocumentFonts: 'Invalid recent document fonts',
341386
translationMode: 'Invalid translation mode',
342387
interactionMode: 'Invalid interaction mode',
343388
direction: 'Invalid layout direction',

‎scripts/pdf-bionic-runtime.mjs‎

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// The upstream public API has no per-glyph paint hook. Extend this exact runtime
2-
// at bundle time, and fail the build if its implementation changes.
2+
// at bundle time, and fail the build if its implementation changes. Two hooks
3+
// share it: bionic glyph strokes and local font substitution.
34
export function extendPDFRuntime(source) {
45
if (!source.includes('const version = "6.3.289";'))
56
throw new Error('Unsupported pdf.js version: bionic glyph hook needs review');
@@ -10,15 +11,15 @@ export function extendPDFRuntime(source) {
1011
}
1112
replace(
1213
' recordOperations = false,\n operationsFilter = null',
13-
' recordOperations = false,\n bionicGlyphs = null,\n operationsFilter = null',
14+
' recordOperations = false,\n bionicGlyphs = null,\n fontSubstitution = null,\n operationsFilter = null',
1415
);
1516
replace(
1617
' transform,\n background\n },\n objs: this.objs,',
17-
' transform,\n background,\n bionicGlyphs\n },\n objs: this.objs,',
18+
' transform,\n background,\n bionicGlyphs,\n fontSubstitution\n },\n objs: this.objs,',
1819
);
1920
replace(
2021
' this.gfx.beginDrawing({',
21-
' this.gfx.bionicGlyphs = this.params.bionicGlyphs;\n this.gfx.beginDrawing({',
22+
' this.gfx.bionicGlyphs = this.params.bionicGlyphs;\n this.gfx.fontSubstitution = this.params.fontSubstitution;\n this.gfx.beginDrawing({',
2223
);
2324
replace(
2425
' const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;',
@@ -49,6 +50,53 @@ export function extendPDFRuntime(source) {
4950
}
5051
const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection;`,
5152
);
53+
// Font substitution: setFont records the local face a PDF font maps to, and
54+
// showText draws that face's glyph for the text's Unicode at the original
55+
// glyph's position, fitted to its width the way pdf.js fits missing fonts.
56+
replace(
57+
' this.current.font = fontObj;\n this.current.fontSize = size;',
58+
' this.current.font = fontObj;\n this.current.fontSize = size;\n this.current.substituteFonts = null;',
59+
);
60+
replace(
61+
' this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;\n }',
62+
` this.ctx.font = \`\${italic} \${bold} \${browserFontSize}px \${typeface}\`;
63+
const substitute = this.fontSubstitution?.face(fontObj);
64+
if (substitute) {
65+
const css = (family) => family ? \`\${substitute.style} \${substitute.weight} \${browserFontSize}px \${family}\` : null;
66+
this.current.substituteFonts = { text: css(substitute.text), cjk: css(substitute.cjk) };
67+
}
68+
}`,
69+
);
70+
replace(
71+
' let x = 0,\n i;\n for (i = 0; i < glyphsLength; ++i) {',
72+
` const substituteFonts = !vertical && !font.disableFontFace && !current.patternFill && !current.patternStroke &&
73+
!(current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) &&
74+
fillStrokeMode !== TextRenderingMode.INVISIBLE ? current.substituteFonts : null;
75+
const originalFont = ctx.font;
76+
let activeFont = originalFont;
77+
let x = 0,
78+
i;
79+
for (i = 0; i < glyphsLength; ++i) {`,
80+
);
81+
replace(
82+
' const character = glyph.fontChar;\n const accent = glyph.accent;',
83+
` const accent = glyph.accent;
84+
const substituteFont = substituteFonts && !accent && glyph.unicode ? substituteFonts[this.fontSubstitution.kind(glyph.unicode)] : null;
85+
const substituted = !!substituteFont;
86+
const character = substituted ? glyph.unicode : glyph.fontChar;
87+
const glyphFont = substituted ? substituteFont : originalFont;
88+
if (glyphFont !== activeFont) {
89+
ctx.font = activeFont = glyphFont;
90+
}`,
91+
);
92+
replace(
93+
' if (font.remeasure && width > 0) {',
94+
' if ((font.remeasure || substituted) && width > 0) {',
95+
);
96+
replace(
97+
' if (width < measuredWidth && this.isFontSubpixelAAEnabled) {',
98+
' if (width < measuredWidth && (this.isFontSubpixelAAEnabled || substituted)) {',
99+
);
52100
return source;
53101
}
54102

‎server/app.mjs‎

Lines changed: 3 additions & 0 deletions

0 commit comments

Comments
 (0)