UNPKG

@aplus-frontend/antdv

Version:

Vue basic component library maintained based on ant-design-vue

320 lines (319 loc) 11.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.QRCodeSVG = exports.QRCodeCanvas = void 0; var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2")); var _vue = require("vue"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _interface = require("./interface"); var _qrcodegen = _interopRequireDefault(require("./qrcodegen")); const ERROR_LEVEL_MAP = { L: _qrcodegen.default.QrCode.Ecc.LOW, M: _qrcodegen.default.QrCode.Ecc.MEDIUM, Q: _qrcodegen.default.QrCode.Ecc.QUARTILE, H: _qrcodegen.default.QrCode.Ecc.HIGH }; const DEFAULT_SIZE = 128; const DEFAULT_LEVEL = 'L'; const DEFAULT_BGCOLOR = '#FFFFFF'; const DEFAULT_FGCOLOR = '#000000'; const DEFAULT_INCLUDEMARGIN = false; const SPEC_MARGIN_SIZE = 4; const DEFAULT_MARGIN_SIZE = 0; // This is *very* rough estimate of max amount of QRCode allowed to be covered. // It is "wrong" in a lot of ways (area is a terrible way to estimate, it // really should be number of modules covered), but if for some reason we don't // get an explicit height or width, I'd rather default to something than throw. const DEFAULT_IMG_SCALE = 0.1; function generatePath(modules) { let margin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; const ops = []; modules.forEach(function (row, y) { let start = null; row.forEach(function (cell, x) { if (!cell && start !== null) { // M0 0h7v1H0z injects the space with the move and drops the comma, // saving a char per operation ops.push(`M${start + margin} ${y + margin}h${x - start}v1H${start + margin}z`); start = null; return; } // end of row, clean up or skip if (x === row.length - 1) { if (!cell) { // We would have closed the op above already so this can only mean // 2+ light modules in a row. return; } if (start === null) { // Just a single dark module. ops.push(`M${x + margin},${y + margin} h1v1H${x + margin}z`); } else { // Otherwise finish the current line. ops.push(`M${start + margin},${y + margin} h${x + 1 - start}v1H${start + margin}z`); } return; } if (cell && start === null) { start = x; } }); }); return ops.join(''); } // We could just do this in generatePath, except that we want to support // non-Path2D canvas, so we need to keep it an explicit step. function excavateModules(modules, excavation) { return modules.slice().map((row, y) => { if (y < excavation.y || y >= excavation.y + excavation.h) { return row; } return row.map((cell, x) => { if (x < excavation.x || x >= excavation.x + excavation.w) { return cell; } return false; }); }); } function getImageSettings(cells, size, margin, imageSettings) { if (imageSettings == null) { return null; } const numCells = cells.length + margin * 2; const defaultSize = Math.floor(size * DEFAULT_IMG_SCALE); const scale = numCells / size; const w = (imageSettings.width || defaultSize) * scale; const h = (imageSettings.height || defaultSize) * scale; const x = imageSettings.x == null ? cells.length / 2 - w / 2 : imageSettings.x * scale; const y = imageSettings.y == null ? cells.length / 2 - h / 2 : imageSettings.y * scale; let excavation = null; if (imageSettings.excavate) { const floorX = Math.floor(x); const floorY = Math.floor(y); const ceilW = Math.ceil(w + x - floorX); const ceilH = Math.ceil(h + y - floorY); excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH }; } return { x, y, h, w, excavation }; } function getMarginSize(includeMargin, marginSize) { if (marginSize != null) { return Math.floor(marginSize); } return includeMargin ? SPEC_MARGIN_SIZE : DEFAULT_MARGIN_SIZE; } // For canvas we're going to switch our drawing mode based on whether or not // the environment supports Path2D. We only need the constructor to be // supported, but Edge doesn't actually support the path (string) type // argument. Luckily it also doesn't support the addPath() method. We can // treat that as the same thing. const SUPPORTS_PATH2D = function () { try { new Path2D().addPath(new Path2D()); } catch (e) { return false; } return true; }(); const QRCodeCanvas = exports.QRCodeCanvas = (0, _vue.defineComponent)({ name: 'QRCodeCanvas', inheritAttrs: false, props: (0, _extends2.default)((0, _extends2.default)({}, (0, _interface.qrProps)()), { level: String, bgColor: String, fgColor: String, marginSize: Number }), setup(props, _ref) { let { attrs, expose } = _ref; const imgSrc = (0, _vue.computed)(() => { var _a; return (_a = props.imageSettings) === null || _a === void 0 ? void 0 : _a.src; }); const _canvas = (0, _vue.shallowRef)(null); const _image = (0, _vue.shallowRef)(null); const isImgLoaded = (0, _vue.shallowRef)(false); expose({ toDataURL: (type, quality) => { var _a; return (_a = _canvas.value) === null || _a === void 0 ? void 0 : _a.toDataURL(type, quality); } }); (0, _vue.watchEffect)(() => { const { value, size = DEFAULT_SIZE, level = DEFAULT_LEVEL, bgColor = DEFAULT_BGCOLOR, fgColor = DEFAULT_FGCOLOR, includeMargin = DEFAULT_INCLUDEMARGIN, marginSize, imageSettings } = props; if (_canvas.value != null) { const canvas = _canvas.value; const ctx = canvas.getContext('2d'); if (!ctx) { return; } let cells = _qrcodegen.default.QrCode.encodeText(value, ERROR_LEVEL_MAP[level]).getModules(); const margin = getMarginSize(includeMargin, marginSize); const numCells = cells.length + margin * 2; const calculatedImageSettings = getImageSettings(cells, size, margin, imageSettings); const image = _image.value; const haveImageToRender = isImgLoaded.value && calculatedImageSettings != null && image !== null && image.complete && image.naturalHeight !== 0 && image.naturalWidth !== 0; if (haveImageToRender) { if (calculatedImageSettings.excavation != null) { cells = excavateModules(cells, calculatedImageSettings.excavation); } } // We're going to scale this so that the number of drawable units // matches the number of cells. This avoids rounding issues, but does // result in some potentially unwanted single pixel issues between // blocks, only in environments that don't support Path2D. const pixelRatio = window.devicePixelRatio || 1; canvas.height = canvas.width = size * pixelRatio; const scale = size / numCells * pixelRatio; ctx.scale(scale, scale); // Draw solid background, only paint dark modules. ctx.fillStyle = bgColor; ctx.fillRect(0, 0, numCells, numCells); ctx.fillStyle = fgColor; if (SUPPORTS_PATH2D) { // $FlowFixMe: Path2D c'tor doesn't support args yet. ctx.fill(new Path2D(generatePath(cells, margin))); } else { cells.forEach(function (row, rdx) { row.forEach(function (cell, cdx) { if (cell) { ctx.fillRect(cdx + margin, rdx + margin, 1, 1); } }); }); } if (haveImageToRender) { ctx.drawImage(image, calculatedImageSettings.x + margin, calculatedImageSettings.y + margin, calculatedImageSettings.w, calculatedImageSettings.h); } } }, { flush: 'post' }); (0, _vue.watch)(imgSrc, () => { isImgLoaded.value = false; }); return () => { var _a; const size = (_a = props.size) !== null && _a !== void 0 ? _a : DEFAULT_SIZE; const canvasStyle = { height: `${size}px`, width: `${size}px` }; let img = null; if (imgSrc.value != null) { img = (0, _vue.createVNode)("img", { "src": imgSrc.value, "key": imgSrc.value, "style": { display: 'none' }, "onLoad": () => { isImgLoaded.value = true; }, "ref": _image }, null); } return (0, _vue.createVNode)(_vue.Fragment, null, [(0, _vue.createVNode)("canvas", (0, _objectSpread2.default)((0, _objectSpread2.default)({}, attrs), {}, { "style": [canvasStyle, attrs.style], "ref": _canvas }), null), img]); }; } }); const QRCodeSVG = exports.QRCodeSVG = (0, _vue.defineComponent)({ name: 'QRCodeSVG', inheritAttrs: false, props: (0, _extends2.default)((0, _extends2.default)({}, (0, _interface.qrProps)()), { color: String, level: String, bgColor: String, fgColor: String, marginSize: Number, title: String }), setup(props) { let cells = null; let margin = null; let numCells = null; let calculatedImageSettings = null; let fgPath = null; let image = null; (0, _vue.watchEffect)(() => { const { value, size = DEFAULT_SIZE, level = DEFAULT_LEVEL, includeMargin = DEFAULT_INCLUDEMARGIN, marginSize, imageSettings } = props; cells = _qrcodegen.default.QrCode.encodeText(value, ERROR_LEVEL_MAP[level]).getModules(); margin = getMarginSize(includeMargin, marginSize); numCells = cells.length + margin * 2; calculatedImageSettings = getImageSettings(cells, size, margin, imageSettings); if (imageSettings != null && calculatedImageSettings != null) { if (calculatedImageSettings.excavation != null) { cells = excavateModules(cells, calculatedImageSettings.excavation); } image = (0, _vue.createVNode)("image", { "xlink:href": imageSettings.src, "height": calculatedImageSettings.h, "width": calculatedImageSettings.w, "x": calculatedImageSettings.x + margin, "y": calculatedImageSettings.y + margin, "preserveAspectRatio": "none" }, null); } // Drawing strategy: instead of a rect per module, we're going to create a // single path for the dark modules and layer that on top of a light rect, // for a total of 2 DOM nodes. We pay a bit more in string concat but that's // way faster than DOM ops. // For level 1, 441 nodes -> 2 // For level 40, 31329 -> 2 fgPath = generatePath(cells, margin); }); return () => { const bgColor = props.bgColor && DEFAULT_BGCOLOR; const fgColor = props.fgColor && DEFAULT_FGCOLOR; return (0, _vue.createVNode)("svg", { "height": props.size, "width": props.size, "viewBox": `0 0 ${numCells} ${numCells}` }, [!!props.title && (0, _vue.createVNode)("title", null, [props.title]), (0, _vue.createVNode)("path", { "fill": bgColor, "d": `M0,0 h${numCells}v${numCells}H0z`, "shape-rendering": "crispEdges" }, null), (0, _vue.createVNode)("path", { "fill": fgColor, "d": fgPath, "shape-rendering": "crispEdges" }, null), image]); }; } });