refactor(ocr): 拆分字符集限制枚举并引入声明式多路组合子宏

- 将原本臃肿的 CharsetRestrict 拆分为 CharRestrict(内容过滤)和 IdRestrict(索引过滤),实现职责解耦
 - 引入 any_of! 声明式宏实现无 Box、无堆内存分配的栈上多路组合,规避孤儿规则
 - 完善 estimated_capacity 容量预估函数,实现真正的 O(1) 精准内存开辟
This commit is contained in:
2026-06-16 09:37:15 +08:00
parent b7146831f7
commit 189f2bd697
4 changed files with 111 additions and 102 deletions

View File

@@ -1,4 +1,4 @@
use crate::charset::CharsetRestrict;
use crate::charset::{TokenFilter, ValidationCtx};
use crate::model_metadata::ModelMetadata;
use crate::models::base::ModelArgs;
use crate::models::loader::{ModelLoader, ModelSession, ModelType};
@@ -158,45 +158,34 @@ impl<'a> OcrBuilder<'a> {
self.color_filter_custom_ranges = Some(value);
self
}
pub fn charset_restrict(mut self, restrict: &CharsetRestrict) -> Self {
pub fn charset_restrict(mut self, restrict: &dyn TokenFilter) -> Self {
let charset = &self.ocr.model_metadata.charset;
let tokens = &charset.tokens;
// let mut temp_indices = Vec::new();
let mut has_any_match = false;
let estimated_capacity = match restrict {
CharsetRestrict::Digit => 16,
CharsetRestrict::Lowercase | CharsetRestrict::Uppercase => 32,
CharsetRestrict::CustomList(vec) => vec.len() + 1, // 动态匹配列表大小
CharsetRestrict::TopN(n) => *n + 1,
_ => 128, // 组合子Or等复杂情况给个 128 黄金保底值
};
let estimated_capacity = restrict.estimated_capacity();
// 精准开辟内存,完美避开 8210 个槽位的巨大空置浪费
let mut temp_indices = Vec::with_capacity(estimated_capacity);
if let CharsetRestrict::TopN(n) = *restrict {
let limit = std::cmp::min(n, tokens.len());
// 边界防御:CTC Blank (索引 0) 必须无条件放行
temp_indices.push(0);
// temp_indices.extend(0..limit);
// has_any_match = limit > &0;
// 塞入剩余的有效索引范围排除0从1开始截取
if limit > 1 {
temp_indices.extend(1..limit);
for (idx, token) in tokens.iter().enumerate() {
let token_str = token.as_ref();
// CTC Blank 空字符串无条件放行,其余交给超高性能的 matches
if token_str.is_empty() || idx == 0 {
temp_indices.push(idx);
};
// 组装无拷贝上下文
let ctx = ValidationCtx {
text: token_str,
token_id: idx,
};
if restrict.matches(&ctx) {
temp_indices.push(idx);
has_any_match = true;
}
} else {
for (idx, token) in tokens.iter().enumerate() {
let token_str = token.as_ref();
// CTC Blank 空字符串无条件放行,其余交给超高性能的 matches
if token_str.is_empty() {
temp_indices.push(idx);
} else if restrict.matches(token_str) {
temp_indices.push(idx);
has_any_match = true;
}
}
}
// self.charset_restrict = Some(restrict);
// 终极防御:如果除了 Blank 外什么都没匹配上,退化恢复为 None全量识别
if !has_any_match {
@@ -266,7 +255,7 @@ impl<'a> OcrBuilder<'a> {
// fn valid_indices(&self) -> (bool, HashSet<usize>) {
// let charset = &self.ocr.model_metadata.charset;
/// 🌟 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印
/// 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印
/// 这里的 &str 完美借用了自 tokens依然是彻底的零拷贝
pub fn get_valid_tokens(&self) -> Vec<&str> {
let charset = &self.ocr.model_metadata.charset;