世上最简单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这个变量。

扫描二维码关注公众号,回复: 10245618 查看本文章

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的开闭原则。不然以后维护那将是一件令人痛苦的事。

发布了90 篇原创文章 · 获赞 205 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/oYinHeZhiGuang/article/details/105139775