Skip to content

Commit

Permalink
feat: optimize render; add padding option
Browse files Browse the repository at this point in the history
  • Loading branch information
exzos28 committed May 10, 2024
1 parent 2988953 commit d2dab24
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 110 deletions.
21 changes: 13 additions & 8 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,59 @@ import { QrCodeSvg } from 'react-native-qr-svg';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.firstQr}>
<View style={[styles.qr, styles.firstQr]}>
<QrCodeSvg
value="Hello world!"
frameSize={200}
contentCells={5}
content={
<View>
<Text>👋</Text>
<Text style={styles.icon}>👋</Text>
</View>
}
contentStyle={styles.box}
/>
</View>
<View style={styles.secondQr}>
<View style={[styles.qr, styles.secondQr]}>
<QrCodeSvg
value="Hello world!"
frameSize={200}
contentCells={5}
content={
<View>
<Text>💻</Text>
<Text style={styles.icon}>💻</Text>
</View>
}
dotColor="#ffffff"
backgroundColor="#000000"
contentStyle={styles.box}
/>
</View>
<View style={styles.qr}>
<QrCodeSvg value="Hello world!" padding={0.5} frameSize={200} />
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
box: {
alignItems: 'center',
justifyContent: 'center',
},
firstQr: {
marginRight: 15,
icon: {
fontSize: 20,
},
secondQr: {
qr: {
padding: 15,
},
firstQr: {},
secondQr: {
backgroundColor: '#000000',
},
});
27 changes: 17 additions & 10 deletions src/getCornets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,35 @@
* @param x
* @param y
* @param cellSize
* @param padding
*/
export default function getCorners(x: number, y: number, cellSize: number) {
export default function getCorners(
x: number,
y: number,
cellSize: number,
padding: number
) {
// q4 0 0 0 q1
// 0 0 0 0 0
// 0 0 0 0 0
// 0 0 0 0 0
// q3 0 0 0 q2
const q1 = {
x: x + cellSize,
y: y,
x: x + cellSize - padding,
y: y + padding,
};
const q2 = {
x: x + cellSize,
y: y + cellSize,
x: x + cellSize - padding,
y: y + cellSize - padding,
};
const q3 = {
x: x,
y: y + cellSize,
x: x + padding,
y: y + cellSize - padding,
};
const q4 = {
x: x,
y: y,
x: x + padding,
y: y + padding,
};
return { q1, q2, q3, q4 };
const center = { x: x + cellSize / 2, y: y + cellSize / 2 };
return { q1, q2, q3, q4, center };
}
131 changes: 39 additions & 92 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import { createMatrix } from './createMatrix';
import Svg, { Path, G, Rect, Circle } from 'react-native-svg';
import type { RectProps, PathProps, CircleProps } from 'react-native-svg';
import { StyleSheet, View } from 'react-native';
import type { CircleProps, PathProps, RectProps } from 'react-native-svg';
import Svg, { G, Path, Rect } from 'react-native-svg';
import type { StyleProp, ViewStyle } from 'react-native';
import { StyleSheet, View } from 'react-native';
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
import type { Neighbors } from './types';
import getCorners from './getCornets';
import renderFigure from './renderFigure';

export type QrCodeSvgProps = {
value: string;
Expand All @@ -21,6 +21,7 @@ export type QrCodeSvgProps = {
contentStyle?: StyleProp<ViewStyle>;
figureCircleProps?: CircleProps;
figurePathProps?: PathProps;
padding?: number;
};

export function QrCodeSvg({
Expand All @@ -36,21 +37,22 @@ export function QrCodeSvg({
contentStyle,
figureCircleProps,
figurePathProps,
padding = 0.05,
}: QrCodeSvgProps) {
const originalMatrix = useMemo(
() => createMatrix(value, errorCorrectionLevel),
[errorCorrectionLevel, value]
);
const matrixCellSize = round(frameSize / originalMatrix.length); // Ex. 3.141592653589793 -> 3.1
const cell = round(frameSize / originalMatrix.length); // Ex. 3.141592653589793 -> 3.1
const matrixRowLength = originalMatrix[0]?.length ?? 0;
const roundedContentCells =
(matrixRowLength - contentCells) % 2 === 0
? contentCells
: contentCells + 1;
const contentSize = matrixCellSize * roundedContentCells;
const contentSize = cell * roundedContentCells;
const contentStartIndex = (matrixRowLength - roundedContentCells) / 2;
const contentEndIndex = contentStartIndex + roundedContentCells - 1;
const contentXY = contentStartIndex * matrixCellSize;
const contentXY = contentStartIndex * cell;

const matrix = useMemo(
() =>
Expand All @@ -69,95 +71,40 @@ export function QrCodeSvg({
[content, contentEndIndex, contentStartIndex, originalMatrix]
);

const renderFigure = useCallback(
(x: number, y: number, neighbors: Neighbors) => {
const { q1, q2, q3, q4 } = getCorners(x, y, matrixCellSize);
if (
!(
neighbors.top ||
neighbors.right ||
neighbors.bottom ||
neighbors.left
)
) {
return (
<Circle
cx={x + matrixCellSize / 2}
cy={y + matrixCellSize / 2}
r={matrixCellSize / 2}
fill={dotColor}
{...figureCircleProps}
/>
);
}
// q4 0 d1 0 q1
// 0 0 0 0 0
// d4 0 0 0 d2
// 0 0 0 0 0
// q3 0 d3 0 q2
const d1 = {
x: x + matrixCellSize / 2,
y: y,
};
const d2 = {
x: x + matrixCellSize,
y: y + matrixCellSize / 2,
};
const d1d2 =
neighbors.top || neighbors.right
? `L${q1.x} ${q1.y} L${d2.x} ${d2.y}`
: `Q${q1.x} ${q1.y} ${d2.x} ${d2.y}`;
const d3 = {
x: x + matrixCellSize / 2,
y: y + matrixCellSize,
};
const d2d3 =
neighbors.right || neighbors.bottom
? `L${q2.x} ${q2.y} L${d3.x} ${d3.y}`
: `Q${q2.x} ${q2.y} ${d3.x} ${d3.y}`;
const d4 = {
x: x,
y: y + matrixCellSize / 2,
};
const d3d4 =
neighbors.bottom || neighbors.left
? `L${q3.x} ${q3.y} L${d4.x} ${d4.y}`
: `Q${q3.x} ${q3.y} ${d4.x} ${d4.y}`;
const d4d1 =
neighbors.left || neighbors.top
? `L${q4.x} ${q4.y} L${d1.x} ${d1.y}`
: `Q${q4.x} ${q4.y} ${d1.x} ${d1.y}`;
const d = `M${d1.x} ${d1.y} ${d1d2} ${d2d3} ${d3d4} ${d4d1}`;

return <Path d={d} fill={dotColor} {...figurePathProps} />;
},
[dotColor, figureCircleProps, figurePathProps, matrixCellSize]
const paths = useMemo(
() =>
matrix.flatMap((row, i) =>
row.flatMap((_, j) => {
if (!row?.[j]) {
return [];
}
const neighbors: Neighbors = {
top: Boolean(matrix[i - 1]?.[j]),
bottom: Boolean(matrix[i + 1]?.[j]),
left: Boolean(row[j - 1]),
right: Boolean(row[j + 1]),
};
const x = j * cell;
const y = i * cell;
return [renderFigure(x, y, neighbors, cell, padding)];
})
),
[matrix, padding, cell]
);
const dPath = paths
.filter((_) => _.type === 'path')
.map((_) => _.d)
.join();
const dCircle = paths
.filter((_) => _.type === 'circle')
.map((_) => _.d)
.join();
return (
<View style={[{ backgroundColor }, style]}>
<Svg width={frameSize} height={frameSize}>
<G>
{matrix.map((row, i) =>
row.map((_, j) => {
if (!row?.[j]) {
return null;
}
const neighbors: Neighbors = {
top: Boolean(matrix[i - 1]?.[j]),
bottom: Boolean(matrix[i + 1]?.[j]),
left: Boolean(row[j - 1]),
right: Boolean(row[j + 1]),
};
const x = j * matrixCellSize;
const y = i * matrixCellSize;
const key = `${i}${j}`;
return (
<React.Fragment key={key}>
{renderFigure(x, y, neighbors)}
</React.Fragment>
);
})
)}
<Path d={dPath} fill={dotColor} {...figurePathProps} />
<Path d={dCircle} fill={dotColor} {...figureCircleProps} />
</G>
{content && (
<Rect
Expand Down
65 changes: 65 additions & 0 deletions src/renderFigure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Neighbors } from './types';
import getCorners from './getCornets';

export default function renderFigure(
x: number,
y: number,
neighbors: Neighbors,
cell: number,
padding: number
) {
const half = cell / 2;
const { q1, q2, q3, q4, center } = getCorners(x, y, cell, padding);
if (
!(neighbors.top || neighbors.right || neighbors.bottom || neighbors.left)
) {
return {
type: 'circle',
d: `M${center.x + half} ${center.y} A${half} ${half} 0 1 0 ${center.x - half} ${center.y} A${half} ${half} 0 1 0 ${center.x + half} ${center.y}`,
};
}

// q4 0 d1 0 q1
// 0 0 0 0 0
// d4 0 0 0 d2
// 0 0 0 0 0
// q3 0 d3 0 q2
const d1 = {
x: center.x,
y: center.y - half + padding,
};
const d2 = {
x: center.x + half - padding,
y: center.y,
};
const d3 = {
x: center.x,
y: center.y + half - padding,
};
const d4 = {
x: center.x - half + padding,
y: center.y,
};

const d1d2 =
neighbors.top || neighbors.right
? `L${q1.x} ${q1.y} L${d2.x} ${d2.y}`
: `Q${q1.x} ${q1.y} ${d2.x} ${d2.y}`;
const d2d3 =
neighbors.right || neighbors.bottom
? `L${q2.x} ${q2.y} L${d3.x} ${d3.y}`
: `Q${q2.x} ${q2.y} ${d3.x} ${d3.y}`;
const d3d4 =
neighbors.bottom || neighbors.left
? `L${q3.x} ${q3.y} L${d4.x} ${d4.y}`
: `Q${q3.x} ${q3.y} ${d4.x} ${d4.y}`;
const d4d1 =
neighbors.left || neighbors.top
? `L${q4.x} ${q4.y} L${d1.x} ${d1.y}`
: `Q${q4.x} ${q4.y} ${d1.x} ${d1.y}`;

return {
type: 'path',
d: `M${d1.x} ${d1.y} ${d1d2} ${d2d3} ${d3d4} ${d4d1}`,
};
}

0 comments on commit d2dab24

Please sign in to comment.