Skip to content

Commit

Permalink
feat(qr): noExcavate option, react support
Browse files Browse the repository at this point in the history
  • Loading branch information
gugu committed Oct 23, 2024
1 parent 2b837c5 commit 010917c
Show file tree
Hide file tree
Showing 25 changed files with 237 additions and 85 deletions.
13 changes: 0 additions & 13 deletions CHANGELOG.md

This file was deleted.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const pngBuffer = await getPNG("I love QR", {
| `color` | module color in rgba or hex format | number | `#000000` - `#000000` | `#000000`<br />(black with 100% opacity) |
| `bgColor` | background color in rgba or hex format | number | `#000000` - `#FFFFFF` | `#FFFFFF`<br />(white with 100% opacity) |
| `borderRadius` | border-radius (in pixels) | number | 0 - `size / 2` | `0` |
| `noExcavate` | don't remove partially covered modules | boolean | `true`, `false` | `false` |


## Benchmarks

Expand Down
87 changes: 87 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"./lib/pdf": {
"types": "./lib/pdf.d.ts",
"default": "./lib/pdf.js"
},
"./lib/react": {
"types": "./lib/react.d.ts",
"default": "./lib/react.js"
}
},
"scripts": {
Expand Down Expand Up @@ -65,6 +69,7 @@
"@types/diff": "^5.2.1",
"@types/jsdom": "^21.1.6",
"@types/pixelmatch": "^5.2.6",
"@types/react": "^18.3.1",
"@types/sharp": "^0.31.1",
"@types/svg-path-parser": "^1.1.6",
"ava": "^6.1.3",
Expand All @@ -80,16 +85,19 @@
"microtime": "^3.1.1",
"path2d-polyfill": "^3.1.0",
"pixelmatch": "^5.3.0",
"react": "^18.3.1",
"rollup": "^4.18.0",
"size-limit": "^11.1.4",
"svg-path-parser": "^1.1.0",
"typescript": "^5.4.5",
"xml-formatter": "^3.6.2"
},
"dependencies": {
"@types/react-dom": "^18.3.1",
"color-string": "^1.9.1",
"js-base64": "^3.7.7",
"pdf-lib": "^1.17.1",
"react-dom": "^18.3.1",
"sharp": "^0.33.5"
},
"overrides": {
Expand Down
13 changes: 13 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,17 @@ export default [{
plugins: [typescript({
outDir: 'lib/browser'
}), nodeResolve(), commonjs(), json()]
}, {
input: 'src/react.tsx',
output: {
file: "lib/browser/react.umd.js",
format: 'umd',
name: 'svgQrCode',
sourcemap: true
},
jsx: "react-jsx",
extensions: [".tsx", ".ts"],
plugins: [typescript({
outDir: 'lib/browser'
}), nodeResolve(), commonjs()]
}];
53 changes: 53 additions & 0 deletions src/bitMatrix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { BitMatrix } from "./typing/types";

/**
* Finders require different UI representation, so we zero-fill finders and draw them later
*/
export function zeroFillFinders(matrix: BitMatrix): BitMatrix {
matrix = structuredClone(matrix); // avoid mutating input arg
const N = matrix.length;
const zeroPixel = 0;
// squares
for (let i = -3; i <= 3; i++) {
for (let j = -3; j <= 3; j++) {
matrix[3 + i][3 + j] = zeroPixel;
matrix[3 + i][N - 4 + j] = zeroPixel;
matrix[N - 4 + i][3 + j] = zeroPixel;
}
}
// border
for (let i = 0; i < 8; i++) {
matrix[7][i] =
matrix[i][7] =
matrix[7][N - i - 1] =
matrix[i][N - 8] =
matrix[N - 8][i] =
matrix[N - 1 - i][7] =
zeroPixel;
}
return matrix;
}

/**
* Before we insert logo in the QR we need to clear pixels under the logo. This function clears pixels
*/
export function clearMatrixCenter(matrix: BitMatrix, widthPct: number, heightPct: number): BitMatrix {
matrix = structuredClone(matrix); // avoid mutating input arg

// TODO: Here's a homegrown formula, perhaps could be simplified
const mW = matrix.length;
const cW = Math.ceil(((mW * widthPct) / 100 + (mW % 2)) / 2) * 2 - (mW % 2);
const mH = matrix[0]?.length ?? 0;
const cH = Math.ceil(((mH * heightPct) / 100 + (mH % 2)) / 2) * 2 - (mH % 2);

// Given the formula, these must be whole numbers, but round anyway to account for js EPSILON
const clearStartX = Math.round((mW - cW) / 2);
const clearStartY = Math.round((mH - cH) / 2);

for (let x = clearStartX; x < clearStartX + cW; x += 1) {
for (let y = clearStartY; y < clearStartY + cH; y += 1) {
matrix[x][y] = 0;
}
}
return matrix;
}
52 changes: 1 addition & 51 deletions src/matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,6 @@ export function fillFinders(matrix: Matrix) {
}
}

/**
* Finders require different UI representation, so we zero-fill finders and draw them later
*/
export function zeroFillFinders(matrix: BitMatrix) {
const N = matrix.length;
const zeroPixel = 0;
// squares
for (let i = -3; i <= 3; i++) {
for (let j = -3; j <= 3; j++) {
matrix[3 + i][3 + j] = zeroPixel;
matrix[3 + i][N - 4 + j] = zeroPixel;
matrix[N - 4 + i][3 + j] = zeroPixel;
}
}
// border
for (let i = 0; i < 8; i++) {
matrix[7][i] =
matrix[i][7] =
matrix[7][N - i - 1] =
matrix[i][N - 8] =
matrix[N - 8][i] =
matrix[N - 1 - i][7] =
zeroPixel;
}
}


// {{{1 Put align and timinig
export function fillAlignAndTiming(matrix: Matrix) {
Expand Down Expand Up @@ -412,28 +386,4 @@ export function getMatrix(data: Data): BitMatrix {
fillReserved(matrix, data.ec_level, bestMask);

return matrix.map((row) => row.map((cell) => (cell & 1) as 0 | 1));
}

/**
* Before we insert logo in the QR we need to clear pixels under the logo. This function clears pixels
*/
export function clearMatrixCenter(matrix: BitMatrix, widthPct: number, heightPct: number): BitMatrix {
matrix = matrix.map((x) => x.slice()); // avoid mutating input arg

// TODO: Here's a homegrown formula, perhaps could be simplified
const mW = matrix.length;
const cW = Math.ceil(((mW * widthPct) / 100 + (mW % 2)) / 2) * 2 - (mW % 2);
const mH = matrix[0]?.length ?? 0;
const cH = Math.ceil(((mH * heightPct) / 100 + (mH % 2)) / 2) * 2 - (mH % 2);

// Given the formula, these must be whole numbers, but round anyway to account for js EPSILON
const clearStartX = Math.round((mW - cW) / 2);
const clearStartY = Math.round((mH - cH) / 2);

for (let x = clearStartX; x < clearStartX + cW; x += 1) {
for (let y = clearStartY; y < clearStartY + cH; y += 1) {
matrix[x][y] = 0;
}
}
return matrix;
}
}
6 changes: 3 additions & 3 deletions src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { QR } from "./qr-base.js";
import { ImageOptions, Matrix } from "./typing/types";
import { getOptions, getDotsSVGPath, getFindersSVGPath } from "./utils.js";
import colorString from "color-string";
import { clearMatrixCenter, zeroFillFinders } from "./matrix.js";
import { clearMatrixCenter, zeroFillFinders } from "./bitMatrix.js";

const textDec = new TextDecoder();

export async function getPDF(text: string, inOptions: ImageOptions) {
const options = getOptions(inOptions);

let matrix = QR(text, options.ec_level, options.parse_url);
zeroFillFinders(matrix)
if (options.logo && options.logoWidth && options.logoHeight) {
matrix = zeroFillFinders(matrix)
if (options.logo && options.logoWidth && options.logoHeight && !options.noExcavate) {
matrix = clearMatrixCenter(matrix, options.logoWidth, options.logoHeight);
}

Expand Down
8 changes: 4 additions & 4 deletions src/png.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { QR } from "./qr-base.js";
import { createSVG } from "./svg.js";
import { getOptions } from "./utils.js";
import sharp from "sharp";
import { clearMatrixCenter, zeroFillFinders } from "./matrix.js";
import { clearMatrixCenter, zeroFillFinders } from "./bitMatrix.js";

export async function getPNG(text: string, inOptions: ImageOptions = {}) {
const options = getOptions({ ...inOptions, type: "png" });

let matrix = QR(text, options.ec_level, options.parse_url);
zeroFillFinders(matrix)
if (options.logo && options.logoWidth && options.logoHeight) {
matrix = zeroFillFinders(matrix)
if (options.logo && options.logoWidth && options.logoHeight && !options.noExcavate) {
matrix = clearMatrixCenter(matrix, options.logoWidth, options.logoHeight);
}

Expand All @@ -36,7 +36,7 @@ export async function generateImage({
throw new Error("Module size is too big, resulting image is too large: " + imageSizePx);
}

const svg = await createSVG({
const svg = createSVG({
matrix,
size,
margin,
Expand Down
Loading

0 comments on commit 010917c

Please sign in to comment.