返回 presentation-ai
cssVariableResolver.ts
根目录 / src / components / presentation / export / cssVariableResolver.ts
1 /**
2 * Utility to resolve CSS variables to their computed values
3 * Critical for extracting actual colors, fonts, and other styles from the presentation theme
4 */
5
6 import { type PresentationStyles } from "./types";
7
8 /**
9 * Resolve a single CSS variable value from an element
10 * @param varName - CSS variable name (with or without --)
11 * @param element - Element to get computed style from
12 * @returns Resolved value as a hex color or string
13 */
14 function resolveCssVariable(varName: string, element: Element): string {
15 const computed = getComputedStyle(element);
16 const cleanName = varName.startsWith("--") ? varName : `--${varName}`;
17 const value = computed.getPropertyValue(cleanName).trim();
18
19 // If value is empty, return a fallback
20 if (!value) {
21 return "";
22 }
23
24 return value;
25 }
26
27 /**
28 * Convert any color format to hex
29 * @param color - Color in any CSS format (rgb, rgba, hsl, hex, named)
30 * @returns Hex color string (without #)
31 */
32 function colorToHex(color: string): string {
33 if (!color) return "000000";
34
35 // Already hex
36 if (color.startsWith("#")) {
37 return color.slice(1).toUpperCase();
38 }
39
40 // Named colors or other formats - use canvas to convert
41 const canvas = document.createElement("canvas");
42 canvas.width = 1;
43 canvas.height = 1;
44 const ctx = canvas.getContext("2d");
45 if (!ctx) return "000000";
46
47 ctx.fillStyle = color;
48 ctx.fillRect(0, 0, 1, 1);
49 const data = ctx.getImageData(0, 0, 1, 1).data;
50
51 const toHex = (n: number) => n.toString(16).padStart(2, "0");
52 return `${toHex(data[0]!)}${toHex(data[1]!)}${toHex(data[2]!)}`.toUpperCase();
53 }
54
55 /**
56 * Extract all presentation styles from a slide element
57 * This resolves all CSS variables used in the presentation theme
58 */
59 export function extractPresentationStyles(
60 slideElement: Element,
61 ): PresentationStyles {
62 const getVar = (name: string, fallback: string = "#000000"): string => {
63 const value = resolveCssVariable(name, slideElement);
64 return colorToHex(value || fallback);
65 };
66
67 const getFontVar = (name: string, fallback: string = "Inter"): string => {
68 const value = resolveCssVariable(name, slideElement);
69 // Font values might have quotes, remove them
70 return (value || fallback).replace(/['"]/g, "").trim();
71 };
72
73 const getStringVar = (name: string, fallback: string = ""): string => {
74 const value = resolveCssVariable(name, slideElement);
75 return value || fallback;
76 };
77
78 const maskClipPath = getStringVar("--presentation-mask-clip-path");
79 const maskImage = getStringVar("--presentation-mask-image");
80
81 // Extract background image from the slide element
82 const bgImage = getComputedStyle(slideElement).backgroundImage;
83 let backgroundImageUrl: string | undefined;
84 if (bgImage && bgImage !== "none") {
85 const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
86 if (urlMatch?.[1]) {
87 backgroundImageUrl = urlMatch[1];
88 }
89 }
90
91 return {
92 // Colors
93 primaryColor: getVar("--presentation-primary", "#3B82F6"),
94 secondaryColor: getVar("--presentation-secondary", "#1F2937"),
95 accentColor: getVar("--presentation-accent", "#60A5FA"),
96 backgroundColor: getVar("--presentation-background", "#FFFFFF"),
97 textColor: getVar("--presentation-text", "#1F2937"),
98 headingColor: getVar("--presentation-heading", "#111827"),
99 cardBackground: getVar("--presentation-card-background", "#F3F4F6"),
100 smartLayoutColor: getVar("--presentation-smart-layout", "#3B82F6"),
101
102 // Fonts
103 headingFont: getFontVar("--presentation-heading-font", "Inter"),
104 bodyFont: getFontVar("--presentation-body-font", "Inter"),
105
106 cardBorderRadius: getStringVar("--presentation-card-border-radius", "1rem"),
107 slideBorderRadius: getStringVar(
108 "--presentation-slide-border-radius",
109 "0px",
110 ),
111 buttonBorderRadius: getStringVar(
112 "--presentation-button-border-radius",
113 "0.5rem",
114 ),
115
116 // Shadows
117 cardShadow: getStringVar("--presentation-card-shadow", "none"),
118 buttonShadow: getStringVar("--presentation-button-shadow", "none"),
119 slideShadow: getStringVar("--presentation-slide-shadow", "none"),
120
121 transition: getStringVar("--presentation-transition", "none"),
122 mask:
123 maskClipPath || maskImage
124 ? {
125 clipPath: maskClipPath || undefined,
126 maskImage: maskImage || undefined,
127 maskSize: getStringVar("--presentation-mask-size") || undefined,
128 maskPosition:
129 getStringVar("--presentation-mask-position") || undefined,
130 maskRepeat: getStringVar("--presentation-mask-repeat") || undefined,
131 }
132 : undefined,
133 backgroundImageUrl,
134 };
135 }
136
137 /**
138 * Extract text styles from a DOM element
139 */
140 export function extractTextStyles(element: Element): {
141 fontFamily: string;
142 fontSize: number;
143 fontWeight: string | number;
144 color: string;
145 backgroundColor?: string;
146 textAlign?: "left" | "center" | "right" | "justify";
147 textDecoration?: string;
148 fontStyle?: string;
149 lineHeight?: number;
150 } {
151 const computed = getComputedStyle(element);
152
153 return {
154 fontFamily:
155 computed.fontFamily.replace(/['"]/g, "").split(",")[0]?.trim() || "Inter",
156 fontSize: parseFloat(computed.fontSize) || 16,
157 fontWeight: computed.fontWeight,
158 color: colorToHex(computed.color),
159 backgroundColor:
160 computed.backgroundColor !== "rgba(0, 0, 0, 0)"
161 ? colorToHex(computed.backgroundColor)
162 : undefined,
163 textAlign: computed.textAlign as "left" | "center" | "right" | "justify",
164 textDecoration:
165 computed.textDecorationLine !== "none"
166 ? computed.textDecorationLine
167 : undefined,
168 fontStyle: computed.fontStyle !== "normal" ? computed.fontStyle : undefined,
169 lineHeight: parseFloat(computed.lineHeight) || undefined,
170 };
171 }
172
172 lines TYPESCRIPT