From 9032f47fcbc750549015c3ae9aa8816bd86ded82 Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Sat, 14 Mar 2026 18:43:13 +0900 Subject: [PATCH 1/3] fix(pptx): skip master/layout placeholders, render custGeom, fix wrap and list numbering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip shapes with when parsing master/layout layers so placeholder text (e.g. "마스터 제목 스타일 편집") is not rendered - Handle self-closing in handle_empty as well as handle_start - Treat as rect fallback so custom geometry fills render - Parse bodyPr wrap="none" attribute and use context+measure() to auto-size text box width, preventing last-character line wrapping caused by font metric differences with substitute fonts - Fix enum numbering: use (..nums) variadic parameter capture instead of plain nums to prevent "cannot spread integer" Typst error Signed-off-by: Yonghye Kwon --- crates/office2pdf/src/ir/elements.rs | 3 + crates/office2pdf/src/lib_render_tests.rs | 2 + crates/office2pdf/src/parser/pptx_slides.rs | 72 +++++++++++++++++-- crates/office2pdf/src/parser/pptx_text.rs | 4 ++ crates/office2pdf/src/render/typst_gen.rs | 59 +++++++++++---- .../typst_gen_fixed_page_textbox_tests.rs | 12 ++++ .../src/render/typst_gen_list_tests.rs | 2 +- .../office2pdf/src/render/typst_gen_lists.rs | 2 +- .../office2pdf/src/render/typst_gen_tests.rs | 2 + 9 files changed, 135 insertions(+), 23 deletions(-) diff --git a/crates/office2pdf/src/ir/elements.rs b/crates/office2pdf/src/ir/elements.rs index 8f0de33..c9465ab 100644 --- a/crates/office2pdf/src/ir/elements.rs +++ b/crates/office2pdf/src/ir/elements.rs @@ -149,6 +149,9 @@ pub struct TextBoxData { /// Shape geometry when the text box originates from a non-rectangular shape /// (e.g., `roundRect`, `homePlate`). `None` means default rectangle. pub shape_kind: Option, + /// When true, text should not wrap — the content width is unconstrained. + /// Corresponds to `` in OOXML. + pub no_wrap: bool, } /// The kind of list: ordered (numbered) or unordered (bulleted). diff --git a/crates/office2pdf/src/lib_render_tests.rs b/crates/office2pdf/src/lib_render_tests.rs index 0f779a5..caec8a7 100644 --- a/crates/office2pdf/src/lib_render_tests.rs +++ b/crates/office2pdf/src/lib_render_tests.rs @@ -584,6 +584,7 @@ fn test_render_pptx_style_document_size() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], })); @@ -642,6 +643,7 @@ fn test_render_document_with_centered_fixed_text_box() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], })], diff --git a/crates/office2pdf/src/parser/pptx_slides.rs b/crates/office2pdf/src/parser/pptx_slides.rs index 06955eb..4d98e03 100644 --- a/crates/office2pdf/src/parser/pptx_slides.rs +++ b/crates/office2pdf/src/parser/pptx_slides.rs @@ -76,7 +76,7 @@ fn parse_layer_elements( ) -> (Vec, Vec) { let images: SlideImageMap = load_slide_images(layer_path, archive); let empty_table_styles: table_styles::TableStyleMap = table_styles::TableStyleMap::new(); - parse_slide_xml( + parse_slide_xml_inner( layer_xml, &images, theme, @@ -84,6 +84,7 @@ fn parse_layer_elements( label, text_style_defaults, &empty_table_styles, + true, // skip placeholder shapes in master/layout layers ) .unwrap_or_default() } @@ -480,6 +481,7 @@ fn finalize_shape( paragraphs: &mut Vec, text_box_padding: Insets, text_box_vertical_align: TextBoxVerticalAlign, + text_box_no_wrap: bool, ) -> Vec { // Resolve effective fill: explicit > noFill > style fallback. let effective_fill: Option = if shape.fill.is_some() { @@ -556,6 +558,7 @@ fn finalize_shape( opacity: None, stroke: None, shape_kind: None, + no_wrap: text_box_no_wrap, }), }); } else { @@ -573,6 +576,7 @@ fn finalize_shape( opacity: shape.opacity, stroke, shape_kind: None, + no_wrap: text_box_no_wrap, }), }); } @@ -700,6 +704,12 @@ struct SlideXmlParser<'a> { inherited_text_body_defaults: &'a PptxTextBodyStyleDefaults, table_styles: &'a table_styles::TableStyleMap, + // ── Options ───────────────────────────────────────────────────── + /// When true, shapes with `` (placeholder) are skipped. + /// Used when parsing master/layout layers whose placeholder content + /// should not render unless the slide overrides it. + skip_placeholders: bool, + // ── Output accumulators ───────────────────────────────────────── elements: Vec, warnings: Vec, @@ -713,6 +723,7 @@ struct SlideXmlParser<'a> { paragraphs: Vec, text_box_padding: Insets, text_box_vertical_align: TextBoxVerticalAlign, + text_box_no_wrap: bool, text_body_style_defaults: PptxTextBodyStyleDefaults, // ── Paragraph state (``) ─────────────────────────────────── @@ -770,6 +781,8 @@ impl<'a> SlideXmlParser<'a> { inherited_text_body_defaults, table_styles, + skip_placeholders: false, + elements: Vec::new(), warnings: Vec::new(), @@ -780,6 +793,7 @@ impl<'a> SlideXmlParser<'a> { paragraphs: Vec::new(), text_box_padding: default_pptx_text_box_padding(), text_box_vertical_align: TextBoxVerticalAlign::Top, + text_box_no_wrap: false, text_body_style_defaults: PptxTextBodyStyleDefaults::default(), in_para: false, @@ -863,6 +877,7 @@ impl<'a> SlideXmlParser<'a> { self.paragraphs.clear(); self.text_box_padding = default_pptx_text_box_padding(); self.text_box_vertical_align = TextBoxVerticalAlign::Top; + self.text_box_no_wrap = false; } b"sp" | b"cxnSp" if self.in_shape => { self.shape.depth += 1; @@ -885,6 +900,10 @@ impl<'a> SlideXmlParser<'a> { self.shape.prst_geom = Some(prst); } } + // Treat custom geometry as a rectangle fallback so the fill renders. + b"custGeom" if self.shape.in_sp_pr && self.shape.prst_geom.is_none() => { + self.shape.prst_geom = Some("rect".to_string()); + } b"noFill" if self.shape.in_sp_pr && !self.shape.in_ln && !self.in_rpr => { self.shape.explicit_no_fill = true; } @@ -944,6 +963,7 @@ impl<'a> SlideXmlParser<'a> { e, &mut self.text_box_padding, &mut self.text_box_vertical_align, + &mut self.text_box_no_wrap, ); } b"lstStyle" if self.in_shape && self.in_txbody => { @@ -1172,12 +1192,17 @@ impl<'a> SlideXmlParser<'a> { .map(pptx_dash_to_border_style) .unwrap_or(BorderLineStyle::Solid); } + // Handle self-closing (placeholder marker). + b"ph" if self.in_shape => { + self.shape.has_placeholder = true; + } // Handle self-closing (no child elements). b"bodyPr" if self.in_shape && self.in_txbody => { extract_pptx_text_box_body_props( e, &mut self.text_box_padding, &mut self.text_box_vertical_align, + &mut self.text_box_no_wrap, ); } b"prstGeom" if self.shape.in_sp_pr => { @@ -1185,6 +1210,9 @@ impl<'a> SlideXmlParser<'a> { self.shape.prst_geom = Some(prst); } } + b"custGeom" if self.shape.in_sp_pr && self.shape.prst_geom.is_none() => { + self.shape.prst_geom = Some("rect".to_string()); + } b"ln" if self.shape.in_sp_pr => { self.shape.ln_width_emu = get_attr_i64(e, b"w").unwrap_or(12700); } @@ -1336,12 +1364,19 @@ impl<'a> SlideXmlParser<'a> { b"sp" | b"cxnSp" if self.in_shape => { self.shape.depth -= 1; if self.shape.depth == 0 { - self.elements.extend(finalize_shape( - &mut self.shape, - &mut self.paragraphs, - self.text_box_padding, - self.text_box_vertical_align, - )); + // Skip placeholder shapes when parsing master/layout layers. + // Placeholder content is only visible when the slide itself + // overrides it; master/layout placeholder text (e.g. + // "마스터 제목 스타일 편집") should never be rendered. + if !(self.skip_placeholders && self.shape.has_placeholder) { + self.elements.extend(finalize_shape( + &mut self.shape, + &mut self.paragraphs, + self.text_box_padding, + self.text_box_vertical_align, + self.text_box_no_wrap, + )); + } self.in_shape = false; } } @@ -1458,6 +1493,28 @@ pub(super) fn parse_slide_xml( warning_context: &str, inherited_text_body_defaults: &PptxTextBodyStyleDefaults, table_styles: &table_styles::TableStyleMap, +) -> Result<(Vec, Vec), ConvertError> { + parse_slide_xml_inner( + xml, + images, + theme, + color_map, + warning_context, + inherited_text_body_defaults, + table_styles, + false, + ) +} + +fn parse_slide_xml_inner( + xml: &str, + images: &SlideImageMap, + theme: &ThemeData, + color_map: &ColorMapData, + warning_context: &str, + inherited_text_body_defaults: &PptxTextBodyStyleDefaults, + table_styles: &table_styles::TableStyleMap, + skip_placeholders: bool, ) -> Result<(Vec, Vec), ConvertError> { let mut reader = Reader::from_str(xml); let mut parser = SlideXmlParser::new( @@ -1469,6 +1526,7 @@ pub(super) fn parse_slide_xml( inherited_text_body_defaults, table_styles, ); + parser.skip_placeholders = skip_placeholders; loop { match reader.read_event() { diff --git a/crates/office2pdf/src/parser/pptx_text.rs b/crates/office2pdf/src/parser/pptx_text.rs index 9755e3c..78131c5 100644 --- a/crates/office2pdf/src/parser/pptx_text.rs +++ b/crates/office2pdf/src/parser/pptx_text.rs @@ -509,6 +509,7 @@ pub(super) fn extract_pptx_text_box_body_props( e: &quick_xml::events::BytesStart, padding: &mut Insets, vertical_align: &mut TextBoxVerticalAlign, + no_wrap: &mut bool, ) { if let Some(value) = get_attr_i64(e, b"lIns") { padding.left = emu_to_pt(value); @@ -529,6 +530,9 @@ pub(super) fn extract_pptx_text_box_body_props( _ => TextBoxVerticalAlign::Top, }; } + if get_attr_str(e, b"wrap").as_deref() == Some("none") { + *no_wrap = true; + } } pub(super) fn extract_pptx_table_cell_props( diff --git a/crates/office2pdf/src/render/typst_gen.rs b/crates/office2pdf/src/render/typst_gen.rs index 8383749..373d784 100644 --- a/crates/office2pdf/src/render/typst_gen.rs +++ b/crates/office2pdf/src/render/typst_gen.rs @@ -588,6 +588,9 @@ fn generate_fixed_text_box( format_f64(outer_height_pt), format_insets(&text_box.padding), ); + if text_box.no_wrap { + out.push_str(", clip: false"); + } // For non-rectangular shapes, render fill/stroke as a placed background shape. if has_custom_shape { // Transparent outer block — shape background is placed inside. @@ -612,19 +615,42 @@ fn generate_fixed_text_box( &text_box.stroke, ); } - let _ = writeln!( - out, - " #let text_box_content_{text_box_id} = block(width: {}pt)[", - format_f64(inner_width_pt), - ); - for (index, block) in text_box.content.iter().enumerate() { - if index > 0 { - out.push('\n'); + if text_box.no_wrap { + // For wrap="none" text boxes: measure the natural content width first, + // then use the larger of (measured width, original width) so that text + // with slightly wider substitute fonts does not wrap. + let _ = writeln!(out, " #let text_box_content_{text_box_id} = context {{"); + let _ = writeln!(out, " let _nowrap_draft = ["); + for (index, block) in text_box.content.iter().enumerate() { + if index > 0 { + out.push('\n'); + } + out.push_str(" "); + generate_fixed_text_box_block(out, block, ctx, Some(inner_width_pt), false)?; + } + let _ = writeln!(out, " ]"); + let _ = writeln!( + out, + " let _nowrap_w = calc.max(measure(_nowrap_draft).width, {}pt)", + format_f64(inner_width_pt), + ); + let _ = writeln!(out, " block(width: _nowrap_w, _nowrap_draft)"); + let _ = writeln!(out, " }}"); + } else { + let _ = writeln!( + out, + " #let text_box_content_{text_box_id} = block(width: {}pt)[", + format_f64(inner_width_pt), + ); + for (index, block) in text_box.content.iter().enumerate() { + if index > 0 { + out.push('\n'); + } + out.push_str(" "); + generate_fixed_text_box_block(out, block, ctx, Some(inner_width_pt), false)?; } - out.push_str(" "); - generate_fixed_text_box_block(out, block, ctx, Some(inner_width_pt))?; + out.push_str(" ]\n"); } - out.push_str(" ]\n"); match text_box.vertical_align { TextBoxVerticalAlign::Top => { @@ -1045,7 +1071,7 @@ fn generate_floating_text_box_content( if index > 0 { out.push('\n'); } - generate_fixed_text_box_block(out, block, ctx, Some(ftb.width))?; + generate_fixed_text_box_block(out, block, ctx, Some(ftb.width), false)?; } out.push_str("]\n"); Ok(()) @@ -1056,17 +1082,22 @@ fn generate_fixed_text_box_block( block: &Block, ctx: &mut GenCtx, available_width_pt: Option, + no_wrap: bool, ) -> Result<(), ConvertError> { match block { Block::List(list) if can_render_fixed_text_list_inline(list) => { generate_fixed_text_list(out, list, true, available_width_pt) } - Block::Paragraph(para) => generate_fixed_text_paragraph(out, para), + Block::Paragraph(para) => generate_fixed_text_paragraph(out, para, no_wrap), _ => generate_block(out, block, ctx), } } -fn generate_fixed_text_paragraph(out: &mut String, para: &Paragraph) -> Result<(), ConvertError> { +fn generate_fixed_text_paragraph( + out: &mut String, + para: &Paragraph, + no_wrap: bool, +) -> Result<(), ConvertError> { let style: &ParagraphStyle = ¶.style; let needs_text_scope: bool = common_text_style(¶.runs).is_some(); let has_para_style: bool = needs_block_wrapper(style) || needs_text_scope; diff --git a/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs b/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs index bda8dee..711daf5 100644 --- a/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs +++ b/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs @@ -96,6 +96,7 @@ fn test_fixed_page_text_box_multiple_paragraphs_preserve_breaks() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -177,6 +178,7 @@ fn test_fixed_page_text_box_ordered_list_preserves_textbox_styling() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -263,6 +265,7 @@ fn test_fixed_page_text_box_compact_list_items_use_full_width_blocks() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -323,6 +326,7 @@ fn test_fixed_page_text_box_compact_list_preserves_hanging_indent() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -390,6 +394,7 @@ fn test_fixed_page_text_box_compact_list_preserves_marker_origin_offset() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -460,6 +465,7 @@ fn test_fixed_page_text_box_compact_bulleted_list_uses_custom_marker_style() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -556,6 +562,7 @@ fn test_fixed_page_text_box_dash_bullets_use_generic_list_path() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -613,6 +620,7 @@ fn test_fixed_page_text_box_compact_list_preserves_soft_line_breaks() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -665,6 +673,7 @@ fn test_fixed_page_text_box_with_solid_fill() { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -710,6 +719,7 @@ fn test_fixed_page_text_box_with_fill_and_stroke() { style: BorderLineStyle::Solid, }), shape_kind: None, + no_wrap: false, }), }], )]); @@ -756,6 +766,7 @@ fn test_fixed_page_text_box_with_fill_and_opacity() { opacity: Some(0.5), stroke: None, shape_kind: None, + no_wrap: false, }), }], )]); @@ -806,6 +817,7 @@ fn test_fixed_page_text_box_with_polygon_shape_kind() { (0.0, 1.0), ], }), + no_wrap: false, }), }], )]); diff --git a/crates/office2pdf/src/render/typst_gen_list_tests.rs b/crates/office2pdf/src/render/typst_gen_list_tests.rs index cdb03d9..ed569d7 100644 --- a/crates/office2pdf/src/render/typst_gen_list_tests.rs +++ b/crates/office2pdf/src/render/typst_gen_list_tests.rs @@ -402,7 +402,7 @@ fn test_generate_ordered_list_with_custom_marker_style_uses_numbering_function() let output = generate_typst(&doc).unwrap(); assert!(output.source.contains("#enum(")); - assert!(output.source.contains("numbering: nums => [")); + assert!(output.source.contains("numbering: (..nums) => [")); assert!(output.source.contains("#numbering(\"1)\", ..nums)")); assert!(output.source.contains("Pretendard Medium")); } diff --git a/crates/office2pdf/src/render/typst_gen_lists.rs b/crates/office2pdf/src/render/typst_gen_lists.rs index 71b3095..60a1926 100644 --- a/crates/office2pdf/src/render/typst_gen_lists.rs +++ b/crates/office2pdf/src/render/typst_gen_lists.rs @@ -76,7 +76,7 @@ fn write_list_open( fn write_ordered_list_numbering_function(out: &mut String, style: &EffectiveListStyle<'_>) { let pattern: &str = style.numbering_pattern.unwrap_or("1."); - out.push_str("numbering: nums => ["); + out.push_str("numbering: (..nums) => ["); if let Some(marker_style) = style .marker_style .filter(|style| has_text_properties(style)) diff --git a/crates/office2pdf/src/render/typst_gen_tests.rs b/crates/office2pdf/src/render/typst_gen_tests.rs index 7671dc0..41add99 100644 --- a/crates/office2pdf/src/render/typst_gen_tests.rs +++ b/crates/office2pdf/src/render/typst_gen_tests.rs @@ -84,6 +84,7 @@ fn make_text_box(x: f64, y: f64, w: f64, h: f64, text: &str) -> FixedElement { opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), } } @@ -137,6 +138,7 @@ fn make_fixed_text_box( opacity: None, stroke: None, shape_kind: None, + no_wrap: false, }), } } From 610c4200b0d856873f633ad2793a725a5e130e3e Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Sat, 14 Mar 2026 18:54:53 +0900 Subject: [PATCH 2/3] style: fix rustfmt and clippy warnings Signed-off-by: Yonghye Kwon --- .../src/parser/pptx_preset_shape_tests.rs | 11 +------ crates/office2pdf/src/parser/pptx_slides.rs | 1 + crates/office2pdf/src/render/typst_gen.rs | 4 +-- .../typst_gen_fixed_page_textbox_tests.rs | 30 +++++++++---------- .../office2pdf/src/render/typst_gen_shapes.rs | 8 ++--- .../office2pdf/src/render/typst_gen_tests.rs | 4 +-- 6 files changed, 24 insertions(+), 34 deletions(-) diff --git a/crates/office2pdf/src/parser/pptx_preset_shape_tests.rs b/crates/office2pdf/src/parser/pptx_preset_shape_tests.rs index 8a4d6c9..199a582 100644 --- a/crates/office2pdf/src/parser/pptx_preset_shape_tests.rs +++ b/crates/office2pdf/src/parser/pptx_preset_shape_tests.rs @@ -351,16 +351,7 @@ fn test_shape_home_plate() { #[test] fn test_shape_home_plate_square() { // Square bounding box: the notch should be at x = 0.5 - let shape = make_shape( - 0, - 0, - 1_000_000, - 1_000_000, - "homePlate", - None, - None, - None, - ); + let shape = make_shape(0, 0, 1_000_000, 1_000_000, "homePlate", None, None, None); let slide = make_slide_xml(&[shape]); let data = build_test_pptx(SLIDE_CX, SLIDE_CY, &[slide]); let parser = PptxParser; diff --git a/crates/office2pdf/src/parser/pptx_slides.rs b/crates/office2pdf/src/parser/pptx_slides.rs index 4d98e03..815edba 100644 --- a/crates/office2pdf/src/parser/pptx_slides.rs +++ b/crates/office2pdf/src/parser/pptx_slides.rs @@ -1506,6 +1506,7 @@ pub(super) fn parse_slide_xml( ) } +#[allow(clippy::too_many_arguments)] fn parse_slide_xml_inner( xml: &str, images: &SlideImageMap, diff --git a/crates/office2pdf/src/render/typst_gen.rs b/crates/office2pdf/src/render/typst_gen.rs index 373d784..5520dac 100644 --- a/crates/office2pdf/src/render/typst_gen.rs +++ b/crates/office2pdf/src/render/typst_gen.rs @@ -1096,7 +1096,7 @@ fn generate_fixed_text_box_block( fn generate_fixed_text_paragraph( out: &mut String, para: &Paragraph, - no_wrap: bool, + _no_wrap: bool, ) -> Result<(), ConvertError> { let style: &ParagraphStyle = ¶.style; let needs_text_scope: bool = common_text_style(¶.runs).is_some(); @@ -1126,7 +1126,7 @@ fn generate_fixed_text_paragraph( Some(Alignment::Right) => "right", _ => "left", }; - let _ = write!(out, "#block(width: 100%)[#set align({align_str})\n"); + let _ = writeln!(out, "#block(width: 100%)[#set align({align_str})"); } generate_runs_with_tabs(out, ¶.runs, style.tab_stops.as_deref()); diff --git a/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs b/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs index 711daf5..de1742c 100644 --- a/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs +++ b/crates/office2pdf/src/render/typst_gen_fixed_page_textbox_tests.rs @@ -96,7 +96,7 @@ fn test_fixed_page_text_box_multiple_paragraphs_preserve_breaks() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -178,7 +178,7 @@ fn test_fixed_page_text_box_ordered_list_preserves_textbox_styling() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -465,7 +465,7 @@ fn test_fixed_page_text_box_compact_bulleted_list_uses_custom_marker_style() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -562,7 +562,7 @@ fn test_fixed_page_text_box_dash_bullets_use_generic_list_path() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -620,7 +620,7 @@ fn test_fixed_page_text_box_compact_list_preserves_soft_line_breaks() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -673,7 +673,7 @@ fn test_fixed_page_text_box_with_solid_fill() { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -719,7 +719,7 @@ fn test_fixed_page_text_box_with_fill_and_stroke() { style: BorderLineStyle::Solid, }), shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -766,7 +766,7 @@ fn test_fixed_page_text_box_with_fill_and_opacity() { opacity: Some(0.5), stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), }], )]); @@ -805,17 +805,15 @@ fn test_fixed_page_text_box_with_polygon_shape_kind() { left: 7.2, }, vertical_align: crate::ir::TextBoxVerticalAlign::Center, - fill: Some(Color { r: 0, g: 37, b: 154 }), + fill: Some(Color { + r: 0, + g: 37, + b: 154, + }), opacity: None, stroke: None, shape_kind: Some(ShapeKind::Polygon { - vertices: vec![ - (0.0, 0.0), - (0.85, 0.0), - (1.0, 0.5), - (0.85, 1.0), - (0.0, 1.0), - ], + vertices: vec![(0.0, 0.0), (0.85, 0.0), (1.0, 0.5), (0.85, 1.0), (0.0, 1.0)], }), no_wrap: false, }), diff --git a/crates/office2pdf/src/render/typst_gen_shapes.rs b/crates/office2pdf/src/render/typst_gen_shapes.rs index be880e4..9fa7a70 100644 --- a/crates/office2pdf/src/render/typst_gen_shapes.rs +++ b/crates/office2pdf/src/render/typst_gen_shapes.rs @@ -446,7 +446,7 @@ pub(super) fn write_text_box_shape_background( write_fill_color(out, c, opacity); } write_shape_stroke(out, stroke); - out.push_str(")"); + out.push(')'); } ShapeKind::Polygon { vertices } => { out.push_str("#polygon("); @@ -455,7 +455,7 @@ pub(super) fn write_text_box_shape_background( write_fill_color(out, c, opacity); } write_shape_stroke(out, stroke); - out.push_str(")"); + out.push(')'); } ShapeKind::Ellipse => { let _ = write!( @@ -468,7 +468,7 @@ pub(super) fn write_text_box_shape_background( write_fill_color(out, c, opacity); } write_shape_stroke(out, stroke); - out.push_str(")"); + out.push(')'); } // Rectangle or line/polyline — shouldn't reach here, but handle gracefully. _ => { @@ -482,7 +482,7 @@ pub(super) fn write_text_box_shape_background( write_fill_color(out, c, opacity); } write_shape_stroke(out, stroke); - out.push_str(")"); + out.push(')'); } } out.push_str("]\n"); diff --git a/crates/office2pdf/src/render/typst_gen_tests.rs b/crates/office2pdf/src/render/typst_gen_tests.rs index 41add99..6b34305 100644 --- a/crates/office2pdf/src/render/typst_gen_tests.rs +++ b/crates/office2pdf/src/render/typst_gen_tests.rs @@ -84,7 +84,7 @@ fn make_text_box(x: f64, y: f64, w: f64, h: f64, text: &str) -> FixedElement { opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), } } @@ -138,7 +138,7 @@ fn make_fixed_text_box( opacity: None, stroke: None, shape_kind: None, - no_wrap: false, + no_wrap: false, }), } } From 67bbca0d9c9b50b10c8bce4d5a0daa91afc00573 Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Sat, 14 Mar 2026 19:01:22 +0900 Subject: [PATCH 3/3] style: suppress too_many_arguments clippy warning on parse_group_shape Signed-off-by: Yonghye Kwon --- crates/office2pdf/src/parser/pptx_shapes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/office2pdf/src/parser/pptx_shapes.rs b/crates/office2pdf/src/parser/pptx_shapes.rs index 5d39c7b..b68eac0 100644 --- a/crates/office2pdf/src/parser/pptx_shapes.rs +++ b/crates/office2pdf/src/parser/pptx_shapes.rs @@ -78,6 +78,7 @@ impl GroupTransform { /// Reads through the group's header sections (`nvGrpSpPr`, `grpSpPr`), /// extracts the coordinate transform, then slices the original XML to /// get the child shapes, and recursively parses them via `parse_slide_xml`. +#[allow(clippy::too_many_arguments)] pub(super) fn parse_group_shape( reader: &mut Reader<&[u8]>, xml: &str,