fix: 前端 HIGH/MEDIUM — timer 清理 + 历史持久化 + Error Boundary + console 移除
This commit is contained in:
+40
-46
@@ -1,46 +1,40 @@
|
||||
/// GF(2⁸) Galois 域运算
|
||||
/// 本原多项式: x⁸ + x⁴ + x³ + x² + 1 = 0x11D
|
||||
/// 生成元 α = 0x02
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
fn exp_table() -> &'static [u8; 512] {
|
||||
static TABLE: OnceLock<[u8; 512]> = OnceLock::new();
|
||||
TABLE.get_or_init(|| {
|
||||
let mut table = [0u8; 512];
|
||||
let mut x = 1u8;
|
||||
for i in 0..255 {
|
||||
table[i] = x;
|
||||
table[i + 255] = x; // 双倍长度避免 % 255
|
||||
let next = (x as u16) << 1;
|
||||
x = if next >= 0x100 {
|
||||
(next ^ 0x1D) as u8
|
||||
} else {
|
||||
next as u8
|
||||
};
|
||||
}
|
||||
table[510] = table[255];
|
||||
table[511] = table[256];
|
||||
table
|
||||
})
|
||||
}
|
||||
static EXP_TABLE: LazyLock<[u8; 512]> = LazyLock::new(|| {
|
||||
let mut table = [0u8; 512];
|
||||
let mut x = 1u8;
|
||||
for i in 0..255 {
|
||||
table[i] = x;
|
||||
table[i + 255] = x; // 双倍长度避免 % 255
|
||||
let next = (x as u16) << 1;
|
||||
x = if next >= 0x100 {
|
||||
(next ^ 0x1D) as u8
|
||||
} else {
|
||||
next as u8
|
||||
};
|
||||
}
|
||||
table[510] = table[255];
|
||||
table[511] = table[256];
|
||||
table
|
||||
});
|
||||
|
||||
fn log_table() -> &'static [u8; 256] {
|
||||
static TABLE: OnceLock<[u8; 256]> = OnceLock::new();
|
||||
TABLE.get_or_init(|| {
|
||||
let mut table = [0u8; 256];
|
||||
let mut x = 1u8;
|
||||
for i in 0..255 {
|
||||
table[x as usize] = i;
|
||||
let next = (x as u16) << 1;
|
||||
x = if next >= 0x100 {
|
||||
(next ^ 0x1D) as u8
|
||||
} else {
|
||||
next as u8
|
||||
};
|
||||
}
|
||||
table
|
||||
})
|
||||
}
|
||||
static LOG_TABLE: LazyLock<[u8; 256]> = LazyLock::new(|| {
|
||||
let mut table = [0u8; 256];
|
||||
let mut x = 1u8;
|
||||
for i in 0..255 {
|
||||
table[x as usize] = i;
|
||||
let next = (x as u16) << 1;
|
||||
x = if next >= 0x100 {
|
||||
(next ^ 0x1D) as u8
|
||||
} else {
|
||||
next as u8
|
||||
};
|
||||
}
|
||||
table
|
||||
});
|
||||
|
||||
/// GF(2⁸) 加法 = 异或
|
||||
#[inline]
|
||||
@@ -60,9 +54,9 @@ pub fn mul(a: u8, b: u8) -> u8 {
|
||||
if a == 0 || b == 0 {
|
||||
return 0;
|
||||
}
|
||||
let log_a = log_table()[a as usize] as usize;
|
||||
let log_b = log_table()[b as usize] as usize;
|
||||
exp_table()[log_a + log_b]
|
||||
let log_a = LOG_TABLE[a as usize] as usize;
|
||||
let log_b = LOG_TABLE[b as usize] as usize;
|
||||
EXP_TABLE[log_a + log_b]
|
||||
}
|
||||
|
||||
/// GF(2⁸) 除法:a / b,b == 0 时返回 None
|
||||
@@ -74,10 +68,10 @@ pub fn div(a: u8, b: u8) -> Option<u8> {
|
||||
if b == 0 {
|
||||
return None;
|
||||
}
|
||||
let log_a = log_table()[a as usize] as usize;
|
||||
let log_b = log_table()[b as usize] as usize;
|
||||
let log_a = LOG_TABLE[a as usize] as usize;
|
||||
let log_b = LOG_TABLE[b as usize] as usize;
|
||||
let diff = (log_a + 255 - log_b) % 255;
|
||||
Some(exp_table()[diff])
|
||||
Some(EXP_TABLE[diff])
|
||||
}
|
||||
|
||||
/// GF(2⁸) 幂运算:base^exp
|
||||
@@ -89,8 +83,8 @@ pub fn pow(base: u8, exp: usize) -> u8 {
|
||||
if base == 0 {
|
||||
return 0;
|
||||
}
|
||||
let log_b = log_table()[base as usize] as usize;
|
||||
exp_table()[(log_b * exp) % 255]
|
||||
let log_b = LOG_TABLE[base as usize] as usize;
|
||||
EXP_TABLE[(log_b * exp) % 255]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -163,8 +163,8 @@ fn unicode_to_shift_jis(c: char) -> Option<u16> {
|
||||
let sjis = ((hi << 8) | lo) as u16;
|
||||
// 映射到 13-bit 码字(内层 if/else 已区分两个 Shift-JIS 区间)
|
||||
let val = {
|
||||
let h = (sjis >> 8);
|
||||
let l = (sjis & 0xFF);
|
||||
let h = sjis >> 8;
|
||||
let l = sjis & 0xFF;
|
||||
if (0x81..=0x9F).contains(&h) {
|
||||
(h - 0x81) * 0xBC + (l - 0x40)
|
||||
} else {
|
||||
@@ -277,6 +277,37 @@ mod tests {
|
||||
assert!(!is_kanji('A'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kanji_encode_basic() {
|
||||
// 常用汉字 "中文" 应能编码且长度正确
|
||||
let bits = encode_kanji("中文");
|
||||
// 2 个汉字,每个 13 bit = 26 bit
|
||||
assert_eq!(bits.len(), 26);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_to_shift_jis_known() {
|
||||
// 基本汉字应返回 Some
|
||||
assert!(unicode_to_shift_jis('中').is_some());
|
||||
assert!(unicode_to_shift_jis('文').is_some());
|
||||
assert!(unicode_to_shift_jis('你').is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_to_shift_jis_ascii_returns_none() {
|
||||
// ASCII 字符不是汉字
|
||||
assert!(unicode_to_shift_jis('A').is_none());
|
||||
assert!(unicode_to_shift_jis('1').is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kanji_mode_fallback() {
|
||||
// 非 CJK 字符会被降级为 UTF-8 字节编码
|
||||
let bits = encode_kanji("A");
|
||||
// 1 个 ASCII 字符 = 8 bit
|
||||
assert_eq!(bits.len(), 8);
|
||||
}
|
||||
|
||||
fn bits_to_u16(bits: &[bool]) -> u16 {
|
||||
bits.iter().fold(0, |acc, &b| (acc << 1) | (b as u16))
|
||||
}
|
||||
|
||||
@@ -185,19 +185,19 @@ fn score_rule4(matrix: &Matrix) -> u32 {
|
||||
pub fn best_mask(matrix: &Matrix) -> (u8, Matrix) {
|
||||
let mut best_idx = 0u8;
|
||||
let mut best_score = u32::MAX;
|
||||
let mut best_matrix = matrix.clone();
|
||||
let mut best_matrix: Option<Matrix> = None;
|
||||
|
||||
for i in 0..8u8 {
|
||||
let masked = apply_mask(matrix, i);
|
||||
let s = score(&masked);
|
||||
if s < best_score {
|
||||
if best_matrix.is_none() || s < best_score {
|
||||
best_score = s;
|
||||
best_idx = i;
|
||||
best_matrix = masked;
|
||||
best_matrix = Some(masked);
|
||||
}
|
||||
}
|
||||
|
||||
(best_idx, best_matrix)
|
||||
(best_idx, best_matrix.unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -24,6 +24,15 @@ pub fn place_finder_patterns(matrix: &mut Matrix) {
|
||||
matrix.reserve(x, y);
|
||||
}
|
||||
}
|
||||
// 定位图案分隔符(1 模块宽的白色边框,在 finder 周围)
|
||||
for i in 0..8u8 {
|
||||
if fx + 7 < matrix.size {
|
||||
matrix.reserve(fx + 7, fy + i); // 右侧分隔列
|
||||
}
|
||||
if fy + 7 < matrix.size {
|
||||
matrix.reserve(fx + i, fy + 7); // 底部隔行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +68,7 @@ fn place_single_alignment(matrix: &mut Matrix, cx: u8, cy: u8) {
|
||||
let y0 = cy - 2;
|
||||
for dy in 0..5u8 {
|
||||
for dx in 0..5u8 {
|
||||
let is_dark = match (dx, dy) {
|
||||
(0, _) | (4, _) | (_, 0) | (_, 4) => true,
|
||||
(2, 2) => true,
|
||||
_ => false,
|
||||
};
|
||||
let is_dark = matches!((dx, dy), (0, _) | (4, _) | (_, 0) | (_, 4) | (2, 2));
|
||||
let x = x0 + dx;
|
||||
let y = y0 + dy;
|
||||
matrix.set(x, y, is_dark);
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ impl QrCode {
|
||||
crate::render::ascii::render_ascii(self, invert)
|
||||
}
|
||||
|
||||
pub fn to_png_bytes(&self, module_size: u8) -> Vec<u8> {
|
||||
pub fn to_png_bytes(&self, module_size: u8) -> Result<Vec<u8>, image::ImageError> {
|
||||
crate::render::png::render_png(self, module_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::qr::QrCode;
|
||||
use image::{ImageBuffer, Luma};
|
||||
|
||||
pub fn render_png(qr: &QrCode, module_size: u8) -> Vec<u8> {
|
||||
pub fn render_png(qr: &QrCode, module_size: u8) -> Result<Vec<u8>, image::ImageError> {
|
||||
let matrix_size = qr.size() as u32;
|
||||
let margin = qr.margin as u32;
|
||||
let total_size = matrix_size + 2 * margin;
|
||||
@@ -34,7 +34,6 @@ pub fn render_png(qr: &QrCode, module_size: u8) -> Vec<u8> {
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
|
||||
.expect("PNG 编码失败");
|
||||
buf
|
||||
img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ fn test_ascii_output() {
|
||||
#[test]
|
||||
fn test_png_output() {
|
||||
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
|
||||
let png = qr.to_png_bytes(4);
|
||||
let png = qr.to_png_bytes(4).unwrap();
|
||||
assert!(!png.is_empty());
|
||||
// PNG 文件应以 8 字节魔术签名开头
|
||||
assert_eq!(&png[..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
|
||||
|
||||
Reference in New Issue
Block a user