| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| const fs = require('fs'); |
| const path = require('path'); |
| |
| const stylesDir = path.resolve(__dirname, 'node_modules/highlight.js/styles'); // Path to Highlight.js styles directory |
| const jsOutputFile = path.resolve(__dirname, 'themeStyles.js'); // Path to output JavaScript file |
| const goOutputFile = path.resolve(__dirname, 'theme_list.go'); // Path to output Go file |
| const colorInfoOutputFile = path.resolve(__dirname, 'themeColors.js'); // Path to output color information file |
| |
| // Read all CSS files from the styles directory |
| let themes = fs.readdirSync(stylesDir).filter(file => file.endsWith('.css')); |
| |
| // Prioritize .min.css files |
| const minifiedFiles = new Set(themes.filter(file => file.endsWith('.min.css')).map(file => file.replace('.min.css', ''))); |
| themes = themes.filter(file => { |
| const baseName = file.replace('.css', '').replace('.min', ''); |
| // Skip unminified versions if corresponding .min.css file exists |
| return !minifiedFiles.has(baseName) || file.endsWith('.min.css'); |
| }); |
| |
| // Group themes and classify by naming conventions |
| const themeMap = {}; |
| let themeList = []; |
| const themeColors = []; |
| let defaultDarkTheme = null; |
| let defaultLightTheme = null; |
| |
| const cssColorNames = { |
| black: '#000000', |
| white: '#ffffff', |
| navy: '#000080', |
| // Add more color names as needed if the background color in css is not defined in a standard method |
| }; |
| |
| // Convert color names (e.g., 'black', 'white') to hex values |
| function convertColorNameToHex(colorName) { |
| return cssColorNames[colorName.toLowerCase()] || null; |
| } |
| |
| // Normalize hex color code (e.g., convert shorthand hex to full length) |
| function normalizeHexColor(hexColor) { |
| hexColor = hexColor.startsWith('#') ? hexColor.slice(1) : hexColor; |
| if (hexColor.length === 3) { |
| hexColor = hexColor.split('').map(char => char + char).join(''); |
| } |
| return hexColor; |
| } |
| |
| // Determine if a theme is dark based on its background color |
| function isDarkTheme(color) { |
| if (!color.startsWith('#') && !color.startsWith('rgb')) { |
| const hexColor = convertColorNameToHex(color); |
| if (hexColor) { |
| color = hexColor; |
| } else { |
| return false; |
| } |
| } |
| const hexColor = normalizeHexColor(color); |
| const rgb = parseInt(hexColor, 16); |
| const r = (rgb >> 16) & 0xff; |
| const g = (rgb >> 8) & 0xff; |
| const b = (rgb >> 0) & 0xff; |
| const brightness = (r * 299 + g * 587 + b * 114) / 1000; // Calculate brightness based on RGB values |
| return brightness < 128; // Dark theme if brightness is below the threshold |
| } |
| |
| // Process each theme file |
| themes.forEach(file => { |
| const themeName = file.replace('.css', '').replace('.min', ''); |
| const [base, ...variantParts] = themeName.split('-'); |
| const variant = variantParts.join('-'); |
| |
| if (!themeMap[base]) { |
| themeMap[base] = {}; |
| } |
| |
| let isDark = false; |
| let backgroundColor = null; |
| |
| // Identify light or dark themes based on the variant name |
| if (variant.includes('light')) { |
| if (!themeMap[base].light || themeMap[base].light.length > file.length) { |
| themeMap[base].light = `() => import('highlight.js/styles/${file}?inline')`; |
| if (!defaultLightTheme) { |
| defaultLightTheme = themeMap[base].light; |
| } |
| } |
| } else if (variant.includes('dark')) { |
| if (!themeMap[base].dark || themeMap[base].dark.length > file.length) { |
| themeMap[base].dark = `() => import('highlight.js/styles/${file}?inline')`; |
| if (!defaultDarkTheme) { |
| defaultDarkTheme = themeMap[base].dark; |
| } |
| } |
| } else { |
| // Extract background color from CSS content and determine if it's a dark theme |
| const cssContent = fs.readFileSync(path.resolve(stylesDir, file), 'utf-8'); |
| const backgroundMatch = cssContent.match(/\.hljs\s*{[^}]*?\s*background(?:-color)?:\s*(#[0-9a-fA-F]{3,6}|rgb\([^)]+\)|[a-zA-Z]+|url\([^)]+\))/i); |
| backgroundColor = backgroundMatch ? backgroundMatch[1].trim() : null; |
| |
| if (backgroundColor) { |
| if (backgroundColor.startsWith('url')) { |
| backgroundColor = null; |
| } else if (backgroundColor.startsWith('#')) { |
| isDark = isDarkTheme(backgroundColor); |
| } else if (backgroundColor.startsWith('rgb')) { |
| const rgbValues = backgroundColor.match(/\d+/g).map(Number); |
| const brightness = (rgbValues[0] * 299 + rgbValues[1] * 587 + rgbValues[2] * 114) / 1000; |
| isDark = brightness < 128; |
| } else { |
| isDark = isDarkTheme(backgroundColor); |
| } |
| } |
| |
| // Assign the theme to light or dark based on the background color |
| if (isDark) { |
| if (!themeMap[base].dark || themeMap[base].dark.length > file.length) { |
| themeMap[base].dark = `() => import('highlight.js/styles/${file}?inline')`; |
| if (!defaultDarkTheme) { |
| defaultDarkTheme = themeMap[base].dark; |
| } |
| } |
| } else { |
| if (!themeMap[base].light || themeMap[base].light.length > file.length) { |
| themeMap[base].light = `() => import('highlight.js/styles/${file}?inline')`; |
| if (!defaultLightTheme) { |
| defaultLightTheme = themeMap[base].light; |
| } |
| } |
| } |
| } |
| |
| // Add theme to the theme list |
| if (!themeList.includes(base)) { |
| themeList.push(base); |
| } |
| |
| // Store theme color information |
| if (backgroundColor) { |
| themeColors.push({ |
| theme: base, |
| variant: isDark ? 'dark' : 'light', |
| color: backgroundColor |
| }); |
| } |
| }); |
| |
| // Classify themes based on the presence of light and dark variants |
| themeList = themeList.map(base => { |
| if (themeMap[base].light && !themeMap[base].dark) { |
| return `${base}-light`; |
| } else if (!themeMap[base].light && themeMap[base].dark) { |
| return `${base}-dark`; |
| } else if (themeMap[base].light && themeMap[base].dark) { |
| return `${base}-all`; |
| } else { |
| return base; |
| } |
| }); |
| |
| // Assign default light and dark themes if missing |
| Object.keys(themeMap).forEach(base => { |
| if (!themeMap[base].dark && defaultDarkTheme) { |
| themeMap[base].dark = defaultDarkTheme; |
| } |
| if (!themeMap[base].light && defaultLightTheme) { |
| themeMap[base].light = defaultLightTheme; |
| } |
| }); |
| |
| // Generate the JavaScript output for theme styles |
| const jsOutput = `export const themeStyles = {\n${Object.entries(themeMap) |
| .map(([theme, variants]) => |
| ` ${JSON.stringify(theme)}: {\n light: ${variants.light},\n dark: ${variants.dark}\n }` |
| ).join(',\n')}\n};`; |
| |
| fs.writeFileSync(jsOutputFile, jsOutput); // Write the theme styles to JavaScript file |
| |
| // Generate the Go output for theme list |
| const goOutput = ` |
| package render_markdown_codehighlight |
| |
| var ThemeList = []string{ |
| ${themeList.map(theme => `"${theme}"`).join(",\n ")}, |
| } |
| `; |
| |
| fs.writeFileSync(goOutputFile, goOutput); // Write the theme list to Go file |
| |
| console.log('Theme styles, Go theme list, and color information generated successfully!'); |