-
Notifications
You must be signed in to change notification settings - Fork 335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
separate components in different files #188
Open
caub
wants to merge
1
commit into
zpao:trunk
Choose a base branch
from
caub:split
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,158 @@ | ||||||||||||
/** | ||||||||||||
* @license qrcode.react | ||||||||||||
* Copyright (c) Paul O'Shannessy | ||||||||||||
* SPDX-License-Identifier: ISC | ||||||||||||
*/ | ||||||||||||
|
||||||||||||
import React, {useRef, useEffect} from 'react'; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
or
Suggested change
if yu want to use Default export from |
||||||||||||
import qrcodegen from './third-party/qrcodegen'; | ||||||||||||
import { | ||||||||||||
QRProps, | ||||||||||||
DEFAULT_PROPS, | ||||||||||||
MARGIN_SIZE, | ||||||||||||
ERROR_LEVEL_MAP, | ||||||||||||
generatePath, | ||||||||||||
excavateModules, | ||||||||||||
getImageSettings, | ||||||||||||
} from './helpers'; | ||||||||||||
|
||||||||||||
// For canvas we're going to switch our drawing mode based on whether or not | ||||||||||||
// the environment supports Path2D. We only need the constructor to be | ||||||||||||
// supported, but Edge doesn't actually support the path (string) type | ||||||||||||
// argument. Luckily it also doesn't support the addPath() method. We can | ||||||||||||
// treat that as the same thing. | ||||||||||||
const SUPPORTS_PATH2D = (function () { | ||||||||||||
try { | ||||||||||||
new Path2D().addPath(new Path2D()); | ||||||||||||
} catch (e) { | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
return true; | ||||||||||||
})(); | ||||||||||||
|
||||||||||||
export default function QRCodeCanvas(props: QRProps) { | ||||||||||||
const _canvas = useRef<HTMLCanvasElement>(null); | ||||||||||||
const _image = useRef<HTMLImageElement>(null); | ||||||||||||
|
||||||||||||
function update() { | ||||||||||||
const {value, size, level, bgColor, fgColor, includeMargin} = props; | ||||||||||||
|
||||||||||||
if (_canvas.current != null) { | ||||||||||||
const canvas = _canvas.current; | ||||||||||||
|
||||||||||||
const ctx = canvas.getContext('2d'); | ||||||||||||
if (!ctx) { | ||||||||||||
return; | ||||||||||||
} | ||||||||||||
|
||||||||||||
let cells = qrcodegen.QrCode.encodeText( | ||||||||||||
value, | ||||||||||||
ERROR_LEVEL_MAP[level] | ||||||||||||
).getModules(); | ||||||||||||
|
||||||||||||
const margin = includeMargin ? MARGIN_SIZE : 0; | ||||||||||||
const numCells = cells.length + margin * 2; | ||||||||||||
const calculatedImageSettings = getImageSettings(props, cells); | ||||||||||||
|
||||||||||||
const image = _image.current; | ||||||||||||
const haveImageToRender = | ||||||||||||
calculatedImageSettings != null && | ||||||||||||
image !== null && | ||||||||||||
image.complete && | ||||||||||||
image.naturalHeight !== 0 && | ||||||||||||
image.naturalWidth !== 0; | ||||||||||||
|
||||||||||||
if (haveImageToRender) { | ||||||||||||
if (calculatedImageSettings.excavation != null) { | ||||||||||||
cells = excavateModules(cells, calculatedImageSettings.excavation); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
// We're going to scale this so that the number of drawable units | ||||||||||||
// matches the number of cells. This avoids rounding issues, but does | ||||||||||||
// result in some potentially unwanted single pixel issues between | ||||||||||||
// blocks, only in environments that don't support Path2D. | ||||||||||||
const pixelRatio = window.devicePixelRatio || 1; | ||||||||||||
canvas.height = canvas.width = size * pixelRatio; | ||||||||||||
const scale = (size / numCells) * pixelRatio; | ||||||||||||
ctx.scale(scale, scale); | ||||||||||||
|
||||||||||||
// Draw solid background, only paint dark modules. | ||||||||||||
ctx.fillStyle = bgColor; | ||||||||||||
ctx.fillRect(0, 0, numCells, numCells); | ||||||||||||
|
||||||||||||
ctx.fillStyle = fgColor; | ||||||||||||
if (SUPPORTS_PATH2D) { | ||||||||||||
// $FlowFixMe: Path2D c'tor doesn't support args yet. | ||||||||||||
ctx.fill(new Path2D(generatePath(cells, margin))); | ||||||||||||
} else { | ||||||||||||
cells.forEach(function (row, rdx) { | ||||||||||||
row.forEach(function (cell, cdx) { | ||||||||||||
if (cell) { | ||||||||||||
ctx.fillRect(cdx + margin, rdx + margin, 1, 1); | ||||||||||||
} | ||||||||||||
}); | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
|
||||||||||||
if (haveImageToRender) { | ||||||||||||
ctx.drawImage( | ||||||||||||
image, | ||||||||||||
calculatedImageSettings.x + margin, | ||||||||||||
calculatedImageSettings.y + margin, | ||||||||||||
calculatedImageSettings.w, | ||||||||||||
calculatedImageSettings.h | ||||||||||||
); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
useEffect(() => { | ||||||||||||
// Always update the canvas. It's cheap enough and we want to be correct | ||||||||||||
// with the current state. | ||||||||||||
update(); | ||||||||||||
}); | ||||||||||||
|
||||||||||||
const { | ||||||||||||
value, | ||||||||||||
size, | ||||||||||||
level, | ||||||||||||
bgColor, | ||||||||||||
fgColor, | ||||||||||||
style, | ||||||||||||
includeMargin, | ||||||||||||
imageSettings, | ||||||||||||
...otherProps | ||||||||||||
} = props; | ||||||||||||
const canvasStyle = {height: size, width: size, ...style}; | ||||||||||||
let img = null; | ||||||||||||
let imgSrc = imageSettings?.src; | ||||||||||||
if (imgSrc != null) { | ||||||||||||
img = ( | ||||||||||||
<img | ||||||||||||
src={imgSrc} | ||||||||||||
key={imgSrc} | ||||||||||||
style={{display: 'none'}} | ||||||||||||
onLoad={() => { | ||||||||||||
update(); | ||||||||||||
}} | ||||||||||||
ref={_image} | ||||||||||||
/> | ||||||||||||
); | ||||||||||||
} | ||||||||||||
return ( | ||||||||||||
<> | ||||||||||||
<canvas | ||||||||||||
style={canvasStyle} | ||||||||||||
height={size} | ||||||||||||
width={size} | ||||||||||||
ref={_canvas} | ||||||||||||
{...otherProps} | ||||||||||||
/> | ||||||||||||
{img} | ||||||||||||
</> | ||||||||||||
); | ||||||||||||
} | ||||||||||||
|
||||||||||||
QRCodeCanvas.defaultProps = DEFAULT_PROPS; | ||||||||||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/** | ||
* @license qrcode.react | ||
* Copyright (c) Paul O'Shannessy | ||
* SPDX-License-Identifier: ISC | ||
*/ | ||
|
||
import qrcodegen from './third-party/qrcodegen'; | ||
import type {CSSProperties} from 'react'; | ||
|
||
type Modules = ReturnType<qrcodegen.QrCode['getModules']>; | ||
type Excavation = {x: number; y: number; w: number; h: number}; | ||
|
||
export const ERROR_LEVEL_MAP: {[index: string]: qrcodegen.QrCode.Ecc} = { | ||
L: qrcodegen.QrCode.Ecc.LOW, | ||
M: qrcodegen.QrCode.Ecc.MEDIUM, | ||
Q: qrcodegen.QrCode.Ecc.QUARTILE, | ||
H: qrcodegen.QrCode.Ecc.HIGH, | ||
}; | ||
|
||
export type QRProps = { | ||
value: string; | ||
size: number; | ||
// Should be a real enum, but doesn't seem to be compatible with real code. | ||
level: string; | ||
bgColor: string; | ||
fgColor: string; | ||
style?: CSSProperties; | ||
includeMargin: boolean; | ||
imageSettings?: { | ||
src: string; | ||
height: number; | ||
width: number; | ||
excavate: boolean; | ||
x?: number; | ||
y?: number; | ||
}; | ||
}; | ||
|
||
export const DEFAULT_PROPS = { | ||
size: 128, | ||
level: 'L', | ||
bgColor: '#FFFFFF', | ||
fgColor: '#000000', | ||
includeMargin: false, | ||
}; | ||
|
||
export const MARGIN_SIZE = 4; | ||
|
||
// This is *very* rough estimate of max amount of QRCode allowed to be covered. | ||
// It is "wrong" in a lot of ways (area is a terrible way to estimate, it | ||
// really should be number of modules covered), but if for some reason we don't | ||
// get an explicit height or width, I'd rather default to something than throw. | ||
const DEFAULT_IMG_SCALE = 0.1; | ||
|
||
export function generatePath(modules: Modules, margin: number = 0): string { | ||
const ops: Array<string> = []; | ||
modules.forEach(function (row, y) { | ||
let start: number | null = null; | ||
row.forEach(function (cell, x) { | ||
if (!cell && start !== null) { | ||
// M0 0h7v1H0z injects the space with the move and drops the comma, | ||
// saving a char per operation | ||
ops.push( | ||
`M${start + margin} ${y + margin}h${x - start}v1H${start + margin}z` | ||
); | ||
start = null; | ||
return; | ||
} | ||
|
||
// end of row, clean up or skip | ||
if (x === row.length - 1) { | ||
if (!cell) { | ||
// We would have closed the op above already so this can only mean | ||
// 2+ light modules in a row. | ||
return; | ||
} | ||
if (start === null) { | ||
// Just a single dark module. | ||
ops.push(`M${x + margin},${y + margin} h1v1H${x + margin}z`); | ||
} else { | ||
// Otherwise finish the current line. | ||
ops.push( | ||
`M${start + margin},${y + margin} h${x + 1 - start}v1H${ | ||
start + margin | ||
}z` | ||
); | ||
} | ||
return; | ||
} | ||
|
||
if (cell && start === null) { | ||
start = x; | ||
} | ||
}); | ||
}); | ||
return ops.join(''); | ||
} | ||
|
||
// We could just do this in generatePath, except that we want to support | ||
// non-Path2D canvas, so we need to keep it an explicit step. | ||
export 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; | ||
}); | ||
}); | ||
} | ||
|
||
export function getImageSettings( | ||
props: QRProps, | ||
cells: Modules | ||
): null | { | ||
x: number; | ||
y: number; | ||
h: number; | ||
w: number; | ||
excavation: Excavation | null; | ||
} { | ||
const {imageSettings, size, includeMargin} = props; | ||
if (imageSettings == null) { | ||
return null; | ||
} | ||
const margin = includeMargin ? MARGIN_SIZE : 0; | ||
const numCells = cells.length + margin * 2; | ||
const defaultSize = Math.floor(size * DEFAULT_IMG_SCALE); | ||
const scale = numCells / size; | ||
const w = (imageSettings.width || defaultSize) * scale; | ||
const h = (imageSettings.height || defaultSize) * scale; | ||
const x = | ||
imageSettings.x == null | ||
? cells.length / 2 - w / 2 | ||
: imageSettings.x * scale; | ||
const y = | ||
imageSettings.y == null | ||
? cells.length / 2 - h / 2 | ||
: imageSettings.y * 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}; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.