helper.ts 5.73 KB
// Shared methods of svg and svg-ssr

import { MatrixArray } from '../core/matrix';
import Transformable, { TransformProp } from '../core/Transformable';
import { RADIAN_TO_DEGREE, retrieve2, logError, isFunction } from '../core/util';
import Displayable from '../graphic/Displayable';
import { GradientObject } from '../graphic/Gradient';
import { LinearGradientObject } from '../graphic/LinearGradient';
import Path from '../graphic/Path';
import { ImagePatternObject, PatternObject, SVGPatternObject } from '../graphic/Pattern';
import { RadialGradientObject } from '../graphic/RadialGradient';
import { parse } from '../tool/color';
import env from '../core/env';

const mathRound = Math.round;

export function normalizeColor(color: string): { color: string; opacity: number; } {
    let opacity;
    if (!color || color === 'transparent') {
        color = 'none';
    }
    else if (typeof color === 'string' && color.indexOf('rgba') > -1) {
        const arr = parse(color);
        if (arr) {
            // TODO use hex?
            color = 'rgb(' + arr[0] + ',' + arr[1] + ',' + arr[2] + ')';
            opacity = arr[3];
        }
    }
    return {
        color,
        opacity: opacity == null ? 1 : opacity
    };
}
const EPSILON = 1e-4;
export function isAroundZero(transform: number) {
    return transform < EPSILON && transform > -EPSILON;
}

export function round3(transform: number) {
    return mathRound(transform * 1e3) / 1e3;
}
export function round4(transform: number) {
    return mathRound(transform * 1e4) / 1e4;
}
export function round1(transform: number) {
    return mathRound(transform * 10) / 10;
}

export function getMatrixStr(m: MatrixArray) {
    return 'matrix('
        // Avoid large string of matrix
        // PENDING If have precision issue when scaled
        + round3(m[0]) + ','
        + round3(m[1]) + ','
        + round3(m[2]) + ','
        + round3(m[3]) + ','
        + round4(m[4]) + ','
        + round4(m[5])
        + ')';
}

export const TEXT_ALIGN_TO_ANCHOR = {
    left: 'start',
    right: 'end',
    center: 'middle',
    middle: 'middle'
};

export function adjustTextY(y: number, lineHeight: number, textBaseline: CanvasTextBaseline): number {
    // TODO Other baselines.
    if (textBaseline === 'top') {
        y += lineHeight / 2;
    }
    else if (textBaseline === 'bottom') {
        y -= lineHeight / 2;
    }
    return y;
}


export function hasShadow(style: Displayable['style']) {
    // TODO: textBoxShadowBlur is not supported yet
    return style
        && (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY);
}

export function getShadowKey(displayable: Displayable) {
    const style = displayable.style;
    const globalScale = displayable.getGlobalScale();
    return [
        style.shadowColor,
        (style.shadowBlur || 0).toFixed(2), // Reduce the precision
        (style.shadowOffsetX || 0).toFixed(2),
        (style.shadowOffsetY || 0).toFixed(2),
        globalScale[0],
        globalScale[1]
    ].join(',');
}

export function getClipPathsKey(clipPaths: Path[]) {
    let key: number[] = [];
    if (clipPaths) {
        for (let i = 0; i < clipPaths.length; i++) {
            const clipPath = clipPaths[i];
            key.push(clipPath.id);
        }
    }
    return key.join(',');
}

export function isImagePattern(val: any): val is ImagePatternObject {
    return val && (!!(val as ImagePatternObject).image);
}
export function isSVGPattern(val: any): val is SVGPatternObject {
    return val && (!!(val as SVGPatternObject).svgElement);
}
export function isPattern(val: any): val is PatternObject {
    return isImagePattern(val) || isSVGPattern(val);
}

export function isLinearGradient(val: GradientObject): val is LinearGradientObject {
    return val.type === 'linear';
}

export function isRadialGradient(val: GradientObject): val is RadialGradientObject {
    return val.type === 'radial';
}

export function isGradient(val: any): val is GradientObject {
    return val && (
        (val as GradientObject).type === 'linear'
        || (val as GradientObject).type === 'radial'
    );
}

export function getIdURL(id: string) {
    return `url(#${id})`;
}

export function getPathPrecision(el: Path) {
    const scale = el.getGlobalScale();
    const size = Math.max(scale[0], scale[1]);
    return Math.max(Math.ceil(Math.log(size) / Math.log(10)), 1);
}

export function getSRTTransformString(
    transform: Partial<Pick<Transformable, TransformProp>>
) {
    const x = transform.x || 0;
    const y = transform.y || 0;
    const rotation = (transform.rotation || 0) * RADIAN_TO_DEGREE;
    const scaleX = retrieve2(transform.scaleX, 1);
    const scaleY = retrieve2(transform.scaleY, 1);
    const skewX = transform.skewX || 0;
    const skewY = transform.skewY || 0;
    const res = [];
    if (x || y) {
        // TODO not using px unit?
        res.push(`translate(${x}px,${y}px)`);
    }
    if (rotation) {
        res.push(`rotate(${rotation})`);
    }
    if (scaleX !== 1 || scaleY !== 1) {
        res.push(`scale(${scaleX},${scaleY})`);
    }
    if (skewX || skewY) {
        res.push(`skew(${mathRound(skewX * RADIAN_TO_DEGREE)}deg, ${mathRound(skewY * RADIAN_TO_DEGREE)}deg)`);
    }

    return res.join(' ');
}

export const encodeBase64 = (function () {
    if (env.hasGlobalWindow && isFunction(window.btoa)) {
        return function (str: string) {
            return window.btoa(unescape(encodeURIComponent(str)));
        };
    }
    if (typeof Buffer !== 'undefined') {
        return function (str: string) {
            return Buffer.from(str).toString('base64');
        };
    }
    return function (str: string): string {
        if (process.env.NODE_ENV !== 'production') {
            logError('Base64 isn\'t natively supported in the current environment.');
        }
        return null;
    };
})();