@bytechain.cn/colamd-themes 0.3.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.claude/skills/colamd-themes-preview.skill.md +333 -0
  2. package/.claude/skills/colamd-themes.skill.md +10 -0
  3. package/.dsh/skills/colamd-themes/SKILL.md +14 -0
  4. package/.github/supported-topics.yml +17 -0
  5. package/.hermes/skills/tools/colamd-themes/SKILL.md +14 -0
  6. package/.openclaw/skills/colamd-themes/SKILL.md +1 -37
  7. package/.opencode/skills/colamd-themes/SKILL.md +17 -0
  8. package/.trae/rules/colamd-themes.md +0 -36
  9. package/CHANGELOG.md +331 -241
  10. package/DSH_SUPPORT_SUMMARY.md +87 -0
  11. package/README.md +414 -384
  12. package/README_CN.md +412 -384
  13. package/RELEASENOTE.md +541 -389
  14. package/dist/cli.js +36 -55
  15. package/dist/cli.js.map +1 -1
  16. package/dist/color-utils.d.ts +1 -0
  17. package/dist/color-utils.js +25 -6
  18. package/dist/color-utils.js.map +1 -1
  19. package/dist/extractors/pdf-extractor.js +6 -1
  20. package/dist/extractors/pdf-extractor.js.map +1 -1
  21. package/dist/extractors/url-extractor.d.ts +0 -4
  22. package/dist/extractors/url-extractor.js +13 -126
  23. package/dist/extractors/url-extractor.js.map +1 -1
  24. package/dist/generator.d.ts +1 -1
  25. package/dist/generator.js +32 -11
  26. package/dist/generator.js.map +1 -1
  27. package/dist/models.d.ts +5 -4
  28. package/dist/models.js +2 -3
  29. package/dist/models.js.map +1 -1
  30. package/dist/renderer.d.ts +6 -3
  31. package/dist/renderer.js +54 -93
  32. package/dist/renderer.js.map +1 -1
  33. package/dist/templates/template.css +220 -498
  34. package/dist/templates/theme-paradigm.md +530 -658
  35. package/dist/templates/theme-template.hbs +256 -370
  36. package/dist/theme-store.js +19 -3
  37. package/dist/theme-store.js.map +1 -1
  38. package/dist/utils/document-paradigm.d.ts +12 -0
  39. package/dist/utils/document-paradigm.js +109 -0
  40. package/dist/utils/document-paradigm.js.map +1 -0
  41. package/dist/validators.d.ts +4 -1
  42. package/dist/validators.js +115 -1
  43. package/dist/validators.js.map +1 -1
  44. package/mcp-server/dist/index.d.ts +13 -0
  45. package/mcp-server/dist/index.d.ts.map +1 -0
  46. package/mcp-server/dist/index.js +0 -0
  47. package/mcp-server/dist/index.js.map +1 -0
  48. package/mcp-server/package-lock.json +1759 -0
  49. package/mcp-server/package.json +24 -0
  50. package/mcp-server/src/index.ts +0 -0
  51. package/mcp-server/tsconfig.json +19 -0
  52. package/package.json +54 -36
  53. package/skills/build-skills.sh +30 -5
  54. package/skills/colamd-themes/SKILL.md +180 -178
  55. package/src/cli.ts +474 -487
  56. package/src/color-systems.ts +289 -289
  57. package/src/color-utils.ts +137 -120
  58. package/src/contrast.ts +66 -66
  59. package/src/docx-style-paradigm.ts +811 -811
  60. package/src/export-cli.ts +356 -356
  61. package/src/extractors/base.ts +8 -8
  62. package/src/extractors/docx-extractor.ts +417 -417
  63. package/src/extractors/pdf-extractor.ts +533 -527
  64. package/src/extractors/url-extractor.ts +846 -981
  65. package/src/generator.ts +197 -169
  66. package/src/index.ts +29 -29
  67. package/src/models.ts +212 -212
  68. package/src/renderer.ts +310 -359
  69. package/src/templates/template.css +220 -498
  70. package/src/templates/theme-paradigm.md +530 -658
  71. package/src/templates/theme-template.hbs +256 -370
  72. package/src/theme-store.ts +185 -170
  73. package/src/types/pdf-parse.d.ts +12 -12
  74. package/src/utils/cli-utils.ts +84 -84
  75. package/src/utils/document-paradigm.ts +118 -0
  76. package/src/validators.ts +298 -176
  77. package/themes/academic-paper-pt.css +353 -1805
  78. package/themes/academic-paper.css +350 -1099
  79. package/themes/academic-style.css +342 -2430
  80. package/themes/colly.css +256 -370
  81. package/themes/elegant.css +288 -671
  82. package/themes/forest-ink.css +240 -664
  83. package/themes/guizang.css +250 -732
  84. package/themes/hermes-security.css +279 -0
  85. package/themes/meeting-minutes.css +256 -370
  86. package/themes/pixso-design.css +278 -1261
  87. package/themes/salvator-blog.css +256 -370
  88. package/themes/shimo-theme.css +256 -0
  89. package/themes/swiss-design.css +285 -746
  90. package/themes/template.css +220 -498
  91. package/themes/trae-blue.css +280 -737
  92. package/themes/word-contract.css +249 -0
  93. package/themes-preview/demo-content.md +187 -187
  94. package/tsconfig.json +18 -18
  95. package/src/mermaid-presets.ts +0 -142
  96. package/themes/swiss-design copy.css +0 -746
  97. package/themes-preview/colly-preview.html +0 -623
  98. package/themes-preview/meeting-minutes-preview.html +0 -625
  99. package/themes-preview/salvator-blog-preview.html +0 -623
