package com.ruoyi.inspect.util;
|
|
import com.aspose.words.Document;
|
import com.aspose.words.LayoutCollector;
|
import com.aspose.words.LayoutEnumerator;
|
import com.aspose.words.License;
|
import com.aspose.words.Node;
|
import com.aspose.words.NodeCollection;
|
import com.aspose.words.NodeType;
|
import com.aspose.words.PageSetup;
|
import com.aspose.words.Paragraph;
|
import com.aspose.words.RelativeHorizontalPosition;
|
import com.aspose.words.RelativeVerticalPosition;
|
import com.aspose.words.SaveFormat;
|
import com.aspose.words.Section;
|
import com.aspose.words.Shape;
|
import com.aspose.words.ShapeType;
|
import com.aspose.words.WrapType;
|
|
import javax.imageio.ImageIO;
|
import java.awt.image.BufferedImage;
|
import java.awt.geom.Rectangle2D;
|
import java.io.ByteArrayInputStream;
|
import java.io.InputStream;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.List;
|
|
/**
|
* 将报告中的印章改为浮动图片,避免图片参与正文行高计算而改变原报告分页。
|
*/
|
public final class ReportSealLayoutUtil {
|
|
private static final double MA_LEFT = 30D;
|
private static final double MA_TOP = 30D;
|
private static final double MA_MAX_WIDTH = 90D;
|
private static final double MA_MAX_HEIGHT = 80D;
|
private static final double REPORT_MAX_WIDTH = 105D;
|
private static final double REPORT_MAX_HEIGHT = 105D;
|
private static final double LAST_PAGE_MAX_WIDTH = 110D;
|
private static final double LAST_PAGE_MAX_HEIGHT = 110D;
|
private static final double POSITION_GAP = 14D;
|
|
private ReportSealLayoutUtil() {
|
}
|
|
public static void apply(String wordPath, String reportSealPath, String maSealPath) throws Exception {
|
applyAsposeLicense();
|
Document document = new Document(wordPath);
|
byte[] reportSealBytes = readImage(reportSealPath);
|
byte[] maSealBytes = readImage(maSealPath);
|
|
if (reportSealBytes != null) {
|
removeMatchingImages(document, reportSealBytes);
|
document.updatePageLayout();
|
addCoverReportSeal(document, reportSealPath);
|
addConclusionReportSeal(document, reportSealPath);
|
addLastPageReportSeal(document, reportSealPath);
|
}
|
if (maSealBytes != null) {
|
removeMatchingImages(document, maSealBytes);
|
document.updatePageLayout();
|
// reportSeal 与 maSeal 可能选择同一张图片,移除后需要恢复三个报告章位置。
|
if (reportSealBytes != null && Arrays.equals(reportSealBytes, maSealBytes)) {
|
addCoverReportSeal(document, reportSealPath);
|
addConclusionReportSeal(document, reportSealPath);
|
addLastPageReportSeal(document, reportSealPath);
|
}
|
addFirstPageMaSeal(document, maSealPath);
|
}
|
|
document.updatePageLayout();
|
document.save(wordPath, SaveFormat.DOCX);
|
}
|
|
private static void applyAsposeLicense() throws Exception {
|
try (InputStream inputStream = ReportSealLayoutUtil.class.getResourceAsStream("/lib/license.xml")) {
|
if (inputStream == null) {
|
throw new IllegalStateException("未找到 Aspose 授权文件");
|
}
|
License license = new License();
|
license.setLicense(inputStream);
|
if (!license.getIsLicensed()) {
|
throw new IllegalStateException("Aspose 授权验证失败");
|
}
|
}
|
}
|
|
private static byte[] readImage(String imagePath) throws Exception {
|
if (imagePath == null || imagePath.trim().isEmpty()) {
|
return null;
|
}
|
Path path = java.nio.file.Paths.get(imagePath);
|
return Files.isRegularFile(path) ? Files.readAllBytes(path) : null;
|
}
|
|
private static void removeMatchingImages(Document document, byte[] imageBytes) throws Exception {
|
for (Shape shape : imageShapes(document)) {
|
if (sameImage(shape, imageBytes)) {
|
shape.remove();
|
}
|
}
|
}
|
|
private static void addFirstPageMaSeal(Document document, String imagePath) throws Exception {
|
Paragraph anchor = firstBodyParagraph(document);
|
double[] size = fitImage(imagePath, MA_MAX_WIDTH, MA_MAX_HEIGHT);
|
Shape shape = createFloatingImage(document, imagePath, size[0], size[1]);
|
shape.setLeft(MA_LEFT);
|
shape.setTop(MA_TOP);
|
anchor.appendChild(shape);
|
}
|
|
private static void addCoverReportSeal(Document document, String imagePath) throws Exception {
|
Paragraph anchor = firstBodyParagraph(document);
|
PageSetup pageSetup = document.getFirstSection().getPageSetup();
|
double[] size = fitImage(imagePath, REPORT_MAX_WIDTH, REPORT_MAX_HEIGHT);
|
Shape shape = createFloatingImage(document, imagePath, size[0], size[1]);
|
shape.setLeft(pageSetup.getLeftMargin() + 20D);
|
shape.setTop(pageSetup.getPageHeight() - pageSetup.getBottomMargin()
|
- size[1] - POSITION_GAP);
|
shape.setBehindText(true);
|
anchor.appendChild(shape);
|
}
|
|
private static void addConclusionReportSeal(Document document, String imagePath) throws Exception {
|
document.updatePageLayout();
|
LayoutCollector collector = new LayoutCollector(document);
|
Paragraph anchor = findBodyParagraphContaining(document, "盖章", collector, null);
|
if (anchor == null) {
|
return;
|
}
|
Rectangle2D rectangle = getLayoutRectangle(document, collector, anchor);
|
Section section = (Section) anchor.getAncestor(NodeType.SECTION);
|
PageSetup pageSetup = section.getPageSetup();
|
int page = collector.getStartPageIndex(anchor);
|
Paragraph placementAnchor = findPageBodyAnchor(document, collector, page);
|
if (placementAnchor == null) {
|
placementAnchor = anchor;
|
}
|
double[] size = fitImage(imagePath, REPORT_MAX_WIDTH, REPORT_MAX_HEIGHT);
|
Shape shape = createFloatingImage(document, imagePath, size[0], size[1]);
|
shape.setLeft((pageSetup.getPageWidth() - size[0]) / 2D);
|
double preferredTop = rectangle == null
|
? pageSetup.getPageHeight() - pageSetup.getBottomMargin() - size[1] - 120D
|
: rectangle.getY() - size[1] + rectangle.getHeight() / 2D;
|
shape.setTop(clamp(preferredTop,
|
pageSetup.getTopMargin(),
|
pageSetup.getPageHeight() - pageSetup.getBottomMargin() - size[1]));
|
// 结论章位于表格单元格内,必须置于文字上方,否则会被单元格底纹遮住。
|
shape.setBehindText(false);
|
placementAnchor.appendChild(shape);
|
}
|
|
private static void addLastPageReportSeal(Document document, String imagePath) throws Exception {
|
int lastPage = document.getPageCount();
|
LayoutCollector collector = new LayoutCollector(document);
|
Paragraph anchor = findLastPageAnchor(document, collector, lastPage);
|
if (anchor == null) {
|
return;
|
}
|
Section section = (Section) anchor.getAncestor(NodeType.SECTION);
|
PageSetup pageSetup = section.getPageSetup();
|
double[] size = fitImage(imagePath, LAST_PAGE_MAX_WIDTH, LAST_PAGE_MAX_HEIGHT);
|
Shape shape = createFloatingImage(document, imagePath, size[0], size[1]);
|
shape.setLeft((pageSetup.getPageWidth() - size[0]) / 2D);
|
Rectangle2D rectangle = getLayoutRectangle(document, collector, anchor);
|
double preferredTop = rectangle == null
|
? pageSetup.getPageHeight() - pageSetup.getBottomMargin() - size[1] - POSITION_GAP
|
: rectangle.getY() + rectangle.getHeight() + POSITION_GAP;
|
shape.setTop(clamp(preferredTop,
|
pageSetup.getTopMargin(),
|
pageSetup.getPageHeight() - pageSetup.getBottomMargin() - size[1]));
|
shape.setBehindText(true);
|
anchor.appendChild(shape);
|
}
|
|
private static Shape createFloatingImage(Document document, String imagePath,
|
double width, double height) throws Exception {
|
Shape shape = new Shape(document, ShapeType.IMAGE);
|
shape.getImageData().setImage(imagePath);
|
shape.setWidth(width);
|
shape.setHeight(height);
|
shape.setWrapType(WrapType.NONE);
|
shape.setRelativeHorizontalPosition(RelativeHorizontalPosition.PAGE);
|
shape.setRelativeVerticalPosition(RelativeVerticalPosition.PAGE);
|
shape.setAllowOverlap(true);
|
return shape;
|
}
|
|
private static Paragraph firstBodyParagraph(Document document) {
|
Paragraph paragraph = document.getFirstSection().getBody().getFirstParagraph();
|
if (paragraph != null) {
|
return paragraph;
|
}
|
return document.getFirstSection().getBody().appendParagraph("");
|
}
|
|
private static Paragraph findLastPageAnchor(Document document, LayoutCollector collector, int lastPage)
|
throws Exception {
|
Paragraph fallback = null;
|
NodeCollection paragraphs = document.getChildNodes(NodeType.PARAGRAPH, true);
|
for (int i = paragraphs.getCount() - 1; i >= 0; i--) {
|
Paragraph paragraph = (Paragraph) paragraphs.get(i);
|
if (paragraph.getAncestor(NodeType.BODY) == null) {
|
continue;
|
}
|
if (collector.getStartPageIndex(paragraph) != lastPage) {
|
continue;
|
}
|
if (fallback == null) {
|
fallback = paragraph;
|
}
|
if (paragraph.getText().contains("报告结束")) {
|
return paragraph;
|
}
|
}
|
return fallback;
|
}
|
|
private static Paragraph findBodyParagraphContaining(Document document, String text,
|
LayoutCollector collector, Integer page)
|
throws Exception {
|
NodeCollection paragraphs = document.getChildNodes(NodeType.PARAGRAPH, true);
|
for (int i = 0; i < paragraphs.getCount(); i++) {
|
Paragraph paragraph = (Paragraph) paragraphs.get(i);
|
if (paragraph.getAncestor(NodeType.BODY) == null || !paragraph.getText().contains(text)) {
|
continue;
|
}
|
if (page == null || collector.getStartPageIndex(paragraph) == page) {
|
return paragraph;
|
}
|
}
|
return null;
|
}
|
|
private static Paragraph findPageBodyAnchor(Document document, LayoutCollector collector, int page)
|
throws Exception {
|
NodeCollection paragraphs = document.getChildNodes(NodeType.PARAGRAPH, true);
|
for (int i = 0; i < paragraphs.getCount(); i++) {
|
Paragraph paragraph = (Paragraph) paragraphs.get(i);
|
if (paragraph.getAncestor(NodeType.BODY) == null
|
|| paragraph.getAncestor(NodeType.CELL) != null) {
|
continue;
|
}
|
if (collector.getStartPageIndex(paragraph) == page) {
|
return paragraph;
|
}
|
}
|
return null;
|
}
|
|
private static Rectangle2D getLayoutRectangle(Document document, LayoutCollector collector,
|
Paragraph paragraph) throws Exception {
|
Object entity = collector.getEntity(paragraph);
|
if (entity == null) {
|
NodeCollection runs = paragraph.getChildNodes(NodeType.RUN, true);
|
for (int i = 0; i < runs.getCount() && entity == null; i++) {
|
entity = collector.getEntity(runs.get(i));
|
}
|
}
|
if (entity == null) {
|
return null;
|
}
|
LayoutEnumerator enumerator = new LayoutEnumerator(document);
|
enumerator.setCurrent(entity);
|
return enumerator.getRectangle();
|
}
|
|
private static double clamp(double value, double min, double max) {
|
return Math.max(min, Math.min(value, max));
|
}
|
|
private static List<Shape> imageShapes(Document document) throws Exception {
|
List<Shape> result = new ArrayList<>();
|
NodeCollection nodes = document.getChildNodes(NodeType.SHAPE, true);
|
for (int i = 0; i < nodes.getCount(); i++) {
|
Shape shape = (Shape) nodes.get(i);
|
if (shape.hasImage()) {
|
result.add(shape);
|
}
|
}
|
return result;
|
}
|
|
private static boolean sameImage(Shape shape, byte[] imageBytes) throws Exception {
|
return Arrays.equals(shape.getImageData().toByteArray(), imageBytes);
|
}
|
|
private static double[] fitImage(String imagePath, double maxWidth, double maxHeight) throws Exception {
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(Files.readAllBytes(
|
java.nio.file.Paths.get(imagePath))));
|
if (image == null || image.getWidth() <= 0 || image.getHeight() <= 0) {
|
return new double[]{maxWidth, maxHeight};
|
}
|
double scale = Math.min(maxWidth / image.getWidth(), maxHeight / image.getHeight());
|
return new double[]{image.getWidth() * scale, image.getHeight() * scale};
|
}
|
|
}
|