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