@courtifyai/docx-render 1.0.0 → 1.0.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.
@@ -1,172 +0,0 @@
1
- /**
2
- * 主题解析器
3
- * 解析 DOCX 主题 XML(word/theme/theme1.xml)
4
- */
5
-
6
- import { ITheme, IColorScheme, IFontScheme, IFontInfo, IThemeColors } from '../types'
7
-
8
- /**
9
- * 解析主题 XML
10
- */
11
- export function parseTheme(root: Element): ITheme {
12
- const theme: ITheme = {
13
- colorScheme: { name: '', colors: {} },
14
- fontScheme: { name: '', majorFont: {}, minorFont: {} },
15
- }
16
-
17
- // 查找 a:themeElements
18
- const themeElements = findElement(root, 'themeElements')
19
- if (!themeElements) return theme
20
-
21
- // 遍历主题元素
22
- for (const el of getChildElements(themeElements)) {
23
- const localName = el.localName
24
-
25
- if (localName === 'clrScheme') {
26
- theme.colorScheme = parseColorScheme(el)
27
- } else if (localName === 'fontScheme') {
28
- theme.fontScheme = parseFontScheme(el)
29
- }
30
- }
31
-
32
- return theme
33
- }
34
-
35
- /**
36
- * 解析颜色方案
37
- * <a:clrScheme name="Office">
38
- * <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
39
- * <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
40
- * <a:accent1><a:srgbClr val="4472C4"/></a:accent1>
41
- * ...
42
- * </a:clrScheme>
43
- */
44
- export function parseColorScheme(el: Element): IColorScheme {
45
- const result: IColorScheme = {
46
- name: el.getAttribute('name') || '',
47
- colors: {},
48
- }
49
-
50
- // 遍历每个颜色定义
51
- for (const colorEl of getChildElements(el)) {
52
- const colorName = colorEl.localName
53
- const colorValue = extractColorValue(colorEl)
54
-
55
- if (colorValue) {
56
- result.colors[colorName as keyof IThemeColors] = colorValue
57
- }
58
- }
59
-
60
- return result
61
- }
62
-
63
- /**
64
- * 提取颜色值
65
- * 支持 srgbClr 和 sysClr 两种格式
66
- */
67
- function extractColorValue(colorEl: Element): string | null {
68
- // 尝试 srgbClr
69
- const srgbClr = findElement(colorEl, 'srgbClr')
70
- if (srgbClr) {
71
- const val = srgbClr.getAttribute('val')
72
- return val ? `#${val}` : null
73
- }
74
-
75
- // 尝试 sysClr(系统颜色,取 lastClr 属性)
76
- const sysClr = findElement(colorEl, 'sysClr')
77
- if (sysClr) {
78
- const lastClr = sysClr.getAttribute('lastClr')
79
- return lastClr ? `#${lastClr}` : null
80
- }
81
-
82
- return null
83
- }
84
-
85
- /**
86
- * 解析字体方案
87
- * <a:fontScheme name="Office">
88
- * <a:majorFont>
89
- * <a:latin typeface="Calibri Light"/>
90
- * <a:ea typeface=""/>
91
- * <a:cs typeface=""/>
92
- * </a:majorFont>
93
- * <a:minorFont>
94
- * <a:latin typeface="Calibri"/>
95
- * <a:ea typeface=""/>
96
- * <a:cs typeface=""/>
97
- * </a:minorFont>
98
- * </a:fontScheme>
99
- */
100
- export function parseFontScheme(el: Element): IFontScheme {
101
- const result: IFontScheme = {
102
- name: el.getAttribute('name') || '',
103
- majorFont: {},
104
- minorFont: {},
105
- }
106
-
107
- for (const fontEl of getChildElements(el)) {
108
- const localName = fontEl.localName
109
-
110
- if (localName === 'majorFont') {
111
- result.majorFont = parseFontInfo(fontEl)
112
- } else if (localName === 'minorFont') {
113
- result.minorFont = parseFontInfo(fontEl)
114
- }
115
- }
116
-
117
- return result
118
- }
119
-
120
- /**
121
- * 解析字体信息
122
- */
123
- function parseFontInfo(el: Element): IFontInfo {
124
- const result: IFontInfo = {}
125
-
126
- for (const child of getChildElements(el)) {
127
- const localName = child.localName
128
- const typeface = child.getAttribute('typeface')
129
-
130
- if (typeface) {
131
- if (localName === 'latin') {
132
- result.latin = typeface
133
- } else if (localName === 'ea') {
134
- result.ea = typeface
135
- } else if (localName === 'cs') {
136
- result.cs = typeface
137
- }
138
- }
139
- }
140
-
141
- return result
142
- }
143
-
144
- /**
145
- * 查找子元素(忽略命名空间)
146
- */
147
- function findElement(parent: Element, localName: string): Element | null {
148
- for (let i = 0; i < parent.childNodes.length; i++) {
149
- const node = parent.childNodes[i]
150
- if (node.nodeType === Node.ELEMENT_NODE) {
151
- const el = node as Element
152
- if (el.localName === localName) {
153
- return el
154
- }
155
- }
156
- }
157
- return null
158
- }
159
-
160
- /**
161
- * 获取所有子元素
162
- */
163
- function getChildElements(parent: Element): Element[] {
164
- const result: Element[] = []
165
- for (let i = 0; i < parent.childNodes.length; i++) {
166
- const node = parent.childNodes[i]
167
- if (node.nodeType === Node.ELEMENT_NODE) {
168
- result.push(node as Element)
169
- }
170
- }
171
- return result
172
- }
@@ -1,148 +0,0 @@
1
- /**
2
- * 主题颜色工具函数
3
- * 提供主题颜色解析和转换能力
4
- */
5
-
6
- import { ITheme, IThemeColorRef, IThemeColors } from '../types'
7
-
8
- /**
9
- * 主题颜色名称到标准名称的映射
10
- * Word 使用的属性名和 theme.xml 中的元素名可能不同
11
- */
12
- const THEME_COLOR_MAP: Record<string, keyof IThemeColors> = {
13
- // 标准映射
14
- dark1: 'dk1',
15
- dark2: 'dk2',
16
- light1: 'lt1',
17
- light2: 'lt2',
18
- accent1: 'accent1',
19
- accent2: 'accent2',
20
- accent3: 'accent3',
21
- accent4: 'accent4',
22
- accent5: 'accent5',
23
- accent6: 'accent6',
24
- hyperlink: 'hlink',
25
- followedHyperlink: 'folHlink',
26
- // 直接映射(XML 中的原始名称)
27
- dk1: 'dk1',
28
- dk2: 'dk2',
29
- lt1: 'lt1',
30
- lt2: 'lt2',
31
- hlink: 'hlink',
32
- folHlink: 'folHlink',
33
- // 文本颜色别名
34
- text1: 'dk1',
35
- text2: 'dk2',
36
- background1: 'lt1',
37
- background2: 'lt2',
38
- }
39
-
40
- /**
41
- * 解析主题颜色引用,返回实际颜色值
42
- *
43
- * @param theme - 主题对象
44
- * @param colorRef - 主题颜色引用
45
- * @returns 解析后的颜色值(如 #RRGGBB)或 undefined
46
- */
47
- export function resolveThemeColor(
48
- theme: ITheme | undefined,
49
- colorRef: IThemeColorRef
50
- ): string | undefined {
51
- if (!theme?.colorScheme?.colors) return undefined
52
-
53
- const themeColorName = colorRef.themeColor
54
- const mappedName = THEME_COLOR_MAP[themeColorName] || themeColorName
55
- const baseColor = theme.colorScheme.colors[mappedName]
56
-
57
- if (!baseColor) return undefined
58
-
59
- // 如果有 tint 或 shade,需要调整颜色
60
- if (colorRef.themeTint !== undefined || colorRef.themeShade !== undefined) {
61
- return applyTintShade(baseColor, colorRef.themeTint, colorRef.themeShade)
62
- }
63
-
64
- return baseColor
65
- }
66
-
67
- /**
68
- * 应用色调(tint)和阴影(shade)调整
69
- *
70
- * tint: 向白色混合(0 = 原色,255 = 纯白)
71
- * shade: 向黑色混合(0 = 原色,255 = 纯黑)
72
- *
73
- * @param color - 基础颜色(#RRGGBB 格式)
74
- * @param tint - 色调值(0-255)
75
- * @param shade - 阴影值(0-255)
76
- * @returns 调整后的颜色
77
- */
78
- export function applyTintShade(
79
- color: string,
80
- tint?: number,
81
- shade?: number
82
- ): string {
83
- // 解析颜色
84
- const hex = color.replace('#', '')
85
- const r = parseInt(hex.substr(0, 2), 16)
86
- const g = parseInt(hex.substr(2, 2), 16)
87
- const b = parseInt(hex.substr(4, 2), 16)
88
-
89
- let finalR = r
90
- let finalG = g
91
- let finalB = b
92
-
93
- // 应用 tint(向白色混合)
94
- // OOXML 规范: result = color + (255 - color) * tint / 255
95
- if (tint !== undefined && tint > 0) {
96
- const tintFactor = tint / 255
97
- finalR = Math.round(r + (255 - r) * tintFactor)
98
- finalG = Math.round(g + (255 - g) * tintFactor)
99
- finalB = Math.round(b + (255 - b) * tintFactor)
100
- }
101
-
102
- // 应用 shade(向黑色混合)
103
- // OOXML 规范: result = color * (1 - shade / 255)
104
- if (shade !== undefined && shade > 0) {
105
- const shadeFactor = 1 - shade / 255
106
- finalR = Math.round(finalR * shadeFactor)
107
- finalG = Math.round(finalG * shadeFactor)
108
- finalB = Math.round(finalB * shadeFactor)
109
- }
110
-
111
- // 确保在有效范围内
112
- finalR = Math.max(0, Math.min(255, finalR))
113
- finalG = Math.max(0, Math.min(255, finalG))
114
- finalB = Math.max(0, Math.min(255, finalB))
115
-
116
- // 转回 hex
117
- return `#${toHex(finalR)}${toHex(finalG)}${toHex(finalB)}`
118
- }
119
-
120
- /**
121
- * 数字转两位十六进制
122
- */
123
- function toHex(n: number): string {
124
- const hex = n.toString(16)
125
- return hex.length === 1 ? '0' + hex : hex
126
- }
127
-
128
- /**
129
- * 获取主题字体
130
- *
131
- * @param theme - 主题对象
132
- * @param fontType - 字体类型(major/minor)
133
- * @param script - 文字类型(latin/ea/cs)
134
- * @returns 字体名称或 undefined
135
- */
136
- export function resolveThemeFont(
137
- theme: ITheme | undefined,
138
- fontType: 'major' | 'minor',
139
- script: 'latin' | 'ea' | 'cs' = 'latin'
140
- ): string | undefined {
141
- if (!theme?.fontScheme) return undefined
142
-
143
- const fontInfo = fontType === 'major'
144
- ? theme.fontScheme.majorFont
145
- : theme.fontScheme.minorFont
146
-
147
- return fontInfo[script]
148
- }