1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! The order a document is read in, across columns and across pages (#571).
//!
//! WITHIN A PAGE the order is already decided, and this does not decide it
//! again. `crate::text` cuts the page on its LINES and `crate::blocks` keeps
//! the groups in the order that cut produced, so the blocks of a two-column
//! page come back one column at a time -- the column gaps of #567, which is
//! what this ticket asks the order to be built on. A second cut over the
//! blocks was written and measured against that one; what it changed is in
//! the ticket, and it is not shipped, because a coarser instrument re-deciding
//! what a finer one already decided is a way of losing an order, not of
//! gaining one.
//!
//! What IS decided here is the order of the document, and there are two
//! statements the pages cannot make about themselves.
//!
//! * THE FURNITURE IS NOT IN THE FLOW. A running head is the first block of
//! its page and a footer the last (#571, `crate::furniture`), so between the
//! last paragraph of one page and the first of the next stand two lines that
//! belong to neither. They are kept -- with their text, their box and their
//! page -- after the last block of the body, so a caller reading the blocks
//! in order reads the document and then the lines it repeats.
//! * A PARAGRAPH THE PAGE BROKE IN TWO is two blocks on two pages, and
//! nothing on either page can join them. The evidence is the pair: the last
//! block of one page ends without a full stop and the first of the next
//! opens in lower case. Set in the same type, which is what keeps a footnote
//! out -- a footnote is the last thing on its page, often breaks off
//! mid-sentence, and is set smaller than the body it hangs from.
//!
//! The join is a RELATION and not a merge: [`Block::continues_previous`] says
//! it, `crate::markdown` writes the two halves as one paragraph, and both
//! halves keep the box and the page number they were drawn with -- which one
//! merged block could not state, having one of each.
//!
//! Numbering is last, so [`Block::index`] is the reading order of the body
//! with no gap where the furniture came out of it.
// Copyright (c) 2026 Innovation Trigger B.V.
//
// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
// the PDFluent Commercial Licence. See the LICENSE file in this repository --
// that file travels with the copy you received, which a URL does not.
use crate;
/// The characters that end a sentence, and therefore a paragraph the next page
/// does not continue.
///
/// The closing quotes and brackets are in the list because a paragraph that
/// ends `... the Act."` ends on the quote and not on the stop.
const TERMINAL: & = &;
/// How far two blocks' type may differ and still be one paragraph, as a
/// fraction. The same tenth `crate::blocks` merges lines under, and the rail
/// that keeps a footnote from swallowing the next page's first paragraph.
const SIZE_TOLERANCE: f64 = 0.10;
/// Put `blocks` in the document's reading order and number them from
/// `base_index` (#571).
///
/// Consumes and returns the vector because the order is the whole of what it
/// does: the furniture moves to the end and the body keeps the order the page
/// cut gave it.
pub
/// Mark the block at each page break that continues the one before it.
///
/// Over the BODY, after the furniture has gone: the pair the evidence is about
/// is the last paragraph of one page and the first of the next, and before
/// this a running head stood between them.
/// Whether `next`, the first block of its page, continues `previous`, the last
/// block of the page before it.
///
/// LOWER CASE IS THE SIGNAL, so a script that has no case -- Chinese, Arabic,
/// Hebrew -- never joins here. That is the conservative direction: the cost of
/// refusing is a paragraph in two pieces, which is what the reading was
/// before this, and the cost of accepting wrongly is two paragraphs run
/// together. A signal for those scripts is a question of its own.