关于如何将一个Paragraph里shape和groupshape组合

我有一个需求,需要把一个Paragraph里的所有shape和groupshape组合成一个groupshape,然后保存为图片。
我是用了如下代码,但是保存出来的是一个错误的图像。请问如何解决?

代码如下

 private static void CombineShapesInParagraph(Paragraph paragraph)
 {
     var shapes = paragraph.GetChildNodes(NodeType.Shape, false).Cast<Shape>().ToList();
     var groupShapes = paragraph.GetChildNodes(NodeType.GroupShape, false).Cast<GroupShape>().ToList();

     if (shapes.Count + groupShapes.Count <= 1)
     {
         // 如果只有一个或没有形状,无需组合
         return;
     }

     // 创建一个新的组合形状容器
     GroupShape combinedGroup = new GroupShape(paragraph.Document);

     // 计算组合后形状的边界
     double minX = double.MaxValue;
     double minY = double.MaxValue;
     double maxX = double.MinValue;
     double maxY = double.MinValue;

     // 收集所有形状的信息
     foreach (var shape in shapes)
     {
         // 获取形状的位置和大小
         double left = shape.Left;
         double top = shape.Top;
         double right = shape.Left + shape.Width;
         double bottom = shape.Top + shape.Height;

         minX = Math.Min(minX, left);
         minY = Math.Min(minY, top);
         maxX = Math.Max(maxX, right);
         maxY = Math.Max(maxY, bottom);

         combinedGroup.AppendChild(shape.Clone(true));

         paragraph.RemoveChild(shape);
         // 添加到组合形状中
     }

     foreach (var groupShape in groupShapes)
     {
         // 获取组合形状的位置和大小
         double left = groupShape.Left;
         double top = groupShape.Top;
         double right = groupShape.Left + groupShape.Width;
         double bottom = groupShape.Top + groupShape.Height;

         minX = Math.Min(minX, left);
         minY = Math.Min(minY, top);
         maxX = Math.Max(maxX, right);
         maxY = Math.Max(maxY, bottom);


         // 添加到新的组合形状中
         combinedGroup.AppendChild(groupShape.Clone(true));
         paragraph.RemoveChild(groupShape);

   
     }

     // 设置组合形状的属性
     combinedGroup.Width = maxX - minX;
     combinedGroup.Height = maxY - minY;
     combinedGroup.Left = minX;
     combinedGroup.Top = minY;

     var saveOptions = new ImageSaveOptions(SaveFormat.Png)
     {
         // 建议使用 300~600 DPI,数值越大渲染越细腻
         Resolution = 300,

         // 背景透明(如果需要的话)
         PaperColor = Color.Transparent,

         UseHighQualityRendering = true,
         UseAntiAliasing = true
     };
     combinedGroup.GetShapeRenderer().Save(Path.Combine(Application.StartupPath, "math_sample", "test.png"), saveOptions);


     // 将组合后的形状添加回段落
     paragraph.AppendChild(combinedGroup);
 }

@Calvin95

您希望把段落中的所有 ShapeGroupShape 合并为一个 GroupShape,然后保存为图片。当前代码在克隆子形状后直接使用了原始的 Left/Top 坐标,导致子形状在新组合形状内部仍保持绝对位置,渲染时出现偏移或空白。

关键修改要点

  1. 先计算整体边界(您已经完成)。
  2. 克隆子形状后,将它们的坐标转换为相对于新 GroupShape 的本地坐标,即把 LeftTop 坐标分别减去整体最小的 minXminY
  3. 在调用渲染器保存之前,将组合好的 GroupShape 加入段落/文档,这样渲染器才能获取正确的布局信息。

下面给出仅包含关键改动的示例代码(已去除不必要的部分):

