在一次调试考试系统bug时遇到的问题,功能是word录入一些题目,解析其中的数学公式转为浏览器可显示的图片。
正常的最新版的word插入的公式是DocumentFormat.OpenXml.Math.OfficeMath类,

这时主要是通过MathML渲染器Jeuclid来将文档中的数学公式xml转换为png。
但是当使用旧版本的word时,插入的公式为objectElement形式,这时上面提到的方法就不能用了,
仔细查看word中包含数学公式的xml片段,发现其中包含一个Element,并且每个标签上都带有一个r:id属性,

<w:object w:dxaOrig="2280" w:dyaOrig="620" w14:anchorId="271EC68A">
    <v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
      <v:stroke joinstyle="miter"/>
      <v:formulas>
        <v:f eqn="if lineDrawn pixelLineWidth 0"/>
        <v:f eqn="sum @0 1 0"/>
        <v:f eqn="sum 0 0 @1"/>
        <v:f eqn="prod @2 1 2"/>
        <v:f eqn="prod @3 21600 pixelWidth"/>
        <v:f eqn="prod @3 21600 pixelHeight"/>
        <v:f eqn="sum @0 0 1"/>
        <v:f eqn="prod @6 1 2"/>
        <v:f eqn="prod @7 21600 pixelWidth"/>
        <v:f eqn="sum @8 21600 0"/>
        <v:f eqn="prod @7 21600 pixelHeight"/>
        <v:f eqn="sum @10 21600 0"/>
      </v:formulas>
      <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
      <o:lock v:ext="edit" aspectratio="t"/>
    </v:shapetype>
    <v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:114pt;height:31.35pt" o:ole="">
      <v:imagedata r:id="rId6" o:title=""/>
    </v:shape>
    <o:OLEObject Type="Embed" ProgID="Equation.3" ShapeID="_x0000_i1025" DrawAspect="Content" ObjectID="_1560188948" r:id="rId7"/>
  </w:object>

通过查看XWPFDocument(POI提供的word文档解析)的API可以获取文档的所有图片内容,另外还有一个根据ID来获取图片信息的方法,
通过尝试,可以直接通过rid直接获取到对应的图片数据,

XWPFPictureData pictureData = doc.getPictureDataByID(picId);
int type = pictureData.getPictureType();

其中大部分是wmf格式,少部分是emf格式,考虑到浏览器不支持显示这两种格式图片,就打算转为png格式。

其中主要用到了Apache提供的jar包batick-codec相关maven依赖如下

<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-codec</artifactId>
    <version>1.7</version>
</dependency>

主要用到了其中的WMFTranscoder类和JPEGTranscoder类,
WMFTranscoder负责将wmf转为svg,PNGTranscoder将svg转为jpeg,或者用JPEGTranscoder,示例代码如下

import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
public class BatikWmf{
private static final Logger logger = Logger
            .getLogger(BatikWmf.class);
    public static final String wmfFilePath = "/tmp/setsWordImg.wmf";
    public static final String svgFilePath = "/tmp/setsWordImg.svg";
    public static final String pngFilePath = "/tmp/setsWordImg.png";
    private byte[] wmfBytes;
    
