博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 实现 markdown转Image
阅读量:6222 次
发布时间:2019-06-21

本文共 7546 字,大约阅读时间需要 25 分钟。

hot3.png

markdown 转 image

前段时间实现了长图文生成的基本功能,然后想了下能否有个进阶版,直接将markdown生成渲染后的图片呢?

思路

有不少的库可以将 markdown 转为 html,那么这个需求就可以转为 html转Image了

1. markdown 转 html

可以参看之前的博文

2. html 转 图片

主要的核心问题就在这里了,如何实现html转图片?

  • 直接实现html转图片的包没怎么见,看到一个 html2image, 还不太好用
  • 在 AWT or Swing 的Panel上显示网页,在把Panel输出为 image 文件
  • 使用js相关技术实现转换

本篇博文具体实现以 html2image 的实现逻辑作为参考,然后定制实现一把(后面有机会写一篇利用js来实现html转图片的博文)

html2image 的实现原理

html2image 基本上没啥维护了,内部主要是利用了 xhtmlrender 实现html渲染为图片

Graphics2DRenderer renderer = new Graphics2DRenderer();// 设置渲染内容renderer.setDocument(document, document.getDocumentURI());// 获取Graphics2Dgraphics2D = bufferedImage.createGraphics();renderer.layout(graphics2D, dimension);// 内容渲染renderer.render(graphics2D);

说明

  1. 为什么并不直接使用 java-html2image ?
  • 因为有些定制的场景支持得不太友好,加上源码也比较简单,所以干脆站在前人的基础上进行拓展
  1. 设计目标(这里指html转图片的功能)
  • 生成图片的宽可指定
  • 支持对线上网页进行转图片
  • 支持对html中指定的区域进行转换
  • css样式渲染支持

实现

本篇先会先实现一个基本的功能,即读去markdown文档, 并转为一张图片

1. markdown 转 html 封装

利用之前封装的 MarkDown2HtmlWrapper 工具类

具体实现逻辑参考项目工程,和markdown转html博文

2. html 转 image

参数配置项 HtmlRenderOptions

注意

  • html 为 Document 属性
  • autoW, autoH 用于控制是否自适应html实际的长宽
@Datapublic class HtmlRenderOptions {    /**     * 输出图片的宽     */    private Integer w;    /**     * 输出图片的高     */    private Integer h;    /**     * 是否自适应宽     */    private boolean autoW;    /**     * 是否自适应高     */    private boolean autoH;    /**     * 输出图片的格式     */    private String outType;    /**     * html相关内容     */    private Document document;}

封装处理类

同样采用Builder模式来进行配置项设置

public class Html2ImageWrapper {    private static DOMParser domParser;    static {        domParser = new DOMParser(new HTMLConfiguration());        try {            domParser.setProperty("http://cyberneko.org/html/properties/names/elems", "lower");        } catch (Exception e) {            throw new RuntimeException("Can't create HtmlParserImpl", e);        }    }    private HtmlRenderOptions options;    private Html2ImageWrapper(HtmlRenderOptions options) {        this.options = options;    }    private static Document parseDocument(String content) throws Exception {        domParser.parse(new InputSource(new StringReader(content)));        return domParser.getDocument();    }    public static Builder of(String html) {        return new Builder().setHtml(html);    }    public static Builder ofMd(MarkdownEntity entity) {        return new Builder().setHtml(entity);    }    public BufferedImage asImage() {        BufferedImage bf = HtmlRender.parseImage(options);        return bf;    }    public boolean asFile(String absFileName) throws IOException {        File file = new File(absFileName);        FileUtil.mkDir(file);        BufferedImage bufferedImage = asImage();        if (!ImageIO.write(bufferedImage, options.getOutType(), file)) {            throw new IOException("save image error!");        }        return true;    }    public String asString() throws IOException {        BufferedImage img = asImage();        return Base64Util.encode(img, options.getOutType());    }    @Getter    public static class Builder {        /**         * 输出图片的宽         */        private Integer w = 600;        /**         * 输出图片的高度         */        private Integer h;        /**         * true,根据网页的实际宽渲染;         * false, 则根据指定的宽进行渲染         */        private boolean autoW = true;        /**         * true,根据网页的实际高渲染;         * false, 则根据指定的高进行渲染         */        private boolean autoH = false;        /**         * 输出图片的格式         */        private String outType = "jpg";        /**         * 待转换的html内容         */        private MarkdownEntity html;        public Builder setW(Integer w) {            this.w = w;            return this;        }        public Builder setH(Integer h) {            this.h = h;            return this;        }        public Builder setAutoW(boolean autoW) {            this.autoW = autoW;            return this;        }        public Builder setAutoH(boolean autoH) {            this.autoH = autoH;            return this;        }        public Builder setOutType(String outType) {            this.outType = outType;            return this;        }        public Builder setHtml(String html) {            this.html = new MarkdownEntity();            return this;        }        public Builder setHtml(MarkdownEntity html) {            this.html = html;            return this;        }        public Html2ImageWrapper build() throws Exception {            HtmlRenderOptions options = new HtmlRenderOptions();            options.setW(w);            options.setH(h);            options.setAutoW(autoW);            options.setAutoH(autoH);            options.setOutType(outType);            if (fontColor != null) {                html.addDivStyle("style", "color:" + options.getFontColor());            }            html.addDivStyle("style", "width:" + w + ";");            html.addWidthCss("img");            html.addWidthCss("code");            options.setDocument(parseDocument(html.toString()));            return new Html2ImageWrapper(options);        }    }}

