Add drop cap detection and merging
Drop caps are large decorative first letters that span multiple lines. Due to PDF coordinate sorting, they often appear after the text they belong to. This change: - Detects drop caps: single uppercase char with font size >= 2.5x base - Finds the paragraph start (first lowercase-starting line after header) - Merges the drop cap with that line Example: "G" + "lenair brings..." -> "Glenair brings..." Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
04ee58bb9e
commit
fb80f5490d
@@ -1,14 +1,10 @@
|
||||
|
||||
|
||||
Introduction
|
||||
|
||||
### Introduction to Crimp Contacts SAE-AS39029
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
G
|
||||
|
||||
### 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
|
||||
|
||||
@@ -107,6 +107,9 @@ pub fn to_markdown_from_lines(lines: Vec<TextLine>, options: MarkdownOptions) ->
|
||||
.base_font_size
|
||||
.unwrap_or(font_stats.most_common_size);
|
||||
|
||||
// Merge drop caps with following text
|
||||
let lines = merge_drop_caps(lines, base_size);
|
||||
|
||||
let mut output = String::new();
|
||||
let mut current_page = 0u32;
|
||||
let mut prev_y = f32::MAX;
|
||||
@@ -183,6 +186,75 @@ pub fn to_markdown_from_lines(lines: Vec<TextLine>, options: MarkdownOptions) ->
|
||||
clean_markdown(output)
|
||||
}
|
||||
|
||||
/// Merge drop caps with the appropriate line
|
||||
/// A drop cap is a single large letter at the start of a paragraph
|
||||
/// Due to PDF coordinate sorting, the drop cap may appear AFTER the line it belongs to
|
||||
fn merge_drop_caps(lines: Vec<TextLine>, base_size: f32) -> Vec<TextLine> {
|
||||
let mut result: Vec<TextLine> = Vec::with_capacity(lines.len());
|
||||
|
||||
for line in &lines {
|
||||
let text = line.text();
|
||||
let trimmed = text.trim();
|
||||
|
||||
// Check if this looks like a drop cap:
|
||||
// 1. Single character (or single char + space)
|
||||
// 2. Much larger than base font (3x or more)
|
||||
// 3. The character is uppercase
|
||||
let is_drop_cap = trimmed.len() <= 2
|
||||
&& line.items.first().map(|i| i.font_size).unwrap_or(0.0) >= base_size * 2.5
|
||||
&& trimmed.chars().next().map(|c| c.is_uppercase()).unwrap_or(false);
|
||||
|
||||
if is_drop_cap {
|
||||
let drop_char = trimmed.chars().next().unwrap();
|
||||
|
||||
// Find the first line that starts with lowercase and is at the START of a paragraph
|
||||
// (i.e., preceded by a header or non-lowercase-starting line)
|
||||
let mut target_idx: Option<usize> = None;
|
||||
|
||||
for (idx, prev_line) in result.iter().enumerate() {
|
||||
if prev_line.page != line.page {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prev_text = prev_line.text();
|
||||
let prev_trimmed = prev_text.trim();
|
||||
|
||||
// Check if this line starts with lowercase
|
||||
if prev_trimmed.chars().next().map(|c| c.is_lowercase()).unwrap_or(false) {
|
||||
// Check if previous line exists and doesn't start with lowercase
|
||||
// (meaning this is the start of a paragraph)
|
||||
let is_para_start = if idx == 0 {
|
||||
true
|
||||
} else {
|
||||
let before = result[idx - 1].text();
|
||||
let before_trimmed = before.trim();
|
||||
!before_trimmed.chars().next().map(|c| c.is_lowercase()).unwrap_or(true)
|
||||
};
|
||||
|
||||
if is_para_start {
|
||||
target_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge with the target line
|
||||
if let Some(idx) = target_idx {
|
||||
if let Some(first_item) = result[idx].items.first_mut() {
|
||||
let prev_text = first_item.text.trim().to_string();
|
||||
first_item.text = format!("{}{}", drop_char, prev_text);
|
||||
}
|
||||
}
|
||||
// Don't add the drop cap line itself
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(line.clone());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Font statistics for a document
|
||||
struct FontStats {
|
||||
most_common_size: f32,
|
||||
|
||||
Reference in New Issue
Block a user