世上最簡單mxGraph 導出圖片中文亂碼問題_分享一次解決問題的心理歷程

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章