fix: 前端 HIGH/MEDIUM — timer 清理 + 历史持久化 + Error Boundary + console 移除

This commit is contained in:
2026-06-17 09:03:38 +08:00
parent feb5ae709f
commit 91bdf9ecc3
15 changed files with 184 additions and 84 deletions
+40 -46
View File
@@ -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 / bb == 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)]
+33 -2
View File
@@ -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))
}
+4 -4
View File
@@ -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)]
+10 -5
View File
@@ -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
View File
@@ -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)
}
}
+3 -4
View File
@@ -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)
}
+1 -1
View File
@@ -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]);