| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
- import _extends from "@babel/runtime/helpers/esm/extends";
- import { Fragment as _Fragment, createVNode as _createVNode } from "vue";
- import { defineComponent, shallowRef, watch, computed, watchEffect } from 'vue';
- import { qrProps } from './interface';
- import qrcodegen from './qrcodegen';
- const ERROR_LEVEL_MAP = {
- L: qrcodegen.QrCode.Ecc.LOW,
- M: qrcodegen.QrCode.Ecc.MEDIUM,
- Q: qrcodegen.QrCode.Ecc.QUARTILE,
- H: qrcodegen.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;
- }();
- export const QRCodeCanvas = defineComponent({
- name: 'QRCodeCanvas',
- inheritAttrs: false,
- props: _extends(_extends({}, qrProps()), {
- level: String,
- bgColor: String,
- fgColor: String,
- marginSize: Number
- }),
- setup(props, _ref) {
- let {
- attrs,
- expose
- } = _ref;
- const imgSrc = computed(() => {
- var _a;
- return (_a = props.imageSettings) === null || _a === void 0 ? void 0 : _a.src;
- });
- const _canvas = shallowRef(null);
- const _image = shallowRef(null);
- const isImgLoaded = shallowRef(false);
- expose({
- toDataURL: (type, quality) => {
- var _a;
- return (_a = _canvas.value) === null || _a === void 0 ? void 0 : _a.toDataURL(type, quality);
- }
- });
- 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.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'
- });
- 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 = _createVNode("img", {
- "src": imgSrc.value,
- "key": imgSrc.value,
- "style": {
- display: 'none'
- },
- "onLoad": () => {
- isImgLoaded.value = true;
- },
- "ref": _image
- }, null);
- }
- return _createVNode(_Fragment, null, [_createVNode("canvas", _objectSpread(_objectSpread({}, attrs), {}, {
- "style": [canvasStyle, attrs.style],
- "ref": _canvas
- }), null), img]);
- };
- }
- });
- export const QRCodeSVG = defineComponent({
- name: 'QRCodeSVG',
- inheritAttrs: false,
- props: _extends(_extends({}, 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;
- watchEffect(() => {
- const {
- value,
- size = DEFAULT_SIZE,
- level = DEFAULT_LEVEL,
- includeMargin = DEFAULT_INCLUDEMARGIN,
- marginSize,
- imageSettings
- } = props;
- cells = qrcodegen.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 = _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 _createVNode("svg", {
- "height": props.size,
- "width": props.size,
- "viewBox": `0 0 ${numCells} ${numCells}`
- }, [!!props.title && _createVNode("title", null, [props.title]), _createVNode("path", {
- "fill": bgColor,
- "d": `M0,0 h${numCells}v${numCells}H0z`,
- "shape-rendering": "crispEdges"
- }, null), _createVNode("path", {
- "fill": fgColor,
- "d": fgPath,
- "shape-rendering": "crispEdges"
- }, null), image]);
- };
- }
- });
|