@bytechain.cn/colamd-themes 0.3.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 (108) hide show
  1. package/.claude/skills/colamd-themes-preview.skill.md +333 -0
  2. package/.claude/skills/colamd-themes.skill.md +155 -0
  3. package/.hermes/skills/tools/colamd-themes/SKILL.md +159 -0
  4. package/.openclaw/skills/colamd-themes/SKILL.md +166 -0
  5. package/.opencode/skills/colamd-themes/SKILL.md +162 -0
  6. package/.trae/rules/colamd-themes.md +150 -0
  7. package/CHANGELOG.md +102 -0
  8. package/README.md +373 -0
  9. package/README_CN.md +373 -0
  10. package/RELEASENOTE.md +177 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +208 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/color-systems.d.ts +17 -0
  15. package/dist/color-systems.js +251 -0
  16. package/dist/color-systems.js.map +1 -0
  17. package/dist/color-utils.d.ts +9 -0
  18. package/dist/color-utils.js +112 -0
  19. package/dist/color-utils.js.map +1 -0
  20. package/dist/contrast.d.ts +15 -0
  21. package/dist/contrast.js +63 -0
  22. package/dist/contrast.js.map +1 -0
  23. package/dist/docx-style-paradigm.d.ts +153 -0
  24. package/dist/docx-style-paradigm.js +640 -0
  25. package/dist/docx-style-paradigm.js.map +1 -0
  26. package/dist/export-cli.d.ts +10 -0
  27. package/dist/export-cli.js +266 -0
  28. package/dist/export-cli.js.map +1 -0
  29. package/dist/extractors/base.d.ts +6 -0
  30. package/dist/extractors/base.js +3 -0
  31. package/dist/extractors/base.js.map +1 -0
  32. package/dist/extractors/docx-extractor.d.ts +52 -0
  33. package/dist/extractors/docx-extractor.js +370 -0
  34. package/dist/extractors/docx-extractor.js.map +1 -0
  35. package/dist/extractors/pdf-extractor.d.ts +66 -0
  36. package/dist/extractors/pdf-extractor.js +430 -0
  37. package/dist/extractors/pdf-extractor.js.map +1 -0
  38. package/dist/extractors/url-extractor.d.ts +104 -0
  39. package/dist/extractors/url-extractor.js +761 -0
  40. package/dist/extractors/url-extractor.js.map +1 -0
  41. package/dist/generator.d.ts +4 -0
  42. package/dist/generator.js +71 -0
  43. package/dist/generator.js.map +1 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/mermaid-presets.d.ts +14 -0
  48. package/dist/mermaid-presets.js +122 -0
  49. package/dist/mermaid-presets.js.map +1 -0
  50. package/dist/models.d.ts +106 -0
  51. package/dist/models.js +92 -0
  52. package/dist/models.js.map +1 -0
  53. package/dist/renderer.d.ts +70 -0
  54. package/dist/renderer.js +204 -0
  55. package/dist/renderer.js.map +1 -0
  56. package/dist/templates/template.css +498 -0
  57. package/dist/templates/theme-paradigm.md +658 -0
  58. package/dist/templates/theme-template.hbs +370 -0
  59. package/dist/theme-store.d.ts +37 -0
  60. package/dist/theme-store.js +90 -0
  61. package/dist/theme-store.js.map +1 -0
  62. package/dist/validators.d.ts +26 -0
  63. package/dist/validators.js +144 -0
  64. package/dist/validators.js.map +1 -0
  65. package/package.json +36 -0
  66. package/skills/build-skills.sh +166 -0
  67. package/skills/colamd-themes/SKILL.md +148 -0
  68. package/src/cli.ts +200 -0
  69. package/src/color-systems.ts +289 -0
  70. package/src/color-utils.ts +120 -0
  71. package/src/contrast.ts +66 -0
  72. package/src/docx-style-paradigm.ts +811 -0
  73. package/src/export-cli.ts +301 -0
  74. package/src/extractors/base.ts +8 -0
  75. package/src/extractors/docx-extractor.ts +417 -0
  76. package/src/extractors/pdf-extractor.ts +527 -0
  77. package/src/extractors/url-extractor.ts +927 -0
  78. package/src/generator.ts +88 -0
  79. package/src/index.ts +29 -0
  80. package/src/mermaid-presets.ts +142 -0
  81. package/src/models.ts +212 -0
  82. package/src/renderer.ts +277 -0
  83. package/src/templates/template.css +498 -0
  84. package/src/templates/theme-paradigm.md +658 -0
  85. package/src/templates/theme-template.hbs +370 -0
  86. package/src/theme-store.ts +112 -0
  87. package/src/types/pdf-parse.d.ts +12 -0
  88. package/src/validators.ts +176 -0
  89. package/themes/.gitkeep +0 -0
  90. package/themes/academic-paper-pt.css +1805 -0
  91. package/themes/academic-paper.css +1099 -0
  92. package/themes/academic-style.css +2430 -0
  93. package/themes/colly.css +370 -0
  94. package/themes/elegant.css +671 -0
  95. package/themes/forest-ink.css +664 -0
  96. package/themes/guizang.css +732 -0
  97. package/themes/meeting-minutes.css +370 -0
  98. package/themes/pixso-design.css +1261 -0
  99. package/themes/salvator-blog.css +370 -0
  100. package/themes/swiss-design copy.css +746 -0
  101. package/themes/swiss-design.css +746 -0
  102. package/themes/template.css +498 -0
  103. package/themes/trae-blue.css +737 -0
  104. package/themes-preview/colly-preview.html +623 -0
  105. package/themes-preview/demo-content.md +187 -0
  106. package/themes-preview/meeting-minutes-preview.html +625 -0
  107. package/themes-preview/salvator-blog-preview.html +623 -0
  108. package/tsconfig.json +18 -0