    public BatikWmf() {
    }
    public BatikWmf(byte[] wmfBytes) {
        this.wmfBytes = wmfBytes;
    }
    private void transFile(File inFile, File outFile, int type) throws TranscoderException {
        try {
            FileInputStream in = new FileInputStream(inFile);
            FileOutputStream out = new FileOutputStream(outFile);
            TranscoderInput transIn = new TranscoderInput(in);
            TranscoderOutput transOut = new TranscoderOutput(out);
            switch (type) {
                case 0:
                    WMFTranscoder wmfTranscoder = new WMFTranscoder();
                    System.out.println("wmf开始转svg");
                    wmfTranscoder.transcode(transIn, transOut);
                    System.out.println("svg成功");
                    break;
                case 1:
                    JPEGTranscoder pngTranscoder = new JPEGTranscoder();
                    pngTranscoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY,
                            new Float(.8));
                    System.out.println("开始转png");
                    //设置png画质精度0-1之间
                    pngTranscoder.transcode(transIn, transOut);
                    System.out.println("png成功");
                default:
                    break;
            }
        } catch (TranscoderException e) {
            logger.error("wmf->svg出现异常", e);
            throw new TranscoderException(e);
        } catch (FileNotFoundException e) {
            logger.error("wmf->svg出现异常", e);
            throw new TranscoderException(e);
        }
    }
    public static void main(String[] args) {
        try {
            File wmfFile = new File("/tmp/123.wmf");
            FileInputStream fis = new FileInputStream(wmfFile);
            byte[] myBytes = getFisBytes(fis);
            IWmf wmf = new BatikWmf(myBytes);
            myBytes = wmf.getPngBytes();
            System.out.println("结束了");
        } catch (Exception e) {
            System.out.println("transcode出错了");
        }
    }
    @Override
    public byte[] getPngBytes()
            throws IOException {
        File wmfFile = new File(wmfFilePath);
        File svgFile = new File(svgFilePath);
        File pngFile = new File(pngFilePath);
        byte[] pngBytes = null;
        FileOutputStream fos = new FileOutputStream(wmfFile);
        fos.write(wmfBytes);
        fos.close();
        try {
            transFile(wmfFile, svgFile, 0);
        } catch (TranscoderException e) {
            logger.error("wmf转svg异常", e);
        }
        try {
            transFile(svgFile, pngFile, 1);
        } catch (TranscoderException e) {
            logger.error("svg转png异常", e);
        }
        FileInputStream fis = new FileInputStream(pngFile);
        pngBytes = getFisBytes(fis);
        deleteFile(wmfFile);
        deleteFile(svgFile);
        deleteFile(pngFile);
        return pngBytes;
    }
    public byte[] getFisBytes(FileInputStream fis) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            baos.write(b, 0, n);
        }
        IOUtils.closeQuietly(fis);
        IOUtils.closeQuietly(baos);
        return baos.toByteArray();
    }
    private void deleteFile(File file) {
        if (file.isFile() && file.exists()) {
            file.delete();
        }
    }
    public static void main(String[] args) {
            try {
                File wmfFile = new File("/tmp/123.wmf");
    
                FileInputStream fis = new FileInputStream(wmfFile);
    
                byte[] myBytes = getFisBytes(fis);
                IWmf wmf = new BatikWmf(myBytes);
                myBytes = wmf.getPngBytes();
                System.out.println("结束了");
    
            } catch (Exception e) {
                System.out.println("transcode出错了");
            }
        }
    }

需要注意的是,在linux服务器上使用该包时会出现一些问题,因为大多linux服务器是不带图形用户界面的,这时候会报一系列和X11 Server相关的错误,其中操作文件流的代码可能不规范,毕竟我只是个前端开发0.0,细节就请忽略吧。

最后我也没解决,主要还是Xserver安装总出错,启动不起来,有时间再研究一下

最后没办法我就只能用另一种方法将wmf转为svg之后,将svg的字节流直接转为base64的字符串在浏览器段显示了,考虑到公式文件不大,也就凑活用了

maven依赖为

<dependency>
    <groupId>net.arnx</groupId>
    <artifactId>wmf2svg</artifactId>
    <version>0.9.5</version>
</dependency>
import net.arnx.wmf2svg.gdi.svg.SvgGdiException;
import net.arnx.wmf2svg.gdi.wmf.WmfParseException;
import net.arnx.wmf2svg.gdi.wmf.WmfParser;
import net.arnx.wmf2svg.gdi.svg.SvgGdi;
import org.w3c.dom.Document;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import org.apache.log4j.Logger;
import org.apache.poi.util.IOUtils;
/**
 * Created by erry on 17/6/26.
 */
