@bytechain.cn/colamd-themes 0.3.1 → 0.5.0

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 (100) 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 -146
  7. package/.opencode/skills/colamd-themes/SKILL.md +17 -0
  8. package/.trae/rules/colamd-themes.md +0 -145
  9. package/CHANGELOG.md +318 -240
  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 -365
  14. package/bytechain.cn-colamd-themes-0.5.0.tgz +0 -0
  15. package/dist/cli.js +272 -32
  16. package/dist/cli.js.map +1 -1
  17. package/dist/color-utils.d.ts +1 -0
  18. package/dist/color-utils.js +25 -6
  19. package/dist/color-utils.js.map +1 -1
  20. package/dist/extractors/pdf-extractor.js +6 -1
  21. package/dist/extractors/pdf-extractor.js.map +1 -1
  22. package/dist/extractors/url-extractor.d.ts +0 -4
  23. package/dist/extractors/url-extractor.js +13 -126
  24. package/dist/extractors/url-extractor.js.map +1 -1
  25. package/dist/generator.d.ts +1 -1
  26. package/dist/generator.js +32 -11
  27. package/dist/generator.js.map +1 -1
  28. package/dist/models.d.ts +5 -4
  29. package/dist/models.js +2 -3
  30. package/dist/models.js.map +1 -1
  31. package/dist/renderer.d.ts +4 -2
  32. package/dist/renderer.js +68 -25
  33. package/dist/renderer.js.map +1 -1
  34. package/dist/templates/template.css +220 -498
  35. package/dist/templates/theme-paradigm.md +530 -658
  36. package/dist/templates/theme-template.hbs +256 -370
  37. package/dist/theme-store.js +19 -3
  38. package/dist/theme-store.js.map +1 -1
  39. package/dist/utils/document-paradigm.d.ts +12 -0
  40. package/dist/utils/document-paradigm.js +109 -0
  41. package/dist/utils/document-paradigm.js.map +1 -0
  42. package/dist/validators.d.ts +4 -1
  43. package/dist/validators.js +115 -1
  44. package/dist/validators.js.map +1 -1
  45. package/mcp-server/dist/index.d.ts +13 -0
  46. package/mcp-server/dist/index.d.ts.map +1 -0
  47. package/mcp-server/dist/index.js +0 -0
  48. package/mcp-server/dist/index.js.map +1 -0
  49. package/mcp-server/package-lock.json +1759 -0
  50. package/mcp-server/package.json +24 -0
  51. package/mcp-server/src/index.ts +0 -0
  52. package/mcp-server/tsconfig.json +19 -0
  53. package/package.json +54 -36
  54. package/skills/build-skills.sh +30 -5
  55. package/skills/colamd-themes/SKILL.md +180 -148
  56. package/src/cli.ts +474 -217
  57. package/src/color-systems.ts +289 -289
  58. package/src/color-utils.ts +137 -120
  59. package/src/contrast.ts +66 -66
  60. package/src/docx-style-paradigm.ts +811 -811
  61. package/src/export-cli.ts +356 -356
  62. package/src/extractors/base.ts +8 -8
  63. package/src/extractors/docx-extractor.ts +417 -417
  64. package/src/extractors/pdf-extractor.ts +533 -527
  65. package/src/extractors/url-extractor.ts +846 -981
  66. package/src/generator.ts +197 -169
  67. package/src/index.ts +29 -29
  68. package/src/models.ts +212 -212
  69. package/src/renderer.ts +332 -288
  70. package/src/templates/template.css +220 -498
  71. package/src/templates/theme-paradigm.md +530 -658
  72. package/src/templates/theme-template.hbs +256 -370
  73. package/src/theme-store.ts +185 -170
  74. package/src/types/pdf-parse.d.ts +12 -12
  75. package/src/utils/cli-utils.ts +84 -84
  76. package/src/utils/document-paradigm.ts +118 -0
  77. package/src/validators.ts +298 -176
  78. package/themes/academic-paper-pt.css +353 -1805
  79. package/themes/academic-paper.css +350 -1099
  80. package/themes/academic-style.css +342 -2430
  81. package/themes/colly.css +256 -370
  82. package/themes/elegant.css +288 -671
  83. package/themes/forest-ink.css +240 -664
  84. package/themes/guizang.css +250 -732
  85. package/themes/hermes-security.css +279 -0
  86. package/themes/meeting-minutes.css +256 -370
  87. package/themes/pixso-design.css +278 -1261
  88. package/themes/salvator-blog.css +256 -370
  89. package/themes/shimo-theme.css +256 -0
  90. package/themes/swiss-design.css +285 -746
  91. package/themes/template.css +220 -498
  92. package/themes/trae-blue.css +280 -737
  93. package/themes/word-contract.css +249 -0
  94. package/themes-preview/demo-content.md +187 -187
  95. package/tsconfig.json +18 -18
  96. package/src/mermaid-presets.ts +0 -142
  97. package/themes/swiss-design copy.css +0 -746
  98. package/themes-preview/colly-preview.html +0 -623
  99. package/themes-preview/meeting-minutes-preview.html +0 -625
  100. 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
+ }