Files
rxing/src/qrcode/encoder/qrcode_encoder.rs
2023-02-16 07:15:51 +00:00

765 lines
26 KiB
Rust

/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++
*/
use std::collections::HashMap;
use encoding::EncodingRef;
use once_cell::sync::Lazy;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
common::{
reedsolomon::{get_predefined_genericgf, PredefinedGenericGF, ReedSolomonEncoder},
BitArray, CharacterSetECI, Result,
},
qrcode::decoder::{ErrorCorrectionLevel, Mode, Version, VersionRef},
EncodeHintType, EncodeHintValue, EncodingHintDictionary, Exceptions,
};
use super::{mask_util, matrix_util, BlockPair, ByteMatrix, MinimalEncoder, QRCode};
static SHIFT_JIS_CHARSET: Lazy<EncodingRef> =
Lazy::new(|| encoding::label::encoding_from_whatwg_label("SJIS").unwrap());
// The original table is defined in the table 5 of JISX0510:2004 (p.19).
const ALPHANUMERIC_TABLE: [i8; 96] = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
-1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
];
pub const DEFAULT_BYTE_MODE_ENCODING: EncodingRef = encoding::all::ISO_8859_1;
// The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details.
// Basically it applies four rules and summate all penalties.
pub fn calculateMaskPenalty(matrix: &ByteMatrix) -> u32 {
mask_util::applyMaskPenaltyRule1(matrix)
+ mask_util::applyMaskPenaltyRule2(matrix)
+ mask_util::applyMaskPenaltyRule3(matrix)
+ mask_util::applyMaskPenaltyRule4(matrix)
}
/**
* @param content text to encode
* @param ecLevel error correction level to use
* @return {@link QRCode} representing the encoded QR code
* @throws WriterException if encoding can't succeed, because of for example invalid content
* or configuration
*/
pub fn encode(content: &str, ecLevel: ErrorCorrectionLevel) -> Result<QRCode> {
encode_with_hints(content, ecLevel, &HashMap::new())
}
pub fn encode_with_hints(
content: &str,
ec_level: ErrorCorrectionLevel,
hints: &EncodingHintDictionary,
) -> Result<QRCode> {
let version;
let mut header_and_data_bits;
let mode;
let has_gs1_format_hint = matches!(
hints.get(&EncodeHintType::GS1_FORMAT),
Some(EncodeHintValue::Gs1Format(true))
);
let has_compaction_hint =
if let Some(EncodeHintValue::QrCompact(vb)) = hints.get(&EncodeHintType::QR_COMPACT) {
if let Ok(v) = vb.parse::<bool>() {
v
} else {
false
}
} else {
false
};
// Determine what character encoding has been specified by the caller, if any
let mut encoding = None; //DEFAULT_BYTE_MODE_ENCODING;
let mut has_encoding_hint = hints.contains_key(&EncodeHintType::CHARACTER_SET);
if has_encoding_hint {
if let Some(EncodeHintValue::CharacterSet(v)) = hints.get(&EncodeHintType::CHARACTER_SET) {
encoding =
Some(encoding::label::encoding_from_whatwg_label(v).ok_or(Exceptions::writer)?)
}
}
if has_compaction_hint {
mode = Mode::BYTE;
// dbg!("consider this a huge risk, not sure if it should be defaulting to default");
let priority_encoding = encoding; //if encoding.name() == DEFAULT_BYTE_MODE_ENCODING.name() {None} else {Some(encoding)};
let rn = MinimalEncoder::encode_with_details(
content,
None,
priority_encoding,
has_gs1_format_hint,
ec_level,
)?;
header_and_data_bits = BitArray::new();
rn.getBits(&mut header_and_data_bits)?;
version = rn.getVersion();
} else {
//Switch to default encoding
let encoding = if let Some(encoding) = encoding {
encoding
} else if let Ok(_encs) =
DEFAULT_BYTE_MODE_ENCODING.encode(content, encoding::EncoderTrap::Strict)
{
DEFAULT_BYTE_MODE_ENCODING
} else {
has_encoding_hint = true;
encoding::all::UTF_8
};
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient.
mode = chooseModeWithEncoding(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
let mut header_bits = BitArray::new();
// Append ECI segment if applicable
if mode == Mode::BYTE && has_encoding_hint {
if let Some(eci) = CharacterSetECI::getCharacterSetECI(encoding) {
appendECI(&eci, &mut header_bits)?;
}
}
// Append the FNC1 mode header for GS1 formatted data if applicable
if has_gs1_format_hint {
// GS1 formatted codes are prefixed with a FNC1 in first position mode header
appendModeInfo(Mode::FNC1_FIRST_POSITION, &mut header_bits)?;
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, &mut header_bits)?;
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
let mut data_bits = BitArray::new();
appendBytes(content, mode, &mut data_bits, encoding)?;
if hints.contains_key(&EncodeHintType::QR_VERSION) {
let versionNumber = if let Some(EncodeHintValue::QrVersion(v)) =
hints.get(&EncodeHintType::QR_VERSION)
{
if let Ok(vb) = v.parse::<u32>() {
vb
} else {
0
}
} else {
0
};
version = Version::getVersionForNumber(versionNumber)?;
let bitsNeeded = calculateBitsNeeded(mode, &header_bits, &data_bits, version);
if !willFit(bitsNeeded, version, &ec_level) {
return Err(Exceptions::writerWith("Data too big for requested version"));
}
} else {
version = recommendVersion(&ec_level, mode, &header_bits, &data_bits)?;
}
header_and_data_bits = BitArray::new();
header_and_data_bits.appendBitArray(header_bits);
// Find "length" of main segment and write it
let num_letters = if mode == Mode::BYTE {
data_bits.getSizeInBytes()
} else {
content.graphemes(true).count()
};
appendLengthInfo(num_letters as u32, version, mode, &mut header_and_data_bits)?;
// Put data together into the overall payload
header_and_data_bits.appendBitArray(data_bits);
}
let ec_blocks = version.getECBlocksForLevel(ec_level);
let num_data_bytes = version.getTotalCodewords() - ec_blocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(num_data_bytes, &mut header_and_data_bits)?;
// Interleave data bits with error correction code.
let final_bits = interleaveWithECBytes(
&header_and_data_bits,
version.getTotalCodewords(),
num_data_bytes,
ec_blocks.getNumBlocks(),
)?;
let mut qrCode = QRCode::new();
qrCode.setECLevel(ec_level);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
let dimension = version.getDimensionForVersion();
let mut matrix = ByteMatrix::new(dimension, dimension);
// Enable manual selection of the pattern to be used via hint
let mut mask_pattern = -1;
if let Some(EncodeHintValue::QrMaskPattern(v)) = hints.get(&EncodeHintType::QR_MASK_PATTERN) {
let hint_mask_pattern = if let Ok(vb) = v.parse::<i32>() {
vb
} else {
-1
};
mask_pattern = if QRCode::isValidMaskPattern(hint_mask_pattern) {
hint_mask_pattern
} else {
-1
};
}
if mask_pattern == -1 {
mask_pattern = chooseMaskPattern(&final_bits, &ec_level, version, &mut matrix)? as i32;
}
qrCode.setMaskPattern(mask_pattern);
// Build the matrix and set it to "qrCode".
matrix_util::buildMatrix(&final_bits, &ec_level, version, mask_pattern, &mut matrix)?;
qrCode.setMatrix(matrix);
Ok(qrCode)
}
/**
* Decides the smallest version of QR code that will contain all of the provided data.
*
* @throws WriterException if the data cannot fit in any version
*/
fn recommendVersion(
ec_level: &ErrorCorrectionLevel,
mode: Mode,
header_bits: &BitArray,
data_bits: &BitArray,
) -> Result<VersionRef> {
// Hard part: need to know version to know how many bits length takes. But need to know how many
// bits it takes to know version. First we take a guess at version by assuming version will be
// the minimum, 1:
let provisional_bits_needed = calculateBitsNeeded(
mode,
header_bits,
data_bits,
Version::getVersionForNumber(1)?,
);
let provisional_version = chooseVersion(provisional_bits_needed, ec_level)?;
// Use that guess to calculate the right version. I am still not sure this works in 100% of cases.
let bits_needed = calculateBitsNeeded(mode, header_bits, data_bits, provisional_version);
chooseVersion(bits_needed, ec_level)
}
fn calculateBitsNeeded(
mode: Mode,
header_bits: &BitArray,
data_bits: &BitArray,
version: VersionRef,
) -> u32 {
(header_bits.getSize() + mode.getCharacterCountBits(version) as usize + data_bits.getSize())
as u32
}
/**
* @return the code point of the table used in alphanumeric mode or
* -1 if there is no corresponding code in the table.
*/
pub fn getAlphanumericCode(code: u32) -> i8 {
let code = code as usize;
if code < ALPHANUMERIC_TABLE.len() {
ALPHANUMERIC_TABLE[code]
} else {
-1
}
}
pub fn chooseMode(content: &str) -> Mode {
chooseModeWithEncoding(content, encoding::all::ISO_8859_1)
}
/**
* Choose the best mode by examining the content. Note that 'encoding' is used as a hint;
* if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}.
*/
fn chooseModeWithEncoding(content: &str, encoding: EncodingRef) -> Mode {
if SHIFT_JIS_CHARSET.name() == encoding.name() && isOnlyDoubleByteKanji(content) {
// Choose Kanji mode if all input are double-byte characters
return Mode::KANJI;
}
let mut has_numeric = false;
let mut has_alphanumeric = false;
for c in content.chars() {
if ('0'..='9').contains(&c) {
has_numeric = true;
} else if getAlphanumericCode(c as u32) != -1 {
has_alphanumeric = true;
} else {
return Mode::BYTE;
}
}
if has_alphanumeric {
return Mode::ALPHANUMERIC;
}
if has_numeric {
return Mode::NUMERIC;
}
Mode::BYTE
}
pub fn isOnlyDoubleByteKanji(content: &str) -> bool {
let bytes = if let Ok(byt) = SHIFT_JIS_CHARSET.encode(content, encoding::EncoderTrap::Strict) {
byt
} else {
return false;
};
let length = bytes.len();
if length % 2 != 0 {
return false;
}
let mut i = 0;
while i < length {
let byte1 = bytes[i];
if !(0x81..=0x9F).contains(&byte1) && !(0xE0..=0xEB).contains(&byte1) {
return false;
}
i += 2;
}
true
}
fn chooseMaskPattern(
bits: &BitArray,
ec_level: &ErrorCorrectionLevel,
version: VersionRef,
matrix: &mut ByteMatrix,
) -> Result<u32> {
let mut min_penalty = u32::MAX; // Lower penalty is better.
let mut best_mask_pattern = -1;
// We try all mask patterns to choose the best one.
for maskPattern in 0..QRCode::NUM_MASK_PATTERNS {
let mut matrix = matrix.clone();
matrix_util::buildMatrix(bits, ec_level, version, maskPattern, &mut matrix)?;
let penalty = calculateMaskPenalty(&matrix);
if penalty < min_penalty {
min_penalty = penalty;
best_mask_pattern = maskPattern;
}
}
Ok(best_mask_pattern as u32)
}
fn chooseVersion(numInputBits: u32, ecLevel: &ErrorCorrectionLevel) -> Result<VersionRef> {
for versionNum in 1..=40 {
let version = Version::getVersionForNumber(versionNum)?;
if willFit(numInputBits, version, ecLevel) {
return Ok(version);
}
}
Err(Exceptions::writerWith(format!(
"data too big {numInputBits}/{ecLevel:?}"
)))
}
/**
* @return true if the number of input bits will fit in a code with the specified version and
* error correction level.
*/
pub fn willFit(numInputBits: u32, version: VersionRef, ecLevel: &ErrorCorrectionLevel) -> bool {
// In the following comments, we use numbers of Version 7-H.
// numBytes = 196
let num_bytes = version.getTotalCodewords();
// getNumECBytes = 130
let ec_blocks = version.getECBlocksForLevel(*ecLevel);
let num_ec_bytes = ec_blocks.getTotalECCodewords();
// getNumDataBytes = 196 - 130 = 66
let num_data_bytes = num_bytes - num_ec_bytes;
let total_input_bytes = (numInputBits + 7) / 8;
num_data_bytes >= total_input_bytes
}
/**
* Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24).
*/
pub fn terminateBits(num_data_bytes: u32, bits: &mut BitArray) -> Result<()> {
let capacity = num_data_bytes * 8;
if bits.getSize() > capacity as usize {
return Err(Exceptions::writerWith(format!(
"data bits cannot fit in the QR Code{capacity} > "
)));
}
// Append Mode.TERMINATE if there is enough space (value is 0000)
for _i in 0..4 {
if bits.getSize() >= capacity as usize {
break;
}
bits.appendBit(false);
}
// Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details.
// If the last byte isn't 8-bit aligned, we'll add padding bits.
let num_bits_in_last_byte = bits.getSize() & 0x07;
if num_bits_in_last_byte > 0 {
for _i in num_bits_in_last_byte..8 {
bits.appendBit(false);
}
}
// If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).
let num_padding_bytes = num_data_bytes as isize - bits.getSizeInBytes() as isize;
for i in 0..num_padding_bytes {
if i >= num_padding_bytes {
break;
}
bits.appendBits(if (i & 0x01) == 0 { 0xEC } else { 0x11 }, 8)?;
}
if bits.getSize() != capacity as usize {
return Err(Exceptions::writerWith("Bits size does not equal capacity"));
}
Ok(())
}
/**
* Get number of data bytes and number of error correction bytes for block id "blockID". Store
* the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of
* JISX0510:2004 (p.30)
*/
pub fn getNumDataBytesAndNumECBytesForBlockID(
num_total_bytes: u32,
num_data_bytes: u32,
num_rsblocks: u32,
block_id: u32,
// numDataBytesInBlock: &mut [u32],
// numECBytesInBlock: &mut [u32],
) -> Result<(u32, u32)> {
if block_id >= num_rsblocks {
return Err(Exceptions::writerWith("Block ID too large"));
}
// numRsBlocksInGroup2 = 196 % 5 = 1
let num_rs_blocks_in_group2 = num_total_bytes % num_rsblocks;
// numRsBlocksInGroup1 = 5 - 1 = 4
let num_rs_blocks_in_group1 = num_rsblocks - num_rs_blocks_in_group2;
// numTotalBytesInGroup1 = 196 / 5 = 39
let num_total_bytes_in_group1 = num_total_bytes / num_rsblocks;
// numTotalBytesInGroup2 = 39 + 1 = 40
let num_total_bytes_in_group2 = num_total_bytes_in_group1 + 1;
// numDataBytesInGroup1 = 66 / 5 = 13
let num_data_bytes_in_group1 = num_data_bytes / num_rsblocks;
// numDataBytesInGroup2 = 13 + 1 = 14
let num_data_bytes_in_group2 = num_data_bytes_in_group1 + 1;
// numEcBytesInGroup1 = 39 - 13 = 26
let num_ec_bytes_in_group1 = num_total_bytes_in_group1 - num_data_bytes_in_group1;
// numEcBytesInGroup2 = 40 - 14 = 26
let numEcBytesInGroup2 = num_total_bytes_in_group2 - num_data_bytes_in_group2;
// Sanity checks.
// 26 = 26
if num_ec_bytes_in_group1 != numEcBytesInGroup2 {
return Err(Exceptions::writerWith("EC bytes mismatch"));
}
// 5 = 4 + 1.
if num_rsblocks != num_rs_blocks_in_group1 + num_rs_blocks_in_group2 {
return Err(Exceptions::writerWith("RS blocks mismatch"));
}
// 196 = (13 + 26) * 4 + (14 + 26) * 1
if num_total_bytes
!= ((num_data_bytes_in_group1 + num_ec_bytes_in_group1) * num_rs_blocks_in_group1)
+ ((num_data_bytes_in_group2 + numEcBytesInGroup2) * num_rs_blocks_in_group2)
{
return Err(Exceptions::writerWith("total bytes mismatch"));
}
Ok(if block_id < num_rs_blocks_in_group1 {
(num_data_bytes_in_group1, num_ec_bytes_in_group1)
} else {
(num_data_bytes_in_group2, numEcBytesInGroup2)
})
}
/**
* Interleave "bits" with corresponding error correction bytes. On success, store the result in
* "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details.
*/
pub fn interleaveWithECBytes(
bits: &BitArray,
num_total_bytes: u32,
num_data_bytes: u32,
num_rsblocks: u32,
) -> Result<BitArray> {
// "bits" must have "getNumDataBytes" bytes of data.
if bits.getSizeInBytes() as u32 != num_data_bytes {
return Err(Exceptions::writerWith(
"Number of bits and data bytes does not match",
));
}
// Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll
// store the divided data bytes blocks and error correction bytes blocks into "blocks".
let mut data_bytes_offset = 0;
let mut max_num_data_bytes = 0;
let mut max_num_ec_bytes = 0;
// Since, we know the number of reedsolmon blocks, we can initialize the vector with the number.
let mut blocks = Vec::new();
for i in 0..num_rsblocks {
let (numDataBytesInBlock, numEcBytesInBlock) = getNumDataBytesAndNumECBytesForBlockID(
num_total_bytes,
num_data_bytes,
num_rsblocks,
i,
// &mut numDataBytesInBlock,
// &mut numEcBytesInBlock,
)?;
let size = numDataBytesInBlock;
let mut dataBytes = vec![0u8; size as usize];
bits.toBytes(8 * data_bytes_offset, &mut dataBytes, 0, size as usize);
let ec_bytes = generateECBytes(&dataBytes, numEcBytesInBlock as usize)?;
blocks.push(BlockPair::new(dataBytes, ec_bytes.clone()));
max_num_data_bytes = max_num_data_bytes.max(size);
max_num_ec_bytes = max_num_ec_bytes.max(ec_bytes.len());
data_bytes_offset += numDataBytesInBlock as usize;
}
if num_data_bytes != data_bytes_offset as u32 {
return Err(Exceptions::writerWith("Data bytes does not match offset"));
}
let mut result = BitArray::new();
// First, place data blocks.
for i in 0..max_num_data_bytes as usize {
for block in &blocks {
let data_bytes = block.getDataBytes();
if i < data_bytes.len() {
result.appendBits(data_bytes[i] as u32, 8)?;
}
}
}
// Then, place error correction blocks.
for i in 0..max_num_ec_bytes {
for block in &blocks {
let ec_bytes = block.getErrorCorrectionBytes();
if i < ec_bytes.len() {
result.appendBits(ec_bytes[i] as u32, 8)?;
}
}
}
if num_total_bytes != result.getSizeInBytes() as u32 {
// Should be same.
return Err(Exceptions::writerWith(format!(
"Interleaving error: {} and {} differ.",
num_total_bytes,
result.getSizeInBytes()
)));
}
Ok(result)
}
pub fn generateECBytes(dataBytes: &[u8], num_ec_bytes_in_block: usize) -> Result<Vec<u8>> {
let num_data_bytes = dataBytes.len();
let mut to_encode = vec![0; num_data_bytes + num_ec_bytes_in_block];
for i in 0..num_data_bytes {
to_encode[i] = dataBytes[i] as i32;
}
ReedSolomonEncoder::new(get_predefined_genericgf(
PredefinedGenericGF::QrCodeField256,
))?
.encode(&mut to_encode, num_ec_bytes_in_block)?;
let mut ecBytes = vec![0u8; num_ec_bytes_in_block];
for i in 0..num_ec_bytes_in_block {
ecBytes[i] = to_encode[num_data_bytes + i] as u8;
}
Ok(ecBytes)
}
/**
* Append mode info. On success, store the result in "bits".
*/
pub fn appendModeInfo(mode: Mode, bits: &mut BitArray) -> Result<()> {
bits.appendBits(mode.getBits() as u32, 4)
}
/**
* Append length info. On success, store the result in "bits".
*/
pub fn appendLengthInfo(
num_letters: u32,
version: VersionRef,
mode: Mode,
bits: &mut BitArray,
) -> Result<()> {
let numBits = mode.getCharacterCountBits(version);
if num_letters >= (1 << numBits) {
return Err(Exceptions::writerWith(format!(
"{} is bigger than {}",
num_letters,
((1 << numBits) - 1)
)));
}
bits.appendBits(num_letters, numBits as usize)
}
/**
* Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits".
*/
pub fn appendBytes(
content: &str,
mode: Mode,
bits: &mut BitArray,
encoding: EncodingRef,
) -> Result<()> {
match mode {
Mode::NUMERIC => appendNumericBytes(content, bits),
Mode::ALPHANUMERIC => appendAlphanumericBytes(content, bits),
Mode::BYTE => append8BitBytes(content, bits, encoding),
Mode::KANJI => appendKanjiBytes(content, bits),
_ => Err(Exceptions::writerWith(format!("Invalid mode: {mode:?}"))),
}
}
pub fn appendNumericBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let length = content.len();
let mut i = 0;
while i < length {
let num1 = content.chars().nth(i).ok_or(Exceptions::indexOutOfBounds)? as u8 - b'0';
if i + 2 < length {
// Encode three numeric letters in ten bits.
let num2 = content
.chars()
.nth(i + 1)
.ok_or(Exceptions::indexOutOfBounds)? as u8
- b'0';
let num3 = content
.chars()
.nth(i + 2)
.ok_or(Exceptions::indexOutOfBounds)? as u8
- b'0';
bits.appendBits(num1 as u32 * 100 + num2 as u32 * 10 + num3 as u32, 10)?;
i += 3;
} else if i + 1 < length {
// Encode two numeric letters in seven bits.
let num2 = content
.chars()
.nth(i + 1)
.ok_or(Exceptions::indexOutOfBounds)? as u8
- b'0';
bits.appendBits(num1 as u32 * 10 + num2 as u32, 7)?;
i += 2;
} else {
// Encode one numeric letter in four bits.
bits.appendBits(num1 as u32, 4)?;
i += 1;
}
}
Ok(())
}
pub fn appendAlphanumericBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let length = content.len();
let mut i = 0;
while i < length {
let code1 =
getAlphanumericCode(content.chars().nth(i).ok_or(Exceptions::indexOutOfBounds)? as u32);
if code1 == -1 {
return Err(Exceptions::writer);
}
if i + 1 < length {
let code2 = getAlphanumericCode(
content
.chars()
.nth(i + 1)
.ok_or(Exceptions::indexOutOfBounds)? as u32,
);
if code2 == -1 {
return Err(Exceptions::writer);
}
// Encode two alphanumeric letters in 11 bits.
bits.appendBits((code1 as i16 * 45 + code2 as i16) as u32, 11)?;
i += 2;
} else {
// Encode one alphanumeric letter in six bits.
bits.appendBits(code1 as u32, 6)?;
i += 1;
}
}
Ok(())
}
pub fn append8BitBytes(content: &str, bits: &mut BitArray, encoding: EncodingRef) -> Result<()> {
let bytes = encoding
.encode(content, encoding::EncoderTrap::Strict)
.map_err(|e| Exceptions::writerWith(format!("error {e}")))?;
for b in bytes {
bits.appendBits(b as u32, 8)?;
}
Ok(())
}
pub fn appendKanjiBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let sjis = &SHIFT_JIS_CHARSET;
let bytes = sjis
.encode(content, encoding::EncoderTrap::Strict)
.map_err(|e| Exceptions::writerWith(format!("error {e}")))?;
if bytes.len() % 2 != 0 {
return Err(Exceptions::writerWith("Kanji byte size not even"));
}
let max_i = bytes.len() - 1; // bytes.length must be even
let mut i = 0;
while i < max_i {
let byte1 = bytes[i]; // & 0xFF;
let byte2 = bytes[i + 1]; // & 0xFF;
let code: u16 = ((byte1 as u16) << 8u16) | byte2 as u16;
let mut subtracted: i32 = -1;
if (0x8140..=0x9ffc).contains(&code) {
subtracted = code as i32 - 0x8140;
} else if (0xe040..=0xebbf).contains(&code) {
subtracted = code as i32 - 0xc140;
}
if subtracted == -1 {
return Err(Exceptions::writerWith("Invalid byte sequence"));
}
let encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
bits.appendBits(encoded as u32, 13)?;
i += 2;
}
Ok(())
}
fn appendECI(eci: &CharacterSetECI, bits: &mut BitArray) -> Result<()> {
bits.appendBits(Mode::ECI.getBits() as u32, 4)?;
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValueSelf(), 8)
}