Java 實現 markdown轉Image

markdown 轉 image

前段時間實現了長圖文生成的基本功能,而後想了下可否有個進階版,直接將markdown生成渲染後的圖片呢?css

思路

有很多的庫能夠將 markdown 轉爲 html,那麼這個需求就能夠轉爲 html轉Image了html

1. markdown 轉 html

能夠參看以前的博文《Java 實現 markdown轉Html》java

2. html 轉 圖片

主要的核心問題就在這裏了,如何實現html轉圖片?git

  • 直接實現html轉圖片的包沒怎麼見,看到一個 html2image, 還不太好用
  • 在 AWT or Swing 的Panel上顯示網頁,在把Panel輸出爲 image 文件
  • 使用js相關技術實現轉換

本篇博文具體實現以 html2image 的實現邏輯做爲參考,而後定製實現一把(後面有機會寫一篇利用js來實現html轉圖片的博文)github

html2image 的實現原理

html2image 基本上沒啥維護了,內部主要是利用了 xhtmlrender 實現html渲染爲圖片markdown

Graphics2DRenderer renderer = new Graphics2DRenderer();
// 設置渲染內容
renderer.setDocument(document, document.getDocumentURI());

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

// 內容渲染
renderer.render(graphics2D);

說明

  1. 爲何並不直接使用 java-html2image ?
  • 由於有些定製的場景支持得不太友好,加上源碼也比較簡單,因此乾脆站在前人的基礎上進行拓展
  1. 設計目標(這裏指html轉圖片的功能)
  • 生成圖片的寬可指定
  • 支持對線上網頁進行轉圖片
  • 支持對html中指定的區域進行轉換
  • css樣式渲染支持

實現

本篇先會先實現一個基本的功能,即讀去markdown文檔, 並轉爲一張圖片app

1. markdown 轉 html 封裝

利用以前封裝的 MarkDown2HtmlWrapper 工具類dom

具體實現邏輯參考項目工程,和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

我的博客:一灰的我的博客

公衆號獲取更多:

我的信息

參考博文

相關文章
相關標籤/搜索