public class ArnxWmf {
    private static final Logger logger = Logger
            .getLogger(ArnxWmf.class);
    public static final String wmfFilePath = "/tmp/setsWordImg.wmf";
    public static final String svgFilePath = "/tmp/setsWordImg.svg";
    private byte[] wmfBytes;
    public ArnxWmf(byte[] wmfBytes) {
        this.wmfBytes = wmfBytes;
    }
    public ArnxWmf(FileInputStream fis) throws IOException {
        this.wmfBytes = getFisBytes(fis);
    }
    @Override
    public byte[] getSvgBytes() throws IOException {
        byte[] svgBytes=null;
        File svgFile = new File(svgFilePath);
        File wmfFile = new File(wmfFilePath);
        FileOutputStream fos = new FileOutputStream(wmfFile);
        fos.write(wmfBytes);
        try {
            transFile(wmfFile, svgFile);
        } catch (SvgGdiException e) {
            logger.error(e);
        } catch (TransformerConfigurationException e) {
            logger.error(e);
        }
        FileInputStream fis = new FileInputStream(svgFile);
        svgBytes = getFisBytes(fis);
        deleteFile(svgFile);
        deleteFile(wmfFile);
        return svgBytes;
    }
    public void transFile(File wmfFile, File svgFile)
            throws FileNotFoundException, SvgGdiException, TransformerConfigurationException {
        //wmf转svg
        boolean compatible = false;
        FileInputStream fis = new FileInputStream(wmfFile);
        WmfParser wmfParser = new WmfParser();
        final SvgGdi gdi = new SvgGdi(compatible);
        try {
            wmfParser.parse(fis, gdi);
        } catch (IOException e) {
            logger.error(e);
        } catch (WmfParseException e) {
            logger.error("wmf转gdi出现异常", e);
        }
        Document doc = gdi.getDocument();
        FileOutputStream fos = new FileOutputStream(svgFile);
        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer();
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
                "-//W3C//DTD SVG 1.0//EN");
        transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
                "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd");
        try {
            transformer.transform(new DOMSource(doc), new StreamResult(fos));
        } catch (TransformerException e) {
            logger.error("gdi转svg出现异常", e);
        }
        IOUtils.closeQuietly(fis);
        IOUtils.closeQuietly(fos);
    }
    public static void main(String[] args) {
        File wmfFile = new File("/tmp/123.wmf");
        try {
            ArnxWmf arnx = new ArnxWmf(new FileInputStream(wmfFile));
            byte[] bt = arnx.getSvgBytes();
            ImageUtils.deleteFile(wmfFile);
            System.out.println(bt.length);
        } catch (FileNotFoundException e) {
            logger.error(e);
        } catch (IOException e) {
            logger.error(e);
        }
    }
}

最后通过下面代码转为图片的base64

String base64 = new String(Base64.encodeBase64(svgBytes));
String img ="<img src=\"data:image/svg+xml;base64," + base64 + "\"/>";

另外就是将emf图片转为png的问题了,
maven依赖:

<dependency>
    <groupId>org.freehep</groupId>
    <artifactId>freehep-graphicsio-emf</artifactId>
    <version>${freehep.version}</version>
</dependency>

代码如下:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import org.freehep.graphicsio.emf.EMFInputStream;
import org.freehep.graphicsio.emf.EMFRenderer;
import org.apache.poi.util.IOUtils;
import javax.imageio.ImageIO;
/**
 * Created by erry on 17/6/28.
 */
public class ImageUtils {
    public static final String emfFilePath = "/tmp/setsWordImg.emf";
    public static final String pngFilePath = "/tmp/setsWordImg.png";
    public static byte[] getFisBytes(FileInputStream fis) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            baos.write(b, 0, n);
        }
        IOUtils.closeQuietly(fis);
        IOUtils.closeQuietly(baos);
        return baos.toByteArray();
    }
    public static void deleteFile(File file) {
        if (file.isFile() && file.exists()) {
            file.delete();
        }
    }
    public static byte[] emfToPng(byte[] emfBytes) throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        EMFInputStream eis = null;
        byte[] pngBytes = null;
        File emfFile = new File(emfFilePath);
        File pngFile = new File(pngFilePath);
        try {
            fos = new FileOutputStream(emfFile);
            fos.write(emfBytes);
            fis = new FileInputStream(emfFile);
            eis = new EMFInputStream(fis, EMFInputStream.DEFAULT_VERSION);
            EMFRenderer emfRender = new EMFRenderer(eis);
            final int height = (int) eis.readHeader().getBounds().getHeight();
            final int width = (int) eis.readHeader().getBounds().getWidth();
            final BufferedImage bufImage = new BufferedImage(width,
                    height, BufferedImage.TYPE_4BYTE_ABGR);
            Graphics2D g2 = bufImage.createGraphics();
            emfRender.paint(g2);
            ImageIO.write(bufImage, "png", pngFile);
            fis = new FileInputStream(pngFile);
            pngBytes = getFisBytes(fis);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fos);
            IOUtils.closeQuietly(eis);
            deleteFile(emfFile);
            deleteFile(pngFile);
        }
        return pngBytes;
    }
    public static void main(String[] args) throws IOException {
        File emfFile = new File("/tmp/123.emf");
        byte[] bytes = getFisBytes(new FileInputStream(emfFile));
        byte[] emfBytes = emfToPng(bytes);
    }
}

其中IOUtils是POI的一个工具类,简化文件的一些操作,看方法名就知道了,具体细节有问题的可以留言或通过其他方式来告知。

需要注意的问题是该类也默认需要GUI,这点可以通过设置java的启动参数来修改,让java不去调用系统硬件,自己来实现其功能:
-Djava.awt.headless=true

本来以为可以一并解决上边transcoder对X11的调用,结果并未如愿,有大神解决过相关问题的希望可以留言指导下,
✧(≖ ◡ ≖✿)嘿嘿