在一次调试考试系统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的调用,结果并未如愿,有大神解决过相关问题的希望可以留言指导下,
✧(≖ ◡ ≖✿)嘿嘿