Skip to content

Commit 7b7570d

Browse files
committed
Add logo support to QR API
1 parent adec279 commit 7b7570d

File tree

4 files changed

+114
-27
lines changed

4 files changed

+114
-27
lines changed

apps/web/app/api/qr/route.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
22
import { ratelimitOrThrow } from "@/lib/api/utils";
3-
import { QRCodeSVG } from "@/lib/qr/utils";
3+
import { getQRAsSVG } from "@/lib/qr/api";
44
import { getQRCodeQuerySchema } from "@/lib/zod/schemas/qr";
55
import { getSearchParams } from "@dub/utils";
6-
import { ImageResponse } from "next/og";
6+
import { ImageResponse } from "@vercel/og";
77
import { NextRequest } from "next/server";
88

99
export const runtime = "edge";
@@ -16,28 +16,28 @@ export async function GET(req: NextRequest) {
1616
const { url, size, level, fgColor, bgColor, includeMargin } =
1717
getQRCodeQuerySchema.parse(params);
1818

19-
// const logo = req.nextUrl.searchParams.get("logo") || "https://assets.dub.co/logo.png";
19+
const logo =
20+
req.nextUrl.searchParams.get("logo") || "https://assets.dub.co/logo.png";
2021

21-
return new ImageResponse(
22-
QRCodeSVG({
23-
value: url,
24-
size,
25-
level,
26-
includeMargin,
27-
fgColor,
28-
bgColor,
29-
// imageSettings: {
30-
// src: logo,
31-
// height: size / 4,
32-
// width: size / 4,
33-
// excavate: true,
34-
// },
35-
}),
36-
{
37-
width: size,
38-
height: size,
22+
const svg = await getQRAsSVG({
23+
value: url,
24+
size,
25+
level,
26+
includeMargin,
27+
fgColor,
28+
bgColor,
29+
imageSettings: {
30+
src: logo,
31+
height: size / 4,
32+
width: size / 4,
33+
excavate: true,
3934
},
40-
);
35+
});
36+
37+
return new ImageResponse(svg, {
38+
width: size,
39+
height: size,
40+
});
4141
} catch (error) {
4242
return handleAndReturnErrorResponse(error);
4343
}

apps/web/lib/qr/api.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import qrcodegen from "./codegen";
2+
import {
3+
DEFAULT_BGCOLOR,
4+
DEFAULT_FGCOLOR,
5+
DEFAULT_INCLUDEMARGIN,
6+
DEFAULT_LEVEL,
7+
DEFAULT_SIZE,
8+
ERROR_LEVEL_MAP,
9+
MARGIN_SIZE,
10+
} from "./constants";
11+
import { QRPropsSVG } from "./types";
12+
import { excavateModules, generatePath, getImageSettings } from "./utils";
13+
14+
export async function getQRAsSVG(props: QRPropsSVG) {
15+
const {
16+
value,
17+
size = DEFAULT_SIZE,
18+
level = DEFAULT_LEVEL,
19+
bgColor = DEFAULT_BGCOLOR,
20+
fgColor = DEFAULT_FGCOLOR,
21+
includeMargin = DEFAULT_INCLUDEMARGIN,
22+
imageSettings,
23+
...otherProps
24+
} = props;
25+
26+
let cells = qrcodegen.QrCode.encodeText(
27+
value,
28+
ERROR_LEVEL_MAP[level],
29+
).getModules();
30+
31+
const margin = includeMargin ? MARGIN_SIZE : 0;
32+
const numCells = cells.length + margin * 2;
33+
const calculatedImageSettings = getImageSettings(
34+
cells,
35+
size,
36+
includeMargin,
37+
imageSettings,
38+
);
39+
40+
let image = <></>;
41+
if (imageSettings != null && calculatedImageSettings != null) {
42+
if (calculatedImageSettings.excavation != null) {
43+
cells = excavateModules(cells, calculatedImageSettings.excavation);
44+
}
45+
46+
const base64Image = await fetch(
47+
`https://wsrv.nl/?url=${imageSettings.src}&w=100&h=100&encoding=base64`,
48+
).then((res) => res.text());
49+
50+
image = (
51+
<image
52+
href={base64Image}
53+
height={calculatedImageSettings.h}
54+
width={calculatedImageSettings.w}
55+
x={calculatedImageSettings.x + margin}
56+
y={calculatedImageSettings.y + margin}
57+
preserveAspectRatio="none"
58+
/>
59+
);
60+
}
61+
62+
// Drawing strategy: instead of a rect per module, we're going to create a
63+
// single path for the dark modules and layer that on top of a light rect,
64+
// for a total of 2 DOM nodes. We pay a bit more in string concat but that's
65+
// way faster than DOM ops.
66+
// For level 1, 441 nodes -> 2
67+
// For level 40, 31329 -> 2
68+
const fgPath = generatePath(cells, margin);
69+
70+
return (
71+
<svg
72+
height={size}
73+
width={size}
74+
viewBox={`0 0 ${numCells} ${numCells}`}
75+
xmlns="http://www.w3.org/2000/svg"
76+
{...otherProps}
77+
>
78+
<path
79+
fill={bgColor}
80+
d={`M0,0 h${numCells}v${numCells}H0z`}
81+
shapeRendering="crispEdges"
82+
/>
83+
<path fill={fgColor} d={fgPath} shapeRendering="crispEdges" />
84+
{image}
85+
</svg>
86+
);
87+
}

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"@vercel/edge": "^0.3.1",
4242
"@vercel/edge-config": "^0.4.1",
4343
"@vercel/functions": "^1.0.1",
44-
"@vercel/og": "^0.6.2",
44+
"@vercel/og": "^0.6.3",
4545
"@visx/axis": "^2.14.0",
4646
"@visx/curve": "^3.3.0",
4747
"@visx/event": "^2.6.0",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)