react-native-barcode-svg
Version:
React Native component to generate barcode. Uses @react-native-community/react-native-svg.
194 lines (168 loc) • 5.61 kB
JavaScript
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';
import barcodes from 'jsbarcode/src/barcodes';
import { linearizeEncodings, merge } from './shared';
// This encode() handles the Encoder call and builds the binary string to be rendered
const encode = (text, Encoder, options) => {
// If text is not a non-empty string, throw error.
if (typeof text !== 'string' || text.length === 0) {
throw new Error('Barcode value must be a non-empty string');
}
let encoder;
try {
encoder = new Encoder(text, options);
} catch (error) {
// If the encoder could not be instantiated, throw error.
throw new Error('Invalid barcode format.');
}
// If the input is not valid for the encoder, throw error.
if (!encoder.valid()) {
throw new Error('Invalid barcode for selected format.');
}
// Make a request for the binary data (and other information) that should be rendered
// encoded structure is {
// text: 'sample-text',
// data: '110100100001....',
// options: {...}
// }
const encoded = encoder.encode();
// Encodings can be nestled like [[1-1, 1-2], 2, [3-1, 3-2]
// Convert to [1-1, 1-2, 2, 3-1, 3-2]
const linearEncodings = linearizeEncodings(encoded);
// Merge
for (let i = 0; i < linearEncodings.length; i++) {
// linearEncodings[i].key = `bar-group-encoding-${i}`;
linearEncodings[i].options = merge(options, linearEncodings[i].options);
}
return linearEncodings;
};
const getTotalWidthOfEncodings = (encodings) => Array.from(encodings)
.map((encoding) => encoding.data.length)
.reduce((sum, x) => sum + x); // sum(item.length)
const drawRect = (x, y, rectWidth, height) => `M${x},${y}h${rectWidth}v${height}h-${rectWidth}z`;
const drawSvgBar = (encoding, paddingLeft = 0, options = {}) => {
const rects = [];
// binary data of barcode
const binary = encoding.data;
let barWidth = 0;
let x = 0;
const yFrom = 0;
for (let b = 0; b < binary.length; b++) {
x = b * options.singleBarWidth + paddingLeft;
if (binary[b] === '1') {
// eslint-disable-next-line no-plusplus
barWidth++;
} else if (barWidth > 0) {
rects[rects.length] = drawRect(
x - options.singleBarWidth * barWidth,
yFrom,
options.singleBarWidth * barWidth,
options.height,
);
barWidth = 0;
}
}
// Last draw is needed since the barcode ends with 1
if (barWidth > 0) {
rects[rects.length] = drawRect(
x - options.singleBarWidth * (barWidth - 1),
yFrom,
options.singleBarWidth * barWidth,
options.height,
);
}
return rects;
};
const drawSvgBars = (encodings, options = {}) => {
const results = [];
let barPaddingLeft = 0;
Array.from(encodings).forEach((encoding) => {
const bar = drawSvgBar(encoding, barPaddingLeft, options);
results.push(bar);
barPaddingLeft += encoding.data.length * 2;
});
return results.flat();
};
export default function Barcode(props) {
const {
value, format, singleBarWidth, maxWidth, height, lineColor, backgroundColor, onError,
} = props;
const [bars, setBars] = useState([]);
const [barcodeWidth, setBarCodeWidth] = useState(0);
const [barcodeContainerWidth, setBarcodeContainerWidth] = useState(0);
const [barcodeError, setBarcodeError] = useState('');
// const jsBarcodeEncoderOptions = {
// width: singleBarWidth,
// height,
// format,
// text: undefined,
// background: backgroundColor,
// lineColor,
// };
useEffect(() => {
try {
const encoder = barcodes[format];
const linearEncodings = encode(value, encoder, props);
const barcodeTotalWidth = getTotalWidthOfEncodings(linearEncodings) * singleBarWidth;
const theBars = drawSvgBars(linearEncodings, props);
if (linearEncodings.length > 0) {
setBars(theBars);
setBarCodeWidth(barcodeTotalWidth);
setBarcodeContainerWidth((maxWidth && barcodeTotalWidth > maxWidth) ? maxWidth : barcodeTotalWidth);
}
setBarcodeError('');
} catch (e) {
setBarcodeError(e.message);
setBarcodeContainerWidth(200);
onError && onError(e);
}
}, [value, format, singleBarWidth, maxWidth, height, lineColor]);
const containerStyle = { width: barcodeContainerWidth, height, backgroundColor };
return (
<View style={containerStyle}>
{barcodeError
? <View style={styles.errorMessage}><Text>{`${barcodeError}`}</Text></View>
: (
<Svg
width="100%"
height="100%"
fill={lineColor}
viewBox={`0 0 ${barcodeWidth} ${height}`}
preserveAspectRatio="none"
>
<Path d={bars.join(' ')} />
</Svg>
)}
</View>
);
}
Barcode.propTypes = {
value: PropTypes.string,
format: PropTypes.oneOf(Object.keys(barcodes)),
singleBarWidth: PropTypes.number, // width
maxWidth: PropTypes.number,
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
lineColor: PropTypes.string,
backgroundColor: PropTypes.string, // background
onError: PropTypes.func,
};
Barcode.defaultProps = {
value: '',
format: 'CODE128',
singleBarWidth: 2, // width
maxWidth: undefined,
height: 100,
lineColor: '#000000',
backgroundColor: '#FFFFFF',
onError: undefined,
};
const styles = StyleSheet.create({
errorMessage: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});