UNPKG

@visactor/vtable

Version:

canvas table width high performance

202 lines (176 loc) 6.51 kB
import { isNil, isNumber, isString } from "@visactor/vutils"; import { array } from "./helper"; const TYPE_PAREN = 0, TYPE_UNIT = 1, TYPE_OPERATOR = 2, TYPE_NUMBER = 3, NODE_TYPE_UNIT = 10, NODE_TYPE_BINARY_EXPRESSION = 11, NODE_TYPE_NUMBER = 12, TABULATION = 9, CARRIAGE_RETURN = 13, LINE_FEED = 10, FORM_FEED = 12, SPACE = 32, PERCENT = 37, FULL_STOP = 46, DIGIT_0 = 48, DIGIT_9 = 57, LATIN_CAPITAL_A = 65, LATIN_CAPITAL_Z = 90, LATIN_SMALL_A = 97, LATIN_SMALL_Z = 122; function isUpperLetter(cp) { return cp >= LATIN_CAPITAL_A && cp <= LATIN_CAPITAL_Z; } function isLowerLetter(cp) { return cp >= LATIN_SMALL_A && cp <= LATIN_SMALL_Z; } function isLetter(cp) { return isLowerLetter(cp) || isUpperLetter(cp); } function isWhitespace(cp) { return cp === TABULATION || cp === LINE_FEED || cp === FORM_FEED || cp === CARRIAGE_RETURN || cp === SPACE; } function isDigit(cp) { return cp >= DIGIT_0 && cp <= DIGIT_9; } function isDot(cp) { return cp === FULL_STOP; } function isUnit(cp) { return isLetter(cp) || cp === PERCENT; } function createError(calc) { return new Error(`calc parse error: ${calc}`); } function tokenize(calc) { const exp = calc.replace(/calc\(/g, "(").trim(), tokens = [], len = exp.length; for (let index = 0; index < len; index++) { const c = exp[index], cp = c.charCodeAt(0); if ("(" === c || ")" === c) tokens.push({ value: c, type: TYPE_PAREN }); else if ("*" === c || "/" === c) tokens.push({ value: c, type: TYPE_OPERATOR }); else if ("+" === c || "-" === c) index = parseSign(c, index + 1) - 1; else if (isDigit(cp) || isDot(cp)) index = parseNum(c, index + 1) - 1; else if (!isWhitespace(cp)) throw createError(calc); } function parseSign(sign, start) { if (start < len) { const c = exp[start], cp = c.charCodeAt(0); if (isDigit(cp) || isDot(cp)) return parseNum(sign + c, start + 1); } return tokens.push({ value: sign, type: TYPE_OPERATOR }), start; } function parseNum(num, start) { let index = start; for (;index < len; index++) { const c = exp[index], cp = c.charCodeAt(0); if (isDigit(cp)) num += c; else { if ("." !== c) { if (isUnit(cp)) return parseUnit(num, c, index + 1); break; } if (num.indexOf(".") >= 0) throw createError(calc); num += c; } } if ("." === num) throw createError(calc); return tokens.push({ value: parseFloat(num), type: TYPE_NUMBER }), index; } function parseUnit(num, unit, start) { let index = start; for (;index < len; index++) { const c = exp[index]; if (!isUnit(c.charCodeAt(0))) break; unit += c; } return tokens.push({ value: parseFloat(num), unit: unit, type: TYPE_UNIT }), index; } return tokens; } const PRECEDENCE = { "*": 3, "/": 3, "+": 2, "-": 2 }; function lex(tokens, calc) { function buildBinaryExpNode(stack) { const right = stack.pop(), op = stack.pop(), left = stack.pop(); if (!(left && left.nodeType && op && op.type === TYPE_OPERATOR && right && right.nodeType)) throw createError(calc); return { nodeType: NODE_TYPE_BINARY_EXPRESSION, left: left, op: op, right: right }; } const stack = []; for (;tokens.length; ) { const token = tokens.shift(); if (token.type === TYPE_PAREN && "(" === token.value) { let deep = 0; const closeIndex = array.findIndex(tokens, (t => { if (t.type === TYPE_PAREN && "(" === t.value) deep++; else if (t.type === TYPE_PAREN && ")" === t.value) { if (!deep) return !0; deep--; } return !1; })); if (-1 === closeIndex) throw createError(calc); stack.push(lex(tokens.splice(0, closeIndex), calc)), tokens.shift(); } else if (token.type === TYPE_OPERATOR) { if (stack.length >= 3) { const beforeOp = stack[stack.length - 2].value; PRECEDENCE[token.value] <= PRECEDENCE[beforeOp] && stack.push(buildBinaryExpNode(stack)); } stack.push(token); } else if (token.type === TYPE_UNIT) { const {value: num, unit: unit} = token; stack.push({ nodeType: NODE_TYPE_UNIT, value: num, unit: unit }); } else token.type === TYPE_NUMBER && stack.push({ nodeType: NODE_TYPE_NUMBER, value: token.value }); } for (;stack.length > 1; ) stack.push(buildBinaryExpNode(stack)); return stack[0]; } function parse(calcStr) { return lex(tokenize(calcStr), calcStr); } function calcNode(node, context) { if (node.nodeType === NODE_TYPE_BINARY_EXPRESSION) { const left = calcNode(node.left, context), right = calcNode(node.right, context); switch (node.op.value) { case "+": return left + right; case "-": return left - right; case "*": return left * right; case "/": return left / right; default: throw new Error(`calc error. unknown operator: ${node.op.value}`); } } else if (node.nodeType === NODE_TYPE_UNIT) switch (node.unit) { case "%": return node.value * context.full / 100; case "px": return node.value; default: throw new Error(`calc error. unknown unit: ${node.unit}`); } else if (node.nodeType === NODE_TYPE_NUMBER) return node.value; throw new Error("calc error."); } function toPxInternal(value, context) { return calcNode(parse(value), context); } export function toPx(value, context) { return "string" == typeof value ? toPxInternal(value.trim(), context) : value - 0; } export function couldBeValidNumber(v) { return !isNil(v) && (!!isNumber(v) || +v == +v); } export function isPercent(v) { return !!isString(v) && (!!v.endsWith("%") && couldBeValidNumber(v.substring(0, v.length - 1))); } //# sourceMappingURL=calc.js.map