@aplus-frontend/antdv
Version:
Vue basic component library maintained based on ant-design-vue
320 lines (319 loc) • 11.5 kB
JavaScript
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]);
};
}
});
;