@@ -0,0 +1,417 @@
1
+ /**
2
+ * DOCX 文档样式提取器 — v2.0
3
+ *
4
+ * 增强功能:
5
+ * 1. 提取各级标题(H1-H6)的完整字体样式定义
6
+ * 2. 提取正文字体、字号、颜色、行间距
7
+ * 3. 提取表格样式(表头/单元格字体、背景色、边框)
8
+ * 4. 对无法提取的样式应用 DocxStyleParadigm 标准参考值
9
+ * 5. 自动验证 WCAG 对比度合规性
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import { basename } from "node:path";
14
+ import mammoth from "mammoth";
15
+ import type { Extractor } from "./base.js";
16
+ import {
17
+ makeThemeStyle,
18
+ type ThemeStyle,
19
+ type ColorPalette,
20
+ type Typography,
21
+ } from "../models.js";
22
+ import { normalizeThemeStyle, hexToRgb, rgbToHex, lighten, darken } from "../color-utils.js";
23
+ import {
24
+ ACADEMIC_PAPER_DEFAULTS,
25
+ getRecommendedPreset,
26
+ validateDocumentStyle,
27
+ normalizeForPrinting,
28
+ type DocumentStyleParadigm,
29
+ type FontStyleDefinition,
30
+ type TableCellStyle,
31
+ } from "../docx-style-paradigm.js";
32
+
33
+ export class DocxExtractor implements Extractor {
34
+ canHandle(source: string): boolean {
35
+ return /\.docx$/i.test(source);
36
+ }
37
+
38
+ async extract(source: string): Promise<ThemeStyle> {
39
+ const buffer = readFileSync(source);
40
+ const name = basename(source).replace(/\.docx$/i, "");
41
+ const theme = makeThemeStyle(name, "docx", source);
42
+
43
+ const result = await mammoth.convertToHtml(
44
+ { buffer },
45
+ {
46
+ styleMap: [
47
+ "p[style-name='Heading 1'] => h1:fresh",
48
+ "p[style-name='Heading 2'] => h2:fresh",
49
+ "p[style-name='Heading 3'] => h3:fresh",
50
+ "p[style-name='Heading 4'] => h4:fresh",
51
+ "p[style-name='Heading 5'] => h5:fresh",
52
+ "p[style-name='Heading 6'] => h6:fresh",
53
+ "r[style-name='Strong'] => strong",
54
+ ],
55
+ }
56
+ );
57
+
58
+ if (result.value) {
59
+ this.extractDetailedStyles(theme, result.value);
60
+ }
61
+
62
+ this.applyParadigmDefaults(theme);
63
+ normalizeThemeStyle(theme);
64
+ return theme;
65
+ }
66
+
67
+ /**
68
+ * 从 HTML 输出中提取详细的文档元素样式
69
+ */
70
+ private extractDetailedStyles(theme: ThemeStyle, html: string): void {
71
+ const p = theme.palette;
72
+ const t = theme.typography;
73
+
74
+ this.extractBodyStyles(t, html);
75
+ this.extractHeadingStyles(theme, html);
76
+ this.extractTableStyles(p, html);
77
+ this.extractColorPalette(p, html);
78
+ this.extractBlockStyles(p, html);
79
+ }
80
+
81
+ /**
82
+ * 提取正文基础样式(字体、字号、行高)
83
+ */
84
+ private extractBodyStyles(typography: Typography, html: string): void {
85
+ const fontMatch = html.match(/font-family:\s*['"]?([^;'"]+)['"]?/i);
86
+ if (fontMatch) {
87
+ typography.bodyFontFamily = fontMatch[1].trim();
88
+ }
89
+
90
+ const fontSizeMatch = html.match(/font-size:\s*([\d.]+)(pt|px)/i);
91
+ if (fontSizeMatch) {
92
+ const size = parseFloat(fontSizeMatch[1]);
93
+ const unit = fontSizeMatch[2];
94
+ typography.baseFontSize = unit === "pt" ? `${size * 1.333}px` : `${size}px`;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 提取各级标题的详细样式
100
+ */
101
+ private extractHeadingStyles(theme: ThemeStyle, html: string): void {
102
+ const headingLevels = ["h1", "h2", "h3", "h4", "h5", "h6"];
103
+
104
+ for (const level of headingLevels) {
105
+ const regex = new RegExp(
106
+ `<${level}[^>]*style="[^"]*"`,
107
+ "gi"
108
+ );
109
+ let match = regex.exec(html);
110
+
111
+ if (match) {
112
+ const styleStr = match[0];
113
+ const extracted = this.parseInlineStyle(styleStr);
114
+
115
+ if (extracted.fontSize) {
116
+ const size = parseFloat(extracted.fontSize);
117
+ const unit = extracted.fontSize.replace(/[\d.]/g, "") || "px";
118
+ const pxSize = unit === "pt" ? size * 1.333 : size;
119
+
120
+ switch (level) {
121
+ case "h1": theme.seedTypography.fontScaleH1 = pxSize / 16; break;
122
+ case "h2": theme.seedTypography.fontScaleH2 = pxSize / 16; break;
123
+ case "h3": theme.seedTypography.fontScaleH3 = pxSize / 16; break;
124
+ case "h4": theme.seedTypography.fontScaleH4 = pxSize / 16; break;
125
+ case "h5": theme.seedTypography.fontScaleH5 = pxSize / 16; break;
126
+ case "h6": theme.seedTypography.fontScaleH6 = pxSize / 16; break;
127
+ }
128
+ }
129
+
130
+ if (extracted.fontWeight) {
131
+ const weight = parseInt(extracted.fontWeight) || 700;
132
+ if (weight > theme.typography.headingWeight) {
133
+ theme.typography.headingWeight = weight;
134
+ }
135
+ }
136
+
137
+ if (extracted.color && level === "h1") {
138
+ theme.palette.headingColor = extracted.color;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 提取表格详细样式(表头、单元格、边框)
146
+ */
147
+ private extractTableStyles(palette: ColorPalette, html: string): void {
148
+ const thRegex = /<th[^>]*style="([^"]*)"/gi;
149
+ let thMatch = thRegex.exec(html);
150
+
151
+ if (thMatch) {
152
+ const thStyle = this.parseInlineStyle(thMatch[0]);
153
+ if (thStyle.backgroundColor) {
154
+ palette.tableHeaderBg = thStyle.backgroundColor;
155
+ }
156
+ if (thStyle.color) {
157
+ palette.headingColor = palette.headingColor || thStyle.color;
158
+ }
159
+ if (thStyle.fontSize) {
160
+ const size = parseFloat(thStyle.fontSize);
161
+ palette.tableHeaderBg = palette.tableHeaderBg || "#f0f0f0";
162
+ }
163
+ }
164
+
165
+ const tdRegex = /<td[^>]*style="([^"]*)"/gi;
166
+ let tdMatch = tdRegex.exec(html);
167
+ if (tdMatch) {
168
+ const tdStyle = this.parseInlineStyle(tdMatch[0]);
169
+ if (tdStyle.backgroundColor) {
170
+ palette.codeBg = tdStyle.backgroundColor;
171
+ }
172
+ }
173
+
174
+ const tableBorderMatch = html.match(/<table[^>]*style="[^"]*border[^;]*:[^;]*([^;"'\s]+)/i);
175
+ if (tableBorderMatch) {
176
+ palette.borderColor = palette.borderColor || "#cccccc";
177
+ }
178
+
179
+ if (!palette.tableHeaderBg) {
180
+ palette.tableHeaderBg = "#f0f0f0";
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 提取全局色彩方案
186
+ */
187
+ private extractColorPalette(palette: ColorPalette, html: string): void {
188
+ const colorMatch = html.match(/color:\s*(#[0-9a-fA-F]{3,6})/i);
189
+ if (colorMatch) {
190
+ palette.textColor = colorMatch[1];
191
+ }
192
+
193
+ const bgMatch = html.match(/background(?:-color)?:\s*(#[0-9a-fA-F]{3,6})/i);
194
+ if (bgMatch && bgMatch[1].toLowerCase() !== "#ffffff") {
195
+ palette.bgColor = bgMatch[1];
196
+ }
197
+
198
+ const strongMatch = html.match(/<strong[^>]*style="[^"]*color:\s*(#[0-9a-fA-F]{3,6})/i);
199
+ if (strongMatch) {
200
+ palette.accentColor = strongMatch[1];
201
+ }
202
+
203
+ const linkMatch = html.match(/<a[^>]*style="[^"]*color:\s*(#[0-9a-fA-F]{3,6})/i);
204
+ if (linkMatch) {
205
+ palette.accentColor = linkMatch[1];
206
+ }
207
+ }
208
+
209
+ /**
210
+ * 提取引用块和代码块样式
211
+ */
212
+ private extractBlockStyles(palette: ColorPalette, html: string): void {
213
+ const preMatch = html.match(/<pre[^>]*style="[^"]*background(?:-color)?:\s*(#[0-9a-fA-F]{3,6})/i);
214
+ if (preMatch) {
215
+ palette.codeBlockBg = preMatch[1];
216
+ }
217
+
218
+ const preColorMatch = html.match(/<pre[^>]*style="[^"]*color:\s*(#[0-9a-fA-F]{3,6})/i);
219
+ if (preColorMatch) {
220
+ palette.codeBlockText = preColorMatch[1];
221
+ }
222
+
223
+ if (!palette.blockquoteBorder) {
224
+ palette.blockquoteBorder = palette.accentColor;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * 解析内联 style 字符串为键值对对象
230
+ */
231
+ private parseInlineStyle(styleTag: string): Record<string, string> {
232
+ const styleMatch = styleTag.match(/style="([^"]*)"/i);
233
+ if (!styleMatch) return {};
234
+
235
+ const styles: Record<string, string> = {};
236
+ const declarations = styleMatch[1].split(";");
237
+
238
+ for (const decl of declarations) {
239
+ const [prop, ...valueParts] = decl.split(":").map((s) => s.trim());
240
+ if (prop && valueParts.length > 0) {
241
+ const camelProp = prop.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
242
+ styles[camelProp] = valueParts.join(":").trim();
243
+ }
244
+ }
245
+
246
+ return styles;
247
+ }
248
+
249
+ /**
250
+ * 应用 DocxStyleParadigm 标准参考值填充缺失字段
251
+ */
252
+ private applyParadigmDefaults(theme: ThemeStyle): void {
253
+ const preset = getRecommendedPreset();
254
+ const p = theme.palette;
255
+ const t = theme.typography;
256
+ const st = theme.seedTypography;
257
+
258
+ if (!p.textColor || p.textColor === "#000000") {
259
+ p.textColor = preset.body.color;
260
+ }
261
+ if (!p.headingColor) {
262
+ p.headingColor = preset.headings.h1.color;
263
+ }
264
+ if (!p.bgColor || p.bgColor === "#ffffff") {
265
+ p.bgColor = preset.pageBackground;
266
+ }
267
+ if (!p.textMuted) {
268
+ p.textMuted = preset.mutedTextColor;
269
+ }
270
+ if (!p.accentColor || p.accentColor === "#0000ff") {
271
+ p.accentColor = preset.accentColor;
272
+ }
273
+ if (!p.tableHeaderBg) {
274
+ p.tableHeaderBg = preset.table.headerBgColor;
275
+ }
276
+ if (!p.codeBg) {
277
+ p.codeBg = preset.inlineCode.bgColor;
278
+ }
279
+ if (!p.codeBlockBg) {
280
+ p.codeBlockBg = preset.codeBlock.bgColor;
281
+ }
282
+ if (!p.blockquoteBg) {
283
+ p.blockquoteBg = preset.blockquote.bgColor;
284
+ }
285
+ if (!p.selectionBg) {
286
+ p.selectionBg = preset.selectionBg;
287
+ }
288
+
289
+ if (!t.bodyFontFamily || t.bodyFontFamily === "Georgia, serif") {
290
+ t.bodyFontFamily = preset.body.fontFamily;
291
+ }
292
+ if (!t.headingFontFamily) {
293
+ t.headingFontFamily = preset.headings.h1.fontFamily;
294
+ }
295
+ if (!t.baseFontSize) {
296
+ t.baseFontSize = `${preset.body.fontSize}px`;
297
+ }
298
+ if (t.baseLineHeight < 1.5) {
299
+ t.baseLineHeight = preset.body.lineHeight;
300
+ }
301
+
302
+ st.fontBody = preset.body.fontFamily;
303
+ st.fontHeading = preset.headings.h1.fontFamily;
304
+ st.lineHeightBody = preset.body.lineHeight;
305
+ st.lineHeightHeading = preset.headings.h1.lineHeight;
306
+
307
+ const issues = validateDocumentStyle(this.themeToParadigm(theme));
308
+ for (const issue of issues) {
309
+ if (issue.level === "error") {
310
+ console.warn(`[DocxExtractor] ${issue.element}: ${issue.message}`);
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * 将 ThemeStyle 转换为 DocumentStyleParadigm 用于验证
317
+ */
318
+ private themeToParadigm(theme: ThemeStyle): DocumentStyleParadigm {
319
+ return {
320
+ pageBackground: theme.palette.bgColor,
321
+ headings: {
322
+ h1: {
323
+ fontFamily: theme.seedTypography.fontHeading,
324
+ fontSize: 16 * theme.seedTypography.fontScaleH1,
325
+ fontWeight: theme.typography.headingWeight,
326
+ color: theme.palette.headingColor,
327
+ lineHeight: theme.seedTypography.lineHeightHeading,
328
+ },
329
+ h2: {
330
+ fontFamily: theme.seedTypography.fontHeading,
331
+ fontSize: 16 * theme.seedTypography.fontScaleH2,
332
+ fontWeight: theme.typography.headingWeight - 100 || 600,
333
+ color: theme.palette.headingColor,
334
+ lineHeight: theme.seedTypography.lineHeightHeading,
335
+ },
336
+ h3: {
337
+ fontFamily: theme.seedTypography.fontHeading,
338
+ fontSize: 16 * theme.seedTypography.fontScaleH3,
339
+ fontWeight: 600,
340
+ color: theme.palette.headingColor,
341
+ lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
342
+ },
343
+ h4: {
344
+ fontFamily: theme.seedTypography.fontHeading,
345
+ fontSize: 16 * theme.seedTypography.fontScaleH4,
346
+ fontWeight: 600,
347
+ color: theme.palette.textMuted,
348
+ lineHeight: theme.seedTypography.lineHeightHeading + 0.05,
349
+ },
350
+ h5: {
351
+ fontFamily: theme.seedTypography.fontHeading,
352
+ fontSize: 16 * theme.seedTypography.fontScaleH5,
353
+ fontWeight: 500,
354
+ color: theme.palette.textMuted,
355
+ lineHeight: theme.seedTypography.lineHeightBody - 0.25,
356
+ },
357
+ h6: {
358
+ fontFamily: theme.seedTypography.fontHeading,
359
+ fontSize: 16 * theme.seedTypography.fontScaleH6,
360
+ fontWeight: 500,
361
+ color: theme.palette.textMuted,
362
+ lineHeight: theme.seedTypography.lineHeightBody - 0.2,
363
+ },
364
+ },
365
+ body: {
366
+ fontFamily: theme.seedTypography.fontBody,
367
+ fontSize: parseInt(theme.typography.baseFontSize) || 16,
368
+ fontWeight: 400,
369
+ color: theme.palette.textColor,
370
+ lineHeight: theme.typography.baseLineHeight,
371
+ },
372
+ blockquote: {
373
+ fontFamily: theme.seedTypography.fontBody,
374
+ fontSize: Math.max(13, (parseInt(theme.typography.baseFontSize) || 16) - 2),
375
+ fontWeight: 400,
376
+ color: theme.palette.textMuted,
377
+ lineHeight: theme.typography.baseLineHeight - 0.15,
378
+ borderLeftColor: theme.palette.blockquoteBorder,
379
+ bgColor: theme.palette.blockquoteBg,
380
+ },
381
+ codeBlock: {
382
+ fontFamily: theme.typography.codeFontFamily,
383
+ fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
384
+ fontWeight: 400,
385
+ color: theme.palette.codeBlockText || theme.palette.textColor,
386
+ lineHeight: theme.seedTypography.lineHeightCode,
387
+ bgColor: theme.palette.codeBlockBg,
388
+ },
389
+ inlineCode: {
390
+ fontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
391
+ color: theme.palette.accentColor,
392
+ bgColor: theme.palette.codeBg,
393
+ },
394
+ table: {
395
+ headerFontFamily: theme.seedTypography.fontHeading,
396
+ headerFontSize: Math.max(12, (parseInt(theme.typography.baseFontSize) || 16) - 3),
397
+ headerFontWeight: 700,
398
+ headerColor: "#ffffff",
399
+ headerBgColor: theme.palette.tableHeaderBg,
400
+ headerLineHeight: 1.4,
401
+
402
+ cellFontFamily: theme.seedTypography.fontBody,
403
+ cellFontSize: Math.max(11, (parseInt(theme.typography.baseFontSize) || 16) - 4),
404
+ cellColor: theme.palette.textColor,
405
+ cellBgColor: theme.palette.bgColor,
406
+ cellLineHeight: 1.5,
407
+ cellPadding: "8px 12px",
408
+ borderColor: theme.palette.borderColor,
409
+ borderWidth: "1px",
410
+ },
411
+
412
+ accentColor: theme.palette.accentColor,
413
+ mutedTextColor: theme.palette.textMuted,
414
+ selectionBg: theme.palette.selectionBg,
415
+ };
416
+ }
417
+ }