如何将 SVG 图像添加到 XSSFWorkBook

How to add SVG image to XSSFWorkBook

在尝试将 SVG 图像添加到 XSSFWorkbook 时,我注意到没有选项可以将 PictureType 设置为 SVG

FileInputStream in = new FileInputStream("testWorkbook.xlsx");
XSSFWorkbook wb = (XSSFWorkbook)WorkbookFactory.create(in);
InputStream inputStream = new FileInputStream("/home/test/test1.svg");
//Get the contents of an InputStream as a byte[].
byte[] imageData = IOUtils.toByteArray(inputStream);
// XSSFWorkbook.PICTURE_TYPE_SVG is not supported yet.
int pictureIndex = wb.addPicture(imageData, XSSFWorkbook.PICTURE_TYPE_SVG); // Preferred support

但是,我注意到支持将 SVG 图片添加到 XSLF

有没有办法将 SVG 图片添加到 XSSFWorkbook

SVG 的支持是 Microsoft Office 365 的一项新功能。这就是为什么它直到现在(2021 年 5 月,apache poi 5.0.0)才得到 apache poi 的完全支持。

要在 Excel 中实现该支持,需要了解 Excel 如何插入 SVG 图像。它将图像转换为 PNG 以实现向后兼容性。它将 SVG 图像和 PNG 图像都放入工作簿中。然后它将 PNG 图像显示为绘图中的形状。该形状对 SVG 图像有额外的引用,因此如果使用 Excel 365,也可以获得 SVG 图像。

要在 apache poi 中实施该支持,需要以下内容:

  1. 一个 SVGPNG 的转换器。那里可以用Apache Batik Transcoder

  2. 扩展的 XSSFWorkbook 提供单独的 addSVGPicture 方法或在 addPicture 方法中提供对 SVG 的支持。但是扩展 XSSF... 类 并不像它想象的那么简单,因为一些将成员或方法设为私有的奇怪决定。

  3. A XSSFRelation.IMAGE_SVG 在创建图片和形状时提供创建关系。但是 XSSFRelation 根本不可扩展。所以需要延长低位POIXMLRelation

  4. 扩展的 XSSFPictureData 为新 POIXMLRelation 所需的图片构造器提供支持。

以下代码提供了所有这些。在需要的地方进行了评论。

import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;

import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

class CreateExcelXSSFPictureSVG {
 
 // use org.apache.batik.transcoder to convert SVG to PNG 
 static byte[] svgToPng(InputStream svg) throws Exception {
  org.apache.batik.transcoder.image.PNGTranscoder t = new org.apache.batik.transcoder.image.PNGTranscoder();
  org.apache.batik.transcoder.TranscoderInput input = new org.apache.batik.transcoder.TranscoderInput(svg);
  
  java.io.ByteArrayOutputStream ostream = new java.io.ByteArrayOutputStream();
  org.apache.batik.transcoder.TranscoderOutput output = new org.apache.batik.transcoder.TranscoderOutput(ostream);

  t.transcode(input, output);
  ostream.flush();
  
  return ostream.toByteArray();  
 }
    
 public static void main(String[] args) throws Exception {
     
  String svgFilePath = "./Freesample.svg";
  
  MyXSSFWorkbook workbook = new MyXSSFWorkbook(); // see MyXSSFWorkbook.java

  // add SVG Image to workbook  
  FileInputStream is = new FileInputStream(svgFilePath);
  int svgPictureIdx = workbook.addSVGPicture(is);
  is.close();
  
  // add PNG image to workbook
  is = new FileInputStream(svgFilePath);
  int pngPictureIdx = workbook.addPicture(svgToPng(is), Workbook.PICTURE_TYPE_PNG);
  is.close();

  // create sheet and get drawing
  XSSFSheet sheet = workbook.createSheet("Sheet1");
  XSSFDrawing drawing = sheet.createDrawingPatriarch();
 
  // add SVG Image relation to drawing 
  XSSFPictureData pictureData = workbook.getAllPictures().get(svgPictureIdx);
  org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart rp = drawing.addRelation(null, XSSFRelation.IMAGES, pictureData);
  String svgRId = rp.getRelationship().getId();

  // create anchor for picture shape
  XSSFCreationHelper helper = workbook.getCreationHelper();
  XSSFClientAnchor anchor = helper.createClientAnchor();
  anchor.setCol1(1);
  anchor.setRow1(1); 

  // create PNG picture shape
  XSSFPicture pngPicture = drawing.createPicture(anchor, pngPictureIdx);
  pngPicture.resize();
  
  // set SVG extension to PNG picture shape
  org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtension ext = pngPicture.getCTPicture().getBlipFill().getBlip().addNewExtLst().addNewExt();
  ext.setUri("{96DAC541-7B7A-43D3-8B79-37D633B846F1}");
  org.apache.xmlbeans.XmlCursor cursor = ext.newCursor();
  cursor.toNextToken();
  cursor.toNextToken();
  cursor.beginElement(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2016/SVG/main", "svgBlip", "asvg"));
  cursor.insertNamespace("asvg", "http://schemas.microsoft.com/office/drawing/2016/SVG/main");
  cursor.insertAttributeWithValue(new javax.xml.namespace.QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed", "r"), svgRId);
  cursor.dispose();

  FileOutputStream out = new FileOutputStream("CreateExcelXSSFPictureSVG.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();

 }
}

使用扩展 类:

MyXSSFWorkbook.java

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFFactory;
import org.apache.poi.util.IOUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import java.lang.reflect.Field;

public class MyXSSFWorkbook extends XSSFWorkbook {

 public int addSVGPicture(InputStream is) throws Exception {
  Field _xssfFactory = XSSFWorkbook.class.getDeclaredField("xssfFactory");
  _xssfFactory.setAccessible(true);
  XSSFFactory xssfFactory = (XSSFFactory)_xssfFactory.get(this);
  
  int imageNumber = getAllPictures().size() + 1;
  
  Field _pictures = XSSFWorkbook.class.getDeclaredField("pictures");
  _pictures.setAccessible(true);
  @SuppressWarnings("unchecked")
  List<XSSFPictureData> pictures = (List<XSSFPictureData>)_pictures.get(this);

  MyXSSFPictureData img = createRelationship(MyXSSFRelation.IMAGE_SVG, xssfFactory, imageNumber, true).getDocumentPart(); // see MyXSSFPictureData.java and MyXSSFRelation.java
  try (OutputStream out = img.getPackagePart().getOutputStream()) {
   IOUtils.copy(is, out);
  }
  pictures.add(img);

  return imageNumber - 1;
 }

}

MyXSSFRelation.java

import org.apache.poi.ooxml.POIXMLRelation;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;

public final class MyXSSFRelation extends POIXMLRelation {

 public static final MyXSSFRelation IMAGE_SVG = new MyXSSFRelation(
  "image/svg",
  PackageRelationshipTypes.IMAGE_PART,
  "/xl/media/image#.svg",
  MyXSSFPictureData::new, MyXSSFPictureData::new // see MyXSSFPictureData.java
 );

 private MyXSSFRelation(String type, String rel, String defaultName,
                      NoArgConstructor noArgConstructor,
                      PackagePartConstructor packagePartConstructor) {
  super(type, rel, defaultName, noArgConstructor, packagePartConstructor, null);
 }

}

MyXSSFPictureData.java

import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.xssf.usermodel.XSSFPictureData; 

public class MyXSSFPictureData extends XSSFPictureData {

 protected MyXSSFPictureData() {
  super();
 }
 
 protected MyXSSFPictureData(PackagePart part) {
  super(part);
 }

}