styimat 1.5.0 → 1.6.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 (4) hide show
  1. package/README.md +28 -7
  2. package/package.json +1 -1
  3. package/styimat.js +1436 -1429
  4. package/styimat.min.js +126 -126
package/styimat.js CHANGED
@@ -11,1530 +11,1537 @@
11
11
  * 增强 math() 函数,支持复杂数学计算
12
12
  */
13
13
  (function(root, factory) {
14
- if (typeof define === 'function' && define.amd) {
15
- // AMD 支持 (RequireJS)
16
- define([], factory);
17
- } else if (typeof module === 'object' && module.exports) {
18
- // CommonJS 支持 (Node.js, Browserify, Webpack)
19
- module.exports = factory();
20
- } else {
21
- // 浏览器全局变量
22
- root.styimat = factory();
23
- }
14
+ if (typeof define === 'function' && define.amd) {
15
+ // AMD 支持 (RequireJS)
16
+ define([], factory);
17
+ } else if (typeof module === 'object' && module.exports) {
18
+ // CommonJS 支持 (Node.js, Browserify, Webpack)
19
+ module.exports = factory();
20
+ } else {
21
+ // 浏览器全局变量
22
+ root.styimat = factory();
23
+ }
24
24
  }(typeof self !== 'undefined' ? self : this, function() {
25
- // 默认配置
26
- let defaultConfig = {
27
- rootSelector: ':root',
28
- variablePrefix: '--',
29
- preserveOriginal: false,
30
- indentSize: 2,
31
- enableNesting: true,
32
- autoProcessStyleTags: true,
33
- styleTagAttribute: 'e',
34
- convertLabToRGB: true,
35
- convertLchToRGB: true,
36
- enableP3: true,
37
- enableMath: true, // 启用math()函数增强
38
- mathPrecision: 6, // 数学计算精度
39
- };
25
+ // 默认配置
26
+ let defaultConfig = {
27
+ rootSelector: ':root',
28
+ variablePrefix: '--',
29
+ preserveOriginal: false,
30
+ indentSize: 2,
31
+ enableNesting: true,
32
+ autoProcessStyleTags: true,
33
+ styleTagAttribute: 'e',
34
+ convertLabToRGB: true,
35
+ convertLchToRGB: true,
36
+ enableP3: true,
37
+ enableMath: true, // 启用math()函数增强
38
+ mathPrecision: 6, // 数学计算精度
39
+ };
40
40
 
41
- // 全局P3支持检测结果
42
- let p3Supported = null;
41
+ // 全局P3支持检测结果
42
+ let p3Supported = null;
43
43
 
44
- /**
45
- * 检测浏览器是否支持 Display P3
46
- * @returns {boolean} 是否支持P3
47
- */
48
- function detectP3Support() {
49
- if (p3Supported !== null) return p3Supported;
50
-
51
- if (typeof window === 'undefined' || !window.CSS) {
52
- p3Supported = false;
53
- return false;
54
- }
55
-
56
- try {
57
- p3Supported = CSS.supports('color', 'color(display-p3 1 0 0)');
58
- } catch (error) {
59
- console.warn('P3支持检测失败:', error);
60
- p3Supported = false;
44
+ /**
45
+ * 检测浏览器是否支持 Display P3
46
+ * @returns {boolean} 是否支持P3
47
+ */
48
+ function detectP3Support() {
49
+ if (p3Supported !== null) return p3Supported;
50
+
51
+ if (typeof window === 'undefined' || !window.CSS) {
52
+ p3Supported = false;
53
+ return false;
54
+ }
55
+
56
+ try {
57
+ p3Supported = CSS.supports('color', 'color(display-p3 1 0 0)');
58
+ } catch (error) {
59
+ console.warn('P3支持检测失败:', error);
60
+ p3Supported = false;
61
+ }
62
+
63
+ return p3Supported;
61
64
  }
62
-
63
- return p3Supported;
64
- }
65
65
 
66
- /**
67
- * 解析一行CSS(可能多个语句在同一行,可能最后一个语句没有分号)
68
- * 返回格式:[{属性名:值}, {属性名:值}, ...]
69
- * @param {string} cssString - 要解析的CSS字符串
70
- * @returns {Array} 包含属性名值对象的数组
71
- */
72
- function parseSingleLineCSS(cssString) {
73
- // 移除首尾空白字符
74
- let str = cssString.trim();
75
-
76
- // 如果以分号结尾,移除最后一个分号
77
- if (str.endsWith(';')) {
78
- str = str.slice(0, -1);
79
- }
80
-
81
- // 如果字符串为空,返回空数组
82
- if (!str) {
83
- return [];
84
- }
85
-
86
- // 分割多个CSS声明(以分号分隔)
87
- const declarations = [];
88
- let currentDeclaration = '';
89
- let inParens = 0; // 记录括号嵌套层数,用于处理函数内的分号
90
- let inQuotes = false; // 是否在引号内
91
- let quoteChar = ''; // 当前引号字符
92
-
93
- for (let i = 0; i < str.length; i++) {
94
- const char = str[i];
95
-
96
- // 处理引号
97
- if ((char === '"' || char === "'") && !inQuotes) {
98
- inQuotes = true;
99
- quoteChar = char;
100
- } else if (char === quoteChar && inQuotes) {
101
- inQuotes = false;
102
- quoteChar = '';
103
- }
104
-
105
- // 处理括号(不在引号内时)
106
- if (!inQuotes) {
107
- if (char === '(') {
108
- inParens++;
109
- } else if (char === ')') {
110
- inParens--;
66
+ /**
67
+ * 解析一行CSS(可能多个语句在同一行,可能最后一个语句没有分号)
68
+ * 返回格式:[{属性名:值}, {属性名:值}, ...]
69
+ * @param {string} cssString - 要解析的CSS字符串
70
+ * @returns {Array} 包含属性名值对象的数组
71
+ */
72
+ function parseSingleLineCSS(cssString) {
73
+ // 移除首尾空白字符
74
+ let str = cssString.trim();
75
+
76
+ // 如果以分号结尾,移除最后一个分号
77
+ if (str.endsWith(';')) {
78
+ str = str.slice(0, -1);
79
+ }
80
+
81
+ // 如果字符串为空,返回空数组
82
+ if (!str) {
83
+ return [];
84
+ }
85
+
86
+ // 分割多个CSS声明(以分号分隔)
87
+ const declarations = [];
88
+ let currentDeclaration = '';
89
+ let inParens = 0; // 记录括号嵌套层数,用于处理函数内的分号
90
+ let inQuotes = false; // 是否在引号内
91
+ let quoteChar = ''; // 当前引号字符
92
+
93
+ for (let i = 0; i < str.length; i++) {
94
+ const char = str[i];
95
+
96
+ // 处理引号
97
+ if ((char === '"' || char === "'") && !inQuotes) {
98
+ inQuotes = true;
99
+ quoteChar = char;
100
+ } else if (char === quoteChar && inQuotes) {
101
+ inQuotes = false;
102
+ quoteChar = '';
103
+ }
104
+
105
+ // 处理括号(不在引号内时)
106
+ if (!inQuotes) {
107
+ if (char === '(') {
108
+ inParens++;
109
+ } else if (char === ')') {
110
+ inParens--;
111
+ }
112
+ }
113
+
114
+ // 如果遇到分号且不在括号和引号内,则分割声明
115
+ if (char === ';' && !inQuotes && inParens === 0) {
116
+ if (currentDeclaration.trim()) {
117
+ declarations.push(currentDeclaration.trim());
118
+ currentDeclaration = '';
119
+ }
120
+ } else {
121
+ currentDeclaration += char;
122
+ }
111
123
  }
112
- }
113
-
114
- // 如果遇到分号且不在括号和引号内,则分割声明
115
- if (char === ';' && !inQuotes && inParens === 0) {
124
+
125
+ // 添加最后一个声明(如果没有分号结尾)
116
126
  if (currentDeclaration.trim()) {
117
- declarations.push(currentDeclaration.trim());
118
- currentDeclaration = '';
127
+ declarations.push(currentDeclaration.trim());
119
128
  }
120
- } else {
121
- currentDeclaration += char;
122
- }
123
- }
124
-
125
- // 添加最后一个声明(如果没有分号结尾)
126
- if (currentDeclaration.trim()) {
127
- declarations.push(currentDeclaration.trim());
128
- }
129
-
130
- // 解析每个声明为对象
131
- const result = [];
132
-
133
- for (const declaration of declarations) {
134
- // 跳过空声明
135
- if (!declaration.trim()) continue;
136
-
137
- // 查找第一个冒号的位置
138
- const colonIndex = findFirstColonOutsideQuotes(declaration);
139
-
140
- if (colonIndex === -1) {
141
- console.warn(`无效的CSS声明: "${declaration}"`);
142
- continue;
143
- }
144
-
145
- // 分割属性名和值
146
- const property = declaration.substring(0, colonIndex).trim();
147
- let value = declaration.substring(colonIndex + 1).trim();
148
-
149
- // 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
150
- if (value.endsWith(';')) {
151
- value = value.slice(0, -1).trim();
152
- }
153
-
154
- // 添加到结果数组
155
- result.push({ [property]: value });
129
+
130
+ // 解析每个声明为对象
131
+ const result = [];
132
+
133
+ for (const declaration of declarations) {
134
+ // 跳过空声明
135
+ if (!declaration.trim()) continue;
136
+
137
+ // 查找第一个冒号的位置
138
+ const colonIndex = findFirstColonOutsideQuotes(declaration);
139
+
140
+ if (colonIndex === -1) {
141
+ console.warn(`无效的CSS声明: "${declaration}"`);
142
+ continue;
143
+ }
144
+
145
+ // 分割属性名和值
146
+ const property = declaration.substring(0, colonIndex).trim();
147
+ let value = declaration.substring(colonIndex + 1).trim();
148
+
149
+ // 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
150
+ if (value.endsWith(';')) {
151
+ value = value.slice(0, -1).trim();
152
+ }
153
+
154
+ // 添加到结果数组
155
+ result.push({ [property]: value });
156
+ }
157
+
158
+ return result;
156
159
  }
157
-
158
- return result;
159
- }
160
160
 
161
- /**
162
- * 查找不在引号内的第一个冒号位置
163
- * @param {string} str
164
- * @returns {number}
165
- */
166
- function findFirstColonOutsideQuotes(str) {
167
- let inQuotes = false;
168
- let quoteChar = '';
169
-
170
- for (let i = 0; i < str.length; i++) {
171
- const char = str[i];
172
-
173
- // 处理引号
174
- if ((char === '"' || char === "'") && !inQuotes) {
175
- inQuotes = true;
176
- quoteChar = char;
177
- } else if (char === quoteChar && inQuotes) {
178
- inQuotes = false;
179
- quoteChar = '';
180
- }
181
-
182
- // 如果找到冒号且不在引号内,返回位置
183
- if (char === ':' && !inQuotes) {
184
- return i;
185
- }
161
+ /**
162
+ * 查找不在引号内的第一个冒号位置
163
+ * @param {string} str
164
+ * @returns {number}
165
+ */
166
+ function findFirstColonOutsideQuotes(str) {
167
+ let inQuotes = false;
168
+ let quoteChar = '';
169
+
170
+ for (let i = 0; i < str.length; i++) {
171
+ const char = str[i];
172
+
173
+ // 处理引号
174
+ if ((char === '"' || char === "'") && !inQuotes) {
175
+ inQuotes = true;
176
+ quoteChar = char;
177
+ } else if (char === quoteChar && inQuotes) {
178
+ inQuotes = false;
179
+ quoteChar = '';
180
+ }
181
+
182
+ // 如果找到冒号且不在引号内,返回位置
183
+ if (char === ':' && !inQuotes) {
184
+ return i;
185
+ }
186
+ }
187
+
188
+ return -1;
186
189
  }
187
-
188
- return -1;
189
- }
190
190
 
191
- /**
192
- * 增强的数学表达式解析和计算
193
- * @param {string} expression - 数学表达式
194
- * @param {Object} config - 配置对象
195
- * @returns {string} 计算结果
196
- */
197
- function evaluateMathExpression(expression, config) {
198
- // 如果禁用math()增强,则返回原始表达式
199
- if (!config.enableMath) {
200
- return `math(${expression})`;
201
- }
202
-
203
- try {
204
- // 清理表达式:移除空白字符
205
- let cleanExpr = expression.replace(/\s+/g, '');
206
-
207
- // 解析数学表达式
208
- const result = parseMathExpression(cleanExpr, config);
209
-
210
- // 如果结果包含单位,保留原始格式
211
- if (hasUnits(cleanExpr)) {
212
- // 处理带单位的表达式
213
- return processUnitExpression(cleanExpr, result);
214
- } else {
215
- // 纯数字表达式,直接计算结果
216
- return roundNumber(result, config.mathPrecision);
217
- }
218
- } catch (error) {
219
- console.warn('math()表达式解析失败:', expression, error);
220
- return `math(${expression})`;
191
+ /**
192
+ * 增强的数学表达式解析和计算
193
+ * @param {string} expression - 数学表达式
194
+ * @param {Object} config - 配置对象
195
+ * @returns {string} 计算结果
196
+ */
197
+ function evaluateMathExpression(expression, config) {
198
+ // 如果禁用math()增强,则返回原始表达式
199
+ if (!config.enableMath) {
200
+ return `math(${expression})`;
201
+ }
202
+
203
+ try {
204
+ // 清理表达式:移除空白字符
205
+ let cleanExpr = expression.replace(/\s+/g, '');
206
+
207
+ // 解析数学表达式
208
+ const result = parseMathExpression(cleanExpr, config);
209
+
210
+ // 如果结果包含单位,保留原始格式
211
+ if (hasUnits(cleanExpr)) {
212
+ // 处理带单位的表达式
213
+ return processUnitExpression(cleanExpr, result);
214
+ } else {
215
+ // 纯数字表达式,直接计算结果
216
+ return roundNumber(result, config.mathPrecision);
217
+ }
218
+ } catch (error) {
219
+ console.warn('math()表达式解析失败:', expression, error);
220
+ return `math(${expression})`;
221
+ }
221
222
  }
222
- }
223
223
 
224
- /**
225
- * 解析数学表达式
226
- * @param {string} expr - 清理后的表达式
227
- * @param {Object} config - 配置对象
228
- * @returns {number} 计算结果
229
- */
230
- function parseMathExpression(expr, config) {
231
- // 处理括号
232
- while (expr.includes('(') && expr.includes(')')) {
233
- const start = expr.lastIndexOf('(');
234
- const end = expr.indexOf(')', start);
235
-
236
- if (end === -1) break;
237
-
238
- const inner = expr.substring(start + 1, end);
239
- const innerResult = parseMathExpression(inner, config);
240
-
241
- expr = expr.substring(0, start) + innerResult + expr.substring(end + 1);
224
+ /**
225
+ * 解析数学表达式
226
+ * @param {string} expr - 清理后的表达式
227
+ * @param {Object} config - 配置对象
228
+ * @returns {number} 计算结果
229
+ */
230
+ function parseMathExpression(expr, config) {
231
+ // 处理括号
232
+ while (expr.includes('(') && expr.includes(')')) {
233
+ const start = expr.lastIndexOf('(');
234
+ const end = expr.indexOf(')', start);
235
+
236
+ if (end === -1) break;
237
+
238
+ const inner = expr.substring(start + 1, end);
239
+ const innerResult = parseMathExpression(inner, config);
240
+
241
+ expr = expr.substring(0, start) + innerResult + expr.substring(end + 1);
242
+ }
243
+
244
+ // 现在expr应该没有括号了
245
+ return evaluateSimpleExpression(expr, config);
242
246
  }
243
-
244
- // 现在expr应该没有括号了
245
- return evaluateSimpleExpression(expr, config);
246
- }
247
247
 
248
- /**
249
- * 评估简单表达式(无括号)
250
- * @param {string} expr - 简单表达式
251
- * @param {Object} config - 配置对象
252
- * @returns {number} 计算结果
253
- */
254
- function evaluateSimpleExpression(expr, config) {
255
- // 处理操作符优先级:乘除优先于加减
256
- const operators = [
257
- { regex: /([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/, handler: multiply },
258
- { regex: /([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/, handler: divide },
259
- { regex: /([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/, handler: add },
260
- { regex: /([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/, handler: subtract }
261
- ];
262
-
263
- let lastExpr = expr;
264
-
265
- // 按照优先级处理操作符
266
- for (const op of operators) {
267
- let match;
268
- while ((match = expr.match(op.regex)) !== null) {
269
- const left = parseValueWithUnit(match[1]);
270
- const right = parseValueWithUnit(match[2]);
271
- const result = op.handler(left, right);
272
-
273
- // 替换匹配的部分
274
- expr = expr.substring(0, match.index) +
275
- result.value +
276
- expr.substring(match.index + match[0].length);
277
- }
278
- }
279
-
280
- // 如果表达式无法进一步简化,尝试解析为数字
281
- if (expr !== lastExpr) {
282
- return evaluateSimpleExpression(expr, config);
248
+ /**
249
+ * 评估简单表达式(无括号)
250
+ * @param {string} expr - 简单表达式
251
+ * @param {Object} config - 配置对象
252
+ * @returns {number} 计算结果
253
+ */
254
+ function evaluateSimpleExpression(expr, config) {
255
+ // 处理操作符优先级:乘除优先于加减
256
+ const operators = [
257
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/, handler: multiply },
258
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/, handler: divide },
259
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/, handler: add },
260
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/, handler: subtract }
261
+ ];
262
+
263
+ let lastExpr = expr;
264
+
265
+ // 按照优先级处理操作符
266
+ for (const op of operators) {
267
+ let match;
268
+ while ((match = expr.match(op.regex)) !== null) {
269
+ const left = parseValueWithUnit(match[1]);
270
+ const right = parseValueWithUnit(match[2]);
271
+ const result = op.handler(left, right);
272
+
273
+ // 替换匹配的部分
274
+ expr = expr.substring(0, match.index) +
275
+ result.value +
276
+ expr.substring(match.index + match[0].length);
277
+ }
278
+ }
279
+
280
+ // 如果表达式无法进一步简化,尝试解析为数字
281
+ if (expr !== lastExpr) {
282
+ return evaluateSimpleExpression(expr, config);
283
+ }
284
+
285
+ // 解析最终结果
286
+ const parsed = parseValueWithUnit(expr);
287
+ return parsed.value;
283
288
  }
284
-
285
- // 解析最终结果
286
- const parsed = parseValueWithUnit(expr);
287
- return parsed.value;
288
- }
289
289
 
290
- /**
291
- * 解析带单位的数值
292
- * @param {string} str - 字符串值
293
- * @returns {Object} {value: number, unit: string}
294
- */
295
- function parseValueWithUnit(str) {
296
- // 匹配数值和单位
297
- const match = str.match(/^([\d.]+)([a-zA-Z%]*)$/);
298
-
299
- if (!match) {
300
- throw new Error(`无法解析值: ${str}`);
290
+ /**
291
+ * 解析带单位的数值
292
+ * @param {string} str - 字符串值
293
+ * @returns {Object} {value: number, unit: string}
294
+ */
295
+ function parseValueWithUnit(str) {
296
+ // 匹配数值和单位
297
+ const match = str.match(/^([\d.]+)([a-zA-Z%]*)$/);
298
+
299
+ if (!match) {
300
+ throw new Error(`无法解析值: ${str}`);
301
+ }
302
+
303
+ const value = parseFloat(match[1]);
304
+ const unit = match[2] || '';
305
+
306
+ return { value, unit };
301
307
  }
302
-
303
- const value = parseFloat(match[1]);
304
- const unit = match[2] || '';
305
-
306
- return { value, unit };
307
- }
308
-
309
- /**
310
- * 检查表达式是否包含单位
311
- * @param {string} expr - 表达式
312
- * @returns {boolean} 是否包含单位
313
- */
314
- function hasUnits(expr) {
315
- return /[a-zA-Z%]/.test(expr);
316
- }
317
308
 
318
- /**
319
- * 处理带单位的表达式
320
- * @param {string} originalExpr - 原始表达式
321
- * @param {number} result - 计算结果
322
- * @returns {string} 处理后的表达式
323
- */
324
- function processUnitExpression(originalExpr, result) {
325
- // 提取原始表达式中的单位
326
- const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
327
-
328
- if (unitMatch) {
329
- return result + unitMatch[1];
330
- }
331
-
332
- // 如果没有明确单位,检查是否为百分比
333
- if (originalExpr.includes('%')) {
334
- return result + '%';
309
+ /**
310
+ * 检查表达式是否包含单位
311
+ * @param {string} expr - 表达式
312
+ * @returns {boolean} 是否包含单位
313
+ */
314
+ function hasUnits(expr) {
315
+ return /[a-zA-Z%]/.test(expr);
335
316
  }
336
-
337
- // 默认为像素
338
- return result + 'px';
339
- }
340
317
 
341
- // 数学运算函数
342
- function multiply(a, b) {
343
- if (a.unit === b.unit || (!a.unit && !b.unit)) {
344
- return { value: a.value * b.value, unit: a.unit || b.unit };
318
+ /**
319
+ * 处理带单位的表达式
320
+ * @param {string} originalExpr - 原始表达式
321
+ * @param {number} result - 计算结果
322
+ * @returns {string} 处理后的表达式
323
+ */
324
+ function processUnitExpression(originalExpr, result) {
325
+ // 提取原始表达式中的单位
326
+ const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
327
+
328
+ if (unitMatch) {
329
+ return result + unitMatch[1];
330
+ }
331
+
332
+ // 如果没有明确单位,检查是否为百分比
333
+ if (originalExpr.includes('%')) {
334
+ return result + '%';
335
+ }
336
+
337
+ // 默认为像素
338
+ return result + 'px';
345
339
  }
346
-
347
- // 单位不匹配,返回原始表达式
348
- return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
349
- }
350
340
 
351
- function divide(a, b) {
352
- if (b.value === 0) {
353
- throw new Error('除以零');
354
- }
355
-
356
- if (a.unit && !b.unit) {
357
- // 如 100px / 2
358
- return { value: a.value / b.value, unit: a.unit };
359
- } else if (a.unit === b.unit) {
360
- // 如 100px / 2px
361
- return { value: a.value / b.value, unit: '' };
341
+ // 数学运算函数
342
+ function multiply(a, b) {
343
+ if (a.unit === b.unit || (!a.unit && !b.unit)) {
344
+ return { value: a.value * b.value, unit: a.unit || b.unit };
345
+ }
346
+
347
+ // 单位不匹配,返回原始表达式
348
+ return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
362
349
  }
363
-
364
- // 单位不匹配,返回原始表达式
365
- return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
366
- }
367
350
 
368
- function add(a, b) {
369
- if (a.unit === b.unit) {
370
- return { value: a.value + b.value, unit: a.unit };
351
+ function divide(a, b) {
352
+ if (b.value === 0) {
353
+ throw new Error('除以零');
354
+ }
355
+
356
+ if (a.unit && !b.unit) {
357
+ // 如 100px / 2
358
+ return { value: a.value / b.value, unit: a.unit };
359
+ } else if (a.unit === b.unit) {
360
+ // 如 100px / 2px
361
+ return { value: a.value / b.value, unit: '' };
362
+ }
363
+
364
+ // 单位不匹配,返回原始表达式
365
+ return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
371
366
  }
372
-
373
- // 单位不匹配,返回原始表达式
374
- return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
375
- }
376
367
 
377
- function subtract(a, b) {
378
- if (a.unit === b.unit) {
379
- return { value: a.value - b.value, unit: a.unit };
368
+ function add(a, b) {
369
+ if (a.unit === b.unit) {
370
+ return { value: a.value + b.value, unit: a.unit };
371
+ }
372
+
373
+ // 单位不匹配,返回原始表达式
374
+ return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
380
375
  }
381
-
382
- // 单位不匹配,返回原始表达式
383
- return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
384
- }
385
376
 
386
- /**
387
- * 四舍五入到指定精度
388
- * @param {number} num - 要四舍五入的数字
389
- * @param {number} precision - 精度
390
- * @returns {string} 四舍五入后的数字字符串
391
- */
392
- function roundNumber(num, precision = 6) {
393
- const factor = Math.pow(10, precision);
394
- const rounded = Math.round(num * factor) / factor;
395
-
396
- // 移除不必要的尾随零
397
- const str = rounded.toString();
398
- if (str.includes('.')) {
399
- return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
377
+ function subtract(a, b) {
378
+ if (a.unit === b.unit) {
379
+ return { value: a.value - b.value, unit: a.unit };
380
+ }
381
+
382
+ // 单位不匹配,返回原始表达式
383
+ return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
400
384
  }
401
- return str;
402
- }
403
385
 
404
- /**
405
- * 处理所有的math()函数
406
- * @param {string} cssValue - CSS属性值
407
- * @param {Object} config - 配置对象
408
- * @returns {string} 转换后的CSS值
409
- */
410
- function processMathFunctions(cssValue, config) {
411
- if (!config.enableMath) {
412
- return cssValue;
386
+ /**
387
+ * 四舍五入到指定精度
388
+ * @param {number} num - 要四舍五入的数字
389
+ * @param {number} precision - 精度
390
+ * @returns {string} 四舍五入后的数字字符串
391
+ */
392
+ function roundNumber(num, precision = 6) {
393
+ const factor = Math.pow(10, precision);
394
+ const rounded = Math.round(num * factor) / factor;
395
+
396
+ // 移除不必要的尾随零
397
+ const str = rounded.toString();
398
+ if (str.includes('.')) {
399
+ return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
400
+ }
401
+ return str;
413
402
  }
414
-
415
- let result = cssValue;
416
-
417
- // 匹配math()函数
418
- const mathRegex = /math\(([^)]+)\)/gi;
419
-
420
- // 递归处理嵌套的math()函数
421
- const processMath = (str) => {
422
- return str.replace(mathRegex, (match, expression) => {
423
- return evaluateMathExpression(expression, config);
424
- });
425
- };
426
-
427
- // 递归处理,直到没有更多math()函数
428
- let lastResult;
429
- do {
430
- lastResult = result;
431
- result = processMath(result);
432
- } while (result !== lastResult && result.includes('math('));
433
-
434
- return result;
435
- }
436
403
 
437
- /**
438
- * 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
439
- * @param {string} cssValue - CSS属性值
440
- * @param {Object} config - 配置对象
441
- * @returns {string} 转换后的CSS值
442
- */
443
- function convertAllLabLchColors(cssValue, config) {
444
- if (!config.convertLabToRGB && !config.convertLchToRGB) {
445
- return cssValue;
446
- }
447
-
448
- let result = cssValue;
449
-
450
- // 首先处理特殊的十六进制格式
451
- result = parseHexColorFormats(result, config);
452
-
453
- // 一次性处理所有的 lab() 和 lch() 函数
454
- const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
455
-
456
- // 存储已经处理过的颜色字符串,避免重复处理
457
- const processedColors = new Map();
458
-
459
- // 使用一个函数来递归处理嵌套的颜色函数
460
- const processColorFunctions = (str) => {
461
- return str.replace(colorFunctionRegex, (match) => {
462
- // 如果这个颜色已经处理过,直接返回缓存结果
463
- if (processedColors.has(match)) {
464
- return processedColors.get(match);
404
+ /**
405
+ * 处理所有的math()函数
406
+ * @param {string} cssValue - CSS属性值
407
+ * @param {Object} config - 配置对象
408
+ * @returns {string} 转换后的CSS值
409
+ */
410
+ function processMathFunctions(cssValue, config) {
411
+ if (!config.enableMath) {
412
+ return cssValue;
465
413
  }
466
414
 
467
- let converted = match;
468
-
469
- // 根据函数类型处理
470
- if (match.toLowerCase().startsWith('lab(')) {
471
- if (config.convertLabToRGB) {
472
- converted = convertSingleLabColor(match, config);
473
- }
474
- } else if (match.toLowerCase().startsWith('lch(')) {
475
- if (config.convertLchToRGB) {
476
- converted = convertSingleLchColor(match, config);
477
- }
478
- }
415
+ let result = cssValue;
479
416
 
480
- // 缓存结果
481
- processedColors.set(match, converted);
482
- return converted;
483
- });
484
- };
485
-
486
- // 递归处理,直到没有更多颜色函数
487
- let lastResult;
488
- do {
489
- lastResult = result;
490
- result = processColorFunctions(result);
491
- } while (result !== lastResult);
492
-
493
- return result;
494
- }
495
-
496
- /**
497
- * 解析十六进制颜色格式:lab#L16A16B16 或 lch#L16C16H
498
- * @param {string} value - 颜色值
499
- * @param {Object} config - 配置对象
500
- * @returns {string} 转换后的CSS颜色
501
- */
502
- function parseHexColorFormats(value, config) {
503
- let result = value;
504
-
505
- // 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
506
- const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
507
- const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
508
-
509
- // 存储处理过的十六进制颜色,避免重复处理
510
- const processedHexColors = new Map();
511
-
512
- // 处理 lab# 格式
513
- result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
514
- if (processedHexColors.has(match)) {
515
- return processedHexColors.get(match);
516
- }
517
-
518
- try {
519
- // 将十六进制转换为十进制,并映射到Lab范围
520
- const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
521
- const A = (parseInt(A_hex, 16) - 128) * 1.5; // 大约 -192 到 192
522
- const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
523
-
524
- const converted = generateColorString(L, A, B, config);
525
- processedHexColors.set(match, converted);
526
- return converted;
527
- } catch (error) {
528
- console.warn(`无法解析lab#十六进制颜色: ${match}`, error);
529
- return match;
530
- }
531
- });
532
-
533
- // 处理 lch# 格式
534
- result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
535
- if (processedHexColors.has(match)) {
536
- return processedHexColors.get(match);
537
- }
538
-
539
- try {
540
- // 将十六进制转换为十进制,并映射到LCH范围
541
- const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
542
- const C = parseInt(C_hex, 16) / 255 * 150; // 0-150
543
- const H = parseInt(H_dec) / 100 * 360; // 0-360
544
-
545
- const lab = lchToLab(L, C, H);
546
- const converted = generateColorString(lab.L, lab.a, lab.b, config);
547
- processedHexColors.set(match, converted);
548
- return converted;
549
- } catch (error) {
550
- console.warn(`无法解析lch#十六进制颜色: ${match}`, error);
551
- return match;
552
- }
553
- });
554
-
555
- return result;
556
- }
557
-
558
- /**
559
- * 转换单个 lab() 颜色函数
560
- * @param {string} labString - lab() 颜色字符串
561
- * @param {Object} config - 配置对象
562
- * @returns {string} 转换后的颜色字符串
563
- */
564
- function convertSingleLabColor(labString, config) {
565
- // 使用更精确的正则匹配 lab() 函数
566
- const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
567
- const match = labString.match(labRegex);
568
-
569
- if (!match) {
570
- return labString;
571
- }
572
-
573
- try {
574
- // 转换为数字
575
- let L = parseFloat(match[1]);
576
-
577
- // 如果L有百分号,转换为0-100范围的值
578
- if (match[2] === '%') {
579
- L = L; // 百分比值直接使用 (0-100)
580
- } else {
581
- // 没有百分号,确保在0-100范围内
582
- if (L < 0) L = 0;
583
- if (L > 100) L = 100;
584
- }
585
-
586
- const A = parseFloat(match[3]);
587
- const B = parseFloat(match[4]);
588
-
589
- // 处理 alpha 通道(如果有)
590
- const alphaValue = match[5] !== undefined
591
- ? match[5].includes('%')
592
- ? parseFloat(match[5]) / 100
593
- : parseFloat(match[5])
594
- : null;
595
-
596
- return generateColorString(L, A, B, config, alphaValue);
597
- } catch (error) {
598
- console.warn(`无法转换LAB颜色: ${labString}`, error);
599
- return labString;
417
+ // 匹配math()函数
418
+ const mathRegex = /math\(([^)]+)\)/gi;
419
+
420
+ // 递归处理嵌套的math()函数
421
+ const processMath = (str) => {
422
+ return str.replace(mathRegex, (match, expression) => {
423
+ return evaluateMathExpression(expression, config);
424
+ });
425
+ };
426
+
427
+ // 递归处理,直到没有更多math()函数
428
+ let lastResult;
429
+ do {
430
+ lastResult = result;
431
+ result = processMath(result);
432
+ } while (result !== lastResult && result.includes('math('));
433
+
434
+ return result;
600
435
  }
601
- }
602
436
 
603
- /**
604
- * 转换单个 lch() 颜色函数
605
- * @param {string} lchString - lch() 颜色字符串
606
- * @param {Object} config - 配置对象
607
- * @returns {string} 转换后的颜色字符串
608
- */
609
- function convertSingleLchColor(lchString, config) {
610
- // 使用更精确的正则匹配 lch() 函数
611
- const lchRegex = /lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;
612
- const match = lchString.match(lchRegex);
613
-
614
- if (!match) {
615
- return lchString;
616
- }
617
-
618
- try {
619
- // 转换为数字
620
- let L = parseFloat(match[1]);
621
-
622
- // 如果L有百分号,转换为0-100范围的值
623
- if (match[2] === '%') {
624
- L = L; // 百分比值直接使用 (0-100)
625
- } else {
626
- // 没有百分号,确保在0-100范围内
627
- if (L < 0) L = 0;
628
- if (L > 100) L = 100;
629
- }
630
-
631
- const C = parseFloat(match[3]);
632
- let H = parseFloat(match[4]);
633
-
634
- // 验证C范围:>= 0
635
- if (C < 0) {
636
- console.warn(`LCH中的C值不能为负: ${C}`);
637
- }
638
-
639
- // 验证H范围:0-360,有deg单位或无单位都是0-360
640
- H = ((H % 360) + 360) % 360; // 标准化到0-360
641
-
642
- // LCH -> Lab
643
- const lab = lchToLab(L, C, H);
644
-
645
- // 处理 alpha 通道(如果有)
646
- const alphaValue = match[6] !== undefined
647
- ? match[6].includes('%')
648
- ? parseFloat(match[6]) / 100
649
- : parseFloat(match[6])
650
- : null;
651
-
652
- return generateColorString(lab.L, lab.a, lab.b, config, alphaValue);
653
- } catch (error) {
654
- console.warn(`无法转换LCH颜色: ${lchString}`, error);
655
- return lchString;
437
+ /**
438
+ * 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
439
+ * @param {string} cssValue - CSS属性值
440
+ * @param {Object} config - 配置对象
441
+ * @returns {string} 转换后的CSS值
442
+ */
443
+ function convertAllLabLchColors(cssValue, config) {
444
+ if (!config.convertLabToRGB && !config.convertLchToRGB) {
445
+ return cssValue;
446
+ }
447
+
448
+ let result = cssValue;
449
+
450
+ // 首先处理特殊的十六进制格式
451
+ result = parseHexColorFormats(result, config);
452
+
453
+ // 一次性处理所有的 lab() 和 lch() 函数
454
+ const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
455
+
456
+ // 存储已经处理过的颜色字符串,避免重复处理
457
+ const processedColors = new Map();
458
+
459
+ // 使用一个函数来递归处理嵌套的颜色函数
460
+ const processColorFunctions = (str) => {
461
+ return str.replace(colorFunctionRegex, (match) => {
462
+ // 如果这个颜色已经处理过,直接返回缓存结果
463
+ if (processedColors.has(match)) {
464
+ return processedColors.get(match);
465
+ }
466
+
467
+ let converted = match;
468
+
469
+ // 根据函数类型处理
470
+ if (match.toLowerCase().startsWith('lab(')) {
471
+ if (config.convertLabToRGB) {
472
+ converted = convertSingleLabColor(match, config);
473
+ }
474
+ } else if (match.toLowerCase().startsWith('lch(')) {
475
+ if (config.convertLchToRGB) {
476
+ converted = convertSingleLchColor(match, config);
477
+ }
478
+ }
479
+
480
+ // 缓存结果
481
+ processedColors.set(match, converted);
482
+ return converted;
483
+ });
484
+ };
485
+
486
+ // 递归处理,直到没有更多颜色函数
487
+ let lastResult;
488
+ do {
489
+ lastResult = result;
490
+ result = processColorFunctions(result);
491
+ } while (result !== lastResult);
492
+
493
+ return result;
656
494
  }
657
- }
658
495
 
659
- /**
660
- * LCH 转换为 Lab (CSS标准)
661
- * @param {number} L - 明度 0-100 或 0%-100%
662
- * @param {number} C - 色度 >=0
663
- * @param {number} H - 色相角 0-360 度
664
- * @returns {Object} Lab 值 {L, a, b}
665
- */
666
- function lchToLab(L, C, H) {
667
- // 角度转换为弧度
668
- const H_rad = H * Math.PI / 180;
669
-
670
- // LCH -> Lab 转换公式
671
- const a = C * Math.cos(H_rad);
672
- const b = C * Math.sin(H_rad);
673
-
674
- return { L, a, b };
675
- }
496
+ /**
497
+ * 解析十六进制颜色格式:lab#L16A16B16 lch#L16C16H
498
+ * @param {string} value - 颜色值
499
+ * @param {Object} config - 配置对象
500
+ * @returns {string} 转换后的CSS颜色
501
+ */
502
+ function parseHexColorFormats(value, config) {
503
+ let result = value;
504
+
505
+ // 使用正则表达式一次性匹配所有 lab# lch# 格式
506
+ const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
507
+ const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
508
+
509
+ // 存储处理过的十六进制颜色,避免重复处理
510
+ const processedHexColors = new Map();
511
+
512
+ // 处理 lab# 格式
513
+ result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
514
+ if (processedHexColors.has(match)) {
515
+ return processedHexColors.get(match);
516
+ }
517
+
518
+ try {
519
+ // 将十六进制转换为十进制,并映射到Lab范围
520
+ const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
521
+ const A = (parseInt(A_hex, 16) - 128) * 1.5; // 大约 -192 到 192
522
+ const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
523
+
524
+ const converted = generateColorString(L, A, B, config);
525
+ processedHexColors.set(match, converted);
526
+ return converted;
527
+ } catch (error) {
528
+ console.warn(`无法解析lab#十六进制颜色: ${match}`, error);
529
+ return match;
530
+ }
531
+ });
532
+
533
+ // 处理 lch# 格式
534
+ result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
535
+ if (processedHexColors.has(match)) {
536
+ return processedHexColors.get(match);
537
+ }
538
+
539
+ try {
540
+ // 将十六进制转换为十进制,并映射到LCH范围
541
+ const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
542
+ const C = parseInt(C_hex, 16) / 255 * 150; // 0-150
543
+ const H = parseInt(H_dec) / 100 * 360; // 0-360
544
+
545
+ const lab = lchToLab(L, C, H);
546
+ const converted = generateColorString(lab.L, lab.a, lab.b, config);
547
+ processedHexColors.set(match, converted);
548
+ return converted;
549
+ } catch (error) {
550
+ console.warn(`无法解析lch#十六进制颜色: ${match}`, error);
551
+ return match;
552
+ }
553
+ });
554
+
555
+ return result;
556
+ }
676
557
 
677
- // 精确的 Lab -> sRGB 转换函数
678
- function preciseLabToRGB(L, a, b) {
679
- // 1. Lab -> XYZ (D65白点)
680
- const labToXyz = (L, a, b) => {
681
- // D65 白点参考值
682
- const refX = 0.95047;
683
- const refY = 1.00000;
684
- const refZ = 1.08883;
685
-
686
- const epsilon = 0.008856;
687
- const kappa = 903.3;
688
-
689
- const fy = (L + 16) / 116;
690
- const fx = a / 500 + fy;
691
- const fz = fy - b / 200;
692
-
693
- const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
694
- const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
695
- const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
696
-
697
- return [
698
- xr * refX,
699
- yr * refY,
700
- zr * refZ
701
- ];
702
- };
703
-
704
- // 2. XYZ -> Linear RGB
705
- const xyzToLinearRgb = (x, y, z) => {
706
- // sRGB 转换矩阵 (D65)
707
- const M = [
708
- [ 3.2404542, -1.5371385, -0.4985314],
709
- [-0.9692660, 1.8760108, 0.0415560],
710
- [ 0.0556434, -0.2040259, 1.0572252]
711
- ];
712
-
713
- const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
714
- const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
715
- const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
716
-
717
- return [r, g, b];
718
- };
719
-
720
- // 3. sRGB Gamma 校正
721
- const applyGamma = (c) => {
722
- const sign = c < 0 ? -1 : 1;
723
- const absC = Math.abs(c);
724
-
725
- if (absC <= 0.0031308) {
726
- return sign * 12.92 * absC;
727
- } else {
728
- return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
729
- }
730
- };
731
-
732
- // 4. 钳制到 [0, 255]
733
- const clamp = (value) => {
734
- return Math.max(0, Math.min(255, Math.round(value * 255)));
735
- };
736
-
737
- // 执行转换
738
- const [X, Y, Z] = labToXyz(L, a, b);
739
- const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
740
-
741
- const r = applyGamma(linearR);
742
- const g = applyGamma(linearG);
743
- const bOut = applyGamma(linearB);
744
-
745
- // 返回钳制后的 RGB 值
746
- return {
747
- r: clamp(r),
748
- g: clamp(g),
749
- b: clamp(bOut)
750
- };
751
- }
558
+ /**
559
+ * 转换单个 lab() 颜色函数
560
+ * @param {string} labString - lab() 颜色字符串
561
+ * @param {Object} config - 配置对象
562
+ * @returns {string} 转换后的颜色字符串
563
+ */
564
+ function convertSingleLabColor(labString, config) {
565
+ // 使用更精确的正则匹配 lab() 函数
566
+ const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
567
+ const match = labString.match(labRegex);
568
+
569
+ if (!match) {
570
+ return labString;
571
+ }
572
+
573
+ try {
574
+ // 转换为数字
575
+ let L = parseFloat(match[1]);
576
+
577
+ // 如果L有百分号,转换为0-100范围的值
578
+ if (match[2] === '%') {
579
+ L = L; // 百分比值直接使用 (0-100)
580
+ } else {
581
+ // 没有百分号,确保在0-100范围内
582
+ if (L < 0) L = 0;
583
+ if (L > 100) L = 100;
584
+ }
585
+
586
+ const A = parseFloat(match[3]);
587
+ const B = parseFloat(match[4]);
588
+
589
+ // 处理 alpha 通道(如果有)
590
+ const alphaValue = match[5] !== undefined
591
+ ? match[5].includes('%')
592
+ ? parseFloat(match[5]) / 100
593
+ : parseFloat(match[5])
594
+ : null;
595
+
596
+ return generateColorString(L, A, B, config, alphaValue);
597
+ } catch (error) {
598
+ console.warn(`无法转换LAB颜色: ${labString}`, error);
599
+ return labString;
600
+ }
601
+ }
752
602
 
753
- /**
754
- * Lab -> Display P3 转换
755
- * @param {number} L - 明度 0-100
756
- * @param {number} a - a分量
757
- * @param {number} b - b分量
758
- * @returns {Object} P3颜色 {r, g, b}
759
- */
760
- function labToP3(L, a, b) {
761
- // 1. Lab -> XYZ (使用与RGB相同的转换)
762
- const labToXyz = (L, a, b) => {
763
- const refX = 0.95047;
764
- const refY = 1.00000;
765
- const refZ = 1.08883;
766
-
767
- const epsilon = 0.008856;
768
- const kappa = 903.3;
769
-
770
- const fy = (L + 16) / 116;
771
- const fx = a / 500 + fy;
772
- const fz = fy - b / 200;
773
-
774
- const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
775
- const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
776
- const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
777
-
778
- return [
779
- xr * refX,
780
- yr * refY,
781
- zr * refZ
782
- ];
783
- };
784
-
785
- // 2. XYZ -> Linear P3
786
- const xyzToLinearP3 = (x, y, z) => {
787
- // Display P3 转换矩阵 (D65)
788
- const M = [
789
- [ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
790
- [-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
791
- [ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
792
- ];
793
-
794
- const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
795
- const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
796
- const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
797
-
798
- return [r, g, b];
799
- };
800
-
801
- // 3. P3 Gamma 校正(与sRGB相同)
802
- const applyGamma = (c) => {
803
- const sign = c < 0 ? -1 : 1;
804
- const absC = Math.abs(c);
805
-
806
- if (absC <= 0.0031308) {
807
- return sign * 12.92 * absC;
808
- } else {
809
- return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
810
- }
811
- };
812
-
813
- // 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
814
- const clamp = (value) => {
815
- return Math.max(0, Math.min(255, Math.round(value * 255)));
816
- };
817
-
818
- // 执行转换
819
- try {
820
- const [X, Y, Z] = labToXyz(L, a, b);
821
- const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
822
-
823
- const r = applyGamma(linearR);
824
- const g = applyGamma(linearG);
825
- const bOut = applyGamma(linearB);
826
-
827
- // 转换为0-1范围的浮点数用于P3颜色格式
828
- return {
829
- r: Math.max(0, Math.min(1, r)),
830
- g: Math.max(0, Math.min(1, g)),
831
- b: Math.max(0, Math.min(1, bOut))
832
- };
833
- } catch (error) {
834
- console.warn('P3转换失败:', error);
835
- const rgb = preciseLabToRGB(L, a, b);
836
- return {
837
- r: rgb.r / 255,
838
- g: rgb.g / 255,
839
- b: rgb.b / 255
840
- };
603
+ /**
604
+ * 转换单个 lch() 颜色函数
605
+ * @param {string} lchString - lch() 颜色字符串
606
+ * @param {Object} config - 配置对象
607
+ * @returns {string} 转换后的颜色字符串
608
+ */
609
+ function convertSingleLchColor(lchString, config) {
610
+ // 使用更精确的正则匹配 lch() 函数
611
+ const lchRegex = /lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;
612
+ const match = lchString.match(lchRegex);
613
+
614
+ if (!match) {
615
+ return lchString;
616
+ }
617
+
618
+ try {
619
+ // 转换为数字
620
+ let L = parseFloat(match[1]);
621
+
622
+ // 如果L有百分号,转换为0-100范围的值
623
+ if (match[2] === '%') {
624
+ L = L; // 百分比值直接使用 (0-100)
625
+ } else {
626
+ // 没有百分号,确保在0-100范围内
627
+ if (L < 0) L = 0;
628
+ if (L > 100) L = 100;
629
+ }
630
+
631
+ const C = parseFloat(match[3]);
632
+ let H = parseFloat(match[4]);
633
+
634
+ // 验证C范围:>= 0
635
+ if (C < 0) {
636
+ console.warn(`LCH中的C值不能为负: ${C}`);
637
+ }
638
+
639
+ // 验证H范围:0-360,有deg单位或无单位都是0-360
640
+ H = ((H % 360) + 360) % 360; // 标准化到0-360
641
+
642
+ // LCH -> Lab
643
+ const lab = lchToLab(L, C, H);
644
+
645
+ // 处理 alpha 通道(如果有)
646
+ const alphaValue = match[6] !== undefined
647
+ ? match[6].includes('%')
648
+ ? parseFloat(match[6]) / 100
649
+ : parseFloat(match[6])
650
+ : null;
651
+
652
+ return generateColorString(lab.L, lab.a, lab.b, config, alphaValue);
653
+ } catch (error) {
654
+ console.warn(`无法转换LCH颜色: ${lchString}`, error);
655
+ return lchString;
656
+ }
841
657
  }
842
- }
843
658
 
844
- /**
845
- * 生成颜色字符串(根据P3支持情况输出P3或RGB)
846
- * @param {number} L - Lab L值
847
- * @param {number} a - Lab a值
848
- * @param {number} b - Lab b值
849
- * @param {Object} config - 配置对象
850
- * @param {number|null} alpha - 透明度值
851
- * @returns {string} CSS颜色字符串
852
- */
853
- function generateColorString(L, a, b, config, alpha = null) {
854
- if (!config.enableP3 || !detectP3Support()) {
855
- // 使用RGB
856
- const rgb = preciseLabToRGB(L, a, b);
857
- if (alpha !== null) {
858
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
859
- }
860
- return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
861
- } else {
862
- // 使用P3
863
- const p3 = labToP3(L, a, b);
864
- if (alpha !== null) {
865
- return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
866
- }
867
- return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`;
659
+ /**
660
+ * LCH 转换为 Lab (CSS标准)
661
+ * @param {number} L - 明度 0-100 或 0%-100%
662
+ * @param {number} C - 色度 >=0
663
+ * @param {number} H - 色相角 0-360 度
664
+ * @returns {Object} Lab {L, a, b}
665
+ */
666
+ function lchToLab(L, C, H) {
667
+ // 角度转换为弧度
668
+ const H_rad = H * Math.PI / 180;
669
+
670
+ // LCH -> Lab 转换公式
671
+ const a = C * Math.cos(H_rad);
672
+ const b = C * Math.sin(H_rad);
673
+
674
+ return { L, a, b };
868
675
  }
869
- }
870
676
 
871
- /**
872
- * 处理CSS值,根据配置转换LAB、LCH颜色和math()函数
873
- * @param {string} value - CSS属性值
874
- * @param {Object} config - 配置对象
875
- * @returns {string} 处理后的值
876
- */
877
- function processCSSValue(value, config) {
878
- let result = value;
879
-
880
- // 1. 首先处理math()函数
881
- if (config.enableMath) {
882
- result = processMathFunctions(result, config);
677
+ // 精确的 Lab -> sRGB 转换函数
678
+ function preciseLabToRGB(L, a, b) {
679
+ // 1. Lab -> XYZ (D65白点)
680
+ const labToXyz = (L, a, b) => {
681
+ // D65 白点参考值
682
+ const refX = 0.95047;
683
+ const refY = 1.00000;
684
+ const refZ = 1.08883;
685
+
686
+ const epsilon = 0.008856;
687
+ const kappa = 903.3;
688
+
689
+ const fy = (L + 16) / 116;
690
+ const fx = a / 500 + fy;
691
+ const fz = fy - b / 200;
692
+
693
+ const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
694
+ const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
695
+ const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
696
+
697
+ return [
698
+ xr * refX,
699
+ yr * refY,
700
+ zr * refZ
701
+ ];
702
+ };
703
+
704
+ // 2. XYZ -> Linear RGB
705
+ const xyzToLinearRgb = (x, y, z) => {
706
+ // sRGB 转换矩阵 (D65)
707
+ const M = [
708
+ [ 3.2404542, -1.5371385, -0.4985314],
709
+ [-0.9692660, 1.8760108, 0.0415560],
710
+ [ 0.0556434, -0.2040259, 1.0572252]
711
+ ];
712
+
713
+ const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
714
+ const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
715
+ const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
716
+
717
+ return [r, g, b];
718
+ };
719
+
720
+ // 3. sRGB Gamma 校正
721
+ const applyGamma = (c) => {
722
+ const sign = c < 0 ? -1 : 1;
723
+ const absC = Math.abs(c);
724
+
725
+ if (absC <= 0.0031308) {
726
+ return sign * 12.92 * absC;
727
+ } else {
728
+ return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
729
+ }
730
+ };
731
+
732
+ // 4. 钳制到 [0, 255]
733
+ const clamp = (value) => {
734
+ return Math.max(0, Math.min(255, Math.round(value * 255)));
735
+ };
736
+
737
+ // 执行转换
738
+ const [X, Y, Z] = labToXyz(L, a, b);
739
+ const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
740
+
741
+ const r = applyGamma(linearR);
742
+ const g = applyGamma(linearG);
743
+ const bOut = applyGamma(linearB);
744
+
745
+ // 返回钳制后的 RGB 值
746
+ return {
747
+ r: clamp(r),
748
+ g: clamp(g),
749
+ b: clamp(bOut)
750
+ };
883
751
  }
884
-
885
- // 2. 然后处理LAB和LCH颜色
886
- if (config.convertLabToRGB || config.convertLchToRGB) {
887
- result = convertAllLabLchColors(result, config);
752
+
753
+ /**
754
+ * Lab -> Display P3 转换
755
+ * @param {number} L - 明度 0-100
756
+ * @param {number} a - a分量
757
+ * @param {number} b - b分量
758
+ * @returns {Object} P3颜色 {r, g, b}
759
+ */
760
+ function labToP3(L, a, b) {
761
+ // 1. Lab -> XYZ (使用与RGB相同的转换)
762
+ const labToXyz = (L, a, b) => {
763
+ const refX = 0.95047;
764
+ const refY = 1.00000;
765
+ const refZ = 1.08883;
766
+
767
+ const epsilon = 0.008856;
768
+ const kappa = 903.3;
769
+
770
+ const fy = (L + 16) / 116;
771
+ const fx = a / 500 + fy;
772
+ const fz = fy - b / 200;
773
+
774
+ const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
775
+ const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
776
+ const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
777
+
778
+ return [
779
+ xr * refX,
780
+ yr * refY,
781
+ zr * refZ
782
+ ];
783
+ };
784
+
785
+ // 2. XYZ -> Linear P3
786
+ const xyzToLinearP3 = (x, y, z) => {
787
+ // Display P3 转换矩阵 (D65)
788
+ const M = [
789
+ [ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
790
+ [-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
791
+ [ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
792
+ ];
793
+
794
+ const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
795
+ const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
796
+ const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
797
+
798
+ return [r, g, b];
799
+ };
800
+
801
+ // 3. P3 Gamma 校正(与sRGB相同)
802
+ const applyGamma = (c) => {
803
+ const sign = c < 0 ? -1 : 1;
804
+ const absC = Math.abs(c);
805
+
806
+ if (absC <= 0.0031308) {
807
+ return sign * 12.92 * absC;
808
+ } else {
809
+ return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
810
+ }
811
+ };
812
+
813
+ // 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
814
+ const clamp = (value) => {
815
+ return Math.max(0, Math.min(255, Math.round(value * 255)));
816
+ };
817
+
818
+ // 执行转换
819
+ try {
820
+ const [X, Y, Z] = labToXyz(L, a, b);
821
+ const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
822
+
823
+ const r = applyGamma(linearR);
824
+ const g = applyGamma(linearG);
825
+ const bOut = applyGamma(linearB);
826
+
827
+ // 转换为0-1范围的浮点数用于P3颜色格式
828
+ return {
829
+ r: Math.max(0, Math.min(1, r)),
830
+ g: Math.max(0, Math.min(1, g)),
831
+ b: Math.max(0, Math.min(1, bOut))
832
+ };
833
+ } catch (error) {
834
+ console.warn('P3转换失败:', error);
835
+ const rgb = preciseLabToRGB(L, a, b);
836
+ return {
837
+ r: rgb.r / 255,
838
+ g: rgb.g / 255,
839
+ b: rgb.b / 255
840
+ };
841
+ }
888
842
  }
889
-
890
- return result;
891
- }
892
843
 
893
- // 私有方法:提取变量定义并移除(支持嵌套作用域)
894
- function extractVariablesAndCSS(cssText, config) {
895
- const lines = cssText.split('\n');
896
- const globalVariables = {};
897
- const selectorVariables = new Map();
898
- let cssWithoutVars = '';
899
- let currentSelector = null;
900
- let inSelectorBlock = false;
901
- let currentIndent = 0;
902
-
903
- for (let line of lines) {
904
- const trimmed = line.trim();
905
-
906
- // 检查是否是变量定义
907
- const varMatch = trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);
908
-
909
- if (varMatch) {
910
- const [, varName, varValue] = varMatch;
911
- // 处理变量值中的数学表达式和颜色转换
912
- const processedValue = processCSSValue(
913
- replaceVariableUsesInValue(varValue.trim(), {
914
- ...globalVariables,
915
- ...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
916
- }),
917
- config
918
- );
919
-
920
- if (currentSelector) {
921
- // 选择器内部的变量
922
- if (!selectorVariables.has(currentSelector)) {
923
- selectorVariables.set(currentSelector, {});
924
- }
925
- selectorVariables.get(currentSelector)[varName] = processedValue;
844
+ /**
845
+ * 生成颜色字符串(根据P3支持情况输出P3或RGB)
846
+ * @param {number} L - Lab L值
847
+ * @param {number} a - Lab a值
848
+ * @param {number} b - Lab b值
849
+ * @param {Object} config - 配置对象
850
+ * @param {number|null} alpha - 透明度值
851
+ * @returns {string} CSS颜色字符串
852
+ */
853
+ function generateColorString(L, a, b, config, alpha = null) {
854
+ if (!config.enableP3 || !detectP3Support()) {
855
+ // 使用RGB
856
+ const rgb = preciseLabToRGB(L, a, b);
857
+ if (alpha !== null) {
858
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
859
+ }
860
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
926
861
  } else {
927
- // 全局变量(:root 中)
928
- globalVariables[varName] = processedValue;
929
- }
930
- continue;
931
- }
932
-
933
- // 检查是否是选择器开始
934
- if (trimmed.endsWith('{')) {
935
- currentSelector = trimmed.slice(0, -1).trim();
936
- inSelectorBlock = true;
937
- cssWithoutVars += line + '\n';
938
- continue;
939
- }
940
-
941
- // 检查是否是选择器结束
942
- if (trimmed === '}') {
943
- if (inSelectorBlock) {
944
- // 在选择器结束前插入变量声明
945
- if (currentSelector && selectorVariables.has(currentSelector)) {
946
- const vars = selectorVariables.get(currentSelector);
947
- const indent = ' '.repeat(currentIndent);
948
- for (const [varName, varValue] of Object.entries(vars)) {
949
- cssWithoutVars += `${indent} --${varName}: ${varValue};\n`;
862
+ // 使用P3
863
+ const p3 = labToP3(L, a, b);
864
+ if (alpha !== null) {
865
+ return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
950
866
  }
951
- }
867
+ return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`;
952
868
  }
953
- inSelectorBlock = false;
954
- currentSelector = null;
955
- }
956
-
957
- cssWithoutVars += line + '\n';
958
-
959
- // 更新缩进级别
960
- if (line.includes('{')) {
961
- currentIndent += 2;
962
- }
963
- if (line.includes('}')) {
964
- currentIndent = Math.max(0, currentIndent - 2);
965
- }
966
869
  }
967
-
968
- return {
969
- globalVariables,
970
- selectorVariables,
971
- cssWithoutVars: cssWithoutVars.trim()
972
- };
973
- }
974
870
 
975
- // 修复的嵌套解析函数
976
- function parseNestedRules(cssText, config) {
977
- const lines = cssText.split('\n');
978
- const stack = []; // 存储规则信息:{selector, properties, children}
979
- const rootRules = [];
980
- let currentIndent = 0;
981
-
982
- for (let i = 0; i < lines.length; i++) {
983
- const line = lines[i];
984
- const trimmed = line.trim();
985
-
986
- if (!trimmed) continue;
987
-
988
- // 计算缩进级别(假设使用空格缩进)
989
- const indent = line.match(/^(\s*)/)[0].length;
990
- const indentLevel = Math.floor(indent / config.indentSize);
991
-
992
- // 处理规则块结束
993
- if (trimmed === '}') {
994
- if (stack.length > 0) {
995
- const rule = stack.pop();
996
-
997
- // 如果这个规则有父规则,添加到父规则的children中
998
- if (stack.length > 0) {
999
- const parent = stack[stack.length - 1];
1000
- if (!parent.children) parent.children = [];
1001
- parent.children.push(rule);
1002
- } else {
1003
- // 否则是根级规则
1004
- rootRules.push(rule);
1005
- }
871
+ /**
872
+ * 处理CSS值,根据配置转换LAB、LCH颜色和math()函数
873
+ * @param {string} value - CSS属性值
874
+ * @param {Object} config - 配置对象
875
+ * @returns {string} 处理后的值
876
+ */
877
+ function processCSSValue(value, config) {
878
+ let result = value;
879
+
880
+ // 1. 首先处理math()函数
881
+ if (config.enableMath) {
882
+ result = processMathFunctions(result, config);
1006
883
  }
1007
- continue;
1008
- }
1009
-
1010
- // 处理规则开始
1011
- if (trimmed.endsWith('{')) {
1012
- const selector = trimmed.slice(0, -1).trim();
1013
-
1014
- // 创建新规则
1015
- const rule = {
1016
- selector: selector,
1017
- properties: [],
1018
- children: [],
1019
- indentLevel: indentLevel
1020
- };
1021
884
 
1022
- // 推入堆栈
1023
- stack.push(rule);
1024
- continue;
1025
- }
1026
-
1027
- // 处理属性行(不包含 { 或 } 的行)
1028
- if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
1029
- if (stack.length > 0) {
1030
- const currentRule = stack[stack.length - 1];
1031
- // 处理属性值中的math()函数和颜色转换
1032
- const parsed = parseSingleLineCSS(trimmed);
1033
- parsed.forEach(obj => {
1034
- const key = Object.keys(obj)[0];
1035
- const value = processCSSValue(obj[key], config);
1036
- currentRule.properties.push(`${key}: ${value}`);
1037
- });
885
+ // 2. 然后处理LAB和LCH颜色
886
+ if (config.convertLabToRGB || config.convertLchToRGB) {
887
+ result = convertAllLabLchColors(result, config);
1038
888
  }
1039
- continue;
1040
- }
889
+
890
+ return result;
1041
891
  }
1042
-
1043
- // 处理栈中剩余规则
1044
- while (stack.length > 0) {
1045
- const rule = stack.pop();
1046
- if (stack.length === 0) {
1047
- rootRules.push(rule);
1048
- } else {
1049
- const parent = stack[stack.length - 1];
1050
- if (!parent.children) parent.children = [];
1051
- parent.children.push(rule);
1052
- }
892
+
893
+ // 私有方法:提取变量定义并移除(支持嵌套作用域)
894
+ function extractVariablesAndCSS(cssText, config) {
895
+ const lines = cssText.split('\n');
896
+ const globalVariables = {};
897
+ const selectorVariables = new Map();
898
+ let cssWithoutVars = '';
899
+ let currentSelector = null;
900
+ let inSelectorBlock = false;
901
+ let currentIndent = 0;
902
+
903
+ for (let line of lines) {
904
+ const trimmed = line.trim();
905
+
906
+ // 检查是否是变量定义
907
+ const varMatch = trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);
908
+
909
+ if (varMatch) {
910
+ const [, varName, varValue] = varMatch;
911
+ // 处理变量值中的数学表达式和颜色转换
912
+ const processedValue = processCSSValue(
913
+ replaceVariableUsesInValue(varValue.trim(), {
914
+ ...globalVariables,
915
+ ...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
916
+ }),
917
+ config
918
+ );
919
+
920
+ if (currentSelector) {
921
+ // 选择器内部的变量
922
+ if (!selectorVariables.has(currentSelector)) {
923
+ selectorVariables.set(currentSelector, {});
924
+ }
925
+ selectorVariables.get(currentSelector)[varName] = processedValue;
926
+ } else {
927
+ // 全局变量(:root 中)
928
+ globalVariables[varName] = processedValue;
929
+ }
930
+ continue;
931
+ }
932
+
933
+ // 检查是否是选择器开始
934
+ if (trimmed.endsWith('{')) {
935
+ currentSelector = trimmed.slice(0, -1).trim();
936
+ inSelectorBlock = true;
937
+ cssWithoutVars += line + '\n';
938
+ continue;
939
+ }
940
+
941
+ // 检查是否是选择器结束
942
+ if (trimmed === '}') {
943
+ if (inSelectorBlock) {
944
+ // 在选择器结束前插入变量声明
945
+ if (currentSelector && selectorVariables.has(currentSelector)) {
946
+ const vars = selectorVariables.get(currentSelector);
947
+ const indent = ' '.repeat(currentIndent);
948
+ for (const [varName, varValue] of Object.entries(vars)) {
949
+ cssWithoutVars += `${indent} --${varName}: ${varValue};\n`;
950
+ }
951
+ }
952
+ }
953
+ inSelectorBlock = false;
954
+ currentSelector = null;
955
+ }
956
+
957
+ cssWithoutVars += line + '\n';
958
+
959
+ // 更新缩进级别
960
+ if (line.includes('{')) {
961
+ currentIndent += 2;
962
+ }
963
+ if (line.includes('}')) {
964
+ currentIndent = Math.max(0, currentIndent - 2);
965
+ }
966
+ }
967
+
968
+ return {
969
+ globalVariables,
970
+ selectorVariables,
971
+ cssWithoutVars: cssWithoutVars.trim()
972
+ };
1053
973
  }
1054
-
1055
- // 将规则树转换为CSS字符串
1056
- return convertRulesToCSS(rootRules, config);
1057
- }
1058
-
1059
- // 将规则树转换为CSS字符串
1060
- function convertRulesToCSS(rules, config, parentSelector = '') {
1061
- let result = '';
1062
- const isParentAt = parentSelector.startsWith("@");
1063
-
1064
- for (const rule of rules) {
1065
- // 构建完整选择器
1066
- const isAt = rule.selector.startsWith("@");
1067
- let fullSelector = (isParentAt ? ' ' : '') + rule.selector;
1068
-
1069
- if (isAt) {
1070
- fullSelector = rule.selector + " {\n";
1071
- }
1072
-
1073
- if (parentSelector) {
1074
- // 处理 & 选择器
1075
- if (fullSelector.includes('&')) {
1076
- fullSelector = fullSelector.replace(/&/g, parentSelector);
1077
- } else if (fullSelector.trim().startsWith(':')) {
1078
- fullSelector = parentSelector + fullSelector;
1079
- } else {
1080
- fullSelector = parentSelector + (isParentAt ? '' : ' ') + fullSelector;
974
+
975
+ // 修复的嵌套解析函数
976
+ function parseNestedRules(cssText, config) {
977
+ const lines = cssText.split('\n');
978
+ const stack = []; // 存储规则信息:{selector, properties, children}
979
+ const rootRules = [];
980
+ let currentIndent = 0;
981
+
982
+ for (let i = 0; i < lines.length; i++) {
983
+ const line = lines[i];
984
+ const trimmed = line.trim();
985
+
986
+ if (!trimmed) continue;
987
+
988
+ // 计算缩进级别(假设使用空格缩进)
989
+ const indent = line.match(/^(\s*)/)[0].length;
990
+ const indentLevel = Math.floor(indent / config.indentSize);
991
+
992
+ // 处理规则块结束
993
+ if (trimmed === '}') {
994
+ if (stack.length > 0) {
995
+ const rule = stack.pop();
996
+
997
+ // 如果这个规则有父规则,添加到父规则的children中
998
+ if (stack.length > 0) {
999
+ const parent = stack[stack.length - 1];
1000
+ if (!parent.children) parent.children = [];
1001
+ parent.children.push(rule);
1002
+ } else {
1003
+ // 否则是根级规则
1004
+ rootRules.push(rule);
1005
+ }
1006
+ }
1007
+ continue;
1008
+ }
1009
+
1010
+ // 处理规则开始
1011
+ if (trimmed.endsWith('{')) {
1012
+ const selector = trimmed.slice(0, -1).trim();
1013
+
1014
+ // 创建新规则
1015
+ const rule = {
1016
+ selector: selector,
1017
+ properties: [],
1018
+ children: [],
1019
+ indentLevel: indentLevel
1020
+ };
1021
+
1022
+ // 推入堆栈
1023
+ stack.push(rule);
1024
+ continue;
1025
+ }
1026
+
1027
+ // 处理属性行(不包含 { 或 } 的行)
1028
+ if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
1029
+ if (stack.length > 0) {
1030
+ const currentRule = stack[stack.length - 1];
1031
+ // 处理属性值中的math()函数和颜色转换
1032
+ const parsed = parseSingleLineCSS(trimmed);
1033
+ parsed.forEach(obj => {
1034
+ const key = Object.keys(obj)[0];
1035
+ const value = processCSSValue(obj[key], config);
1036
+ currentRule.properties.push(`${key}: ${value}`);
1037
+ });
1038
+ }
1039
+ continue;
1040
+ }
1081
1041
  }
1082
- }
1083
-
1084
- // 如果有属性,生成规则块
1085
- if (rule.properties.length > 0) {
1086
- result += (isAt ? "" : fullSelector) + ' {\n';
1087
- for (const prop of rule.properties) {
1088
- // 再次处理属性值(确保math()函数和颜色被转换)
1089
- const parsed = parseSingleLineCSS(prop);
1090
- parsed.forEach(obj => {
1091
- const key = Object.keys(obj)[0];
1092
- const value = processCSSValue(obj[key], config);
1093
- result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
1094
- });
1042
+
1043
+ // 处理栈中剩余规则
1044
+ while (stack.length > 0) {
1045
+ const rule = stack.pop();
1046
+ if (stack.length === 0) {
1047
+ rootRules.push(rule);
1048
+ } else {
1049
+ const parent = stack[stack.length - 1];
1050
+ if (!parent.children) parent.children = [];
1051
+ parent.children.push(rule);
1052
+ }
1095
1053
  }
1096
- result += isParentAt ? ' }\n' : '}\n\n';
1097
- }
1098
-
1099
- // 递归处理子规则
1100
- if (rule.children && rule.children.length > 0) {
1101
- result += convertRulesToCSS(rule.children, config, fullSelector);
1102
- }
1103
-
1104
- if (isParentAt){
1105
- result += "}\n\n";
1106
- }
1054
+
1055
+ // 将规则树转换为CSS字符串
1056
+ return convertRulesToCSS(rootRules, config);
1107
1057
  }
1108
1058
 
1109
- return result.trim() + (isParentAt ? "\n\n" : "");
1110
- }
1111
-
1112
- // 替换变量值中的变量引用
1113
- function replaceVariableUsesInValue(value, variables) {
1114
- return value.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
1115
- if (variables[varName]) {
1116
- return `var(--${varName})`;
1117
- }
1118
- return match;
1119
- });
1120
- }
1121
-
1122
- // 替换变量使用
1123
- function replaceVariableUses(cssText, globalVariables, selectorVariables, config) {
1124
- let result = cssText;
1125
-
1126
- // 先替换全局变量
1127
- for (const [varName, varValue] of Object.entries(globalVariables)) {
1128
- const varRegex = new RegExp(`\\$${varName}(?![a-zA-Z0-9_-])`, 'g');
1129
- result = result.replace(varRegex, `var(--${varName})`);
1059
+ // 将规则树转换为CSS字符串
1060
+ function convertRulesToCSS(rules, config, parentSelector = '') {
1061
+ let result = '';
1062
+ const isParentAt = parentSelector.startsWith("@");
1063
+
1064
+ for (const rule of rules) {
1065
+ // 构建完整选择器
1066
+ const isAt = rule.selector.startsWith("@");
1067
+ let fullSelector = (isParentAt ? ' ' : '') + rule.selector;
1068
+
1069
+ if (isAt) {
1070
+ fullSelector = rule.selector + " {\n";
1071
+ }
1072
+
1073
+ if (parentSelector) {
1074
+ // 处理 & 选择器
1075
+ if (fullSelector.includes('&')) {
1076
+ fullSelector = fullSelector.replace(/&/g, parentSelector);
1077
+ } else if (fullSelector.trim().startsWith(':')) {
1078
+ fullSelector = parentSelector + fullSelector;
1079
+ } else {
1080
+ fullSelector = parentSelector + (isParentAt ? '' : ' ') + fullSelector;
1081
+ }
1082
+ }
1083
+
1084
+ // 如果有属性,生成规则块
1085
+ if (rule.properties.length > 0) {
1086
+ result += (isAt ? "" : fullSelector) + ' {\n';
1087
+ for (const prop of rule.properties) {
1088
+ // 再次处理属性值(确保math()函数和颜色被转换)
1089
+ const parsed = parseSingleLineCSS(prop);
1090
+ parsed.forEach(obj => {
1091
+ const key = Object.keys(obj)[0];
1092
+ const value = processCSSValue(obj[key], config);
1093
+ result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
1094
+ });
1095
+ }
1096
+ result += isParentAt ? ' }\n' : '}\n\n';
1097
+ }
1098
+
1099
+ // 递归处理子规则
1100
+ if (rule.children && rule.children.length > 0) {
1101
+ result += convertRulesToCSS(rule.children, config, fullSelector);
1102
+ }
1103
+
1104
+ if (isParentAt){
1105
+ result += "}\n\n";
1106
+ }
1107
+ }
1108
+
1109
+ return result.trim() + (isParentAt ? "\n\n" : "");
1130
1110
  }
1131
-
1132
- // 然后处理选择器特定的变量
1133
- result = result.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
1134
- return `var(--${varName})`;
1135
- });
1136
-
1137
- // 最后处理所有的LAB、LCH颜色和math()函数
1138
- result = processCSSValue(result, config);
1139
-
1140
- return result;
1141
- }
1142
1111
 
1143
- // 生成根规则
1144
- function generateRootRule(variables, config) {
1145
- if (Object.keys(variables).length === 0) {
1146
- return '';
1112
+ // 替换变量值中的变量引用
1113
+ function replaceVariableUsesInValue(value, variables) {
1114
+ return value.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
1115
+ if (variables[varName]) {
1116
+ return `var(--${varName})`;
1117
+ }
1118
+ return match;
1119
+ });
1147
1120
  }
1148
-
1149
- const declarations = Object.entries(variables)
1150
- .map(([name, value]) => {
1151
- const processedValue = processCSSValue(
1152
- replaceVariableUsesInValue(value, variables),
1153
- config
1154
- );
1155
- return ` --${name}: ${processedValue};`;
1156
- })
1157
- .join('\n');
1158
-
1159
- return `${config.rootSelector} {\n${declarations}\n}\n\n`;
1160
- }
1161
1121
 
1162
- // 处理选择器内部的变量
1163
- function injectSelectorVariables(cssText, selectorVariables, config) {
1164
- let result = '';
1165
- const lines = cssText.split('\n');
1166
- let currentSelector = null;
1167
-
1168
- for (let line of lines) {
1169
- const trimmed = line.trim();
1170
-
1171
- if (trimmed.endsWith('{')) {
1172
- currentSelector = trimmed.slice(0, -1).trim();
1173
- }
1174
-
1175
- if (trimmed === '}' && currentSelector) {
1176
- // 在选择器结束前插入变量声明
1177
- if (selectorVariables.has(currentSelector)) {
1178
- const vars = selectorVariables.get(currentSelector);
1179
- const indent = line.match(/^(\s*)/)[0];
1180
- for (const [varName, varValue] of Object.entries(vars)) {
1181
- result += indent + ' --' + varName + ': ' + varValue + ';\n';
1182
- }
1122
+ // 替换变量使用
1123
+ function replaceVariableUses(cssText, globalVariables, selectorVariables, config) {
1124
+ let result = cssText;
1125
+
1126
+ // 先替换全局变量
1127
+ for (const [varName, varValue] of Object.entries(globalVariables)) {
1128
+ const varRegex = new RegExp(`\\$${varName}(?![a-zA-Z0-9_-])`, 'g');
1129
+ result = result.replace(varRegex, `var(--${varName})`);
1183
1130
  }
1184
- currentSelector = null;
1185
- }
1186
-
1187
- // 使用全局处理函数处理所有math()和颜色
1188
- result += processCSSValue(line, config) + '\n';
1131
+
1132
+ // 然后处理选择器特定的变量
1133
+ result = result.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
1134
+ return `var(--${varName})`;
1135
+ });
1136
+
1137
+ // 最后处理所有的LAB、LCH颜色和math()函数
1138
+ result = processCSSValue(result, config);
1139
+
1140
+ return result;
1189
1141
  }
1190
-
1191
- return result.trim();
1192
- }
1193
1142
 
1194
- // 主转换函数
1195
- function convert(cssText, customConfig = {}) {
1196
- const config = { ...defaultConfig, ...customConfig };
1197
-
1198
- // 1. 提取变量定义(区分全局和选择器局部)
1199
- const { globalVariables, selectorVariables, cssWithoutVars } =
1200
- extractVariablesAndCSS(cssText, config);
1201
-
1202
- // 2. 解析嵌套规则(如果启用)
1203
- let processedCSS = cssWithoutVars.trim();
1204
- if (config.enableNesting && cssWithoutVars.includes('{')) {
1205
- try {
1206
- processedCSS = parseNestedRules(cssWithoutVars, config);
1207
- } catch (error) {
1208
- console.warn('嵌套解析失败,使用原始CSS:', error);
1209
- }
1210
- }
1211
-
1212
- // 3. 生成根规则(全局变量)
1213
- const rootRule = generateRootRule(globalVariables, config);
1214
-
1215
- // 4. 注入选择器局部变量
1216
- let finalCSS = processedCSS;
1217
- if (selectorVariables.size > 0) {
1218
- finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
1143
+ // 生成根规则
1144
+ function generateRootRule(variables, config) {
1145
+ if (Object.keys(variables).length === 0) {
1146
+ return '';
1147
+ }
1148
+
1149
+ const declarations = Object.entries(variables)
1150
+ .map(([name, value]) => {
1151
+ const processedValue = processCSSValue(
1152
+ replaceVariableUsesInValue(value, variables),
1153
+ config
1154
+ );
1155
+ return ` --${name}: ${processedValue};`;
1156
+ })
1157
+ .join('\n');
1158
+
1159
+ return `${config.rootSelector} {\n${declarations}\n}\n\n`;
1219
1160
  }
1220
-
1221
- // 5. 替换变量使用
1222
- finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, config);
1223
-
1224
- // 6. 组合结果
1225
- return rootRule + finalCSS;
1226
- }
1227
1161
 
1228
- // 自动处理带有 e 属性的 style 标签
1229
- function autoProcessStyleTags(customConfig = {}) {
1230
- const config = { ...defaultConfig, ...customConfig };
1231
- const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
1232
-
1233
- styleTags.forEach(styleTag => {
1234
- const originalCSS = styleTag.textContent;
1235
- const convertedCSS = convert(originalCSS, config);
1236
-
1237
- // 创建新的 style 标签
1238
- const newStyleTag = document.createElement('style');
1239
- newStyleTag.textContent = convertedCSS;
1240
-
1241
- // 插入到原标签后面
1242
- styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
1243
-
1244
- // 可选:移除原标签
1245
- if (!config.preserveOriginal) {
1246
- styleTag.remove();
1247
- } else {
1248
- styleTag.style.display = 'none';
1249
- }
1250
- });
1251
-
1252
- return styleTags.length;
1253
- }
1162
+ // 处理选择器内部的变量
1163
+ function injectSelectorVariables(cssText, selectorVariables, config) {
1164
+ let result = '';
1165
+ const lines = cssText.split('\n');
1166
+ let currentSelector = null;
1167
+
1168
+ for (let line of lines) {
1169
+ const trimmed = line.trim();
1170
+
1171
+ if (trimmed.endsWith('{')) {
1172
+ currentSelector = trimmed.slice(0, -1).trim();
1173
+ }
1174
+
1175
+ if (trimmed === '}' && currentSelector) {
1176
+ // 在选择器结束前插入变量声明
1177
+ if (selectorVariables.has(currentSelector)) {
1178
+ const vars = selectorVariables.get(currentSelector);
1179
+ const indent = line.match(/^(\s*)/)[0];
1180
+ for (const [varName, varValue] of Object.entries(vars)) {
1181
+ result += indent + ' --' + varName + ': ' + varValue + ';\n';
1182
+ }
1183
+ }
1184
+ currentSelector = null;
1185
+ }
1186
+
1187
+ // 使用全局处理函数处理所有math()和颜色
1188
+ result += processCSSValue(line, config) + '\n';
1189
+ }
1190
+
1191
+ return result.trim();
1192
+ }
1254
1193
 
1255
- function config(config = {}) {
1256
- defaultConfig = { ...defaultConfig, ...config };
1257
- }
1194
+ // 主转换函数
1195
+ function convert(cssText, customConfig = {}) {
1196
+ const config = { ...defaultConfig, ...customConfig };
1197
+
1198
+ // 1. 提取变量定义(区分全局和选择器局部)
1199
+ const { globalVariables, selectorVariables, cssWithoutVars } =
1200
+ extractVariablesAndCSS(cssText, config);
1201
+
1202
+ // 2. 解析嵌套规则(如果启用)
1203
+ let processedCSS = cssWithoutVars.trim();
1204
+ if (config.enableNesting && cssWithoutVars.includes('{')) {
1205
+ try {
1206
+ processedCSS = parseNestedRules(cssWithoutVars, config);
1207
+ } catch (error) {
1208
+ console.warn('嵌套解析失败,使用原始CSS:', error);
1209
+ }
1210
+ }
1211
+
1212
+ // 3. 生成根规则(全局变量)
1213
+ const rootRule = generateRootRule(globalVariables, config);
1214
+
1215
+ // 4. 注入选择器局部变量
1216
+ let finalCSS = processedCSS;
1217
+ if (selectorVariables.size > 0) {
1218
+ finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
1219
+ }
1220
+
1221
+ // 5. 替换变量使用
1222
+ finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, config);
1223
+
1224
+ // 6. 组合结果
1225
+ return rootRule + finalCSS;
1226
+ }
1258
1227
 
1259
- // 初始化自动处理
1260
- function apply(cssText, customConfig = {}) {
1261
- const config = { ...defaultConfig, ...customConfig };
1262
-
1263
- if (cssText) {
1264
- const converted = convert(cssText, config);
1265
- const styleEl = document.createElement('style');
1266
- styleEl.textContent = converted;
1267
- document.head.appendChild(styleEl);
1268
- return styleEl;
1269
- } else {
1270
- if (document.readyState === 'loading') {
1271
- document.addEventListener('DOMContentLoaded', () => {
1272
- autoProcessStyleTags(config);
1228
+ // 自动处理带有 e 属性的 style 标签
1229
+ function autoProcessStyleTags(customConfig = {}) {
1230
+ const config = { ...defaultConfig, ...customConfig };
1231
+ const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
1232
+
1233
+ styleTags.forEach(styleTag => {
1234
+ const originalCSS = styleTag.textContent;
1235
+ const convertedCSS = convert(originalCSS, config);
1236
+
1237
+ // 创建新的 style 标签
1238
+ const newStyleTag = document.createElement('style');
1239
+ newStyleTag.textContent = convertedCSS;
1240
+
1241
+ // 插入到原标签后面
1242
+ styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
1243
+
1244
+ // 可选:移除原标签
1245
+ if (!config.preserveOriginal) {
1246
+ styleTag.remove();
1247
+ } else {
1248
+ styleTag.style.display = 'none';
1249
+ }
1273
1250
  });
1274
- } else {
1275
- autoProcessStyleTags(config);
1276
- }
1251
+
1252
+ return styleTags.length;
1277
1253
  }
1278
- }
1279
1254
 
1280
- // 返回公共 API
1281
- const api = {
1282
- convert,
1283
- apply,
1284
- config,
1285
- version: '1.9.0',
1286
-
1287
- // 检测P3支持
1288
- supportsP3: detectP3Support(),
1289
-
1290
- // 重新检测P3支持(如果配置变化)
1291
- detectP3Support: detectP3Support,
1292
-
1293
- // 数学计算工具方法
1294
- math: {
1295
- /**
1296
- * 计算数学表达式
1297
- * @param {string} expression - 数学表达式
1298
- * @returns {string} 计算结果
1299
- */
1300
- evaluate: function(expression) {
1301
- return evaluateMathExpression(expression, defaultConfig);
1302
- },
1303
-
1304
- /**
1305
- * 解析带单位的数值
1306
- * @param {string} value - 带单位的字符串
1307
- * @returns {Object} {value: number, unit: string}
1308
- */
1309
- parseUnit: parseValueWithUnit,
1310
-
1311
- /**
1312
- * 四舍五入数字
1313
- * @param {number} num - 要四舍五入的数字
1314
- * @param {number} precision - 精度
1315
- * @returns {string} 四舍五入后的数字字符串
1316
- */
1317
- round: roundNumber,
1318
-
1319
- /**
1320
- * 测试数学表达式
1321
- * @param {string} expression - 要测试的表达式
1322
- * @param {Object} testConfig - 测试配置
1323
- * @returns {Object} 测试结果
1324
- */
1325
- test: function(expression, testConfig = {}) {
1326
- const config = { ...defaultConfig, ...testConfig };
1327
- try {
1328
- const result = evaluateMathExpression(expression, config);
1329
- return {
1330
- success: true,
1331
- expression,
1332
- result,
1333
- parsed: parseValueWithUnit(result.replace(/^calc\(|\)$/g, ''))
1334
- };
1335
- } catch (error) {
1336
- return {
1337
- success: false,
1338
- expression,
1339
- error: error.message
1340
- };
1255
+ function config(config = {}) {
1256
+ defaultConfig = { ...defaultConfig, ...config };
1257
+ }
1258
+
1259
+ // 初始化自动处理
1260
+ function apply(cssText, customConfig = {}) {
1261
+ const config = { ...defaultConfig, ...customConfig };
1262
+
1263
+ if (cssText) {
1264
+ const converted = convert(cssText, config);
1265
+ const styleEl = document.createElement('style');
1266
+ styleEl.textContent = converted;
1267
+ document.head.appendChild(styleEl);
1268
+ return styleEl;
1269
+ } else {
1270
+ if (document.readyState === 'loading') {
1271
+ document.addEventListener('DOMContentLoaded', () => {
1272
+ autoProcessStyleTags(config);
1273
+ });
1274
+ } else {
1275
+ autoProcessStyleTags(config);
1276
+ }
1341
1277
  }
1342
- }
1343
- },
1344
-
1345
- // 颜色转换工具方法
1346
- colorUtils: {
1347
- labToRGB: preciseLabToRGB,
1348
- lchToLab: lchToLab,
1349
- lchToRGB: function(L, C, H) {
1350
- const lab = lchToLab(L, C, H);
1351
- return preciseLabToRGB(lab.L, lab.a, lab.b);
1352
- },
1353
- labToP3: labToP3,
1354
- lchToP3: function(L, C, H) {
1355
- const lab = lchToLab(L, C, H);
1356
- return labToP3(lab.L, lab.a, lab.b);
1357
- },
1358
- parseHexLab: function(hexString) {
1359
- const match = hexString.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);
1360
- if (!match) return null;
1361
-
1362
- const L_hex = match[1];
1363
- const A_hex = match[2];
1364
- const B_hex = match[3];
1365
-
1366
- const L = parseInt(L_hex, 16) / 255 * 100;
1367
- const A = (parseInt(A_hex, 16) - 128) * 1.5;
1368
- const B = (parseInt(B_hex, 16) - 128) * 1.5;
1369
-
1370
- return preciseLabToRGB(L, A, B);
1371
- },
1372
- parseHexLch: function(hexString) {
1373
- const match = hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);
1374
- if (!match) return null;
1375
-
1376
- const L_hex = match[1];
1377
- const C_hex = match[2];
1378
- const H_dec = match[3];
1379
-
1380
- const L = parseInt(L_hex, 16) / 255 * 100;
1381
- const C = parseInt(C_hex, 16) / 255 * 150;
1382
- const H = parseInt(H_dec) / 100 * 360;
1383
-
1384
- const lab = lchToLab(L, C, H);
1385
- return preciseLabToRGB(lab.L, lab.a, lab.b);
1386
- },
1387
- /**
1388
- * 生成颜色字符串(根据P3支持情况)
1389
- * @param {number} L - Lab L值
1390
- * @param {number} a - Lab a值
1391
- * @param {number} b - Lab b值
1392
- * @param {number|null} alpha - 透明度
1393
- * @param {boolean} useP3 - 是否使用P3(自动检测)
1394
- * @returns {string} CSS颜色字符串
1395
- */
1396
- generateColor: function(L, a, b, alpha = null, useP3 = true) {
1397
- return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
1398
- },
1399
- /**
1400
- * 解析CSS颜色字符串
1401
- * @param {string} colorString - CSS颜色字符串
1402
- * @returns {Object} 包含原始Lab值和颜色字符串的对象
1403
- */
1404
- parseColor: function(colorString) {
1405
- try {
1406
- // 尝试解析lab()函数格式
1407
- const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
1408
- if (labMatch) {
1409
- let L = parseFloat(labMatch[1]);
1410
- const A = parseFloat(labMatch[3]);
1411
- const B = parseFloat(labMatch[4]);
1412
- const alpha = labMatch[5] ?
1413
- (labMatch[5].includes('%') ? parseFloat(labMatch[5]) / 100 : parseFloat(labMatch[5])) :
1414
- null;
1415
-
1416
- const colorStr = generateColorString(L, A, B, defaultConfig, alpha);
1278
+ }
1279
+
1280
+ // 返回公共 API
1281
+ const api = {
1282
+ convert,
1283
+ apply,
1284
+ config,
1285
+ version: '1.9.0',
1286
+
1287
+ // 检测P3支持
1288
+ supportsP3: detectP3Support(),
1289
+
1290
+ // 重新检测P3支持(如果配置变化)
1291
+ detectP3Support: detectP3Support,
1292
+
1293
+ // 数学计算工具方法
1294
+ math: {
1295
+ /**
1296
+ * 计算数学表达式
1297
+ * @param {string} expression - 数学表达式
1298
+ * @returns {string} 计算结果
1299
+ */
1300
+ evaluate: function(expression) {
1301
+ return evaluateMathExpression(expression, defaultConfig);
1302
+ },
1417
1303
 
1418
- return {
1419
- L, A, B, alpha,
1420
- rgb: preciseLabToRGB(L, A, B),
1421
- p3: labToP3(L, A, B),
1422
- colorString: colorStr
1423
- };
1424
- }
1425
-
1426
- // 尝试解析lch()函数格式
1427
- const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
1428
- if (lchMatch) {
1429
- let L = parseFloat(lchMatch[1]);
1430
- const C = parseFloat(lchMatch[3]);
1431
- let H = parseFloat(lchMatch[4]);
1432
- const alpha = lchMatch[6] ?
1433
- (lchMatch[6].includes('%') ? parseFloat(lchMatch[6]) / 100 : parseFloat(lchMatch[6])) :
1434
- null;
1304
+ /**
1305
+ * 解析带单位的数值
1306
+ * @param {string} value - 带单位的字符串
1307
+ * @returns {Object} {value: number, unit: string}
1308
+ */
1309
+ parseUnit: parseValueWithUnit,
1435
1310
 
1436
- const lab = lchToLab(L, C, H);
1437
- const colorStr = generateColorString(lab.L, lab.a, lab.b, defaultConfig, alpha);
1311
+ /**
1312
+ * 四舍五入数字
1313
+ * @param {number} num - 要四舍五入的数字
1314
+ * @param {number} precision - 精度
1315
+ * @returns {string} 四舍五入后的数字字符串
1316
+ */
1317
+ round: roundNumber,
1438
1318
 
1439
- return {
1440
- L, C, H, alpha,
1441
- lab: lab,
1442
- rgb: preciseLabToRGB(lab.L, lab.a, lab.b),
1443
- p3: labToP3(lab.L, lab.a, lab.b),
1444
- colorString: colorStr
1445
- };
1446
- }
1447
-
1448
- return null;
1449
- } catch (error) {
1450
- console.warn('无法解析颜色:', colorString, error);
1451
- return null;
1319
+ /**
1320
+ * 测试数学表达式
1321
+ * @param {string} expression - 要测试的表达式
1322
+ * @param {Object} testConfig - 测试配置
1323
+ * @returns {Object} 测试结果
1324
+ */
1325
+ test: function(expression, testConfig = {}) {
1326
+ const config = { ...defaultConfig, ...testConfig };
1327
+ try {
1328
+ const result = evaluateMathExpression(expression, config);
1329
+ return {
1330
+ success: true,
1331
+ expression,
1332
+ result,
1333
+ parsed: parseValueWithUnit(result.replace(/^calc\(|\)$/g, ''))
1334
+ };
1335
+ } catch (error) {
1336
+ return {
1337
+ success: false,
1338
+ expression,
1339
+ error: error.message
1340
+ };
1341
+ }
1342
+ }
1343
+ },
1344
+
1345
+ // 颜色转换工具方法
1346
+ colorUtils: {
1347
+ labToRGB: preciseLabToRGB,
1348
+ lchToLab: lchToLab,
1349
+ lchToRGB: function(L, C, H) {
1350
+ const lab = lchToLab(L, C, H);
1351
+ return preciseLabToRGB(lab.L, lab.a, lab.b);
1352
+ },
1353
+ labToP3: labToP3,
1354
+ lchToP3: function(L, C, H) {
1355
+ const lab = lchToLab(L, C, H);
1356
+ return labToP3(lab.L, lab.a, lab.b);
1357
+ },
1358
+ parseHexLab: function(hexString) {
1359
+ const match = hexString.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);
1360
+ if (!match) return null;
1361
+
1362
+ const L_hex = match[1];
1363
+ const A_hex = match[2];
1364
+ const B_hex = match[3];
1365
+
1366
+ const L = parseInt(L_hex, 16) / 255 * 100;
1367
+ const A = (parseInt(A_hex, 16) - 128) * 1.5;
1368
+ const B = (parseInt(B_hex, 16) - 128) * 1.5;
1369
+
1370
+ return preciseLabToRGB(L, A, B);
1371
+ },
1372
+ parseHexLch: function(hexString) {
1373
+ const match = hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);
1374
+ if (!match) return null;
1375
+
1376
+ const L_hex = match[1];
1377
+ const C_hex = match[2];
1378
+ const H_dec = match[3];
1379
+
1380
+ const L = parseInt(L_hex, 16) / 255 * 100;
1381
+ const C = parseInt(C_hex, 16) / 255 * 150;
1382
+ const H = parseInt(H_dec) / 100 * 360;
1383
+
1384
+ const lab = lchToLab(L, C, H);
1385
+ return preciseLabToRGB(lab.L, lab.a, lab.b);
1386
+ },
1387
+ /**
1388
+ * 生成颜色字符串(根据P3支持情况)
1389
+ * @param {number} L - Lab L值
1390
+ * @param {number} a - Lab a值
1391
+ * @param {number} b - Lab b值
1392
+ * @param {number|null} alpha - 透明度
1393
+ * @param {boolean} useP3 - 是否使用P3(自动检测)
1394
+ * @returns {string} CSS颜色字符串
1395
+ */
1396
+ generateColor: function(L, a, b, alpha = null, useP3 = true) {
1397
+ return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
1398
+ },
1399
+ /**
1400
+ * 解析CSS颜色字符串
1401
+ * @param {string} colorString - CSS颜色字符串
1402
+ * @returns {Object} 包含原始Lab值和颜色字符串的对象
1403
+ */
1404
+ parseColor: function(colorString) {
1405
+ try {
1406
+ // 尝试解析lab()函数格式
1407
+ const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
1408
+ if (labMatch) {
1409
+ let L = parseFloat(labMatch[1]);
1410
+ const A = parseFloat(labMatch[3]);
1411
+ const B = parseFloat(labMatch[4]);
1412
+ const alpha = labMatch[5] ?
1413
+ (labMatch[5].includes('%') ? parseFloat(labMatch[5]) / 100 : parseFloat(labMatch[5])) :
1414
+ null;
1415
+
1416
+ const colorStr = generateColorString(L, A, B, defaultConfig, alpha);
1417
+
1418
+ return {
1419
+ L, A, B, alpha,
1420
+ rgb: preciseLabToRGB(L, A, B),
1421
+ p3: labToP3(L, A, B),
1422
+ colorString: colorStr
1423
+ };
1424
+ }
1425
+
1426
+ // 尝试解析lch()函数格式
1427
+ const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
1428
+ if (lchMatch) {
1429
+ let L = parseFloat(lchMatch[1]);
1430
+ const C = parseFloat(lchMatch[3]);
1431
+ let H = parseFloat(lchMatch[4]);
1432
+ const alpha = lchMatch[6] ?
1433
+ (lchMatch[6].includes('%') ? parseFloat(lchMatch[6]) / 100 : parseFloat(lchMatch[6])) :
1434
+ null;
1435
+
1436
+ const lab = lchToLab(L, C, H);
1437
+ const colorStr = generateColorString(lab.L, lab.a, lab.b, defaultConfig, alpha);
1438
+
1439
+ return {
1440
+ L, C, H, alpha,
1441
+ lab: lab,
1442
+ rgb: preciseLabToRGB(lab.L, lab.a, lab.b),
1443
+ p3: labToP3(lab.L, lab.a, lab.b),
1444
+ colorString: colorStr
1445
+ };
1446
+ }
1447
+
1448
+ return null;
1449
+ } catch (error) {
1450
+ console.warn('无法解析颜色:', colorString, error);
1451
+ return null;
1452
+ }
1453
+ }
1452
1454
  }
1453
- }
1454
- }
1455
- };
1456
-
1455
+ };
1457
1456
 
1458
- // 创建一个可调用的主函数
1459
- const styimat = function(...args) {
1460
- // 检查是否是模板字符串调用(标签函数)
1461
- if (args.length > 1 || (args[0] && args[0].raw)) {
1462
- // 处理模板字符串
1463
- return handleTemplateTag(...args);
1464
- }
1457
+ // 创建一个可调用的主函数
1458
+ const styimat = function(...args) {
1459
+ // 检查是否是模板字符串调用(标签函数)
1460
+ if (args.length > 1 || (args[0] && args[0].raw)) {
1461
+ // 处理模板字符串
1462
+ return handleTemplateTag(...args);
1463
+ }
1465
1464
 
1466
- // 获取第一个参数
1467
- const firstArg = args[0];
1465
+ // 获取第一个参数
1466
+ const firstArg = args[0];
1468
1467
 
1469
- // 如果传入CSS文本,则编译并返回结果
1470
- if (typeof firstArg === 'string') {
1471
- return convert(firstArg, { ...defaultConfig, ...args[1] });
1472
- }
1468
+ // 如果传入CSS文本,则编译并返回结果
1469
+ if (typeof firstArg === 'string') {
1470
+ return convert(firstArg, { ...defaultConfig, ...args[1] });
1471
+ }
1473
1472
 
1474
- // 如果传入配置对象,则应用配置
1475
- if (typeof firstArg === 'object' && firstArg !== null) {
1476
- defaultConfig = { ...defaultConfig, ...firstArg };
1477
- return styimat;
1478
- }
1473
+ // 如果传入配置对象,则应用配置
1474
+ if (typeof firstArg === 'object' && firstArg !== null) {
1475
+ defaultConfig = { ...defaultConfig, ...firstArg };
1476
+ return styimat;
1477
+ }
1479
1478
 
1480
- // 如果没有参数,执行自动处理
1481
- if (args.length === 0) {
1482
- return apply();
1483
- }
1479
+ // 如果没有参数,执行自动处理
1480
+ if (args.length === 0) {
1481
+ return apply();
1482
+ }
1484
1483
 
1485
- return styimat;
1486
- };
1484
+ return styimat;
1485
+ };
1487
1486
 
1488
- // 处理模板字符串的函数
1489
- function handleTemplateTag(strings, ...values) {
1490
- // 拼接模板字符串
1491
- let cssText = strings[0];
1487
+ // 处理模板字符串的函数
1488
+ function handleTemplateTag(strings, ...values) {
1489
+ // 拼接模板字符串
1490
+ let cssText = strings[0];
1492
1491
 
1493
- for (let i = 0; i < values.length; i++) {
1494
- // 处理插值(支持字符串、数字、函数等)
1495
- const value = values[i];
1496
- let result = '';
1492
+ for (let i = 0; i < values.length; i++) {
1493
+ // 处理插值(支持字符串、数字、函数等)
1494
+ const value = values[i];
1495
+ let result = '';
1497
1496
 
1498
- if (typeof value === 'function') {
1499
- result = value();
1500
- } else if (Array.isArray(value)) {
1501
- result = value.join(' ');
1502
- } else {
1503
- result = String(value != null ? value : '');
1504
- }
1497
+ if (typeof value === 'function') {
1498
+ result = value();
1499
+ } else if (Array.isArray(value)) {
1500
+ result = value.join(' ');
1501
+ } else {
1502
+ result = String(value != null ? value : '');
1503
+ }
1505
1504
 
1506
- cssText += result + strings[i + 1];
1507
- }
1505
+ cssText += result + strings[i + 1];
1506
+ }
1508
1507
 
1509
- // 使用convert函数处理
1510
- return convert(cssText, defaultConfig);
1511
- }
1508
+ // 使用convert函数处理
1509
+ return convert(cssText, defaultConfig);
1510
+ }
1512
1511
 
1513
- // 将API的所有方法复制到主函数上
1514
- Object.assign(styimat, api);
1515
- Object.setPrototypeOf(styimat, Function.prototype);
1512
+ // 将API的所有方法复制到主函数上
1513
+ Object.assign(styimat, api);
1514
+ Object.setPrototypeOf(styimat, Function.prototype);
1516
1515
 
1517
- // 自动初始化
1518
- if (typeof window !== 'undefined') {
1519
- apply();
1520
- Object.defineProperty(window.HTMLElement.prototype, 'cssVar', {
1521
- get() {
1522
- const element = this;
1523
- return new Proxy({}, {
1524
- get(target, prop) {
1525
- const varName = prop.startsWith('--') ? prop : `--${prop}`;
1526
- return element.style.getPropertyValue(varName);
1527
- },
1528
-
1529
- set(target, prop, value) {
1530
- const varName = prop.startsWith('--') ? prop : `--${prop}`;
1531
- element.style.setProperty(varName, value);
1532
- return true;
1533
- }
1516
+ // 自动初始化
1517
+ if (typeof window !== 'undefined') {
1518
+ apply();
1519
+ Object.defineProperty(window.HTMLElement.prototype, 'cssVar', {
1520
+ get() {
1521
+ const element = this;
1522
+ return new Proxy(() => {}, {
1523
+ get(target, prop) {
1524
+ const varName = prop.startsWith('--') ? prop : `--${prop}`;
1525
+ return element.style.getPropertyValue(varName);
1526
+ },
1527
+
1528
+ set(target, prop, value) {
1529
+ const varName = prop.startsWith('--') ? prop : `--${prop}`;
1530
+ element.style.setProperty(varName, value);
1531
+ return true;
1532
+ },
1533
+
1534
+ apply(target, thisArg, argumentsList) {
1535
+ const prop = argumentsList[0];
1536
+ const value = argumentsList[1];
1537
+ const varName = prop.startsWith('--') ? prop : `--${prop}`;
1538
+ if (value === undefined) return element.style.getPropertyValue(varName);
1539
+ element.style.setProperty(varName, value);
1540
+ }
1541
+ });
1542
+ }
1534
1543
  });
1535
- }
1536
- });
1537
- }
1538
-
1539
- return styimat;
1544
+ }
1545
+
1546
+ return styimat;
1540
1547
  }));