我有一个需求,需要把一个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
您希望把段落中的所有 Shape 与 GroupShape 合并为一个 GroupShape,然后保存为图片。当前代码在克隆子形状后直接使用了原始的 Left/Top 坐标,导致子形状在新组合形状内部仍保持绝对位置,渲染时出现偏移或空白。
关键修改要点
- 先计算整体边界(您已经完成)。
- 克隆子形状后,将它们的坐标转换为相对于新 GroupShape 的本地坐标,即把
Left、Top 坐标分别减去整体最小的 minX、minY。
- 在调用渲染器保存之前,将组合好的 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);
请问您能否查看一下“形状”元素中有多少个形状?您可能想要分组的形状分别锚定在不同的段落中,而“形状”元素在您的情况下可能只包含一个形状。