From 1a865e2cde2227ee2464a0cea9c7dd52d4f9cad0 Mon Sep 17 00:00:00 2001 From: shelton cheung <274334790@qq.com> Date: Thu, 19 Mar 2026 02:07:43 +0800 Subject: [PATCH 1/4] fix(book-pdf): prevent missing CJK text in generated PDFs --- scripts/generate-book-pdf.ts | 77 ++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/scripts/generate-book-pdf.ts b/scripts/generate-book-pdf.ts index d8d03f54cbf..032a4e22949 100644 --- a/scripts/generate-book-pdf.ts +++ b/scripts/generate-book-pdf.ts @@ -34,6 +34,45 @@ const TRIM_HEIGHT = '9in'; const BLEED_WIDTH = '6.25in'; // 6 + 0.125*2 const BLEED_HEIGHT = '9.25in'; // 9 + 0.125*2 +interface FontStacks { + serif: string; + sans: string; + mono: string; +} + +const DEFAULT_FONT_STACKS: FontStacks = { + serif: `'Palatino Linotype', 'Book Antiqua', Palatino, Georgia, 'Times New Roman', serif`, + sans: `'Helvetica Neue', Helvetica, Arial, sans-serif`, + mono: `'SF Mono', 'Monaco', 'Menlo', 'Inconsolata', 'Fira Code', 'Consolas', monospace`, +}; + +const LOCALE_FONT_STACKS: Partial>> = { + zh: { + serif: `'Songti SC', 'STSong', 'Noto Serif CJK SC', 'Source Han Serif SC', serif`, + sans: `'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC', 'Source Han Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif`, + mono: `'Noto Sans Mono CJK SC', 'Source Han Mono SC', 'Noto Sans CJK SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'SF Mono', 'Monaco', 'Menlo', 'Cascadia Mono', monospace`, + }, + ja: { + serif: `'Hiragino Mincho ProN', 'Yu Mincho', 'Noto Serif CJK JP', 'Source Han Serif JP', serif`, + sans: `'Hiragino Sans', 'Yu Gothic', 'Noto Sans CJK JP', 'Source Han Sans JP', Meiryo, -apple-system, BlinkMacSystemFont, sans-serif`, + mono: `'Noto Sans Mono CJK JP', 'Source Han Mono JP', 'Noto Sans CJK JP', 'Hiragino Sans', 'Yu Gothic', Meiryo, 'SF Mono', 'Monaco', 'Menlo', 'Cascadia Mono', monospace`, + }, + ko: { + serif: `'AppleMyungjo', 'Nanum Myeongjo', 'Noto Serif CJK KR', 'Source Han Serif KR', serif`, + sans: `'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans CJK KR', 'Source Han Sans KR', -apple-system, BlinkMacSystemFont, sans-serif`, + mono: `'Noto Sans Mono CJK KR', 'Source Han Mono KR', 'Noto Sans CJK KR', 'Apple SD Gothic Neo', 'Malgun Gothic', 'SF Mono', 'Monaco', 'Menlo', 'Cascadia Mono', monospace`, + }, +}; + +function getFontStacks(locale: string): FontStacks { + const overrides = LOCALE_FONT_STACKS[locale]; + return { + serif: overrides?.serif || DEFAULT_FONT_STACKS.serif, + sans: overrides?.sans || DEFAULT_FONT_STACKS.sans, + mono: overrides?.mono || DEFAULT_FONT_STACKS.mono, + }; +} + // Components that truly need interactivity (API calls, complex animations) // Everything else gets static rendering const INTERACTIVE_ONLY_COMPONENTS = [ @@ -2142,6 +2181,10 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess const msgSubtitle = t(messages, subtitleKey); const subtitle = (msgSubtitle !== subtitleKey) ? msgSubtitle : 'A Comprehensive Guide to AI Prompt Engineering'; const isRtl = ['ar', 'he', 'fa'].includes(locale); + const isCjk = ['zh', 'ja', 'ko'].includes(locale); + const fonts = getFontStacks(locale); + const headingFont = isCjk ? fonts.serif : fonts.sans; + const promptFont = isCjk ? fonts.serif : fonts.mono; // Helper to translate part name const translatePart = (partName: string): string => { @@ -2228,9 +2271,11 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess --color-bg-muted: ${PRINT_READY ? '#f2f2f2' : '#f5f5f4'}; --color-border: ${PRINT_READY ? '#cccccc' : '#e7e5e4'}; --color-border-dark: ${PRINT_READY ? '#999999' : '#d6d3d1'}; - --font-serif: 'Palatino Linotype', 'Book Antiqua', Palatino, Georgia, 'Times New Roman', serif; - --font-sans: 'Helvetica Neue', Helvetica, Arial, sans-serif; - --font-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Consolas', monospace; + --font-serif: ${fonts.serif}; + --font-sans: ${fonts.sans}; + --font-heading: ${headingFont}; + --font-mono: ${fonts.mono}; + --font-prompt: ${promptFont}; } body { @@ -2267,7 +2312,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .cover h1 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 30pt; font-weight: 800; color: var(--color-text); @@ -2332,7 +2377,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .toc-title { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 18pt; font-weight: 600; color: var(--color-text); @@ -2342,7 +2387,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .toc-part { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 10pt; font-weight: 600; text-transform: uppercase; @@ -2413,7 +2458,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .chapter-part { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 7.5pt; font-weight: 600; text-transform: uppercase; @@ -2424,7 +2469,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .chapter-title { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 22pt; font-weight: 700; color: var(--color-text); @@ -2447,18 +2492,18 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess HEADINGS ======================================== */ h1 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 15pt; font-weight: 700; color: var(--color-text); margin-top: 2em; margin-bottom: 0.5em; line-height: 1.25; - letter-spacing: -0.01em; + letter-spacing: -0.03em; } h2 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 12.5pt; font-weight: 700; color: var(--color-text); @@ -2471,7 +2516,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } h3 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 10.5pt; font-weight: 700; color: var(--color-text); @@ -2481,7 +2526,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } h4 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 10pt; font-weight: 600; color: var(--color-text-muted); @@ -2576,7 +2621,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .prompt-code { - font-family: var(--font-mono); + font-family: var(--font-prompt); font-size: 8.5pt; line-height: 1.5; background: #1e1e1e; @@ -3699,7 +3744,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .back-matter h2 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 18pt; font-weight: 700; border: none; @@ -3753,7 +3798,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess } .half-title h1 { - font-family: var(--font-sans); + font-family: var(--font-heading); font-size: 18pt; font-weight: 600; color: var(--color-text); From f0b71cbf2ed95a5428eb75e3a0abc6bb775459b5 Mon Sep 17 00:00:00 2001 From: Shelton Cheung Date: Thu, 19 Mar 2026 11:59:19 +0800 Subject: [PATCH 2/4] chore(scripts): improve CJK font rendering in generate-book-pdf.ts - Update Chinese (zh) font stacks for sans-serif and monospace fonts. - Prefer 'Hiragino Sans GB' and 'Noto Sans CJK SC' for better rendering. - Switch promptFont to use sans-serif font for CJK locales. --- scripts/generate-book-pdf.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/generate-book-pdf.ts b/scripts/generate-book-pdf.ts index 032a4e22949..8c02bf34b55 100644 --- a/scripts/generate-book-pdf.ts +++ b/scripts/generate-book-pdf.ts @@ -49,8 +49,8 @@ const DEFAULT_FONT_STACKS: FontStacks = { const LOCALE_FONT_STACKS: Partial>> = { zh: { serif: `'Songti SC', 'STSong', 'Noto Serif CJK SC', 'Source Han Serif SC', serif`, - sans: `'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC', 'Source Han Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif`, - mono: `'Noto Sans Mono CJK SC', 'Source Han Mono SC', 'Noto Sans CJK SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'SF Mono', 'Monaco', 'Menlo', 'Cascadia Mono', monospace`, + sans: `'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC', 'Source Han Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif`, + mono: `'Noto Sans Mono CJK SC', 'Source Han Mono SC', 'Noto Sans CJK SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'SF Mono', 'Monaco', 'Menlo', 'Cascadia Mono', monospace`, }, ja: { serif: `'Hiragino Mincho ProN', 'Yu Mincho', 'Noto Serif CJK JP', 'Source Han Serif JP', serif`, @@ -2184,7 +2184,7 @@ function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, mess const isCjk = ['zh', 'ja', 'ko'].includes(locale); const fonts = getFontStacks(locale); const headingFont = isCjk ? fonts.serif : fonts.sans; - const promptFont = isCjk ? fonts.serif : fonts.mono; + const promptFont = isCjk ? fonts.sans : fonts.mono; // Helper to translate part name const translatePart = (partName: string): string => { From c148ecaeb7e0df454c2e04da3ac435cd4d26e132 Mon Sep 17 00:00:00 2001 From: Shelton Cheung Date: Thu, 19 Mar 2026 12:24:03 +0800 Subject: [PATCH 3/4] chore(scripts): add comment for getFontStacks function --- scripts/generate-book-pdf.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/generate-book-pdf.ts b/scripts/generate-book-pdf.ts index 8c02bf34b55..c360b9728a0 100644 --- a/scripts/generate-book-pdf.ts +++ b/scripts/generate-book-pdf.ts @@ -64,6 +64,13 @@ const LOCALE_FONT_STACKS: Partial>> = { }, }; +/** + * Get the font stacks for a specific locale. + * Falls back to default font stacks if no locale-specific override exists. + * + * @param locale - The locale string (e.g., 'en', 'zh', 'ja') + * @returns The FontStacks object containing serif, sans, and mono font families. + */ function getFontStacks(locale: string): FontStacks { const overrides = LOCALE_FONT_STACKS[locale]; return { @@ -568,7 +575,7 @@ function transformMdxForPdf(content: string, locale: string, localeData?: Locale const blanks = extractArrayProp(match, 'blanks'); // Replace {{id}} in template with labeled blanks - let rendered = template.replace(/\{\{(\w+)\}\}/g, (_, id) => { + const rendered = template.replace(/\{\{(\w+)\}\}/g, (_, id) => { const blank = blanks.find(b => b.id === id); const hint = blank?.correctAnswers || blank?.hint || ''; if (hint) { From 6d6e3353ad12014c5fff7c9eef6374d839fd63b6 Mon Sep 17 00:00:00 2001 From: Shelton Cheung Date: Thu, 19 Mar 2026 12:34:04 +0800 Subject: [PATCH 4/4] chore(scripts): add more clear comment --- scripts/generate-book-pdf.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/generate-book-pdf.ts b/scripts/generate-book-pdf.ts index c360b9728a0..db216944b55 100644 --- a/scripts/generate-book-pdf.ts +++ b/scripts/generate-book-pdf.ts @@ -34,18 +34,27 @@ const TRIM_HEIGHT = '9in'; const BLEED_WIDTH = '6.25in'; // 6 + 0.125*2 const BLEED_HEIGHT = '9.25in'; // 9 + 0.125*2 +/** + * Interface representing a set of font families for different typographic roles. + */ interface FontStacks { serif: string; sans: string; mono: string; } +/** + * Default font stacks used for all locales unless overridden. + */ const DEFAULT_FONT_STACKS: FontStacks = { serif: `'Palatino Linotype', 'Book Antiqua', Palatino, Georgia, 'Times New Roman', serif`, sans: `'Helvetica Neue', Helvetica, Arial, sans-serif`, mono: `'SF Mono', 'Monaco', 'Menlo', 'Inconsolata', 'Fira Code', 'Consolas', monospace`, }; +/** + * Locale-specific font stack overrides to improve rendering for CJK and other languages. + */ const LOCALE_FONT_STACKS: Partial>> = { zh: { serif: `'Songti SC', 'STSong', 'Noto Serif CJK SC', 'Source Han Serif SC', serif`, @@ -2165,9 +2174,8 @@ function convertLinksToEndnotes(html: string, messages: Record } /** - * Generate the HTML document for PDF + * Map part slugs to message keys for translation */ -// Map part slugs to message keys for translation const PART_TRANSLATION_KEYS: Record = { 'Introduction': 'introduction', 'Foundations': 'foundations', @@ -2179,6 +2187,15 @@ const PART_TRANSLATION_KEYS: Record = { 'Conclusion': 'conclusion', }; +/** + * Generate the HTML document for PDF + * + * @param chapters - The array of processed chapters to include. + * @param locale - The locale string. + * @param messages - Translation messages for the document metadata and labels. + * @returns The final HTML document as a string. + */ + function generateHtmlDocument(chapters: ProcessedChapter[], locale: string, messages: Record = {}): string { // Print version uses shorter printTitle, screen uses full title const titleKey = PRINT_READY ? 'book.printTitle' : 'book.title';