import { IElement } from '@antv/g-base'; import { isString, each, isArray } from './util'; const regexLG = /^l\s*\(\s*([\d.]+)\s*\)\s*(.*)/i; const regexRG = /^r\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)\s*(.*)/i; const regexPR = /^p\s*\(\s*([axyn])\s*\)\s*(.*)/i; const regexColorStop = /[\d.]+:(#[^\s]+|[^\)]+\))/gi; function addStop(steps, gradient) { const arr: string[] = steps.match(regexColorStop); each(arr, (item) => { const itemArr = item.split(':'); gradient.addColorStop(itemArr[0], itemArr[1]); }); } /** * 将边和填充设置的颜色转换成线性渐变对象 * @param {CanvasRenderingContext2D} context canvas 上下文 * @param {IElement} element 图形元素 * @param {string} gradientStr 颜色 * @returns {any} 渐变对象 */ export function parseLineGradient(context: CanvasRenderingContext2D, element: IElement, gradientStr: string) { const arr = regexLG.exec(gradientStr); const angle = (parseFloat(arr[1]) % 360) * (Math.PI / 180); const steps = arr[2]; const box = element.getBBox(); let start; let end; if (angle >= 0 && angle < (1 / 2) * Math.PI) { start = { x: box.minX, y: box.minY, }; end = { x: box.maxX, y: box.maxY, }; } else if ((1 / 2) * Math.PI <= angle && angle < Math.PI) { start = { x: box.maxX, y: box.minY, }; end = { x: box.minX, y: box.maxY, }; } else if (Math.PI <= angle && angle < (3 / 2) * Math.PI) { start = { x: box.maxX, y: box.maxY, }; end = { x: box.minX, y: box.minY, }; } else { start = { x: box.minX, y: box.maxY, }; end = { x: box.maxX, y: box.minY, }; } const tanTheta = Math.tan(angle); const tanTheta2 = tanTheta * tanTheta; const x = (end.x - start.x + tanTheta * (end.y - start.y)) / (tanTheta2 + 1) + start.x; const y = (tanTheta * (end.x - start.x + tanTheta * (end.y - start.y))) / (tanTheta2 + 1) + start.y; const gradient = context.createLinearGradient(start.x, start.y, x, y); addStop(steps, gradient); return gradient; } /** * 将边和填充设置的颜色转换成圆形渐变对象 * @param {CanvasRenderingContext2D} context canvas 上下文 * @param {IElement} element 图形元素 * @param {string} gradientStr 颜色 * @returns {any} 渐变对象 */ export function parseRadialGradient(context: CanvasRenderingContext2D, element: IElement, gradientStr: string) { const arr = regexRG.exec(gradientStr); const fx = parseFloat(arr[1]); const fy = parseFloat(arr[2]); const fr = parseFloat(arr[3]); const steps = arr[4]; // 环半径为0时,默认无渐变,取渐变序列的最后一个颜色 if (fr === 0) { const colors = steps.match(regexColorStop); return colors[colors.length - 1].split(':')[1]; } const box = element.getBBox(); const width = box.maxX - box.minX; const height = box.maxY - box.minY; const r = Math.sqrt(width * width + height * height) / 2; const gradient = context.createRadialGradient( box.minX + width * fx, box.minY + height * fy, 0, box.minX + width / 2, box.minY + height / 2, fr * r ); addStop(steps, gradient); return gradient; } /** * 边和填充设置的颜色转换成 pattern * @param {CanvasRenderingContext2D} context canvas 上下文 * @param {IElement} element 图形元素 * @param {string} patternStr 生成 pattern 的字符串 */ export function parsePattern(context: CanvasRenderingContext2D, element: IElement, patternStr: string) { // 在转换过程中进行了缓存 if (element.get('patternSource') && element.get('patternSource') === patternStr) { return element.get('pattern'); } let pattern; let img; const arr = regexPR.exec(patternStr); let repeat = arr[1]; const source = arr[2]; // Function to be called when pattern loads function onload() { // Create pattern pattern = context.createPattern(img, repeat); element.set('pattern', pattern); // be a cache element.set('patternSource', patternStr); } switch (repeat) { case 'a': repeat = 'repeat'; break; case 'x': repeat = 'repeat-x'; break; case 'y': repeat = 'repeat-y'; break; case 'n': repeat = 'no-repeat'; break; default: repeat = 'no-repeat'; } img = new Image(); // If source URL is not a data URL if (!source.match(/^data:/i)) { // Set crossOrigin for this image img.crossOrigin = 'Anonymous'; } img.src = source; if (img.complete) { onload(); } else { img.onload = onload; // Fix onload() bug in IE9 img.src = img.src; } return pattern; } export function parseStyle(context: CanvasRenderingContext2D, element: IElement, color: string | CanvasPattern) { const bbox = element.getBBox(); if (isNaN(bbox.x) || isNaN(bbox.y) || isNaN(bbox.width) || isNaN(bbox.height)) { return color; } if (isString(color)) { if (color[1] === '(' || color[2] === '(') { if (color[0] === 'l') { // regexLG.test(color) return parseLineGradient(context, element, color); } if (color[0] === 'r') { // regexRG.test(color) return parseRadialGradient(context, element, color); } if (color[0] === 'p') { // regexPR.test(color) return parsePattern(context, element, color); } } return color; } if (color instanceof CanvasPattern) { return color; } } export function parseRadius(radius) { let r1 = 0; let r2 = 0; let r3 = 0; let r4 = 0; if (isArray(radius)) { if (radius.length === 1) { r1 = r2 = r3 = r4 = radius[0]; } else if (radius.length === 2) { r1 = r3 = radius[0]; r2 = r4 = radius[1]; } else if (radius.length === 3) { r1 = radius[0]; r2 = r4 = radius[1]; r3 = radius[2]; } else { r1 = radius[0]; r2 = radius[1]; r3 = radius[2]; r4 = radius[3]; } } else { r1 = r2 = r3 = r4 = radius; } return [r1, r2, r3, r4]; }