Skip to content

Commit 91ca0cc

Browse files
developer0hyeclaude
andcommitted
fix(pptx): handle self-closing bodyPr in empty element handler for vertical alignment
Self-closing <a:bodyPr anchor="ctr"/> was only handled in handle_start but not in handle_empty, causing vertical text alignment to be lost when bodyPr had no child elements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
1 parent 2ab840d commit 91ca0cc

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

crates/office2pdf/src/parser/pptx_shape_style_tests.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,47 @@ fn test_textbox_fill_from_style_fill_ref() {
468468
let tb = text_box_data(&page.elements[1]);
469469
assert_eq!(tb.fill, None, "Text overlay should have no fill");
470470
}
471+
472+
#[test]
473+
fn test_split_textbox_preserves_alignment() {
474+
// roundRect with centered text, solidFill, and bodyPr anchor="ctr".
475+
let shape_xml = r#"<p:sp><p:nvSpPr><p:cNvPr id="2" name="Shape"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="1696720" cy="650158"/></a:xfrm><a:prstGeom prst="roundRect"><a:avLst/></a:prstGeom><a:solidFill><a:srgbClr val="003481"/></a:solidFill></p:spPr><p:txBody><a:bodyPr rtlCol="0" anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="ctr"/><a:r><a:rPr lang="en-US"/><a:t>Random Sample</a:t></a:r></a:p></p:txBody></p:sp>"#.to_string();
476+
let slide_xml = make_slide_xml(&[shape_xml]);
477+
let data = build_test_pptx(SLIDE_CX, SLIDE_CY, &[slide_xml]);
478+
let parser = PptxParser;
479+
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
480+
481+
let page = first_fixed_page(&doc);
482+
// Should be split into Shape + TextBox
483+
assert_eq!(page.elements.len(), 2, "Expected Shape + TextBox pair");
484+
485+
// TextBox overlay should preserve vertical and horizontal alignment
486+
let tb = text_box_data(&page.elements[1]);
487+
assert_eq!(
488+
tb.vertical_align,
489+
TextBoxVerticalAlign::Center,
490+
"Vertical align should be Center"
491+
);
492+
// Check paragraph alignment
493+
let para = match &tb.content[0] {
494+
Block::Paragraph(p) => p,
495+
_ => panic!("Expected Paragraph"),
496+
};
497+
assert_eq!(
498+
para.style.alignment,
499+
Some(Alignment::Center),
500+
"Paragraph alignment should be Center"
501+
);
502+
assert_eq!(
503+
para.runs[0].text, "Random Sample",
504+
"Text content should be preserved"
505+
);
506+
507+
// Verify Typst output contains #align(center)
508+
let typst_output = crate::render::typst_gen::generate_typst(&doc).unwrap();
509+
assert!(
510+
typst_output.source.contains("#align(center)"),
511+
"Typst output should contain #align(center) for centered paragraph, got:\n{}",
512+
typst_output.source,
513+
);
514+
}

crates/office2pdf/src/parser/pptx_slides.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,14 @@ impl<'a> SlideXmlParser<'a> {
11721172
.map(pptx_dash_to_border_style)
11731173
.unwrap_or(BorderLineStyle::Solid);
11741174
}
1175+
// Handle self-closing <a:bodyPr anchor="ctr"/> (no child elements).
1176+
b"bodyPr" if self.in_shape && self.in_txbody => {
1177+
extract_pptx_text_box_body_props(
1178+
e,
1179+
&mut self.text_box_padding,
1180+
&mut self.text_box_vertical_align,
1181+
);
1182+
}
11751183
b"prstGeom" if self.shape.in_sp_pr => {
11761184
if let Some(prst) = get_attr_str(e, b"prst") {
11771185
self.shape.prst_geom = Some(prst);

0 commit comments

Comments
 (0)