​markdown 转 image

markdown 转 image

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

思路

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

1. markdown 转 html

可以参看之前的博文《Java 实现 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());

// 获取Graphics2D
graphics2D = bufferedImage.createGraphics();
renderer.layout(graphics2D, dimension);

// 内容渲染
renderer.render(graphics2D);
说明
  1. 为什么并不直接使用 java-html2image ?

  • 因为有些定制的场景支持得不太友好,加上源码也比较简单,所以干脆站在前人的基础上进行拓展

  • 设计目标(这里指html转图片的功能)

    • 生成图片的宽可指定

    • 支持对线上网页进行转图片

    • 支持对html中指定的区域进行转换

    • css样式渲染支持

    实现

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

    1. markdown 转 html 封装

    利用之前封装的 MarkDown2HtmlWrapper 工具类

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

    2. html 转 image

    参数配置项 HtmlRenderOptions

    注意

    • html 为 Document 属性

    • autoW, autoH 用于控制是否自适应html实际的长宽

    @Data
    public 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;
        }
    }

    测试

    @Test
    public 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://github.com/liuyueyi/quick-media/blob/master/doc/images/imgGenV2.md

    其他

    相关博文 : Java 实现 markdown转Html

    项目地址:https://github.com/liuyueyi/quick-media

    个人博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值