改进标题渲染:按层级差异化显示(字号/加粗/间距/对齐)
This commit is contained in:
@@ -65,10 +65,11 @@ pub fn strip_html(input: &str) -> String {
|
||||
// Emit text before the tag
|
||||
if tag_start > pos {
|
||||
let text = decode_entities(&input[pos..tag_start]);
|
||||
if heading_level.is_some() {
|
||||
out.push_str("\x01");
|
||||
if let Some(level) = heading_level {
|
||||
out.push('\x01');
|
||||
out.push(char::from_digit(level, 10).unwrap_or('1'));
|
||||
out.push_str(&text);
|
||||
out.push_str("\x02");
|
||||
out.push('\x02');
|
||||
} else {
|
||||
out.push_str(&text);
|
||||
}
|
||||
@@ -182,7 +183,7 @@ pub struct TocEntry {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContentBlock {
|
||||
pub text: String,
|
||||
pub is_heading: bool,
|
||||
pub heading_level: u8, // 0 = body, 1-6 = h1-h6
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
122
src/reader.rs
122
src/reader.rs
@@ -10,22 +10,54 @@ fn parse_blocks(raw_text: &str) -> Vec<ContentBlock> {
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let is_heading = trimmed.contains('\x01');
|
||||
let text = trimmed.replace('\x01', "").replace('\x02', "");
|
||||
let mut heading_level = 0u8;
|
||||
let mut text = trimmed.to_string();
|
||||
if text.contains('\x01') {
|
||||
if let Some(pos) = text.find('\x01') {
|
||||
let rest = &text[pos + 1..];
|
||||
heading_level = rest
|
||||
.chars()
|
||||
.next()
|
||||
.and_then(|c| c.to_digit(10))
|
||||
.unwrap_or(1) as u8;
|
||||
text.drain(pos..pos + 2);
|
||||
}
|
||||
text = text.replace('\x02', "");
|
||||
}
|
||||
let text = text.trim().to_string();
|
||||
if !text.is_empty() {
|
||||
blocks.push(ContentBlock { text, is_heading });
|
||||
blocks.push(ContentBlock { text, heading_level });
|
||||
}
|
||||
}
|
||||
if blocks.is_empty() {
|
||||
blocks.push(ContentBlock {
|
||||
text: String::new(),
|
||||
is_heading: false,
|
||||
heading_level: 0,
|
||||
});
|
||||
}
|
||||
blocks
|
||||
}
|
||||
|
||||
fn heading_font_size(base_size: f32, level: u8) -> f32 {
|
||||
match level {
|
||||
1 => base_size * 1.6,
|
||||
2 => base_size * 1.35,
|
||||
3 => base_size * 1.15,
|
||||
4 => base_size * 1.05,
|
||||
_ => base_size,
|
||||
}
|
||||
}
|
||||
|
||||
fn heading_top_spacing(para_spacing: f32, level: u8) -> f32 {
|
||||
match level {
|
||||
1 => para_spacing * 3.5,
|
||||
2 => para_spacing * 3.0,
|
||||
3 => para_spacing * 2.5,
|
||||
4 => para_spacing * 2.0,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_block_height(
|
||||
ctx: &egui::Context,
|
||||
text: &str,
|
||||
@@ -104,13 +136,19 @@ pub fn recalculate_pages(
|
||||
let mut current_height: f32 = 0.0;
|
||||
|
||||
for (i, block) in section.blocks.iter().enumerate() {
|
||||
let display_text = if block.is_heading || indent_str.is_empty() {
|
||||
let block_font_size = if block.heading_level > 0 {
|
||||
heading_font_size(font_size, block.heading_level)
|
||||
} else {
|
||||
font_size
|
||||
};
|
||||
|
||||
let display_text = if block.heading_level > 0 || indent_str.is_empty() {
|
||||
block.text.clone()
|
||||
} else {
|
||||
format!("{}{}", indent_str, block.text)
|
||||
};
|
||||
|
||||
let block_height = measure_block_height(ctx, &display_text, font_size, available_width);
|
||||
let block_height = measure_block_height(ctx, &display_text, block_font_size, available_width);
|
||||
|
||||
let spacing = if i > page_start_block && i > 0 {
|
||||
para_spacing
|
||||
@@ -413,24 +451,42 @@ egui::ComboBox::from_id_salt("bg_type_selector")
|
||||
ui.add_space(para_spacing);
|
||||
}
|
||||
|
||||
let block = §ion.blocks[i];
|
||||
let display_text = if block.is_heading || indent_str.is_empty() {
|
||||
block.text.clone()
|
||||
} else {
|
||||
format!("{}{}", indent_str, block.text)
|
||||
};
|
||||
let block = §ion.blocks[i];
|
||||
let display_text = if block.heading_level > 0 || indent_str.is_empty() {
|
||||
block.text.clone()
|
||||
} else {
|
||||
format!("{}{}", indent_str, block.text)
|
||||
};
|
||||
|
||||
if display_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if display_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut rt = egui::RichText::new(&display_text)
|
||||
.size(style.font_size)
|
||||
.color(colors.text);
|
||||
if block.is_heading {
|
||||
rt = rt.strong();
|
||||
}
|
||||
ui.add(egui::Label::new(rt).wrap());
|
||||
if block.heading_level > 0 && i > block_start {
|
||||
ui.add_space(heading_top_spacing(para_spacing, block.heading_level));
|
||||
}
|
||||
|
||||
let block_font_size = if block.heading_level > 0 {
|
||||
heading_font_size(style.font_size, block.heading_level)
|
||||
} else {
|
||||
style.font_size
|
||||
};
|
||||
|
||||
let mut rt = egui::RichText::new(&display_text)
|
||||
.size(block_font_size)
|
||||
.color(colors.text);
|
||||
if block.heading_level >= 1 && block.heading_level <= 4 {
|
||||
rt = rt.strong();
|
||||
}
|
||||
|
||||
let label = egui::Label::new(rt).wrap();
|
||||
if block.heading_level == 1 {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.add(label);
|
||||
});
|
||||
} else {
|
||||
ui.add(label);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -544,7 +600,7 @@ mod tests {
|
||||
let blocks = parse_blocks("Hello World");
|
||||
assert_eq!(blocks.len(), 1);
|
||||
assert_eq!(blocks[0].text, "Hello World");
|
||||
assert!(!blocks[0].is_heading);
|
||||
assert_eq!(blocks[0].heading_level, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -558,14 +614,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_blocks_heading() {
|
||||
let blocks = parse_blocks("\x01标题\x02\n\n正文内容");
|
||||
let blocks = parse_blocks("\x011标题\x02\n\n正文内容");
|
||||
assert_eq!(blocks.len(), 2);
|
||||
assert!(blocks[0].is_heading);
|
||||
assert_eq!(blocks[0].heading_level, 1);
|
||||
assert_eq!(blocks[0].text, "标题");
|
||||
assert!(!blocks[1].is_heading);
|
||||
assert_eq!(blocks[1].heading_level, 0);
|
||||
assert_eq!(blocks[1].text, "正文内容");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_blocks_heading_levels() {
|
||||
let blocks = parse_blocks("\x011一级\x02\n\n\x012二级\x02\n\n\x013三级\x02\n\n正文");
|
||||
assert_eq!(blocks.len(), 4);
|
||||
assert_eq!(blocks[0].heading_level, 1);
|
||||
assert_eq!(blocks[0].text, "一级");
|
||||
assert_eq!(blocks[1].heading_level, 2);
|
||||
assert_eq!(blocks[1].text, "二级");
|
||||
assert_eq!(blocks[2].heading_level, 3);
|
||||
assert_eq!(blocks[2].text, "三级");
|
||||
assert_eq!(blocks[3].heading_level, 0);
|
||||
assert_eq!(blocks[3].text, "正文");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_blocks_extra_newlines() {
|
||||
let blocks = parse_blocks("段一\n\n\n\n段二");
|
||||
|
||||
Reference in New Issue
Block a user