molstar
Version:
A comprehensive macromolecular library.
137 lines (136 loc) • 5.17 kB
JavaScript
/**
* Copyright (c) 2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Zach Charlop-Powers <zach.charlop.powers@gmail.com>
*/
import { Color } from './color.js';
import { ColorNames } from './names.js';
const hexColorRegex = /^#([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$/i;
const rgbColorRegex = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i;
export function decodeColor(colorString) {
if (colorString === undefined || colorString === null)
return undefined;
let result;
if (hexColorRegex.test(colorString)) {
if (colorString.length === 4) {
// convert short form to full form (#f0f -> #ff00ff)
colorString = `#${colorString[1]}${colorString[1]}${colorString[2]}${colorString[2]}${colorString[3]}${colorString[3]}`;
}
else if (colorString.length === 5) {
// convert short form with alpha to full form, ignoring alpha (#f0fa -> #ff00ff)
colorString = `#${colorString[1]}${colorString[1]}${colorString[2]}${colorString[2]}${colorString[3]}${colorString[3]}`;
}
else if (colorString.length === 9) {
// strip alpha channel from 8-character hex (#ff00ffaa -> #ff00ff)
colorString = colorString.substring(0, 7);
}
result = Color.fromHexStyle(colorString);
if (result !== undefined && !isNaN(result))
return result;
}
result = ColorNames[colorString.toLowerCase()];
if (result !== undefined)
return result;
const rgbMatch = rgbColorRegex.exec(colorString);
if (rgbMatch) {
const r = parseInt(rgbMatch[1], 10);
const g = parseInt(rgbMatch[2], 10);
const b = parseInt(rgbMatch[3], 10);
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
return Color.fromRgb(r, g, b);
}
}
return undefined;
}
export function getColorGradientBanded(colors) {
const n = colors.length;
const styles = [];
const hasOffsets = colors.every(c => Array.isArray(c));
if (hasOffsets) {
const off = [...colors];
// 0 colors present
if (!off[0]) {
return 'linear-gradient(to right, #000 0%, #000 100%)';
}
off.sort((a, b) => a[1] - b[1]);
styles.push(`${Color.toStyle(off[0][0])} ${(100 * off[0][1]).toFixed(2)}%`);
for (let i = 0, il = off.length - 1; i < il; ++i) {
const [c0, o0] = off[i];
const [c1, o1] = off[i + 1];
const o = o0 + (o1 - o0) / 2;
styles.push(`${Color.toStyle(c0)} ${(100 * o).toFixed(2)}%`, `${Color.toStyle(c1)} ${(100 * o).toFixed(2)}%`);
}
styles.push(`${Color.toStyle(off[off.length - 1][0])} ${(100 * off[off.length - 1][1]).toFixed(2)}%`);
}
else {
styles.push(`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`);
for (let i = 1, il = n - 1; i < il; ++i) {
styles.push(`${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`, `${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`);
}
styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
}
return `linear-gradient(to right, ${styles.join(', ')})`;
}
export function getColorGradient(colors) {
if (colors.length === 0)
return 'linear-gradient(to right, #000 0%, #000 100%)';
const hasOffsets = colors.every(c => Array.isArray(c));
let styles;
if (hasOffsets) {
const off = [...colors];
off.sort((a, b) => a[1] - b[1]);
styles = off.map(c => colorEntryToStyle(c, true));
}
else {
styles = colors.map(c => colorEntryToStyle(c));
}
return `linear-gradient(to right, ${styles.join(', ')})`;
}
function colorEntryToStyle(e, includeOffset = false) {
if (Array.isArray(e)) {
if (includeOffset)
return `${Color.toStyle(e[0])} ${(100 * e[1]).toFixed(2)}%`;
return Color.toStyle(e[0]);
}
return Color.toStyle(e);
}
export function parseColorList(input, separator = /,/) {
const ret = [];
const trimmed = input.replace(/\s+/g, '');
let tokenStart = 0;
let bracketLevel = 0;
for (let i = 0, il = trimmed.length; i < il; ++i) {
const c = trimmed[i];
if (c === '(') {
bracketLevel++;
continue;
}
else if (c === ')') {
if (bracketLevel > 0) {
bracketLevel--;
}
continue;
}
if (bracketLevel > 0)
continue;
if (!separator.test(c)) {
continue;
}
const color = trimmed.substring(tokenStart, i);
tokenStart = i + 1;
const decoded = decodeColor(color);
if (decoded !== undefined) {
ret.push(decoded);
}
}
if (tokenStart < trimmed.length) {
const color = trimmed.substring(tokenStart);
const decoded = decodeColor(color);
console.log(color, decoded);
if (decoded !== undefined) {
ret.push(decoded);
}
}
return ret;
}