Skip to content

Commit

Permalink
feat: support image for SVG.
Browse files Browse the repository at this point in the history
  • Loading branch information
scopewu committed Sep 25, 2024
1 parent c11715c commit cd6ce30
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README-zh_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ createApp({
### `level`

- 类型:`Level('L' | 'M' | 'Q' | 'H')`
- 默认值:`H`
- 默认值:`L`

二维码的容错能力等级,取值为 'L', 'M', 'Q', 'H' 之一。了解更多,[维基百科:QR_code](https://en.wikipedia.org/wiki/QR_code#Error_correction)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Define how much wide the quiet zone should be.
### `level`

- Type: `Level('L' | 'M' | 'Q' | 'H')`
- Default: `H`
- Default: `L`

qrcode Error correction level (one of 'L', 'M', 'Q', 'H'). Know more, [wikipedia: QR_code](https://en.wikipedia.org/wiki/QR_code#Error_correction).

Expand Down
15 changes: 12 additions & 3 deletions example/webpack-entry.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { createApp, defineComponent, onMounted, ref } from 'vue'
import QrcodeVue from '../src'
import type { Level, RenderAs } from '../src'
import type { Level, RenderAs, ImageSettings } from '../src'

const App = defineComponent({
components: { QrcodeVue },
setup() {
const value = ref('https://example.com')
const size = ref(100)
const value = ref('QRCODE.VUE 😄 感谢')
const size = ref(135)
const level = ref<Level>('L')
const background = ref('#ffffff')
const foreground = ref('#000000')
const renderAs = ref<RenderAs>('svg')
const margin = ref(0)
const imageSettings = ref<ImageSettings>({
src: 'https://github.com/scopewu.png',
width: 30,
height: 30,
// x: 10,
// y: 10,
excavate: false,
})

const stargazersCount = ref(700)

Expand All @@ -33,6 +41,7 @@ const App = defineComponent({
foreground,
renderAs,
margin,
imageSettings,
stargazersCount,
}
}
Expand Down
1 change: 1 addition & 0 deletions example/webpack.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ <h1>qrcode.vue:</h1>
:level="level"
:background="background"
:foreground='foreground'
:image-settings='imageSettings'
></qrcode-vue>
</div>
</div>
Expand Down
96 changes: 91 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@ import QR from './qrcodegen'
type Modules = ReturnType<QR.QrCode['getModules']>
export type Level = 'L' | 'M' | 'Q' | 'H'
export type RenderAs = 'canvas' | 'svg'
export type ImageSettings = {
src: string,
x?: number,
y?: number,
height: number,
width: number,
excavate?: boolean,
}
type Excavation = {
x: number,
y: number,
w: number,
h: number,
}

const defaultErrorCorrectLevel = 'H'
const defaultErrorCorrectLevel: Level = 'L'

const ErrorCorrectLevelMap : Readonly<Record<Level, QR.QrCode.Ecc>> = {
L: QR.QrCode.Ecc.LOW,
Expand Down Expand Up @@ -72,6 +86,53 @@ function generatePath(modules: Modules, margin: number = 0): string {
return ops.join('')
}

function getImageSettings(
cells: Modules,
size: number,
margin: number,
imageSettings: ImageSettings
) : {
x: number
y: number
h: number
w: number
excavation: Excavation | null
} {
const { width, height, x: imageX, y: imageY} = imageSettings
const numCells = cells.length + margin * 2
const defaultSize = Math.floor(size * 0.1)
const scale = numCells / size
const w = (width || defaultSize) * scale
const h = (height || defaultSize) * scale
const x = imageX == null ? cells.length / 2 - w / 2 : imageX * scale
const y = imageY == null ? cells.length / 2 - h / 2 : imageY * scale

let excavation = null
if (imageSettings.excavate) {
let floorX = Math.floor(x)
let floorY = Math.floor(y)
let ceilW = Math.ceil(w + x - floorX)
let ceilH = Math.ceil(h + y - floorY)
excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH }
}

return { x, y, h, w, excavation }
}

function excavateModules(modules: Modules, excavation: Excavation): Modules {
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
})
})
}

const QRCodeProps = {
value: {
type: String,
Expand Down Expand Up @@ -100,6 +161,11 @@ const QRCodeProps = {
required: false,
default: 0,
},
imageSettings: {
type: Object as PropType<ImageSettings>,
required: false,
default: () => ({}),
},
}

const QRCodeVueProps = {
Expand All @@ -118,13 +184,28 @@ const QRCodeSvg = defineComponent({
setup(props) {
const numCells = ref(0)
const fgPath = ref('')
const imageProps = ref<{ x: number, y: number, width: number, height: number }>(null!)

const generate = () => {
const { value, level, margin } = props

const cells = QR.QrCode.encodeText(value, ErrorCorrectLevelMap[level]).getModules()
let cells = QR.QrCode.encodeText(value, ErrorCorrectLevelMap[level]).getModules()
numCells.value = cells.length + margin * 2

if(props.imageSettings.src) {
const imageSettings = getImageSettings(cells, props.size, margin, props.imageSettings)
imageProps.value = {
x: imageSettings.x,
y: imageSettings.y,
width: imageSettings.w,
height: imageSettings.h,
}

if (imageSettings.excavation) {
cells = excavateModules(cells, imageSettings.excavation)
}
}

// 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
Expand Down Expand Up @@ -155,6 +236,10 @@ const QRCodeSvg = defineComponent({
d: `M0,0 h${numCells.value}v${numCells.value}H0z`,
}),
h('path', { fill: props.foreground, d: fgPath.value }),
props.imageSettings.src && h('image', {
href: props.imageSettings.src,
...imageProps.value,
}),
]
)
},
Expand All @@ -172,13 +257,13 @@ const QRCodeCanvas = defineComponent({
const canvas = canvasEl.value

if (!canvas) {
return;
return
}

const ctx = canvas.getContext('2d')

if (!ctx) {
return;
return
}

const cells = QR.QrCode.encodeText(value, ErrorCorrectLevelMap[level]).getModules()
Expand Down Expand Up @@ -232,14 +317,15 @@ const QrcodeVue = defineComponent({
level: _level,
background,
foreground,
imageSettings,
} = this.$props
const size = _size >>> 0
const margin = _margin >>> 0
const level = validErrorCorrectLevel(_level) ? _level : defaultErrorCorrectLevel

return h(
renderAs === 'svg' ? QRCodeSvg : QRCodeCanvas,
{ value, size, margin, level, background, foreground },
{ value, size, margin, level, background, foreground, imageSettings },
)
},
props: QRCodeVueProps,
Expand Down

0 comments on commit cd6ce30

Please sign in to comment.