-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathindex.ts
153 lines (133 loc) · 4.49 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import jsdom from 'jsdom'
import { select } from 'd3'
import fs from 'fs'
import QRCode from 'qrcode'
import { resolve } from 'path'
import util from 'util'
import sharp from 'sharp'
import { injectable } from 'inversify'
import ImageFormat from '../../../shared/util/image-format'
import { QrCodeServiceInterface } from '../interfaces/QrCodeServiceInterface'
const { JSDOM } = jsdom
export const IMAGE_WIDTH = 1000
export const QR_CODE_DIMENSIONS = 800
export const MARGIN_VERTICAL = 85
export const FONT_SIZE = 32
export const LINE_HEIGHT = 1.35
// Convert readFile callback function to promise function.
const readFile = util.promisify(fs.readFile)
@injectable()
export class QrCodeService implements QrCodeServiceInterface {
// Build base QR code string without logo.
private makeQrCode: (url: string) => Promise<string> = (url) => {
return QRCode.toString(url, {
type: 'svg',
margin: 0,
errorCorrectionLevel: 'H',
color: {
dark: '#384A51',
},
})
}
// Build QR code string with GoGovSg logo.
private makeGoQrCode: (url: string) => Promise<[Buffer, number]> = async (
url,
) => {
// Splits lines by 36 characters each.
const lines = url.split(/(.{36})/).filter((O) => O)
// Calculate image height using the length of the url.
// Rounded of to next integer as required by sharp.
const imageHeight = Math.ceil(
MARGIN_VERTICAL +
QR_CODE_DIMENSIONS +
MARGIN_VERTICAL +
(lines.length - 1) * FONT_SIZE * LINE_HEIGHT +
FONT_SIZE +
MARGIN_VERTICAL,
)
const qrString = await this.makeQrCode(url)
const dom = new JSDOM(`<!DOCTYPE html><body></body>`)
// Read the logo as a string.
const filePath = resolve(__dirname, 'assets/qrlogo.svg')
const logoSvg = await readFile(filePath, 'utf-8')
const body = select(dom.window.document.querySelector('body'))
const svg = body
.append('svg')
.attr('width', IMAGE_WIDTH)
.attr('height', imageHeight)
.attr('xmlns', 'http://www.w3.org/2000/svg')
// Provides the entire graphic with a white background.
svg
.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', '#ffffff')
// Append generated qr code to jsdom.
const qrCodeOffsetX = (IMAGE_WIDTH - QR_CODE_DIMENSIONS) / 2
svg
.append('svg')
.attr('x', qrCodeOffsetX)
.attr('y', MARGIN_VERTICAL)
.attr('viewBox', `0 0 ${QR_CODE_DIMENSIONS} ${QR_CODE_DIMENSIONS}`)
.attr('width', QR_CODE_DIMENSIONS)
.attr('height', QR_CODE_DIMENSIONS)
.html(qrString)
// Append go logo to the qrcode on jsdom.
const logoDimensions = 0.35 * QR_CODE_DIMENSIONS
const logoOffsetX = (IMAGE_WIDTH - logoDimensions) / 2
const logoOffsetY =
MARGIN_VERTICAL + (QR_CODE_DIMENSIONS - logoDimensions) / 2
svg
.append('svg')
.attr('x', logoOffsetX)
.attr('y', logoOffsetY)
.attr('viewBox', `0 0 ${logoDimensions} ${logoDimensions}`)
.attr('width', logoDimensions)
.attr('height', logoDimensions)
.html(logoSvg)
// Append the relevant shortlink to the bottom of the qrcode.
const textLocationX = IMAGE_WIDTH / 2
const textLocationY = 2 * MARGIN_VERTICAL + QR_CODE_DIMENSIONS
svg
.selectAll('text')
.data(lines)
.enter()
.append('text')
.attr('x', textLocationX)
.attr('y', (_, i) => {
return textLocationY + i * FONT_SIZE * LINE_HEIGHT
})
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-weight', '400')
.attr('font-size', `${FONT_SIZE}px`)
.text((d) => {
return d
})
// Return the result svg as string.
return [Buffer.from(body.html()), imageHeight]
}
// Build QR code of specified file format as a string or buffer.
public createGoQrCode: (
url: string,
format: ImageFormat,
) => Promise<Buffer> = async (url, format) => {
const [qrSvgString, imageHeight] = await this.makeGoQrCode(url)
switch (format) {
case ImageFormat.SVG: {
return qrSvgString
}
case ImageFormat.PNG: {
const buffer = Buffer.from(qrSvgString)
return sharp(buffer).resize(IMAGE_WIDTH, imageHeight).png().toBuffer()
}
case ImageFormat.JPEG: {
const buffer = Buffer.from(qrSvgString)
return sharp(buffer).resize(IMAGE_WIDTH, imageHeight).jpeg().toBuffer()
}
default:
throw Error('Invalid format')
}
}
}
export default QrCodeService