1.前言
正如标题所言,本篇博客并不是写怎么解决这个问题,说实话,这个问题最终解决下来也就是增加了一行有效代码。而真正关注的还是本次我解决这个问题的思路。希望对大家都有所启发、有所帮助。
2.背景及问题描述
最近在做一个实验管理的项目,涉及到工作流方面的知识,毋庸置疑,我们使用的是Activiti框架。当我们生成流程图的时候,发现涉及到中文名称的节点出现了“乱码”(如下图),这里之所以给乱码加引号,是因为他并不是我们通常意义上说的乱码(GBK\UTF-8\ISO-8859-1)之间的不一致。那是什么呢?当看到一个个小方格的时候,我们基本能断定这是由于电脑缺少相对应的字体库。
3.出题初探
经过一系列的debug,分析各个阶段结果的变化,我最终将分析锁定到exportImage上。因为输入的字符串中imageXML内容还是正常的,结果这里将xml转换为图标就变成乱码了。所以我们最终将问题定位到mxGraphicsCanvas2D。
public void exportImage(String path, int w, int h, String imageXML) {
File png = new File(path);
File dir = new File(path);
if (!dir.exists()) {
dir.mkdir();
}
BufferedImage image = mxUtils.createBufferedImage(w, h, Color.WHITE);
Graphics2D g2 = image.createGraphics();
mxUtils.setAntiAlias(g2, true, true);
mxGraphicsCanvas2D gc2 = new mxGraphicsCanvas2D(g2);
gc2.setAutoAntiAlias(true);
try {
parseXmlSax(imageXML, gc2);
ImageIO.write(image, "png", png);
} catch (Exception e) {
e.printStackTrace();
}
}
经过上网查找mxGraphicsCanvas2D 中文乱码问题,找到了一些蛛丝马迹。普遍的处理思路是,找到mxGraphicsCanvas2D 类的text(double x, double y, double w, double h, String str,String align, String valign, boolean vertical)方法。这里面有一段在这个方法中特定位置加一段代码即可。
...
//可以搜索关键词createTextGraphics快速定位到此方法
Graphics2D g2 = createTextGraphics(x, y, w, h, vertical);
//增加一下两行代码这句后就能使打印的中文没有乱码了,这是参考activiti动态打印png图片的乱码问题解决滴!
Font font = new Font("宋体", Font.BOLD, 12);
g2.setFont(font);
FontMetrics fm = g2.getFontMetrics();
...
然而下面的步骤让我慌了神,要将此类编译成class文件,替换了原来jar中对应的文件。对于一个maven项目,这么做会给协同开发以及维护带来很大问题。因为当另外一个人拿到你的项目的时候会直接冲中心仓库下载jar,并不会使用你改造的jar包。这是,会有人说,公司可以有私服啊。那么如果我们过了N年之后要升级这个版本呢。所以,这种解决方式可取,但是解决思路还欠妥。
4.深入解决
通过网络查询,我们知道归于创建出来的饿g2对象,重新设置一个新的字体即可。那么问题来了,怎么在不修改原来的框架的情况下修改他的字体呢?通过上面问题解决思路我们发现,mxGraphicsCanvas2D#text(double x, double y, double w, double h, String str,String align, String valign, boolean vertical)正是我们测试代码创建的对象,那么我们可不可以在创建这个类的时候重写一些方法呢?抱着这种思路,我们开启的探索之旅。
4.1 初探text方法
text方法定义如下:
public void text(double x, double y, double w, double h, String str, String align, String valign, boolean vertical, boolean wrap, String format) {
if (!this.state.fontColorValue.equals(mxConstants.NONE)) {
if (format != null && format.equals("html")) {
x += this.state.dx / this.state.scale;
y += this.state.dy / this.state.scale;
JLabel textRenderer = this.getTextRenderer();
if (textRenderer != null && this.rendererPane != null) {
AffineTransform previous = this.state.g.getTransform();
this.state.g.scale(this.state.scale, this.state.scale);
str = this.createHtmlDocument(str, align, valign, (int)Math.round(w * mxConstants.PX_PER_PIXEL), (int)Math.roh * mxConstants.PX_PER_PIXEL));
textRenderer.setText(str);
this.rendererPane.paintComponent(this.state.g, textRenderer, this.rendererPane, (int)Math.round(x), (Math.round(y), (int)Math.round(w), (int)Math.round(h), true);
this.state.g.setTransform(previous);
}
} else {
x = this.state.dx + x * this.state.scale;
y = this.state.dy + y * this.state.scale;
w *= this.state.scale;
h *= this.state.scale;
Graphics2D g2 = this.createTextGraphics(x, y, w, h, vertical);
FontMetrics fm = g2.getFontMetrics();
String[] lines = str.split("\n");
y = this.getVerticalTextPosition(x, y, w, h, align, valign, vertical, fm, lines);
x = this.getHorizontalTextPosition(x, y, w, h, align, valign, vertical, fm, line
for(int i = 0; i < lines.length; ++i) {
double dx = 0.0D;
if (align != null) {
int sw;
if (align.equals("center")) {
sw = fm.stringWidth(lines[i]);
dx = (w - (double)sw) / 2.0D;
} else if (align.equals("right")) {
sw = fm.stringWidth(lines[i]);
dx = w - (double)sw;
}
if ((this.state.fontStyle & 4) == 4) {
AttributedString as = new AttributedString(lines[i]);
as.addAttribute(TextAttribute.FONT, g2.getFont());
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
g2.drawString(as.getIterator(), (int)Math.round(x + dx), (int)Math.round(y));
} else {
g2.drawString(lines[i], (int)Math.round(x + dx), (int)Math.round(y));
y += (double)(fm.getHeight() + mxConstants.LINESPACING);
}
}
}
看到这个方法是public的方法,我们窃喜,我们可以重写这个方法啊,所以我们就有了如下代码:
mxGraphicsCanvas2D gc2 = new mxGraphicsCanvas2D(g2) {
@Override
public void text(double x, double y, double w, double h, String str, String align, String valign, boolean vertical, boolean wrap, String format) {
if (!this.state.fontColorValue.equals(mxConstants.NONE)) {
....
}
};
不过经过我们一系列引入jar包依赖之后,我们的心凉了一大截。我们并没有权限访问到state.fontColorValue这个变量。
4.2 createTextGraphics碰壁
上面提到,我们在Graphics2D g2 = this.createTextGraphics(x, y, w, h, vertical);下面增加两行代码,说白了就是g2重新设置字体,那么如果我们在创建g2的时候就把字体设置了那岂不很好,所以我们想到重写createTextGraphics方法。不过理想总是没美好的,现实还是很残酷的。不好意思,createTextGraphics被final修饰了,好尴尬。
protected final Graphics2D createTextGraphics(double x, double y, double w, double h, boolean vertical) {
Graphics2D g2 = this.state.g;
this.updateFont();
if (vertical) {
g2 = (Graphics2D)this.state.g.create();
g2.rotate(-1.5707963267948966D, x + w / 2.0D, y + h / 2.0D);
}
if (this.state.fontColor == null) {
this.state.fontColor = this.parseColor(this.state.fontColorValue);
}
g2.setColor(this.state.fontColor);
return g2;
}
4.3 updateFont_初露头角
protected void updateFont() {
if (this.currentFont == null) {
int size = (int)Math.floor(this.state.fontSize);
int style = (this.state.fontStyle & 1) == 1 ? 1 : 0;
style += (this.state.fontStyle & 2) == 2 ? 2 : 0;
this.currentFont = this.createFont(this.state.fontFamily, style, size);
this.state.g.setFont(this.currentFont);
}
}
在这个方法中,this.state.g.setFont(this.currentFont);可以将font设置进去。而通篇查找currentFont变量,发现只有第6行代码对其进行初始化了。好像胜利再向我们招手。
4.4 大功告成
protected Font createFont(String family, int style, int size) {
return new Font(this.getFontName(family), style, size);
}
到这里,我们终于看到了希望,这个方法具备重写各个条件。所以我们将上面的代码进行修改得到最终一下代码:
public void exportImage(String path, int w, int h, String imageXML) {
File png = new File(path);
File dir = new File(path);
if (!dir.exists()) {//若路径不存在则创建此路径
dir.mkdir();
}
BufferedImage image = mxUtils.createBufferedImage(w, h, Color.WHITE);
Graphics2D g2 = image.createGraphics();
mxUtils.setAntiAlias(g2, true, true);
mxGraphicsCanvas2D gc2 = new mxGraphicsCanvas2D(g2) {
//增加一下代码
@Override
protected Font createFont(String family, int style, int size) {
return super.createFont("宋体", style, size);
}
};
gc2.setAutoAntiAlias(true);
try {
parseXmlSax(imageXML, gc2);
ImageIO.write(image, "png", png);
} catch (Exception e) {
e.printStackTrace();
}
}
4.5 验证
5.总结
通过上面的修改,我们并没有修改框架的代码,而是通过重写框架代码的方法实现了上诉问题。在实际的开发过程中,我们要有这种思路,我们要遵循java的开闭原则。不然以后维护那将是一件令人痛苦的事。