private static void CombineShapesInParagraph(Paragraph paragraph)
{
    var shapes = paragraph.GetChildNodes(NodeType.Shape, false).Cast<Shape>().ToList();
    var groupShapes = paragraph.GetChildNodes(NodeType.GroupShape, false).Cast<GroupShape>().ToList();

    if (shapes.Count + groupShapes.Count <= 1) return;

    GroupShape combinedGroup = new GroupShape(paragraph.Document);

    double minX = double.MaxValue, minY = double.MaxValue;
    double maxX = double.MinValue, maxY = double.MinValue;

    // 计算所有子形状的整体边界
    foreach (var s in shapes)
    {
        minX = Math.Min(minX, s.Left);
        minY = Math.Min(minY, s.Top);
        maxX = Math.Max(maxX, s.Left + s.Width);
        maxY = Math.Max(maxY, s.Top + s.Height);
    }
    foreach (var gs in groupShapes)
    {
        minX = Math.Min(minX, gs.Left);
        minY = Math.Min(minY, gs.Top);
        maxX = Math.Max(maxX, gs.Left + gs.Width);
        maxY = Math.Max(maxY, gs.Top + gs.Height);
    }

    // 将 Shape 加入 GroupShape 并调整为相对坐标
    foreach (var shape in shapes)
    {
        var cloned = (Shape)shape.Clone(true);
        cloned.Left -= minX;   // 转为相对坐标
        cloned.Top  -= minY;
        combinedGroup.AppendChild(cloned);
        paragraph.RemoveChild(shape);
    }

    // 将已有 GroupShape 加入新 GroupShape 并调整坐标
    foreach (var gShape in groupShapes)
    {
        var clonedGroup = (GroupShape)gShape.Clone(true);
        clonedGroup.Left -= minX;
        clonedGroup.Top  -= minY;
        combinedGroup.AppendChild(clonedGroup);
        paragraph.RemoveChild(gShape);
    }

    // 设置新 GroupShape 的外框
    combinedGroup.Width  = maxX - minX;
    combinedGroup.Height = maxY - minY;
    combinedGroup.Left   = minX;
    combinedGroup.Top    = minY;

    // 先把组合形状加入段落/文档
    paragraph.AppendChild(combinedGroup);

    var saveOptions = new ImageSaveOptions(SaveFormat.Png)
    {
        Resolution = 300,
        PaperColor = Color.Transparent,
        UseHighQualityRendering = true,
        UseAntiAliasing = true
    };
    combinedGroup.GetShapeRenderer().Save(
        Path.Combine(Application.StartupPath, "math_sample", "test.png"),
        saveOptions);
}

要点说明

  • cloned.Left -= minX; cloned.Top -= minY; 将子形状的坐标转为相对坐标,避免渲染时出现错位。
  • 在调用 GetShapeRenderer().Save 之前把 combinedGroup 加入段落(paragraph.AppendChild(combinedGroup);),这样渲染器能够获取到正确的布局信息。

按照上述修改后,生成的 PNG 应该能够完整地呈现原段落中的所有形状和组合形状。如果仍有异常,请提供生成的 PNG 或更详细的错误信息,以便进一步排查。

@Calvin95 请您将输入文档附在这里以便我们进行测试。我们会检查问题并提供更多信息。

test1.docx (28.2 KB)

这是文档,docx格式的无法组合,
doc格式的可以组合,但是保存下来的是叉的图片

@Calvin95 您可以尝试使用以下代码将形状分组,然后将分组后的形状渲染成图像:

Document doc = new Document(@"C:\Temp\in.docx");

// Get top-level shapes in the first paragraph.
ShapeBase[] shapesToGroup = doc.FirstSection.Body.FirstParagraph.GetChildNodes(NodeType.Any, true)
    .Where(n => n is ShapeBase).Cast<ShapeBase>().Where(s => s.IsTopLevel)
    .ToArray();

DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveToDocumentEnd();
GroupShape g = builder.InsertGroupShape(shapesToGroup.ToArray());

g.GetShapeRenderer().Save(@"C:\Temp\out.png", new ImageSaveOptions(SaveFormat.Png));

我使用了如下代码进行保存,但是只渲染出了一个形状。其他的都没有渲染

var shapes = para.GetChildNodes(NodeType.Any, true)
    .Where(n => n is ShapeBase).Cast<ShapeBase>().Where(s => s.IsTopLevel)
    .ToArray();
DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveToDocumentEnd();
GroupShape g = builder.InsertGroupShape(shapes.ToArray());
var imageOption = new ImageSaveOptions(SaveFormat.Png)
{
    Resolution = 300,
    PaperColor = Color.Transparent,
    UseAntiAliasing = true
};
g.GetShapeRenderer().Save("out1.png", imageOption);

这个是保存下来的图像

文档中的图是这样的

@Calvin95 您使用的是之前附加的同一个输入文档吗?我这边测试了一下,以下是生成的输出结果:

以下是我的完整测试代码:

Document doc = new Document(@"C:\Temp\in.docx");
Paragraph para = doc.FirstSection.Body.FirstParagraph;

var shapes = para.GetChildNodes(NodeType.Any, true)
    .Where(n => n is ShapeBase).Cast<ShapeBase>().Where(s => s.IsTopLevel)
    .ToArray();
DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveToDocumentEnd();
GroupShape g = builder.InsertGroupShape(shapes.ToArray());
var imageOption = new ImageSaveOptions(SaveFormat.Png)
{
    Resolution = 300,
    PaperColor = Color.Transparent,
    UseAntiAliasing = true
};
g.GetShapeRenderer().Save(@"C:\Temp\out.png", imageOption);

请问您能否查看一下“形状”元素中有多少个形状?您可能想要分组的形状分别锚定在不同的段落中,而“形状”元素在您的情况下可能只包含一个形状。