@@ -1,527 +1,533 @@
1
- /**
2
- * PDF 文档样式提取器 — v2.0
3
- *
4
- * 增强功能:
5
- * 1. 基于文本结构启发式分析提取各级标题样式
6
- * 2. 检测正文字体、字号、行间距特征
7
- * 3. 识别表格、代码块等特殊元素
8
- * 4. 集成 DocxStyleParadigm 标准参考值填充缺失字段
9
- * 5. 自动验证 WCAG 对比度合规性
10
- *
11
- * 技术说明:
12
- * pdf-parse 默认不提供字体元数据,因此采用多层启发式策略:
13
- * - 层级1:文本结构分析(短行/大写/编号 = 标题候选)
14
- * - 层级2:格式特征检测(等宽文本 = 代码块)
15
- * - 层级3:DocxStyleParadigm 标准参考值 fallback
16
- */
17
-
18
- import { readFileSync } from "node:fs";
19
- import { basename } from "node:path";
20
- import type { Extractor } from "./base.js";
21
- import {
22
- makeThemeStyle,
23
- type ThemeStyle,
24
- type ColorPalette,
25
- } from "../models.js";
26
- import { normalizeThemeStyle } from "../color-utils.js";
27
- import {
28
- ACADEMIC_PAPER_DEFAULTS,
29
- BUSINESS_REPORT_DEFAULTS,
30
- TECHNICAL_DOC_DEFAULTS,
31
- getRecommendedPreset,
32
- validateDocumentStyle,
33
- type DocumentStyleParadigm,
34
- } from "../docx-style-paradigm.js";
35
-
36
- /** PDF 解析结果数据类型 */
37
- type PDFData = {
38
- text: string;
39
- numpages: number;
40
- info: Record<string, unknown>;
41
- metadata: Record<string, unknown>;
42
- };
43
-
44
- /** 从文本行中推断的字体信息 */
45
- interface InferredFontInfo {
46
- fontName: string;
47
- fontSize: number;
48
- isBold: boolean;
49
- isHeading: boolean;
50
- headingLevel: number | null;
51
- }
52
-
53
- /** 文本行分析结果 */
54
- interface LineAnalysis {
55
- text: string;
56
- length: number;
57
- isAllUppercase: boolean;
58
- isShortLine: boolean;
59
- hasNumbering: boolean;
60
- hasBullet: boolean;
61
- looksLikeHeading: boolean;
62
- looksLikeCode: boolean;
63
- indentLevel: number;
64
- }
65
-
66
- export class PdfExtractor implements Extractor {
67
- canHandle(source: string): boolean {
68
- return /\.pdf$/i.test(source);
69
- }
70
-
71
- async extract(source: string): Promise<ThemeStyle> {
72
- const name = basename(source).replace(/\.pdf$/i, "");
73
- const theme = makeThemeStyle(name, "pdf", source);
74
-
75
- try {
76
- const pdfParse = (await import("pdf-parse")).default;
77
- const buffer = readFileSync(source);
78
- const data: PDFData = await pdfParse(buffer);
79
-
80
- if (data.text) {
81
- this.analyzeTextStructure(theme, data.text);
82
- }
83
-
84
- if (data.metadata) {
85
- this.populateFromMetadata(theme, data.metadata);
86
- }
87
-
88
- if (data.info) {
89
- this.detectDocumentType(theme, data.text, data.numpages);
90
- }
91
- } catch (err) {
92
- if ((err as NodeJS.ErrnoException).code === "ERR_MODULE_NOT_FOUND") {
93
- throw new Error("pdf-parse 未安装,请运行: npm install pdf-parse");
94
- }
95
- throw err;
96
- }
97
-
98
- this.applyParadigmDefaults(theme);
99
- normalizeThemeStyle(theme);
100
- return theme;
101
- }
102
-
103
- /**
104
- * 分析 PDF 文本结构,推断文档元素样式
105
- */
106
- private analyzeTextStructure(theme: ThemeStyle, text: string): void {
107
- const lines = text.split("\n").filter((l) => l.trim().length > 0);
108
- const analyzedLines = lines.map((line) => this.analyzeLine(line));
109
-
110
- this.detectHeadings(theme, analyzedLines);
111
- this.detectBodyText(theme, analyzedLines);
112
- this.detectCodeBlocks(theme, analyzedLines);
113
- this.detectTables(theme, analyzedLines);
114
- this.inferColorPalette(theme, analyzedLines);
115
- }
116
-
117
- /**
118
- * 单行文本特征分析
119
- */
120
- private analyzeLine(line: string): LineAnalysis {
121
- const trimmed = line.trim();
122
- return {
123
- text: trimmed,
124
- length: trimmed.length,
125
- isAllUppercase:
126
- trimmed.length > 3 &&
127
- trimmed.length < 80 &&
128
- /^[A-Z0-9\s\-:.,!?()]+$/.test(trimmed) &&
129
- /[A-Z]/.test(trimmed) &&
130
- trimmed.split("").filter((c) => /[a-z]/.test(c)).length === 0,
131
- isShortLine: trimmed.length < 60,
132
- hasNumbering: /^\s*(\d+[\.\)]\s*|[一二三四五六七八九十]+[\.\、]\s*|[IVX]+\.\s*)/.test(
133
- trimmed
134
- ),
135
- hasBullet: /^\s*[\-\•\*\·\◦\‣]\s*/.test(trimmed),
136
- looksLikeHeading: false,
137
- looksLikeCode: false,
138
- indentLevel: line.search(/\S/),
139
- };
140
- }
141
-
142
- /**
143
- * 基于启发式规则检测标题层级
144
- */
145
- private detectHeadings(theme: ThemeStyle, lines: LineAnalysis[]): void {
146
- const headingCandidates: { level: number; confidence: number; line: LineAnalysis }[] = [];
147
-
148
- for (const line of lines) {
149
- let confidence = 0;
150
-
151
- if (line.isAllUppercase && line.isShortLine) {
152
- confidence += 4;
153
- }
154
- if (line.hasNumbering && line.isShortLine) {
155
- confidence += 3;
156
- }
157
- if (line.indentLevel === 0 && line.isShortLine && line.length > 5) {
158
- confidence += 2;
159
- }
160
- if (/^(摘要|ABSTRACT|目录|TABLE OF CONTENTS|引言|INTRODUCTION|结论|CONCLUSION|参考文献|REFERENCES)$/i.test(line.text)) {
161
- confidence += 5;
162
- }
163
- if (/^第[一二三四五六七八九十]+[章节部分]|^Chapter\s+\d+/i.test(line.text)) {
164
- confidence += 4;
165
- }
166
- if (line.length < 30 && line.length > 3 && /^[^\s]{2,}$/.test(line.text)) {
167
- confidence += 1;
168
- }
169
-
170
- if (confidence >= 4) {
171
- line.looksLikeHeading = true;
172
- let level = 2;
173
- if (confidence >= 6 || /^(摘要|ABSTRACT|标题|TITLE)$/i.test(line.text)) {
174
- level = 1;
175
- } else if (confidence >= 5 || line.hasNumbering) {
176
- level = 2;
177
- } else if (line.indentLevel > 0) {
178
- level = Math.min(4, 2 + Math.floor(line.indentLevel / 2));
179
- }
180
- headingCandidates.push({ level, confidence, line });
181
- }
182
- }
183
-
184
- if (headingCandidates.length > 0) {
185
- theme.palette.headingColor = theme.palette.headingColor || ACADEMIC_PAPER_DEFAULTS.headings.h1.color;
186
- theme.typography.headingWeight = 700;
187
-
188
- const h1Count = headingCandidates.filter((c) => c.level === 1).length;
189
- const h2Count = headingCandidates.filter((c) => c.level === 2).length;
190
-
191
- if (h1Count > 0) {
192
- theme.seedTypography.fontScaleH1 = ACADEMIC_PAPER_DEFAULTS.headings.h1.fontSize / 16;
193
- }
194
- if (h2Count > 0) {
195
- theme.seedTypography.fontScaleH2 = ACADEMIC_PAPER_DEFAULTS.headings.h2.fontSize / 16;
196
- }
197
- if (headingCandidates.some((c) => c.level >= 3)) {
198
- theme.seedTypography.fontScaleH3 = ACADEMIC_PAPER_DEFAULTS.headings.h3.fontSize / 16;
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * 推断正文样式特征
205
- */
206
- private detectBodyText(theme: ThemeStyle, lines: LineAnalysis[]): void {
207
- const bodyLines = lines.filter(
208
- (l) => !l.looksLikeHeading && !l.looksLikeCode && l.length > 20
209
- );
210
-
211
- if (bodyLines.length === 0) return;
212
-
213
- const avgLength =
214
- bodyLines.reduce((sum, l) => sum + l.length, 0) / bodyLines.length;
215
- const avgIndent =
216
- bodyLines.reduce((sum, l) => sum + l.indentLevel, 0) / bodyLines.length;
217
-
218
- if (avgLength > 60) {
219
- theme.typography.baseFontSize = "16px";
220
- theme.typography.baseLineHeight = 1.75;
221
- } else if (avgLength > 40) {
222
- theme.typography.baseFontSize = "15px";
223
- theme.typography.baseLineHeight = 1.7;
224
- } else {
225
- theme.typography.baseFontSize = "14px";
226
- theme.typography.baseLineHeight = 1.65;
227
- }
228
-
229
- if (avgIndent >= 2) {
230
- theme.typography.bodyFontFamily = ACADEMIC_PAPER_DEFAULTS.body.fontFamily;
231
- }
232
-
233
- theme.seedTypography.lineHeightBody = theme.typography.baseLineHeight;
234
- }
235
-
236
- /**
237
- * 检测代码块区域(等宽文本特征)
238
- */
239
- private detectCodeBlocks(theme: ThemeStyle, lines: LineAnalysis[]): void {
240
- const codeIndicators = [
241
- (l: LineAnalysis) =>
242
- l.text.includes("function ") ||
243
- l.text.includes("const ") ||
244
- l.text.includes("let ") ||
245
- l.text.includes("var "),
246
- (l: LineAnalysis) =>
247
- l.text.includes("{") && l.text.includes("}"),
248
- (l: LineAnalysis) =>
249
- /^(\s*(\/\/|#|\/\*|\*\/|\s*import\s+\s*export\s+))/.test(l.text),
250
- (l: LineAnalysis) =>
251
- l.text.match(/[;<>=\[\](){}]/g)?.length &&
252
- l.text.match(/[;<>=\[\](){}]/g)!?.length > 3,
253
- ];
254
-
255
- for (const line of lines) {
256
- const isCode = codeIndicators.some((check) => check(line));
257
- if (isCode) {
258
- line.looksLikeCode = true;
259
- }
260
- }
261
-
262
- const codeLineCount = lines.filter((l) => l.looksLikeCode).length;
263
- if (codeLineCount > 3) {
264
- theme.palette.codeBlockBg = "#f5f5f5";
265
- theme.palette.codeBlockText = "#333333";
266
- theme.palette.codeBg = "#f9f9f9";
267
- }
268
- }
269
-
270
- /**
271
- * 检测表格结构(多列对齐文本)
272
- */
273
- private detectTables(theme: ThemeStyle, lines: LineAnalysis[]): void {
274
- const tablePatterns = [
275
- /^\|.+\|.*\|$/,
276
- /^\s*\|?\s*-{2,}\s*\|/,
277
- /^\s+(\S+)\s{2,}(\S+)\s{2,}(\S+)/,
278
- ];
279
-
280
- let tableLineCount = 0;
281
- for (const line of lines) {
282
- if (tablePatterns.some((p) => p.test(line.text))) {
283
- tableLineCount++;
284
- }
285
- }
286
-
287
- if (tableLineCount >= 3) {
288
- theme.palette.tableHeaderBg = ACADEMIC_PAPER_DEFAULTS.table.headerBgColor;
289
- theme.palette.borderColor = ACADEMIC_PAPER_DEFAULTS.table.borderColor;
290
- }
291
- }
292
-
293
- /**
294
- * 基于文档特征推断配色方案
295
- */
296
- private inferColorPalette(theme: ThemeStyle, lines: LineAnalysis[]): void {
297
- const p = theme.palette;
298
-
299
- const hasChinese = lines.some((l) => /[\u4e00-\u9fa5]/.test(l.text));
300
- const longEnglishBlocks = lines.filter(
301
- (l) => l.length > 100 && /^[a-zA-Z0-9\s\.,;\:'"()\[\]{}]+$/.test(l.text)
302
- ).length;
303
-
304
- if (hasChinese) {
305
- p.textColor = ACADEMIC_PAPER_DEFAULTS.body.color;
306
- p.bgColor = ACADEMIC_PAPER_DEFAULTS.pageBackground;
307
- theme.typography.bodyFontFamily = ACADEMIC_PAPER_DEFAULTS.body.fontFamily;
308
- theme.typography.headingFontFamily = ACADEMIC_PAPER_DEFAULTS.headings.h1.fontFamily;
309
- } else if (longEnglishBlocks > 5) {
310
- p.textColor = BUSINESS_REPORT_DEFAULTS.body.color;
311
- p.bgColor = BUSINESS_REPORT_DEFAULTS.pageBackground;
312
- theme.typography.bodyFontFamily = BUSINESS_REPORT_DEFAULTS.body.fontFamily;
313
- theme.typography.headingFontFamily = BUSINESS_REPORT_DEFAULTS.headings.h1.fontFamily;
314
- }
315
-
316
- p.accentColor = hasChinese ? "#2b579a" : "#2563eb";
317
- p.textMuted = hasChinese ? "#666666" : "#9ca3af";
318
- }
319
-
320
- /**
321
- * 提取 PDF 元数据信息
322
- */
323
- private populateFromMetadata(
324
- theme: ThemeStyle,
325
- metadata: Record<string, unknown>
326
- ): void {
327
- if (metadata.Title && typeof metadata.Title === "string") {
328
- theme.description = `主题提取自: ${metadata.Title}`;
329
- }
330
- if (metadata.Author && typeof metadata.Author === "string") {
331
- theme.description = `${theme.description || ""} (作者: ${metadata.Author})`;
332
- }
333
- }
334
-
335
- /**
336
- * 基于内容特征判断文档类型并应用对应预设
337
- */
338
- private detectDocumentType(
339
- theme: ThemeStyle,
340
- _text: string,
341
- pageCount: number
342
- ): void {
343
- if (pageCount <= 2) {
344
- theme.description = `${theme.description} [单页文档]`;
345
- } else if (pageCount > 20) {
346
- theme.description = `${theme.description} [长篇文档 ${pageCount}页]`;
347
- }
348
- }
349
-
350
- /**
351
- * 应用 DocxStyleParadigm 标准参考值填充所有缺失字段
352
- */
353
- private applyParadigmDefaults(theme: ThemeStyle): void {
354
- const preset = getRecommendedPreset();
355
- const p = theme.palette;
356
- const t = theme.typography;
357
- const st = theme.seedTypography;
358
-
359
- if (!p.textColor || p.textColor === "#000000") {
360
- p.textColor = preset.body.color;
361
- }
362
- if (!p.headingColor) {
363
- p.headingColor = preset.headings.h1.color;
364
- }
365
- if (!p.bgColor || p.bgColor === "#ffffff") {
366
- p.bgColor = preset.pageBackground;
367
- }
368
- if (!p.textMuted) {
369
- p.textMuted = preset.mutedTextColor;
370
- }
371
- if (!p.accentColor || p.accentColor === "#0000ff") {
372
- p.accentColor = preset.accentColor;
373
- }
374
- if (!p.tableHeaderBg) {
375
- p.tableHeaderBg = preset.table.headerBgColor;
376
- }
377
- if (!p.codeBg) {
378
- p.codeBg = preset.inlineCode.bgColor;
379
- }
380
- if (!p.codeBlockBg) {
381
- p.codeBlockBg = preset.codeBlock.bgColor;
382
- }
383
- if (!p.codeBlockText) {
384
- p.codeBlockText = preset.codeBlock.color;
385
- }
386
- if (!p.blockquoteBg) {
387
- p.blockquoteBg = preset.blockquote.bgColor;
388
- }
389
- if (!p.blockquoteBorder) {
390
- p.blockquoteBorder = preset.blockquote.borderLeftColor;
391
- }
392
- if (!p.selectionBg) {
393
- p.selectionBg = preset.selectionBg;
394
- }
395
- if (!p.borderColor) {
396
- p.borderColor = preset.table.borderColor;
397
- }
398
-
399
- if (!t.bodyFontFamily || t.bodyFontFamily === "Georgia, serif") {
400
- t.bodyFontFamily = preset.body.fontFamily;
401
- }
402
- if (!t.headingFontFamily) {
403
- t.headingFontFamily = preset.headings.h1.fontFamily;
404
- }
405
- if (!t.baseFontSize) {
406
- t.baseFontSize = `${preset.body.fontSize}px`;
407
- }
408
- if (t.baseLineHeight < 1.5) {
409
- t.baseLineHeight = preset.body.lineHeight;
410
- }
411
-
412
- st.fontBody = preset.body.fontFamily;
413
- st.fontHeading = preset.headings.h1.fontFamily;
414
- st.lineHeightBody = preset.body.lineHeight;
415
- st.lineHeightHeading = preset.headings.h1.lineHeight;
416
-
417
- const issues = validateDocumentStyle(this.themeToParadigm(theme));
418
- for (const issue of issues) {
419
- if (issue.level === "error") {
420
- console.warn(`[PdfExtractor] ${issue.element}: ${issue.message}`);
421
- }
422
- }
423
- }
424
-
425
- /**
426
- * 将 ThemeStyle 转换为 DocumentStyleParadigm 用于 WCAG 验证
427
- */
428
- private themeToParadigm(theme: ThemeStyle): DocumentStyleParadigm {
429
- return {
430
- pageBackground: theme.palette.bgColor,
431
- headings: {
432
- h1: {
433
- fontFamily: theme.seedTypography.fontHeading,
434
- fontSize: 16 * theme.seedTypography.fontScaleH1,
435
- fontWeight: theme.typography.headingWeight,
436
- color: theme.palette.headingColor,
437
- lineHeight: theme.seedTypography.lineHeightHeading,
438
- },
439
- h2: {
440
- fontFamily: theme.seedTypography.fontHeading,
441
- fontSize: 16 * theme.seedTypography.fontScaleH2,
442
- fontWeight: Math.max(600, theme.typography.headingWeight - 100),
443
- color: theme.palette.headingColor,
444
- lineHeight: theme.seedTypography.lineHeightHeading,
445
- },
446
- h3: {
447
- fontFamily: theme.seedTypography.fontHeading,
448
- fontSize: 16 * theme.seedTypography.fontScaleH3,
449
- fontWeight: 600,
450
- color: theme.palette.headingColor,
451
- lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
452
- },
453
- h4: {
454
- fontFamily: theme.seedTypography.fontHeading,
455
- fontSize: 16 * theme.seedTypography.fontScaleH4,
456
- fontWeight: 600,
457
- color: theme.palette.textMuted,
458
- lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
459
- },
460
- h5: {
461
- fontFamily: theme.seedTypography.fontHeading,
462
- fontSize: 16 * theme.seedTypography.fontScaleH5,
463
- fontWeight: 500,
464
- color: theme.palette.textMuted,
465
- lineHeight: theme.seedTypography.lineHeightBody - 0.25,
466
- },
467
- h6: {
468
- fontFamily: theme.seedTypography.fontHeading,
469
- fontSize: 16 * theme.seedTypography.fontScaleH6,
470
- fontWeight: 500,
471
- color: theme.palette.textMuted,
472
- lineHeight: theme.seedTypography.lineHeightBody - 0.2,
473
- },
474
- },
475
- body: {
476
- fontFamily: theme.seedTypography.fontBody,
477
- fontSize: parseInt(theme.typography.baseFontSize) || 16,
478
- fontWeight: 400,
479
- color: theme.palette.textColor,
480
- lineHeight: theme.typography.baseLineHeight,
481
- },
482
- blockquote: {
483
- fontFamily: theme.seedTypography.fontBody,
484
- fontSize: Math.max(13, (parseInt(theme.typography.baseFontSize) || 16) - 2),
485
- fontWeight: 400,
486
- color: theme.palette.textMuted,
487
- lineHeight: theme.typography.baseLineHeight - 0.15,
488
- borderLeftColor: theme.palette.blockquoteBorder,
489
- bgColor: theme.palette.blockquoteBg,
490
- },
491
- codeBlock: {
492
- fontFamily: theme.typography.codeFontFamily,
493
- fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
494
- fontWeight: 400,
495
- color: theme.palette.codeBlockText || theme.palette.textColor,
496
- lineHeight: theme.seedTypography.lineHeightCode,
497
- bgColor: theme.palette.codeBlockBg,
498
- },
499
- inlineCode: {
500
- fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
501
- color: theme.palette.accentColor,
502
- bgColor: theme.palette.codeBg,
503
- },
504
- table: {
505
- headerFontFamily: theme.seedTypography.fontHeading,
506
- headerFontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
507
- headerFontWeight: 700,
508
- headerColor: "#ffffff",
509
- headerBgColor: theme.palette.tableHeaderBg,
510
- headerLineHeight: 1.4,
511
-
512
- cellFontFamily: theme.seedTypography.fontBody,
513
- cellFontSize: Math.max(11, (parseInt(theme.typography.baseFontSize) || 16) - 4),
514
- cellColor: theme.palette.textColor,
515
- cellBgColor: theme.palette.bgColor,
516
- cellLineHeight: 1.5,
517
- cellPadding: "8px 12px",
518
- borderColor: theme.palette.borderColor,
519
- borderWidth: "1px",
520
- },
521
-
522
- accentColor: theme.palette.accentColor,
523
- mutedTextColor: theme.palette.textMuted,
524
- selectionBg: theme.palette.selectionBg,
525
- };
526
- }
527
- }
1
+ /**
2
+ * PDF 文档样式提取器 — v2.0
3
+ *
4
+ * 增强功能:
5
+ * 1. 基于文本结构启发式分析提取各级标题样式
6
+ * 2. 检测正文字体、字号、行间距特征
7
+ * 3. 识别表格、代码块等特殊元素
8
+ * 4. 集成 DocxStyleParadigm 标准参考值填充缺失字段
9
+ * 5. 自动验证 WCAG 对比度合规性
10
+ *
11
+ * 技术说明:
12
+ * pdf-parse 默认不提供字体元数据,因此采用多层启发式策略:
13
+ * - 层级1:文本结构分析(短行/大写/编号 = 标题候选)
14
+ * - 层级2:格式特征检测(等宽文本 = 代码块)
15
+ * - 层级3:DocxStyleParadigm 标准参考值 fallback
16
+ */
17
+
18
+ import { readFileSync, statSync } from "node:fs";
19
+ import { basename } from "node:path";
20
+ import type { Extractor } from "./base.js";
21
+ import {
22
+ makeThemeStyle,
23
+ type ThemeStyle,
24
+ type ColorPalette,
25
+ } from "../models.js";
26
+ import { normalizeThemeStyle } from "../color-utils.js";
27
+ import {
28
+ ACADEMIC_PAPER_DEFAULTS,
29
+ BUSINESS_REPORT_DEFAULTS,
30
+ TECHNICAL_DOC_DEFAULTS,
31
+ getRecommendedPreset,
32
+ validateDocumentStyle,
33
+ type DocumentStyleParadigm,
34
+ } from "../docx-style-paradigm.js";
35
+
36
+ /** PDF 解析结果数据类型 */
37
+ type PDFData = {
38
+ text: string;
39
+ numpages: number;
40
+ info: Record<string, unknown>;
41
+ metadata: Record<string, unknown>;
42
+ };
43
+
44
+ /** 从文本行中推断的字体信息 */
45
+ interface InferredFontInfo {
46
+ fontName: string;
47
+ fontSize: number;
48
+ isBold: boolean;
49
+ isHeading: boolean;
50
+ headingLevel: number | null;
51
+ }
52
+
53
+ /** 文本行分析结果 */
54
+ interface LineAnalysis {
55
+ text: string;
56
+ length: number;
57
+ isAllUppercase: boolean;
58
+ isShortLine: boolean;
59
+ hasNumbering: boolean;
60
+ hasBullet: boolean;
61
+ looksLikeHeading: boolean;
62
+ looksLikeCode: boolean;
63
+ indentLevel: number;
64
+ }
65
+
66
+ export class PdfExtractor implements Extractor {
67
+ canHandle(source: string): boolean {
68
+ return /\.pdf$/i.test(source);
69
+ }
70
+
71
+ async extract(source: string): Promise<ThemeStyle> {
72
+ const name = basename(source).replace(/\.pdf$/i, "");
73
+ const theme = makeThemeStyle(name, "pdf", source);
74
+
75
+ const MAX_PDF_BYTES = 50 * 1024 * 1024;
76
+ const stats = statSync(source);
77
+ if (stats.size > MAX_PDF_BYTES) {
78
+ throw new Error(`PDF file too large (${(stats.size / 1024 / 1024).toFixed(1)} MB). Maximum allowed: 50 MB.`);
79
+ }
80
+
81
+ try {
82
+ const pdfParse = (await import("pdf-parse")).default;
83
+ const buffer = readFileSync(source);
84
+ const data: PDFData = await pdfParse(buffer);
85
+
86
+ if (data.text) {
87
+ this.analyzeTextStructure(theme, data.text);
88
+ }
89
+
90
+ if (data.metadata) {
91
+ this.populateFromMetadata(theme, data.metadata);
92
+ }
93
+
94
+ if (data.info) {
95
+ this.detectDocumentType(theme, data.text, data.numpages);
96
+ }
97
+ } catch (err) {
98
+ if ((err as NodeJS.ErrnoException).code === "ERR_MODULE_NOT_FOUND") {
99
+ throw new Error("pdf-parse 未安装,请运行: npm install pdf-parse");
100
+ }
101
+ throw err;
102
+ }
103
+
104
+ this.applyParadigmDefaults(theme);
105
+ normalizeThemeStyle(theme);
106
+ return theme;
107
+ }
108
+
109
+ /**
110
+ * 分析 PDF 文本结构,推断文档元素样式
111
+ */
112
+ private analyzeTextStructure(theme: ThemeStyle, text: string): void {
113
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
114
+ const analyzedLines = lines.map((line) => this.analyzeLine(line));
115
+
116
+ this.detectHeadings(theme, analyzedLines);
117
+ this.detectBodyText(theme, analyzedLines);
118
+ this.detectCodeBlocks(theme, analyzedLines);
119
+ this.detectTables(theme, analyzedLines);
120
+ this.inferColorPalette(theme, analyzedLines);
121
+ }
122
+
123
+ /**
124
+ * 单行文本特征分析
125
+ */
126
+ private analyzeLine(line: string): LineAnalysis {
127
+ const trimmed = line.trim();
128
+ return {
129
+ text: trimmed,
130
+ length: trimmed.length,
131
+ isAllUppercase:
132
+ trimmed.length > 3 &&
133
+ trimmed.length < 80 &&
134
+ /^[A-Z0-9\s\-:.,!?()]+$/.test(trimmed) &&
135
+ /[A-Z]/.test(trimmed) &&
136
+ trimmed.split("").filter((c) => /[a-z]/.test(c)).length === 0,
137
+ isShortLine: trimmed.length < 60,
138
+ hasNumbering: /^\s*(\d+[\.\)]\s*|[一二三四五六七八九十]+[\.\、]\s*|[IVX]+\.\s*)/.test(
139
+ trimmed
140
+ ),
141
+ hasBullet: /^\s*[\-\•\*\·\◦\‣]\s*/.test(trimmed),
142
+ looksLikeHeading: false,
143
+ looksLikeCode: false,
144
+ indentLevel: line.search(/\S/),
145
+ };
146
+ }
147
+
148
+ /**
149
+ * 基于启发式规则检测标题层级
150
+ */
151
+ private detectHeadings(theme: ThemeStyle, lines: LineAnalysis[]): void {
152
+ const headingCandidates: { level: number; confidence: number; line: LineAnalysis }[] = [];
153
+
154
+ for (const line of lines) {
155
+ let confidence = 0;
156
+
157
+ if (line.isAllUppercase && line.isShortLine) {
158
+ confidence += 4;
159
+ }
160
+ if (line.hasNumbering && line.isShortLine) {
161
+ confidence += 3;
162
+ }
163
+ if (line.indentLevel === 0 && line.isShortLine && line.length > 5) {
164
+ confidence += 2;
165
+ }
166
+ if (/^(摘要|ABSTRACT|目录|TABLE OF CONTENTS|引言|INTRODUCTION|结论|CONCLUSION|参考文献|REFERENCES)$/i.test(line.text)) {
167
+ confidence += 5;
168
+ }
169
+ if (/^第[一二三四五六七八九十]+[章节部分]|^Chapter\s+\d+/i.test(line.text)) {
170
+ confidence += 4;
171
+ }
172
+ if (line.length < 30 && line.length > 3 && /^[^\s]{2,}$/.test(line.text)) {
173
+ confidence += 1;
174
+ }
175
+
176
+ if (confidence >= 4) {
177
+ line.looksLikeHeading = true;
178
+ let level = 2;
179
+ if (confidence >= 6 || /^(摘要|ABSTRACT|标题|TITLE)$/i.test(line.text)) {
180
+ level = 1;
181
+ } else if (confidence >= 5 || line.hasNumbering) {
182
+ level = 2;
183
+ } else if (line.indentLevel > 0) {
184
+ level = Math.min(4, 2 + Math.floor(line.indentLevel / 2));
185
+ }
186
+ headingCandidates.push({ level, confidence, line });
187
+ }
188
+ }
189
+
190
+ if (headingCandidates.length > 0) {
191
+ theme.palette.headingColor = theme.palette.headingColor || ACADEMIC_PAPER_DEFAULTS.headings.h1.color;
192
+ theme.typography.headingWeight = 700;
193
+
194
+ const h1Count = headingCandidates.filter((c) => c.level === 1).length;
195
+ const h2Count = headingCandidates.filter((c) => c.level === 2).length;
196
+
197
+ if (h1Count > 0) {
198
+ theme.seedTypography.fontScaleH1 = ACADEMIC_PAPER_DEFAULTS.headings.h1.fontSize / 16;
199
+ }
200
+ if (h2Count > 0) {
201
+ theme.seedTypography.fontScaleH2 = ACADEMIC_PAPER_DEFAULTS.headings.h2.fontSize / 16;
202
+ }
203
+ if (headingCandidates.some((c) => c.level >= 3)) {
204
+ theme.seedTypography.fontScaleH3 = ACADEMIC_PAPER_DEFAULTS.headings.h3.fontSize / 16;
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * 推断正文样式特征
211
+ */
212
+ private detectBodyText(theme: ThemeStyle, lines: LineAnalysis[]): void {
213
+ const bodyLines = lines.filter(
214
+ (l) => !l.looksLikeHeading && !l.looksLikeCode && l.length > 20
215
+ );
216
+
217
+ if (bodyLines.length === 0) return;
218
+
219
+ const avgLength =
220
+ bodyLines.reduce((sum, l) => sum + l.length, 0) / bodyLines.length;
221
+ const avgIndent =
222
+ bodyLines.reduce((sum, l) => sum + l.indentLevel, 0) / bodyLines.length;
223
+
224
+ if (avgLength > 60) {
225
+ theme.typography.baseFontSize = "16px";
226
+ theme.typography.baseLineHeight = 1.75;
227
+ } else if (avgLength > 40) {
228
+ theme.typography.baseFontSize = "15px";
229
+ theme.typography.baseLineHeight = 1.7;
230
+ } else {
231
+ theme.typography.baseFontSize = "14px";
232
+ theme.typography.baseLineHeight = 1.65;
233
+ }
234
+
235
+ if (avgIndent >= 2) {
236
+ theme.typography.bodyFontFamily = ACADEMIC_PAPER_DEFAULTS.body.fontFamily;
237
+ }
238
+
239
+ theme.seedTypography.lineHeightBody = theme.typography.baseLineHeight;
240
+ }
241
+
242
+ /**
243
+ * 检测代码块区域(等宽文本特征)
244
+ */
245
+ private detectCodeBlocks(theme: ThemeStyle, lines: LineAnalysis[]): void {
246
+ const codeIndicators = [
247
+ (l: LineAnalysis) =>
248
+ l.text.includes("function ") ||
249
+ l.text.includes("const ") ||
250
+ l.text.includes("let ") ||
251
+ l.text.includes("var "),
252
+ (l: LineAnalysis) =>
253
+ l.text.includes("{") && l.text.includes("}"),
254
+ (l: LineAnalysis) =>
255
+ /^(\s*(\/\/|#|\/\*|\*\/|\s*import\s+\s*export\s+))/.test(l.text),
256
+ (l: LineAnalysis) =>
257
+ l.text.match(/[;<>=\[\](){}]/g)?.length &&
258
+ l.text.match(/[;<>=\[\](){}]/g)!?.length > 3,
259
+ ];
260
+
261
+ for (const line of lines) {
262
+ const isCode = codeIndicators.some((check) => check(line));
263
+ if (isCode) {
264
+ line.looksLikeCode = true;
265
+ }
266
+ }
267
+
268
+ const codeLineCount = lines.filter((l) => l.looksLikeCode).length;
269
+ if (codeLineCount > 3) {
270
+ theme.palette.codeBlockBg = "#f5f5f5";
271
+ theme.palette.codeBlockText = "#333333";
272
+ theme.palette.codeBg = "#f9f9f9";
273
+ }
274
+ }
275
+
276
+ /**
277
+ * 检测表格结构(多列对齐文本)
278
+ */
279
+ private detectTables(theme: ThemeStyle, lines: LineAnalysis[]): void {
280
+ const tablePatterns = [
281
+ /^\|.+\|.*\|$/,
282
+ /^\s*\|?\s*-{2,}\s*\|/,
283
+ /^\s+(\S+)\s{2,}(\S+)\s{2,}(\S+)/,
284
+ ];
285
+
286
+ let tableLineCount = 0;
287
+ for (const line of lines) {
288
+ if (tablePatterns.some((p) => p.test(line.text))) {
289
+ tableLineCount++;
290
+ }
291
+ }
292
+
293
+ if (tableLineCount >= 3) {
294
+ theme.palette.tableHeaderBg = ACADEMIC_PAPER_DEFAULTS.table.headerBgColor;
295
+ theme.palette.borderColor = ACADEMIC_PAPER_DEFAULTS.table.borderColor;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * 基于文档特征推断配色方案
301
+ */
302
+ private inferColorPalette(theme: ThemeStyle, lines: LineAnalysis[]): void {
303
+ const p = theme.palette;
304
+
305
+ const hasChinese = lines.some((l) => /[\u4e00-\u9fa5]/.test(l.text));
306
+ const longEnglishBlocks = lines.filter(
307
+ (l) => l.length > 100 && /^[a-zA-Z0-9\s\.,;\:'"()\[\]{}]+$/.test(l.text)
308
+ ).length;
309
+
310
+ if (hasChinese) {
311
+ p.textColor = ACADEMIC_PAPER_DEFAULTS.body.color;
312
+ p.bgColor = ACADEMIC_PAPER_DEFAULTS.pageBackground;
313
+ theme.typography.bodyFontFamily = ACADEMIC_PAPER_DEFAULTS.body.fontFamily;
314
+ theme.typography.headingFontFamily = ACADEMIC_PAPER_DEFAULTS.headings.h1.fontFamily;
315
+ } else if (longEnglishBlocks > 5) {
316
+ p.textColor = BUSINESS_REPORT_DEFAULTS.body.color;
317
+ p.bgColor = BUSINESS_REPORT_DEFAULTS.pageBackground;
318
+ theme.typography.bodyFontFamily = BUSINESS_REPORT_DEFAULTS.body.fontFamily;
319
+ theme.typography.headingFontFamily = BUSINESS_REPORT_DEFAULTS.headings.h1.fontFamily;
320
+ }
321
+
322
+ p.accentColor = hasChinese ? "#2b579a" : "#2563eb";
323
+ p.textMuted = hasChinese ? "#666666" : "#9ca3af";
324
+ }
325
+
326
+ /**
327
+ * 提取 PDF 元数据信息
328
+ */
329
+ private populateFromMetadata(
330
+ theme: ThemeStyle,
331
+ metadata: Record<string, unknown>
332
+ ): void {
333
+ if (metadata.Title && typeof metadata.Title === "string") {
334
+ theme.description = `主题提取自: ${metadata.Title}`;
335
+ }
336
+ if (metadata.Author && typeof metadata.Author === "string") {
337
+ theme.description = `${theme.description || ""} (作者: ${metadata.Author})`;
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 基于内容特征判断文档类型并应用对应预设
343
+ */
344
+ private detectDocumentType(
345
+ theme: ThemeStyle,
346
+ _text: string,
347
+ pageCount: number
348
+ ): void {
349
+ if (pageCount <= 2) {
350
+ theme.description = `${theme.description} [单页文档]`;
351
+ } else if (pageCount > 20) {
352
+ theme.description = `${theme.description} [长篇文档 ${pageCount}页]`;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * 应用 DocxStyleParadigm 标准参考值填充所有缺失字段
358
+ */
359
+ private applyParadigmDefaults(theme: ThemeStyle): void {
360
+ const preset = getRecommendedPreset();
361
+ const p = theme.palette;
362
+ const t = theme.typography;
363
+ const st = theme.seedTypography;
364
+
365
+ if (!p.textColor || p.textColor === "#000000") {
366
+ p.textColor = preset.body.color;
367
+ }
368
+ if (!p.headingColor) {
369
+ p.headingColor = preset.headings.h1.color;
370
+ }
371
+ if (!p.bgColor || p.bgColor === "#ffffff") {
372
+ p.bgColor = preset.pageBackground;
373
+ }
374
+ if (!p.textMuted) {
375
+ p.textMuted = preset.mutedTextColor;
376
+ }
377
+ if (!p.accentColor || p.accentColor === "#0000ff") {
378
+ p.accentColor = preset.accentColor;
379
+ }
380
+ if (!p.tableHeaderBg) {
381
+ p.tableHeaderBg = preset.table.headerBgColor;
382
+ }
383
+ if (!p.codeBg) {
384
+ p.codeBg = preset.inlineCode.bgColor;
385
+ }
386
+ if (!p.codeBlockBg) {
387
+ p.codeBlockBg = preset.codeBlock.bgColor;
388
+ }
389
+ if (!p.codeBlockText) {
390
+ p.codeBlockText = preset.codeBlock.color;
391
+ }
392
+ if (!p.blockquoteBg) {
393
+ p.blockquoteBg = preset.blockquote.bgColor;
394
+ }
395
+ if (!p.blockquoteBorder) {
396
+ p.blockquoteBorder = preset.blockquote.borderLeftColor;
397
+ }
398
+ if (!p.selectionBg) {
399
+ p.selectionBg = preset.selectionBg;
400
+ }
401
+ if (!p.borderColor) {
402
+ p.borderColor = preset.table.borderColor;
403
+ }
404
+
405
+ if (!t.bodyFontFamily || t.bodyFontFamily === "Georgia, serif") {
406
+ t.bodyFontFamily = preset.body.fontFamily;
407
+ }
408
+ if (!t.headingFontFamily) {
409
+ t.headingFontFamily = preset.headings.h1.fontFamily;
410
+ }
411
+ if (!t.baseFontSize) {
412
+ t.baseFontSize = `${preset.body.fontSize}px`;
413
+ }
414
+ if (t.baseLineHeight < 1.5) {
415
+ t.baseLineHeight = preset.body.lineHeight;
416
+ }
417
+
418
+ st.fontBody = preset.body.fontFamily;
419
+ st.fontHeading = preset.headings.h1.fontFamily;
420
+ st.lineHeightBody = preset.body.lineHeight;
421
+ st.lineHeightHeading = preset.headings.h1.lineHeight;
422
+
423
+ const issues = validateDocumentStyle(this.themeToParadigm(theme));
424
+ for (const issue of issues) {
425
+ if (issue.level === "error") {
426
+ console.warn(`[PdfExtractor] ${issue.element}: ${issue.message}`);
427
+ }
428
+ }
429
+ }
430
+
431
+ /**
432
+ * 将 ThemeStyle 转换为 DocumentStyleParadigm 用于 WCAG 验证
433
+ */
434
+ private themeToParadigm(theme: ThemeStyle): DocumentStyleParadigm {
435
+ return {
436
+ pageBackground: theme.palette.bgColor,
437
+ headings: {
438
+ h1: {
439
+ fontFamily: theme.seedTypography.fontHeading,
440
+ fontSize: 16 * theme.seedTypography.fontScaleH1,
441
+ fontWeight: theme.typography.headingWeight,
442
+ color: theme.palette.headingColor,
443
+ lineHeight: theme.seedTypography.lineHeightHeading,
444
+ },
445
+ h2: {
446
+ fontFamily: theme.seedTypography.fontHeading,
447
+ fontSize: 16 * theme.seedTypography.fontScaleH2,
448
+ fontWeight: Math.max(600, theme.typography.headingWeight - 100),
449
+ color: theme.palette.headingColor,
450
+ lineHeight: theme.seedTypography.lineHeightHeading,
451
+ },
452
+ h3: {
453
+ fontFamily: theme.seedTypography.fontHeading,
454
+ fontSize: 16 * theme.seedTypography.fontScaleH3,
455
+ fontWeight: 600,
456
+ color: theme.palette.headingColor,
457
+ lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
458
+ },
459
+ h4: {
460
+ fontFamily: theme.seedTypography.fontHeading,
461
+ fontSize: 16 * theme.seedTypography.fontScaleH4,
462
+ fontWeight: 600,
463
+ color: theme.palette.textMuted,
464
+ lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
465
+ },
466
+ h5: {
467
+ fontFamily: theme.seedTypography.fontHeading,
468
+ fontSize: 16 * theme.seedTypography.fontScaleH5,
469
+ fontWeight: 500,
470
+ color: theme.palette.textMuted,
471
+ lineHeight: theme.seedTypography.lineHeightBody - 0.25,
472
+ },
473
+ h6: {
474
+ fontFamily: theme.seedTypography.fontHeading,
475
+ fontSize: 16 * theme.seedTypography.fontScaleH6,
476
+ fontWeight: 500,
477
+ color: theme.palette.textMuted,
478
+ lineHeight: theme.seedTypography.lineHeightBody - 0.2,
479
+ },
480
+ },
481
+ body: {
482
+ fontFamily: theme.seedTypography.fontBody,
483
+ fontSize: parseInt(theme.typography.baseFontSize) || 16,
484
+ fontWeight: 400,
485
+ color: theme.palette.textColor,
486
+ lineHeight: theme.typography.baseLineHeight,
487
+ },
488
+ blockquote: {
489
+ fontFamily: theme.seedTypography.fontBody,
490
+ fontSize: Math.max(13, (parseInt(theme.typography.baseFontSize) || 16) - 2),
491
+ fontWeight: 400,
492
+ color: theme.palette.textMuted,
493
+ lineHeight: theme.typography.baseLineHeight - 0.15,
494
+ borderLeftColor: theme.palette.blockquoteBorder,
495
+ bgColor: theme.palette.blockquoteBg,
496
+ },
497
+ codeBlock: {
498
+ fontFamily: theme.typography.codeFontFamily,
499
+ fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
500
+ fontWeight: 400,
501
+ color: theme.palette.codeBlockText || theme.palette.textColor,
502
+ lineHeight: theme.seedTypography.lineHeightCode,
503
+ bgColor: theme.palette.codeBlockBg,
504
+ },
505
+ inlineCode: {
506
+ fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
507
+ color: theme.palette.accentColor,
508
+ bgColor: theme.palette.codeBg,
509
+ },
510
+ table: {
511
+ headerFontFamily: theme.seedTypography.fontHeading,
512
+ headerFontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
513
+ headerFontWeight: 700,
514
+ headerColor: "#ffffff",
515
+ headerBgColor: theme.palette.tableHeaderBg,
516
+ headerLineHeight: 1.4,
517
+
518
+ cellFontFamily: theme.seedTypography.fontBody,
519
+ cellFontSize: Math.max(11, (parseInt(theme.typography.baseFontSize) || 16) - 4),
520
+ cellColor: theme.palette.textColor,
521
+ cellBgColor: theme.palette.bgColor,
522
+ cellLineHeight: 1.5,
523
+ cellPadding: "8px 12px",
524
+ borderColor: theme.palette.borderColor,
525
+ borderWidth: "1px",
526
+ },
527
+
528
+ accentColor: theme.palette.accentColor,
529
+ mutedTextColor: theme.palette.textMuted,
530
+ selectionBg: theme.palette.selectionBg,
531
+ };
532
+ }
533
+ }