render-markdown-codehighlight/generate-theme.cjs (151 lines of code) (raw):

/* * 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!');