qrcode.vue 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * qrcode.vue v3.7.0
2
+ * qrcode.vue v3.8.0
3
3
  * A Vue.js component to generate QRCode. Both support Vue 2 and Vue 3
4
4
  * © 2017-PRESENT @scopewu(https://github.com/scopewu)
5
5
  * MIT License.
6
6
  */
7
- import { defineComponent, ref, watch, h, onMounted, Fragment } from 'vue';
7
+ import { defineComponent, h, ref, onMounted, watchEffect, Fragment, computed } from 'vue';
8
8
 
9
9
  /******************************************************************************
10
10
  Copyright (c) Microsoft Corporation.
@@ -909,6 +909,7 @@ var defaultErrorCorrectLevel = 'L';
909
909
  var DEFAULT_QR_SIZE = 100;
910
910
  var DEFAULT_MARGIN = 0;
911
911
  var DEFAULT_IMAGE_SIZE_RATIO = 0.1;
912
+ var IMAGE_EXCAVATE_THICKNESS = 2;
912
913
  var ErrorCorrectLevelMap = {
913
914
  L: QR.QrCode.Ecc.LOW,
914
915
  M: QR.QrCode.Ecc.MEDIUM,
@@ -928,42 +929,6 @@ var SUPPORTS_PATH2D = (function () {
928
929
  function validErrorCorrectLevel(level) {
929
930
  return level in ErrorCorrectLevelMap;
930
931
  }
931
- function isPointInRoundedRect(px, py, rx, ry, rw, rh, r) {
932
- // Fast check: point outside the bounding box
933
- if (px < rx || px > rx + rw || py < ry || py > ry + rh) {
934
- return false;
935
- }
936
- // If no border radius or point is in the center rectangle
937
- if (r <= 0 || (px > rx + r && px < rx + rw - r) || (py > ry + r && py < ry + rh - r)) {
938
- return true;
939
- }
940
- // Check the four corners
941
- // Top-left corner
942
- if (px < rx + r && py < ry + r) {
943
- var dx = px - (rx + r);
944
- var dy = py - (ry + r);
945
- return dx * dx + dy * dy <= r * r;
946
- }
947
- // Top-right corner
948
- if (px > rx + rw - r && py < ry + r) {
949
- var dx = px - (rx + rw - r);
950
- var dy = py - (ry + r);
951
- return dx * dx + dy * dy <= r * r;
952
- }
953
- // Bottom-left corner
954
- if (px < rx + r && py > ry + rh - r) {
955
- var dx = px - (rx + r);
956
- var dy = py - (ry + rh - r);
957
- return dx * dx + dy * dy <= r * r;
958
- }
959
- // Bottom-right corner
960
- if (px > rx + rw - r && py > ry + rh - r) {
961
- var dx = px - (rx + rw - r);
962
- var dy = py - (ry + rh - r);
963
- return dx * dx + dy * dy <= r * r;
964
- }
965
- return true;
966
- }
967
932
  function generatePath(modules, margin) {
968
933
  if (margin === void 0) { margin = 0; }
969
934
  var path = '';
@@ -1010,42 +975,51 @@ function getImageSettings(cells, size, margin, imageSettings) {
1010
975
  var h = (height || defaultSize) * scale;
1011
976
  var x = imageX == null ? cells.length / 2 - w / 2 : imageX * scale;
1012
977
  var y = imageY == null ? cells.length / 2 - h / 2 : imageY * scale;
978
+ var borderRadius = (imageSettings.borderRadius || 0) * scale;
1013
979
  var excavation = null;
1014
980
  if (imageSettings.excavate) {
1015
981
  var floorX = Math.floor(x);
1016
982
  var floorY = Math.floor(y);
1017
983
  var ceilW = Math.ceil(w + x - floorX);
1018
984
  var ceilH = Math.ceil(h + y - floorY);
1019
- var borderRadius = (imageSettings.borderRadius || 0) * scale;
1020
- excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH, borderRadius: borderRadius };
985
+ excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH };
1021
986
  }
1022
- return { x: x, y: y, h: h, w: w, excavation: excavation };
987
+ return { x: x, y: y, h: h, w: w, borderRadius: borderRadius, excavation: excavation };
1023
988
  }
1024
- function excavateModules(modules, excavation) {
1025
- var borderRadius = excavation.borderRadius;
1026
- // If no border radius, use simple rectangular excavation
1027
- if (!borderRadius || borderRadius <= 0) {
1028
- return modules.map(function (row, y) {
1029
- if (y < excavation.y || y >= excavation.y + excavation.h) {
1030
- return row;
1031
- }
1032
- return row.map(function (cell, x) {
1033
- if (x < excavation.x || x >= excavation.x + excavation.w) {
1034
- return cell;
1035
- }
1036
- return false;
1037
- });
1038
- });
1039
- }
1040
- // For rounded corners, check each module against the rounded rectangle shape
1041
- return modules.map(function (row, y) {
1042
- return row.map(function (cell, x) {
1043
- if (!cell)
1044
- return cell;
1045
- var inExcavation = isPointInRoundedRect(x + 0.5, y + 0.5, excavation.x, excavation.y, excavation.w, excavation.h, borderRadius);
1046
- return inExcavation ? false : cell;
1047
- });
989
+ function useQRCode(props) {
990
+ var margin = computed(function () { var _a; return ((_a = props.margin) !== null && _a !== void 0 ? _a : DEFAULT_MARGIN) >>> 0; });
991
+ var cells = computed(function () {
992
+ var level = validErrorCorrectLevel(props.level) ? props.level : defaultErrorCorrectLevel;
993
+ return QR.QrCode.encodeText(props.value, ErrorCorrectLevelMap[level]).getModules();
994
+ });
995
+ var numCells = computed(function () { return cells.value.length + margin.value * 2; });
996
+ var fgPath = computed(function () { return generatePath(cells.value, margin.value); });
997
+ var imageProps = computed(function () {
998
+ if (!props.imageSettings.src) {
999
+ return { x: 0, y: 0, width: 0, height: 0, borderRadius: 0 };
1000
+ }
1001
+ var settings = getImageSettings(cells.value, props.size, margin.value, props.imageSettings);
1002
+ return {
1003
+ x: settings.x + margin.value,
1004
+ y: settings.y + margin.value,
1005
+ width: settings.w,
1006
+ height: settings.h,
1007
+ borderRadius: settings.borderRadius,
1008
+ };
1048
1009
  });
1010
+ var imageBorderProps = computed(function () {
1011
+ if (!props.imageSettings.excavate || !props.imageSettings.src)
1012
+ return null;
1013
+ var borderThickness = IMAGE_EXCAVATE_THICKNESS / (props.size / numCells.value);
1014
+ return {
1015
+ x: imageProps.value.x - borderThickness,
1016
+ y: imageProps.value.y - borderThickness,
1017
+ width: imageProps.value.width + borderThickness * 2,
1018
+ height: imageProps.value.height + borderThickness * 2,
1019
+ borderRadius: imageProps.value.borderRadius,
1020
+ };
1021
+ });
1022
+ return { margin: margin, numCells: numCells, cells: cells, fgPath: fgPath, imageProps: imageProps, imageBorderProps: imageBorderProps };
1049
1023
  }
1050
1024
  var QRCodeProps = {
1051
1025
  value: {
@@ -1112,35 +1086,8 @@ var QrcodeSvg = defineComponent({
1112
1086
  name: 'QRCodeSvg',
1113
1087
  props: QRCodeProps,
1114
1088
  setup: function (props) {
1115
- var numCells = ref(0);
1116
- var fgPath = ref('');
1117
- var imageProps;
1118
- var generate = function () {
1119
- var value = props.value, _level = props.level, _margin = props.margin;
1120
- var margin = _margin >>> 0;
1121
- var level = validErrorCorrectLevel(_level) ? _level : defaultErrorCorrectLevel;
1122
- var cells = QR.QrCode.encodeText(value, ErrorCorrectLevelMap[level]).getModules();
1123
- numCells.value = cells.length + margin * 2;
1124
- if (props.imageSettings.src) {
1125
- var imageSettings = getImageSettings(cells, props.size, margin, props.imageSettings);
1126
- imageProps = {
1127
- x: imageSettings.x + margin,
1128
- y: imageSettings.y + margin,
1129
- width: imageSettings.w,
1130
- height: imageSettings.h,
1131
- };
1132
- if (imageSettings.excavation) {
1133
- cells = excavateModules(cells, imageSettings.excavation);
1134
- }
1135
- }
1136
- // Drawing strategy: instead of a rect per module, we're going to create a
1137
- // single path for the dark modules and layer that on top of a light rect,
1138
- // for a total of 2 DOM nodes. We pay a bit more in string concat but that's
1139
- // way faster than DOM ops.
1140
- // For level 1, 441 nodes -> 2
1141
- // For level 40, 31329 -> 2
1142
- fgPath.value = generatePath(cells, margin);
1143
- };
1089
+ var _a = useQRCode(props), numCells = _a.numCells, fgPath = _a.fgPath, imageProps = _a.imageProps, imageBorderProps = _a.imageBorderProps;
1090
+ var qrGradientId = 'qrcode.vue-gradient';
1144
1091
  var renderGradient = function () {
1145
1092
  if (!props.gradient)
1146
1093
  return null;
@@ -1158,7 +1105,7 @@ var QrcodeSvg = defineComponent({
1158
1105
  fx: '50%',
1159
1106
  fy: '50%',
1160
1107
  };
1161
- return h(props.gradientType === 'linear' ? 'linearGradient' : 'radialGradient', __assign({ id: 'qr-gradient' }, gradientProps), [
1108
+ return h(props.gradientType === 'linear' ? 'linearGradient' : 'radialGradient', __assign({ id: qrGradientId }, gradientProps), [
1162
1109
  h('stop', {
1163
1110
  offset: '0%',
1164
1111
  style: { stopColor: props.gradientStartColor },
@@ -1169,25 +1116,24 @@ var QrcodeSvg = defineComponent({
1169
1116
  }),
1170
1117
  ]);
1171
1118
  };
1119
+ var qrLogoClipPathId = 'qrcode.vue-logo-clip-path';
1172
1120
  var renderClipPath = function () {
1173
- var borderRadius = props.imageSettings.borderRadius || 0;
1121
+ var borderRadius = imageProps.value.borderRadius;
1174
1122
  if (!props.imageSettings.src)
1175
1123
  return null;
1176
1124
  if (borderRadius <= 0)
1177
1125
  return null;
1178
- return h('clipPath', { id: 'qr-logo-clip' }, [
1126
+ return h('clipPath', { id: qrLogoClipPathId }, [
1179
1127
  h('rect', {
1180
- x: imageProps.x,
1181
- y: imageProps.y,
1182
- width: imageProps.width,
1183
- height: imageProps.height,
1128
+ x: imageProps.value.x,
1129
+ y: imageProps.value.y,
1130
+ width: imageProps.value.width,
1131
+ height: imageProps.value.height,
1184
1132
  rx: borderRadius,
1185
1133
  ry: borderRadius,
1186
1134
  }),
1187
1135
  ]);
1188
1136
  };
1189
- generate();
1190
- watch(props, generate, { deep: true });
1191
1137
  return function () { return h('svg', {
1192
1138
  width: props.size,
1193
1139
  height: props.size,
@@ -1202,10 +1148,19 @@ var QrcodeSvg = defineComponent({
1202
1148
  fill: props.background,
1203
1149
  }),
1204
1150
  h('path', {
1205
- fill: props.gradient ? 'url(#qr-gradient)' : props.foreground,
1151
+ fill: props.gradient ? "url(#".concat(qrGradientId, ")") : props.foreground,
1206
1152
  d: fgPath.value,
1207
1153
  }),
1208
- props.imageSettings.src && h('image', __assign(__assign({ href: props.imageSettings.src }, imageProps), (props.imageSettings.borderRadius ? { 'clip-path': 'url(#qr-logo-clip)' } : {}))),
1154
+ imageBorderProps.value && h('rect', {
1155
+ x: imageBorderProps.value.x,
1156
+ y: imageBorderProps.value.y,
1157
+ width: imageBorderProps.value.width,
1158
+ height: imageBorderProps.value.height,
1159
+ fill: props.background,
1160
+ rx: imageBorderProps.value.borderRadius,
1161
+ ry: imageBorderProps.value.borderRadius,
1162
+ }),
1163
+ props.imageSettings.src && h('image', __assign(__assign({ href: props.imageSettings.src }, imageProps.value), (imageProps.value.borderRadius > 0 ? { 'clip-path': "url(#".concat(qrLogoClipPathId, ")") } : {}))),
1209
1164
  ]); };
1210
1165
  },
1211
1166
  });
@@ -1213,93 +1168,86 @@ var QrcodeCanvas = defineComponent({
1213
1168
  name: 'QRCodeCanvas',
1214
1169
  props: QRCodeProps,
1215
1170
  setup: function (props, ctx) {
1171
+ var _a = useQRCode(props), margin = _a.margin, cells = _a.cells, numCells = _a.numCells, imageProps = _a.imageProps, imageBorderProps = _a.imageBorderProps;
1216
1172
  var canvasEl = ref(null);
1217
1173
  var imageRef = ref(null);
1218
1174
  var generate = function () {
1219
- var value = props.value, _level = props.level, size = props.size, _margin = props.margin, background = props.background, foreground = props.foreground, gradient = props.gradient, gradientType = props.gradientType, gradientStartColor = props.gradientStartColor, gradientEndColor = props.gradientEndColor;
1220
- var margin = _margin >>> 0;
1221
- var level = validErrorCorrectLevel(_level) ? _level : defaultErrorCorrectLevel;
1175
+ var size = props.size, background = props.background, foreground = props.foreground, gradient = props.gradient, gradientType = props.gradientType, gradientStartColor = props.gradientStartColor, gradientEndColor = props.gradientEndColor;
1222
1176
  var canvas = canvasEl.value;
1223
1177
  if (!canvas) {
1224
1178
  return;
1225
1179
  }
1226
- var ctx = canvas.getContext('2d');
1227
- if (!ctx) {
1180
+ var canvasCtx = canvas.getContext('2d');
1181
+ if (!canvasCtx) {
1228
1182
  return;
1229
1183
  }
1230
- var cells = QR.QrCode.encodeText(value, ErrorCorrectLevelMap[level]).getModules();
1231
- var numCells = cells.length + margin * 2;
1184
+ var qrCells = cells.value;
1232
1185
  var image = imageRef.value;
1233
- var imageProps = { x: 0, y: 0, width: 0, height: 0 };
1234
- var showImage = props.imageSettings.src && image != null && image.naturalWidth !== 0 && image.naturalHeight !== 0;
1235
- if (showImage) {
1236
- var imageSettings = getImageSettings(cells, props.size, margin, props.imageSettings);
1237
- imageProps = {
1238
- x: imageSettings.x + margin,
1239
- y: imageSettings.y + margin,
1240
- width: imageSettings.w,
1241
- height: imageSettings.h,
1242
- };
1243
- if (imageSettings.excavation) {
1244
- cells = excavateModules(cells, imageSettings.excavation);
1245
- }
1246
- }
1247
1186
  var devicePixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
1248
- var scale = (size / numCells) * devicePixelRatio;
1187
+ var scale = (size / numCells.value) * devicePixelRatio;
1249
1188
  canvas.height = canvas.width = size * devicePixelRatio;
1250
- ctx.scale(scale, scale);
1251
- ctx.fillStyle = background;
1252
- ctx.fillRect(0, 0, numCells, numCells);
1189
+ canvasCtx.scale(scale, scale);
1190
+ canvasCtx.fillStyle = background;
1191
+ canvasCtx.fillRect(0, 0, numCells.value, numCells.value);
1253
1192
  if (gradient) {
1254
1193
  var grad = void 0;
1255
1194
  if (gradientType === 'linear') {
1256
- grad = ctx.createLinearGradient(0, 0, numCells, numCells);
1195
+ grad = canvasCtx.createLinearGradient(0, 0, numCells.value, numCells.value);
1257
1196
  }
1258
1197
  else {
1259
- grad = ctx.createRadialGradient(numCells / 2, numCells / 2, 0, numCells / 2, numCells / 2, numCells / 2);
1198
+ grad = canvasCtx.createRadialGradient(numCells.value / 2, numCells.value / 2, 0, numCells.value / 2, numCells.value / 2, numCells.value / 2);
1260
1199
  }
1261
1200
  grad.addColorStop(0, gradientStartColor);
1262
1201
  grad.addColorStop(1, gradientEndColor);
1263
- ctx.fillStyle = grad;
1202
+ canvasCtx.fillStyle = grad;
1264
1203
  }
1265
1204
  else {
1266
- ctx.fillStyle = foreground;
1205
+ canvasCtx.fillStyle = foreground;
1267
1206
  }
1268
1207
  if (SUPPORTS_PATH2D) {
1269
- ctx.fill(new Path2D(generatePath(cells, margin)));
1208
+ canvasCtx.fill(new Path2D(generatePath(qrCells, margin.value)));
1270
1209
  }
1271
1210
  else {
1272
- cells.forEach(function (row, rdx) {
1211
+ qrCells.forEach(function (row, rdx) {
1273
1212
  row.forEach(function (cell, cdx) {
1274
1213
  if (cell) {
1275
- ctx.fillRect(cdx + margin, rdx + margin, 1, 1);
1214
+ canvasCtx.fillRect(cdx + margin.value, rdx + margin.value, 1, 1);
1276
1215
  }
1277
1216
  });
1278
1217
  });
1279
1218
  }
1219
+ var showImage = props.imageSettings.src && image && image.naturalWidth !== 0 && image.naturalHeight !== 0;
1280
1220
  if (showImage) {
1281
- var borderRadius = props.imageSettings.borderRadius || 0;
1282
- if (borderRadius > 0) {
1283
- ctx.save();
1221
+ var drawRoundedRect = function (ctx, x, y, width, height, radius) {
1284
1222
  ctx.beginPath();
1285
1223
  if (ctx.roundRect) {
1286
- ctx.roundRect(imageProps.x, imageProps.y, imageProps.width, imageProps.height, borderRadius);
1224
+ ctx.roundRect(x, y, width, height, radius);
1287
1225
  }
1288
1226
  else {
1289
- // Fallback for browsers without roundRect support
1290
- ctx.rect(imageProps.x, imageProps.y, imageProps.width, imageProps.height);
1227
+ ctx.rect(x, y, width, height);
1291
1228
  }
1292
- ctx.clip();
1293
- ctx.drawImage(image, imageProps.x, imageProps.y, imageProps.width, imageProps.height);
1294
- ctx.restore();
1229
+ };
1230
+ if (imageBorderProps.value) {
1231
+ var imageBorder = imageBorderProps.value;
1232
+ canvasCtx.fillStyle = props.background;
1233
+ drawRoundedRect(canvasCtx, imageBorder.x, imageBorder.y, imageBorder.width, imageBorder.height, imageBorder.borderRadius);
1234
+ canvasCtx.fill();
1235
+ }
1236
+ var borderRadius = imageProps.value.borderRadius;
1237
+ if (borderRadius > 0) {
1238
+ canvasCtx.save();
1239
+ drawRoundedRect(canvasCtx, imageProps.value.x, imageProps.value.y, imageProps.value.width, imageProps.value.height, borderRadius);
1240
+ canvasCtx.clip();
1241
+ canvasCtx.drawImage(image, imageProps.value.x, imageProps.value.y, imageProps.value.width, imageProps.value.height);
1242
+ canvasCtx.restore();
1295
1243
  }
1296
1244
  else {
1297
- ctx.drawImage(image, imageProps.x, imageProps.y, imageProps.width, imageProps.height);
1245
+ canvasCtx.drawImage(image, imageProps.value.x, imageProps.value.y, imageProps.value.width, imageProps.value.height);
1298
1246
  }
1299
1247
  }
1300
1248
  };
1301
1249
  onMounted(generate);
1302
- watch(props, generate, { deep: true });
1250
+ watchEffect(generate);
1303
1251
  var style = ctx.attrs.style;
1304
1252
  return function () { return h(Fragment, [
1305
1253
  h('canvas', __assign(__assign({}, ctx.attrs), { ref: canvasEl, style: __assign(__assign({}, style), { width: "".concat(props.size, "px"), height: "".concat(props.size, "px") }) })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qrcode.vue",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "A Vue.js component to generate QRCode. Both support Vue 2 and Vue 3",
5
5
  "type": "module",
6
6
  "main": "./dist/qrcode.vue.cjs.js",
@@ -34,6 +34,7 @@
34
34
  ],
35
35
  "files": [
36
36
  "/dist",
37
+ "CHANGELOG.md",
37
38
  "README-zh_cn.md",
38
39
  "README.md"
39
40
  ],