Skip to content

Commit c14e264

Browse files
Improve text extraction with visual reading order and header detection
Major changes: - Switch from lopdf.extract_text() to position-aware extraction - Text is now sorted by visual reading order (top→bottom, left→right) - Fixed font size calculation to account for text matrix scaling - Headers are now detected based on font size ratios - Skip very short text (≤3 chars) for header detection to avoid drop caps This significantly improves output quality for PDFs with complex layouts. Before: Title appeared at end, no structure detected After: Correct reading order, headers marked with # syntax Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f99d155 commit c14e264

6 files changed

Lines changed: 107 additions & 27 deletions

File tree

samples/lopdf.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Introduction INTRO-2GLENAIR, INC. • 1211 AIR WAY • GLENDALE, CA 91201-2497 • 818-247-6000 • FAX 818-500-9912
2+
© 2011 Glenair, Inc. Printed in U.S.A.CAGE Code 06324 [www.glenair.com](http://www.glenair.com/) E-Mail: [sales@glenair.com](mailto:sales@glenair.com)
3+
Glenair brings a new perspective to the supply of high-performance Mil-Spec and commercial contacts: High Availability! Whether you need a standard duty socket for a MIL-DTL-28840 connector or an extended duty pin for MIL-DTL-38999 Series III, we have you covered with products that are always in stock—with no dollar or quantity minimums. In addition to the broadest selection and availability, Glenair also delivers outstanding interconnection compatibility. Glenair QPL SAE-AS39029 as well as our proprietary contact series are guaranteed to mate properly and perform at the upper limits of application requirements. About SAE AS39029 Crimp Contacts In a marketplace saturated with specialty interconnection media, the SAE-AS39029 crimp contact is still the “bread and butter” of the circular connector world. Serving platforms like the D38999, M28840 and countless others, AS39029 crimp contacts continue to offer reliable power and signal transmission in ruggedized applications. A myriad of sizes, termination options and materials mean that end users don’t have to compromise performance or reliability. Crimping results in a gas tight connection between the wire and its terminal. Although there are certainly alternatives to crimping, such as ultrasonic welding, resistance welding, insulation displacement technologies and so on, no other available technology delivers the short cycle times, flexibility and low overall cost of crimping—making it the termination technology of choice for most high-performance connector systems. Crimp connectors are most commonly used to terminate stranded wire. Crimp-on terminals are attached by inserting the stripped end of the wire into the barrel end of the terminal, which is then mechanically deformed (crimped). Special crimp tools are a must, and are supplied with appropriate accessory attachments to ensure reliable terminations for every wire and contact combination. Military Specification MIL-DTL-22520 provides the aerospace/defense industry with a common set of rugged, reliable hand crimp tools. This specification controls the voltage drop and tensile strength of crimp terminations. Daniels Manufacturing Corporation is the leading manufacturer of these tools. The AS39029 product series supplied by Glenair includes all the most popular standard and extended duty pin and socket contacts for use in high-performance circular and rectangular multi-pin connectors. We are also well positioned to supply special-purpose contacts to meet unique application requirements. Insulators
4+
Environmental Grommet
5+
Interfacial Seal
6+
Connector Shell
7+
Insert Retention ClipEMI Spring
8+
Insert Retention Clip
9+
Crimp Socket Contact with Hood
10+
RTV Sealing
11+
Environmental Grommet
12+
Crimp Pin ContactHigh-Performance Connector with Crimp Contacts (Cutaway View) ContactRetention ClipIntroduction to Crimp Contacts SAE-AS39029

samples/lopdf_new.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
3+
Introduction
4+
5+
### Introduction to Crimp Contacts SAE-AS39029
6+
7+
lenair brings a new perspective to the supply of high-performance Mil-Spec and commercial contacts: High Availability! Whether you need a standard duty socket for a MIL-DTL-28840 connector or an extended duty pin for MIL-DTL-38999 Series III, we have you covered with products that are always in
8+
stock—with no dollar or quantity minimums. In addition to the broadest selection and availability, Glenair also delivers outstanding interconnection compatibility. Glenair QPL SAE-AS39029 as well as our proprietary contact series are guaranteed to mate properly and perform at the upper limits of application requirements.
9+
10+
G
11+
12+
### About SAE AS39029 Crimp Contacts
13+
14+
In a marketplace saturated with specialty interconnection media, the SAE-AS39029 crimp contact is still the “bread and butter” of the circular connector world. Serving platforms like the D38999, M28840 and countless others, AS39029 crimp contacts continue to offer reliable power and signal transmission in
15+
ruggedized applications. A myriad of sizes, termination options and materials mean that end users don’t have to compromise performance or reliability.
16+
17+
Crimping results in a gas tight connection between the wire and its terminal. Although there are certainly alternatives to crimping, such as ultrasonic welding, resistance welding, insulation displacement technologies and so on, no other available technology delivers the short cycle times, flexibility and low overall cost of
18+
crimping—making it the termination technology of choice for most high-performance connector systems. Crimp connectors are most commonly used to terminate stranded wire. Crimp-on terminals are attached by inserting the stripped end of the wire into the barrel end of the terminal, which is then mechanically
19+
deformed (crimped). Special crimp tools are a must, and are supplied with appropriate accessory attachments to ensure reliable terminations for every wire and contact combination. Military Specification MIL-DTL-22520 provides the aerospace/defense industry with a common set of rugged, reliable hand crimp tools. This
20+
specification controls the voltage drop and tensile strength of crimp terminations. Daniels Manufacturing Corporation is the leading manufacturer of these tools. The AS39029 product series supplied by Glenair includes all the most popular standard and extended duty
21+
pin and socket contacts for use in high-performance circular and rectangular multi-pin connectors. We are also well positioned to supply special-purpose contacts to meet unique application requirements.
22+
23+
#### High-Performance Connector with Crimp Contacts (Cutaway View)
24+
25+
Insert Retention Clip EMI Spring
26+
Insert Retention Clip
27+
Crimp Pin Environmental Environmental Contact Grommet Grommet
28+
29+
Crimp Socket Contact with Hood RTV Sealing
30+
Interfacial Seal
31+
Connector Shell Contact Retention Clip
32+
Insulators
33+
34+
© 2011 Glenair, Inc. Printed in U.S.A. CAGE Code 06324
35+
www.glenair.com E-Mail: sales@glenair.com
36+
GLENAIR, INC. • 1211 AIR WAY • GLENDALE, CA 91201-2497 • 818-247-6000 • FAX 818-500-9912
37+
INTRO-2
38+

samples/mineru.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Introduction to Crimp Contacts SAE-AS39029
2+
3+
lenair brings a new perspective to the supply of high-performance Mil-Spec and commercial contacts: High Availability! Whether you need a standard duty socket for a MIL-DTL-28840 connector or an extended duty pin for MIL-DTL-38999 Series III, we have you covered with products that are always in stock—with no dollar or quantity minimums. In addition to the broadest selection and availability, Glenair also delivers outstanding interconnection compatibility. Glenair QPL SAE-AS39029 as well as our proprietary contact series are guaranteed to mate properly and perform at the upper limits of application requirements.
4+
5+
# About SAE AS39029 Crimp Contacts
6+
7+
In a marketplace saturated with specialty interconnection media, the SAE-AS39029 crimp contact is still the “bread and butter” of the circular connector world. Serving platforms like the D38999, M28840 and countless others, AS39029 crimp contacts continue to offer reliable power and signal transmission in ruggedized applications. A myriad of sizes, termination options and materials mean that end users don’t have to compromise performance or reliability.
8+
9+
![](https://www.glenair.com/as39029-qpl-and-glenair-commercial-high-performance-connector-contacts/pdf/intro/images/41f12668e93f5fb676193d8062d6985619c6ddf56b5fca9e6733b620c0c5ac35.jpg)
10+
11+
High-Performance Connector with Crimp Contacts (Cutaway View)
12+
13+
Crimping results in a gas tight connection between the wire and its terminal. Although there are certainly alternatives to crimping, such as ultrasonic welding, resistance welding, insulation displacement technologies and so on, no other available technology delivers the short cycle times, flexibility and low overall cost of crimping—making it the termination technology of choice for most high-performance connector systems.
14+
15+
Crimp connectors are most commonly used to terminate stranded wire. Crimp-on terminals are attached by inserting the stripped end of the wire into the barrel end of the terminal, which is then mechanically deformed (crimped). Special crimp tools are a must, and are supplied with appropriate accessory attachments to ensure reliable terminations for every wire and contact combination. Military Specification MIL-DTL-22520 provides the aerospace/defense industry with a common set of rugged, reliable hand crimp tools. This specification controls the voltage drop and tensile strength of crimp terminations. Daniels Manufacturing Corporation is the leading manufacturer of these tools.
16+
17+
The AS39029 product series supplied by Glenair includes all the most popular standard and extended duty pin and socket contacts for use in high-performance circular and rectangular multi-pin connectors. We are also well positioned to supply special-purpose contacts to meet unique application requirements.

src/extractor.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,15 @@ fn extract_page_text_items(
175175
extract_text_from_operand(&op.operands[0], doc, &fonts, &current_font)
176176
{
177177
if !text.trim().is_empty() {
178+
let rendered_size = effective_font_size(current_font_size, &text_matrix);
178179
items.push(TextItem {
179180
text,
180181
x: text_matrix[4],
181182
y: text_matrix[5],
182183
width: 0.0, // Would need glyph widths
183-
height: current_font_size,
184+
height: rendered_size,
184185
font: current_font.clone(),
185-
font_size: current_font_size,
186+
font_size: rendered_size,
186187
page: page_num,
187188
});
188189
}
@@ -202,14 +203,15 @@ fn extract_page_text_items(
202203
}
203204
}
204205
if !combined_text.trim().is_empty() {
206+
let rendered_size = effective_font_size(current_font_size, &text_matrix);
205207
items.push(TextItem {
206208
text: combined_text,
207209
x: text_matrix[4],
208210
y: text_matrix[5],
209211
width: 0.0,
210-
height: current_font_size,
212+
height: rendered_size,
211213
font: current_font.clone(),
212-
font_size: current_font_size,
214+
font_size: rendered_size,
213215
page: page_num,
214216
});
215217
}
@@ -225,14 +227,15 @@ fn extract_page_text_items(
225227
extract_text_from_operand(&op.operands[0], doc, &fonts, &current_font)
226228
{
227229
if !text.trim().is_empty() {
230+
let rendered_size = effective_font_size(current_font_size, &text_matrix);
228231
items.push(TextItem {
229232
text,
230233
x: text_matrix[4],
231234
y: text_matrix[5],
232235
width: 0.0,
233-
height: current_font_size,
236+
height: rendered_size,
234237
font: current_font.clone(),
235-
font_size: current_font_size,
238+
font_size: rendered_size,
236239
page: page_num,
237240
});
238241
}
@@ -255,6 +258,19 @@ fn get_number(obj: &Object) -> Option<f32> {
255258
}
256259
}
257260

261+
/// Compute effective font size from base size and text matrix
262+
/// Text matrix is [a, b, c, d, tx, ty] where a,d are scale factors
263+
fn effective_font_size(base_size: f32, text_matrix: &[f32; 6]) -> f32 {
264+
// The scale factor is typically the magnitude of the transformation
265+
// For most PDFs, text_matrix[0] (a) is the horizontal scale
266+
// and text_matrix[3] (d) is the vertical scale
267+
let scale_x = (text_matrix[0].powi(2) + text_matrix[1].powi(2)).sqrt();
268+
let scale_y = (text_matrix[2].powi(2) + text_matrix[3].powi(2)).sqrt();
269+
// Use the larger of the two scales (usually they're equal for non-rotated text)
270+
let scale = scale_x.max(scale_y);
271+
base_size * scale
272+
}
273+
258274
/// Extract text from a text operand, handling encoding
259275
fn extract_text_from_operand(
260276
obj: &Object,

src/lib.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub mod markdown;
1111

1212
pub use detector::{detect_pdf_type, PdfType, PdfTypeResult};
1313
pub use extractor::{extract_text, extract_text_with_positions, TextItem};
14-
pub use markdown::{to_markdown, MarkdownOptions};
14+
pub use markdown::{to_markdown, to_markdown_from_items, MarkdownOptions};
1515

1616
use std::path::Path;
1717

@@ -44,13 +44,13 @@ pub fn process_pdf<P: AsRef<Path>>(path: P) -> Result<PdfProcessResult, PdfError
4444

4545
let result = match detection.pdf_type {
4646
PdfType::TextBased => {
47-
// Step 2: Full extraction for text-based PDFs
48-
let text = extract_text(&path)?;
49-
let markdown = to_markdown(&text, MarkdownOptions::default());
47+
// Step 2: Full extraction with position-aware reading order
48+
let items = extract_text_with_positions(&path)?;
49+
let markdown = to_markdown_from_items(items, MarkdownOptions::default());
5050

5151
PdfProcessResult {
5252
pdf_type: PdfType::TextBased,
53-
text: Some(text),
53+
text: None, // We now produce markdown directly
5454
markdown: Some(markdown),
5555
page_count: detection.page_count,
5656
processing_time_ms: start.elapsed().as_millis() as u64,
@@ -67,15 +67,13 @@ pub fn process_pdf<P: AsRef<Path>>(path: P) -> Result<PdfProcessResult, PdfError
6767
}
6868
}
6969
PdfType::Mixed => {
70-
// Try to extract what we can
71-
let text = extract_text(&path).ok();
72-
let markdown = text
73-
.as_ref()
74-
.map(|t| to_markdown(t, MarkdownOptions::default()));
70+
// Try to extract what we can with position-aware reading order
71+
let items = extract_text_with_positions(&path).ok();
72+
let markdown = items.map(|i| to_markdown_from_items(i, MarkdownOptions::default()));
7573

7674
PdfProcessResult {
7775
pdf_type: PdfType::Mixed,
78-
text,
76+
text: None,
7977
markdown,
8078
page_count: detection.page_count,
8179
processing_time_ms: start.elapsed().as_millis() as u64,
@@ -95,13 +93,13 @@ pub fn process_pdf_mem(buffer: &[u8]) -> Result<PdfProcessResult, PdfError> {
9593

9694
let result = match detection.pdf_type {
9795
PdfType::TextBased => {
98-
// Step 2: Full extraction for text-based PDFs
99-
let text = extractor::extract_text_mem(buffer)?;
100-
let markdown = to_markdown(&text, MarkdownOptions::default());
96+
// Step 2: Full extraction with position-aware reading order
97+
let items = extractor::extract_text_with_positions_mem(buffer)?;
98+
let markdown = to_markdown_from_items(items, MarkdownOptions::default());
10199

102100
PdfProcessResult {
103101
pdf_type: PdfType::TextBased,
104-
text: Some(text),
102+
text: None,
105103
markdown: Some(markdown),
106104
page_count: detection.page_count,
107105
processing_time_ms: start.elapsed().as_millis() as u64,
@@ -115,14 +113,12 @@ pub fn process_pdf_mem(buffer: &[u8]) -> Result<PdfProcessResult, PdfError> {
115113
processing_time_ms: start.elapsed().as_millis() as u64,
116114
},
117115
PdfType::Mixed => {
118-
let text = extractor::extract_text_mem(buffer).ok();
119-
let markdown = text
120-
.as_ref()
121-
.map(|t| to_markdown(t, MarkdownOptions::default()));
116+
let items = extractor::extract_text_with_positions_mem(buffer).ok();
117+
let markdown = items.map(|i| to_markdown_from_items(i, MarkdownOptions::default()));
122118

123119
PdfProcessResult {
124120
pdf_type: PdfType::Mixed,
125-
text,
121+
text: None,
126122
markdown,
127123
page_count: detection.page_count,
128124
processing_time_ms: start.elapsed().as_millis() as u64,

src/markdown.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ pub fn to_markdown_from_lines(lines: Vec<TextLine>, options: MarkdownOptions) ->
140140
}
141141

142142
// Detect headers by font size
143-
if options.detect_headers {
143+
// Skip very short text (likely drop caps or labels)
144+
if options.detect_headers && trimmed.len() > 3 {
144145
let line_font_size = line.items.first().map(|i| i.font_size).unwrap_or(base_size);
145146
if let Some(header_level) = detect_header_level(line_font_size, base_size) {
146147
let prefix = "#".repeat(header_level);

0 commit comments

Comments
 (0)