上面的实现,有个需要注意的地方

如何将html格式的字符串,转为 Document 对象

利用了开源工具 nekohtml, 可以较好的实现html标签解析,看一下DOMParse 的初始化过程

private static DOMParser domParser;static {    domParser = new DOMParser(new HTMLConfiguration());    try {        domParser.setProperty("http://cyberneko.org/html/properties/names/elems",         "lower");    } catch (Exception e) {        throw new RuntimeException("Can't create HtmlParserImpl", e);    }}

try语句块中的内容并不能缺少,否则最终的样式会错乱,关于 nekohtml 的使用说明,可以查阅相关教程

上面的封装,主要是HtmlRenderOptions的构建,主要的渲染逻辑则在下面

渲染

利用 xhtmlrenderer 实现html的渲染

  • 宽高的自适应
  • 图片的布局,内容渲染
public class HtmlRender {    /**     * 输出图片     *     * @param options     * @return     */    public static BufferedImage parseImage(HtmlRenderOptions options) {        int width = options.getW();        int height = options.getH() == null ? 1024 : options.getH();        Graphics2DRenderer renderer = new Graphics2DRenderer();        renderer.setDocument(options.getDocument(), options.getDocument().getDocumentURI());        Dimension dimension = new Dimension(width, height);        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);        Graphics2D graphics2D = GraphicUtil.getG2d(bufferedImage);        // 自适应修改生成图片的宽高        if (options.isAutoH() || options.getH() == null) {            // do layout with temp buffer            renderer.layout(graphics2D, new Dimension(width, height));            graphics2D.dispose();            Rectangle size = renderer.getMinimumSize();            final int autoWidth = options.isAutoW() ? (int) size.getWidth() : width;            final int autoHeight = (int) size.getHeight();            bufferedImage = new BufferedImage(autoWidth, autoHeight, BufferedImage.TYPE_INT_RGB);            dimension = new Dimension(autoWidth, autoHeight);            graphics2D = GraphicUtil.getG2d(bufferedImage);        }        renderer.layout(graphics2D, dimension);        renderer.render(graphics2D);        graphics2D.dispose();        return bufferedImage;    }}

测试

@Testpublic void testParse() throws Exception {    String file = "md/tutorial.md";    MarkdownEntity html = MarkDown2HtmlWrapper.ofFile(file);    BufferedImage img = Html2ImageWrapper.ofMd(html)            .setW(600)            .setAutoW(false)            .setAutoH(true)            .setOutType("jpg")            .build()            .asImage();    ImageIO.write(img, "jpg", new File("/Users/yihui/Desktop/md.jpg"));}

输出图片

输入图片说明

然后演示一个对项目中实际的教程文档输出图片的动态示意图, 因为生成的图片特别特别长,所以就不贴输出的图片了,有兴趣的同学可以下载工程,实际跑一下看看

源markdown文件地址:

输入图片说明

其他

相关博文 :

项目地址:

个人博客:

公众号获取更多:

个人信息

参考博文

转载于:https://my.oschina.net/u/566591/blog/1536078

你可能感兴趣的文章
PostConstruct
查看>>
MyEclipse------快速读取特定目录下的文件的内容(字节输入流)
查看>>
Linq查询操作之排序操作
查看>>
Spring 4支持的Java 8新特性一览
查看>>
RHEL6.2下挂载光驱安装软件
查看>>
YYCache 源码分析(二)
查看>>
2016年第9本:系统之美
查看>>
framebuff 显示子系统
查看>>
php手册杂记
查看>>
Yii2 定时任务创建(Console 任务)
查看>>
lombok+slf4j+logback SLF4J和Logback日志框架详解
查看>>
PHP 单例模式继承的实现方式
查看>>
思维杂谈(1)
查看>>
【Java深入研究】3、HashMap源码解析(jdk 1.7)
查看>>
1102 采药 2005年NOIP全国联赛普及组
查看>>
WebSockets Tutorial(教程一)WebSockets简介
查看>>
仿支付宝/微信的password输入框效果GridPasswordView解析
查看>>
【复制虚拟机】虚拟机复制后无ip的问题
查看>>
002-数据库命名开发规范
查看>>
vue、vuex、iview、vue-router报错集锦与爬坑记录
查看>>