1use docvortex_core::{median, quantile};
4use rapiddoc_core::{Error, ErrorCode};
5pub mod bar_geometry;
6pub mod geometry_plan;
7pub mod inline_evidence;
8pub mod raster_charts;
9pub mod text_noise;
10pub mod visual_text;
11
12pub use docvortex_core::note_index::HeightIndex as TableNoteHeightIndex;
14pub use docvortex_core::row_geometry::RowGeometry as TableNoteRowGeometry;
15
16pub type NativeLaneGeometry = docvortex_core::rule_text::LaneGeometry;
17pub type NativeTailLine = docvortex_core::rule_text::TailLine;
18fn lane_finite(n: f64) -> bool {
19 n.is_finite() && n.abs() <= 1e15
20}
21fn validate_lane_geometry(lines: &[NativeLaneGeometry]) -> Result<(), Error> {
22 if lines.is_empty() || lines.len() > rapiddoc_core::resources::limit(512) {
23 return Err(error(ErrorCode::ResourceLimit, "栏带输入行数超限"));
24 }
25 if lines.iter().any(|l| {
26 l.0.iter().any(|&n| !lane_finite(n))
27 || l.0[2] <= l.0[0]
28 || l.0[3] <= l.0[1]
29 || !lane_finite(l.1)
30 || l.1 <= 0.
31 }) {
32 return Err(error(ErrorCode::InvalidInput, "栏带几何无效"));
33 }
34 Ok(())
35}
36fn validate_lane_bounds(lanes: &[(f64, f64)]) -> Result<(), Error> {
37 if lanes.is_empty() || lanes.len() > rapiddoc_core::resources::limit(512) {
38 return Err(error(ErrorCode::ResourceLimit, "栏带数量超限"));
39 }
40 if lanes
41 .iter()
42 .any(|&(l, r)| !lane_finite(l) || !lane_finite(r))
43 {
44 return Err(error(ErrorCode::InvalidInput, "栏带边界无效"));
45 }
46 Ok(())
47}
48pub fn text_supported_lane_intervals(
50 lines: &[NativeLaneGeometry],
51 width: f64,
52 median: f64,
53) -> Result<Vec<(f64, f64, usize)>, Error> {
54 validate_lane_geometry(lines)?;
55 if !lane_finite(width) || width <= 0. || !lane_finite(median) || median <= 0. {
56 return Err(error(ErrorCode::InvalidInput, "栏带尺度无效"));
57 }
58 Ok(docvortex_core::rule_text::supported_lane_intervals(
59 lines, width, median,
60 ))
61}
62pub fn text_lane_assignments(
63 lines: &[NativeLaneGeometry],
64 lanes: &[(f64, f64)],
65 tolerance: f64,
66 nested: Option<(f64, f64)>,
67) -> Result<Vec<Option<usize>>, Error> {
68 validate_lane_geometry(lines)?;
69 validate_lane_bounds(lanes)?;
70 if !lane_finite(tolerance)
71 || tolerance < 0.
72 || nested.is_some_and(|(t, b)| !lane_finite(t) || !lane_finite(b) || b < t)
73 {
74 return Err(error(ErrorCode::InvalidInput, "栏带分配参数无效"));
75 }
76 Ok(docvortex_core::rule_text::lane_assignments(
77 lines, lanes, tolerance, nested,
78 ))
79}
80pub fn text_short_tail_destinations(
81 lines: &[NativeTailLine],
82 lanes: &[(f64, f64)],
83 median: f64,
84) -> Result<Vec<(usize, usize)>, Error> {
85 validate_lane_bounds(lanes)?;
86 if lines.len() > rapiddoc_core::resources::limit(512) {
87 return Err(error(ErrorCode::ResourceLimit, "短尾行数超限"));
88 }
89 if !lane_finite(median)
90 || median <= 0.
91 || lines.iter().any(|l| {
92 l.0.iter().any(|&n| !lane_finite(n))
93 || !lane_finite(l.1)
94 || l.1 <= 0.
95 || !lane_finite(l.3)
96 || l.4.is_some_and(|n| !n.is_finite())
97 || l.5.is_some_and(|n| !lane_finite(n))
98 || l.9 >= lanes.len()
99 })
100 {
101 return Err(error(ErrorCode::InvalidInput, "短尾快照无效"));
102 }
103 Ok(docvortex_core::rule_text::short_tail_destinations(
104 lines, lanes, median,
105 ))
106}
107fn validate_formula_lanes(lines: &[NativeLaneGeometry]) -> Result<(), Error> {
110 if lines.is_empty() || lines.len() > rapiddoc_core::resources::limit(4096) {
111 return Err(error(ErrorCode::ResourceLimit, "公式栏带行数超限"));
112 }
113 if lines
114 .iter()
115 .any(|l| l.0.iter().any(|&n| !lane_finite(n)) || !lane_finite(l.1) || l.1 <= 0.)
116 {
117 return Err(error(ErrorCode::InvalidInput, "公式栏带几何无效"));
118 }
119 Ok(())
120}
121fn validate_formula_bounds(lanes: &[(f64, f64)]) -> Result<(), Error> {
122 if lanes.is_empty() || lanes.len() > rapiddoc_core::resources::limit(4096) {
123 return Err(error(ErrorCode::ResourceLimit, "公式栏带数量超限"));
124 }
125 if lanes
126 .iter()
127 .any(|&(l, r)| !lane_finite(l) || !lane_finite(r))
128 {
129 return Err(error(ErrorCode::InvalidInput, "公式栏带边界无效"));
130 }
131 Ok(())
132}
133pub fn text_formula_lane_intervals(
134 lines: &[NativeLaneGeometry],
135 width: f64,
136 median: f64,
137) -> Result<Vec<(f64, f64, usize)>, Error> {
138 validate_formula_lanes(lines)?;
139 if !lane_finite(width) || width <= 0. || !lane_finite(median) || median <= 0. {
140 return Err(error(ErrorCode::InvalidInput, "公式栏带尺度无效"));
141 }
142 Ok(docvortex_core::rule_text::supported_lane_intervals(
143 lines, width, median,
144 ))
145}
146pub fn text_formula_lane_assignments(
147 lines: &[NativeLaneGeometry],
148 lanes: &[(f64, f64)],
149 tolerance: f64,
150 nested: Option<(f64, f64)>,
151) -> Result<Vec<Option<usize>>, Error> {
152 validate_formula_lanes(lines)?;
153 validate_formula_bounds(lanes)?;
154 if !lane_finite(tolerance)
155 || tolerance < 0.
156 || nested.is_some_and(|(t, b)| !lane_finite(t) || !lane_finite(b) || b < t)
157 {
158 return Err(error(ErrorCode::InvalidInput, "公式栏带分配参数无效"));
159 }
160 Ok(docvortex_core::rule_text::lane_assignments(
161 lines, lanes, tolerance, nested,
162 ))
163}
164pub fn text_formula_tail_destinations(
165 lines: &[NativeTailLine],
166 lanes: &[(f64, f64)],
167 median: f64,
168) -> Result<Vec<(usize, usize)>, Error> {
169 validate_formula_bounds(lanes)?;
170 if lines.len() > rapiddoc_core::resources::limit(4096) {
171 return Err(error(ErrorCode::ResourceLimit, "公式短尾行数超限"));
172 }
173 if !lane_finite(median)
174 || median <= 0.
175 || lines.iter().any(|l| {
176 l.0.iter().any(|&n| !lane_finite(n))
177 || !lane_finite(l.1)
178 || l.1 <= 0.
179 || !lane_finite(l.3)
180 || l.4.is_some_and(|n| !n.is_finite())
181 || l.5.is_some_and(|n| !lane_finite(n))
182 || l.9 >= lanes.len()
183 })
184 {
185 return Err(error(ErrorCode::InvalidInput, "公式短尾快照无效"));
186 }
187 Ok(docvortex_core::rule_text::short_tail_destinations(
188 lines, lanes, median,
189 ))
190}
191pub mod grid;
192pub mod lines;
193pub mod script_geometry;
194pub type NativeGridIndex = (Vec<f64>, Vec<f64>, Vec<Vec<usize>>);
195pub type NativeRule = (u8, f64, f64, f64);
196pub fn table_decisiveness_mean(values: &[f64]) -> Result<f64, Error> {
198 if values.is_empty()
199 || values.len() > rapiddoc_core::resources::limit(20000)
200 || values
201 .iter()
202 .any(|v| !v.is_finite() || !(0.5..=1.).contains(v))
203 {
204 return Err(error(ErrorCode::InvalidInput, "隔断可信度均值输入无效"));
205 }
206 if values.iter().all(|v| *v == 1.) {
207 return Ok(1.);
208 }
209 let (total, power) = docvortex_core::exact_mean::exact_sum_parts(values)
210 .ok_or_else(|| error(ErrorCode::InvalidInput, "隔断可信度精确和失败"))?;
211 let numerator = (total as u128) << (power + 53);
213 let denominator = values.len() as u128;
214 let mut q = numerator / denominator;
215 let r = numerator % denominator;
216 if 2 * r > denominator || (2 * r == denominator && q % 2 == 1) {
217 q += 1
218 }
219 Ok(q as f64 * 2f64.powi(-53))
220}
221pub fn table_component_specs(
223 rows: usize,
224 cols: usize,
225 edges: Vec<(usize, usize)>,
226) -> Result<Option<Vec<CellRegion>>, Error> {
227 let count = rows
228 .checked_mul(cols)
229 .filter(|n| *n > 0 && *n <= rapiddoc_core::resources::limit(10000))
230 .ok_or_else(|| error(ErrorCode::ResourceLimit, "原生表格原子格超限"))?;
231 if edges.len() > count * 2 {
232 return Err(error(ErrorCode::ResourceLimit, "原生表格连接超限"));
233 }
234 if edges.iter().any(|&(a, b)| {
235 a >= count
236 || b >= count
237 || !(a / cols == b / cols && a.abs_diff(b) == 1
238 || a % cols == b % cols && a.abs_diff(b) == cols)
239 }) {
240 return Err(error(ErrorCode::InvalidInput, "原生表格连接无效"));
241 }
242 let parents = docvortex_core::tables::grid_parents(count, edges);
243 let (_, specs) = docvortex_core::tables::component_specs(parents, rows, cols);
244 Ok(specs.map(|s| {
245 s.into_iter()
246 .map(|(row, column, row_span, column_span)| CellRegion {
247 row,
248 column,
249 row_span,
250 column_span,
251 })
252 .collect()
253 }))
254}
255pub fn table_merge_rules(
257 rules: Vec<NativeRule>,
258 coordinates: Vec<(u8, Vec<f64>)>,
259 tolerance: f64,
260 join: f64,
261) -> Result<Vec<NativeRule>, Error> {
262 if rules.len() > rapiddoc_core::resources::limit(20000)
263 || coordinates.iter().map(|c| c.1.len()).sum::<usize>()
264 > rapiddoc_core::resources::limit(20000)
265 {
266 return Err(error(ErrorCode::ResourceLimit, "表格线段超过预算"));
267 }
268 if !tolerance.is_finite()
269 || tolerance < 0.
270 || !join.is_finite()
271 || join < 0.
272 || rules.iter().any(|r| {
273 r.0 > 1
274 || [r.1, r.2, r.3]
275 .iter()
276 .any(|n| !n.is_finite() || n.abs() > 1e9)
277 })
278 || coordinates
279 .iter()
280 .any(|c| c.0 > 1 || c.1.iter().any(|n| !n.is_finite() || n.abs() > 1e9))
281 {
282 return Err(error(ErrorCode::InvalidInput, "表格线段无效"));
283 }
284 docvortex_core::tables::merge_rules(rules, coordinates, tolerance, join)
285 .ok_or_else(|| error(ErrorCode::InvalidInput, "表格线段合并失败"))
286}
287pub fn table_assign_cells(
288 glyphs: Vec<[f64; 4]>,
289 specs: Vec<[f64; 4]>,
290 index: Option<NativeGridIndex>,
291) -> Result<Vec<(Option<usize>, bool)>, Error> {
292 table_assign_cells_bounded(glyphs, specs, index, 3000)
293}
294pub fn table_assign_cells_page(
295 glyphs: Vec<[f64; 4]>,
296 specs: Vec<[f64; 4]>,
297 index: Option<NativeGridIndex>,
298) -> Result<Vec<(Option<usize>, bool)>, Error> {
299 table_assign_cells_bounded(glyphs, specs, index, usize::MAX)
300}
301fn table_assign_cells_bounded(
302 glyphs: Vec<[f64; 4]>,
303 specs: Vec<[f64; 4]>,
304 index: Option<NativeGridIndex>,
305 limit: usize,
306) -> Result<Vec<(Option<usize>, bool)>, Error> {
307 let limit = rapiddoc_core::resources::limit(limit);
308 if glyphs.len() > limit || specs.len() > rapiddoc_core::resources::limit(2000) {
309 return Err(error(ErrorCode::ResourceLimit, "原生表格落格输入超过预算"));
310 }
311 if glyphs
312 .iter()
313 .chain(&specs)
314 .flatten()
315 .any(|x| !x.is_finite() || x.abs() > 1e9)
316 {
317 return Err(error(ErrorCode::InvalidInput, "原生表格落格坐标无效"));
318 }
319 docvortex_core::tables::assign_cells(glyphs, specs, index)
320 .ok_or_else(|| error(ErrorCode::InvalidInput, "原生表格落格索引无效"))
321}
322mod table_latin;
323mod table_unicode;
324pub fn python_latin(c: char) -> bool {
325 let n = c as u32;
326 let a = table_latin::LATIN;
327 let i = a.partition_point(|&(x, _)| x <= n);
328 i > 0 && n <= a[i - 1].1
329}
330pub fn table_cell_visual_groups(
331 boxes: Vec<[f64; 4]>,
332 ids: Vec<usize>,
333 rows: Vec<i64>,
334 height: f64,
335) -> Result<Vec<Vec<usize>>, Error> {
336 table_cell_visual_groups_bounded(boxes, ids, rows, height, 3000)
337}
338pub fn table_cell_visual_groups_page(
339 boxes: Vec<[f64; 4]>,
340 ids: Vec<usize>,
341 rows: Vec<i64>,
342 height: f64,
343) -> Result<Vec<Vec<usize>>, Error> {
344 table_cell_visual_groups_bounded(boxes, ids, rows, height, usize::MAX)
345}
346fn table_cell_visual_groups_bounded(
347 boxes: Vec<[f64; 4]>,
348 ids: Vec<usize>,
349 rows: Vec<i64>,
350 height: f64,
351 limit: usize,
352) -> Result<Vec<Vec<usize>>, Error> {
353 let limit = rapiddoc_core::resources::limit(limit);
354 if boxes.len() > limit {
355 return Err(error(ErrorCode::ResourceLimit, "单元格字形超过预算"));
356 }
357 if !height.is_finite()
358 || height.abs() > 1e9
359 || boxes
360 .iter()
361 .flatten()
362 .any(|x| !x.is_finite() || x.abs() > 1e9)
363 {
364 return Err(error(ErrorCode::InvalidInput, "单元格字形几何无效"));
365 }
366 docvortex_core::tables::cell_visual_groups(boxes, ids, rows, height)
367 .ok_or_else(|| error(ErrorCode::InvalidInput, "单元格字形组无效"))
368}
369pub mod text_boundary;
370mod text_unicode_data;
371
372pub fn python_printable(c: char) -> bool {
374 let n = c as u32;
375 let ranges = table_unicode::PRINTABLE;
376 let i = ranges.partition_point(|&(start, _)| start <= n);
377 i > 0 && n <= ranges[i - 1].1
378}
379pub fn python_decimal(c: char) -> Option<u8> {
380 table_unicode::DECIMALS
381 .binary_search_by_key(&(c as u32), |&(n, _)| n)
382 .ok()
383 .map(|i| table_unicode::DECIMALS[i].1)
384}
385pub fn table_visual_rows(
387 boxes: Vec<[f64; 4]>,
388 ids: Vec<usize>,
389 height: f64,
390) -> Result<Vec<Vec<usize>>, Error> {
391 table_visual_rows_bounded(boxes, ids, height, 3000)
392}
393pub fn table_visual_rows_page(
395 boxes: Vec<[f64; 4]>,
396 ids: Vec<usize>,
397 height: f64,
398) -> Result<Vec<Vec<usize>>, Error> {
399 table_visual_rows_bounded(boxes, ids, height, usize::MAX)
400}
401fn table_visual_rows_bounded(
402 boxes: Vec<[f64; 4]>,
403 ids: Vec<usize>,
404 height: f64,
405 limit: usize,
406) -> Result<Vec<Vec<usize>>, Error> {
407 let limit = rapiddoc_core::resources::limit(limit);
408 if boxes.len() > limit {
409 return Err(error(ErrorCode::ResourceLimit, "原生表格字形超过预算"));
410 }
411 if boxes.len() != ids.len()
412 || !height.is_finite()
413 || boxes
414 .iter()
415 .flatten()
416 .any(|x| !x.is_finite() || x.abs() > 1e9)
417 {
418 return Err(error(ErrorCode::InvalidInput, "原生表格分行输入无效"));
419 }
420 docvortex_core::tables::visual_rows(boxes, ids, height)
421 .ok_or_else(|| error(ErrorCode::InvalidInput, "原生表格分行输入无效"))
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
426pub struct CellRegion {
427 pub row: usize,
428 pub column: usize,
429 pub row_span: usize,
430 pub column_span: usize,
431}
432
433pub fn cell_regions(
435 rows: usize,
436 columns: usize,
437 missing_edges: Vec<(usize, usize)>,
438 max_cells: usize,
439) -> Result<Vec<CellRegion>, Error> {
440 let max_cells = rapiddoc_core::resources::limit(max_cells);
441 let count = rows
442 .checked_mul(columns)
443 .ok_or_else(|| error(ErrorCode::ResourceLimit, "网格尺寸溢出"))?;
444 if rows == 0 || columns == 0 {
445 return Err(error(ErrorCode::InvalidInput, "网格必须有行和列"));
446 }
447 if count > max_cells || missing_edges.len() > count.saturating_mul(2) {
448 return Err(error(ErrorCode::ResourceLimit, "网格超过限制"));
449 }
450 for &(first, last) in &missing_edges {
451 if first >= count || last >= count {
452 return Err(error(ErrorCode::InvalidInput, "网格边索引越界"));
453 }
454 let adjacent = first / columns == last / columns && first.abs_diff(last) == 1
455 || first % columns == last % columns && first.abs_diff(last) == columns;
456 if !adjacent {
457 return Err(error(ErrorCode::InvalidInput, "仅允许连接相邻单元格"));
458 }
459 }
460 let parents = docvortex_core::tables::grid_parents(count, missing_edges);
461 let (_, specs) = docvortex_core::tables::component_specs(parents, rows, columns);
462 let specs =
463 specs.ok_or_else(|| error(ErrorCode::InvalidInput, "非矩形连通分量不能作为合并单元格"))?;
464 Ok(specs
465 .into_iter()
466 .map(|(row, column, row_span, column_span)| CellRegion {
467 row,
468 column,
469 row_span,
470 column_span,
471 })
472 .collect())
473}
474
475pub fn interval_coverage(intervals: Vec<(f64, f64)>, start: f64, end: f64) -> Result<f64, Error> {
477 if !start.is_finite()
478 || !end.is_finite()
479 || end <= start
480 || intervals
481 .iter()
482 .any(|(a, b)| !a.is_finite() || !b.is_finite())
483 {
484 return Err(error(ErrorCode::InvalidInput, "覆盖查询的几何无效"));
485 }
486 let rules = intervals.into_iter().map(|(a, b)| (0, 0., a, b)).collect();
487 let result = docvortex_core::tables::coverage_batch(rules, vec![(0, vec![0.], start, end, 0.)])
488 .ok_or_else(|| error(ErrorCode::InvalidInput, "覆盖查询内核拒绝输入"))?;
489 result
490 .first()
491 .copied()
492 .ok_or_else(|| error(ErrorCode::InvalidInput, "覆盖查询内核返回空结果"))
493}
494
495pub(crate) fn error(code: ErrorCode, message: &str) -> Error {
497 Error {
498 code,
499 message: message.into(),
500 }
501}
502
503#[cfg(test)]
504mod lane_adapter_tests {
505 use super::*;
506 #[test]
507 fn lane_kernels_reject_empty_nonfinite_and_invalid_lane_indices() {
508 assert!(text_supported_lane_intervals(&[], 100., 10.).is_err());
509 let line = ([0., 0., 50., 10.], 10., true, false);
510 assert!(text_supported_lane_intervals(&[line], f64::NAN, 10.).is_err());
511 assert!(text_lane_assignments(&[line], &[], 1., None).is_err());
512 let tail = (line.0, 10., None, 0., None, None, false, None, 0, 1);
513 assert!(text_short_tail_destinations(&[tail], &[(0., 100.)], 10.).is_err());
514 assert_eq!(
515 text_supported_lane_intervals(&[line], 100., 10.).unwrap(),
516 vec![(0., 50., 1)]
517 );
518 }
519}