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的開閉原則。不然以後維護那將是一件令人痛苦的事。