WeasyPrintでhtmlをPDFに変換するEmacs用の拡張を実装した

WeasyPrintを用いていろいろと作業する機会があった。そこでWeasyPrint関連の作業をEmacs上でしやすくする試みを行う。

WeasyPrint

WeasyPrintはHTML、CSS、画像といったWebの技術でレアウトし、それらを元にしてPDFを生成する。 Pythonで実装されておりインターフェースとしてPython用のAPIとCLIツールとしてのweasyprintコマンドを提供している。

デモ

WeasyPrintで変換可能なファイルの状態で M-x weasyprint RET を実行すると、 PDFが生成され結果がEmacsのバッファに表示される。

https://res.cloudinary.com/symdon/image/upload/v1654273623/blog.symdon.info/1654271418/demo_lq834t.gif

仕組み

WeasyPrintは、PythonのWebシステムに組み込む場合、Python用のAPIを用いることが多いだろう。 今回はEmacsに組み込むため、weasyprintコマンドを利用した。 weasyprint 関数は make-process を用いて weasyprint コマンドを呼び出す。 weasyprint コマンドは記述されたファイルに従いPDFを出力する。 PDFの出力先は標準出力となっており、その出力を出力用バッファに書き出す。

https://res.cloudinary.com/symdon/image/upload/v1654274790/blog.symdon.info/1654271418/diag_h8gmlb.png

実装

weasyprint.el

;;; weasyprint for Emacs --- WeasyPrint for Emacs  -*- lexical-binding: t -*-

;; Copyright (C) 2022 TakesxiSximada

;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Version: 1
;; Package-Version: 20220603.0000
;; Package-Requires: ((emacs "27.1"))
;; Date: 2022-05-29

;; This file is part of weasyprint for Emacs.

;; weasyprint for Emacs is free software: you can redistribute it and/or
;; modify it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.

;; weasyprint for Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Affero General Public License for more details.

;; You should have received a copy of the GNU Affero General Public
;; License along with this program.  If not, see
;; <https://www.gnu.org/licenses/>.

;;; Code:

(defgroup weasyprint nil
  "WeasyPRint interface for Emacs."
  :prefix "weasyprint-"
  :group 'tools
  :link '(url-link :tag "Source" "https://blog.symdon.info/emacs/1654271418/weasyprint.el"))

(defcustom weasyprint-executable "weasyprint"
  "Executables of weasyprint command."
  :type 'string)

(defcustom weasyprint-process-name "WeasyPrint"
  "Name of weasyprint subprocess."
  :type 'string)

(defcustom weasyprint-output-buffer-name "*WeasyPrint Output*"
  "Output buffer name of weasyprint subprocess."
  :type 'string)

(defcustom weasyprint-error-buffer-name "*WeasyPrint Error*"
  "Error buffer name of weasyprint subprocess."
  :type 'string)

(defcustom weasyprint-styleseets '("main.css" "ext.css")
  "Output buffer name of weasyprint subprocess."
  :type 'list)

(defcustom weasyprint-content-file "index.html"
  "Output buffer name of weasyprint subprocess."
  :type 'string)

(defvar weasyprint-after-build-hook nil
  "Call after build for weasyprint.")

(defcustom weasyprint-content-file "index.html"
  "Output buffer name of weasyprint subprocess."
  :type 'string)

(defun weasyprint-generate-command ()
  `(,weasyprint-executable
    "--encoding" "UTF-8"
    "--media-type" "page"
    "--base-url" ,default-directory
    ,@(seq-reduce (lambda (options file) (append options  `("--stylesheet" ,file)))  weasyprint-styleseets nil)
    ,weasyprint-content-file
    "-"
    ))


;;;###autoload
(defun weasyprint-open-pdf-buffer ()
  (interactive)
  (with-current-buffer weasyprint-output-buffer-name
    (setq-local buffer-file-coding-system 'binary)
    (doc-view-mode)
    (display-buffer-in-side-window (current-buffer) nil)))


;;;###autoload
(defun weasyprint ()
  (interactive)

  (when (get-buffer weasyprint-output-buffer-name)
    (kill-buffer weasyprint-output-buffer-name))

  (make-process :name weasyprint-process-name
		:buffer (get-buffer-create weasyprint-output-buffer-name)
		:command (weasyprint-generate-command)
		:coding 'utf-8-unix
		:stderr (get-buffer-create weasyprint-error-buffer-name)
		:connection-type 'pipe
		:sentinel (lambda (process event)
			    (when (string-equal event "finished\n")
			      (run-hooks 'weasyprint-after-build-hook)))
		))

(add-hook 'weasyprint-after-build-hook 'weasyprint-open-pdf-buffer)

(provide 'weasyprint)
;;; weasyprint.el ends here