org-drawio: 自动转换 drawio 为 svg,并插入到 orgmode buffer

因为经常需要用 drawio 画图,然后导出成 svg 插入到 orgmode。手动操作略嫌繁琐。 本插件可以根据 #+drawio 关键字,提取文件信息,导出并插入图片。

视频演示:在 orgmode 中直接添加 drawio 手稿_哔哩哔哩_bilibili

5 个赞

大佬,请收下我的膝盖

Mac, Emacs 30 , org 9.6.15 配置

(use-package org-drawio
  :load-path "packages/org-drawio/"
  :commands (org-drawio-add
             org-drawio-open)
  :custom ((org-drawio-input-dir (expand-file-name "mindmap" my-galaxy))
           (org-drawio-output-dir (expand-file-name "pictures" my-galaxy))
           (org-drawio-output-page "0")))
#+begin_src org
#+drawio: concrete.drawio
#+end_src

报下面的错误。

Debugger entered--Lisp error: (wrong-type-argument stringp nil)
  directory-name-p(nil)
  file-name-with-extension(nil "drawio")
  (let* ((keyword-plist (org-drawio-keyword-string-to-plist)) (dio-input-dir (or (plist-get keyword-plist :input-dir) org-drawio-input-dir)) (dio-input (file-name-with-extension (plist-get keyword-plist :input) "drawio")) (dio-page (or (plist-get keyword-plist :page) org-drawio-page)) (dio-output-dir (or (plist-get keyword-plist :output-dir) org-drawio-output-dir)) (dio-output (or (plist-get keyword-plist :output) (file-name-with-extension dio-input "svg"))) (dio-output-svg (file-name-with-extension (concat (file-name-sans-extension dio-output) "-" dio-page) "svg")) (dio-output-pdf (file-name-with-extension dio-output-svg "pdf")) (_ (if (not (file-exists-p dio-output-dir)) (progn (make-directory dio-output-dir)))) (script (concat "draw.io -x " dio-input-dir "/" dio-input " -p " dio-page " -o " dio-output-dir "/" dio-output-pdf " >/dev/null 2>&1 && " "pdf2svg " dio-output-dir "/" dio-output-pdf " " dio-output-dir "/" dio-output-svg " >/dev/null 2>&1"))) (if (org-next-line-empty-p) (progn (end-of-line) (insert-char 10)) (while (s-starts-with? "#+" (org-current-line-string (next-line))))) (shell-command script "*org-drawio-out*" "*org-drawio-err*") (if (s-starts-with? "[[" (org-current-line-string)) (progn (kill-whole-line 0))) (insert (concat "[[" dio-output-dir "/" dio-output-svg "]]")) (delete-file (concat dio-output-dir "/" dio-output-pdf) t) (org-display-inline-images))
  (save-excursion (let* ((keyword-plist (org-drawio-keyword-string-to-plist)) (dio-input-dir (or (plist-get keyword-plist :input-dir) org-drawio-input-dir)) (dio-input (file-name-with-extension (plist-get keyword-plist :input) "drawio")) (dio-page (or (plist-get keyword-plist :page) org-drawio-page)) (dio-output-dir (or (plist-get keyword-plist :output-dir) org-drawio-output-dir)) (dio-output (or (plist-get keyword-plist :output) (file-name-with-extension dio-input "svg"))) (dio-output-svg (file-name-with-extension (concat (file-name-sans-extension dio-output) "-" dio-page) "svg")) (dio-output-pdf (file-name-with-extension dio-output-svg "pdf")) (_ (if (not (file-exists-p dio-output-dir)) (progn (make-directory dio-output-dir)))) (script (concat "draw.io -x " dio-input-dir "/" dio-input " -p " dio-page " -o " dio-output-dir "/" dio-output-pdf " >/dev/null 2>&1 && " "pdf2svg " dio-output-dir "/" dio-output-pdf " " dio-output-dir "/" dio-output-svg " >/dev/null 2>&1"))) (if (org-next-line-empty-p) (progn (end-of-line) (insert-char 10)) (while (s-starts-with? "#+" (org-current-line-string (next-line))))) (shell-command script "*org-drawio-out*" "*org-drawio-err*") (if (s-starts-with? "[[" (org-current-line-string)) (progn (kill-whole-line 0))) (insert (concat "[[" dio-output-dir "/" dio-output-svg "]]")) (delete-file (concat dio-output-dir "/" dio-output-pdf) t) (org-display-inline-images)))
  org-drawio-add()
  funcall-interactively(org-drawio-add)
  call-interactively(org-drawio-add record nil)
  command-execute(org-drawio-add record)
  execute-extended-command(nil "org-drawio-add" nil)
  funcall-interactively(execute-extended-command nil "org-drawio-add" nil)
  call-interactively(execute-extended-command nil nil)
  command-execute(execute-extended-command)

不好意思,可能误导了。 这些不需要的: #+begin_src org #+end_src

#+drawio: concrete.drawio

这样就可以了。

还是有报错

Debugger entered--Lisp error: (file-missing "Removing old name" "No such file or directory" "/Users/jousimies/Nextcloud/L.Personal.Galaxy/pictures/concrete-0.pdf")
  system-move-file-to-trash("/Users/jousimies/Nextcloud/L.Personal.Galaxy/pictures/concrete-0.pdf")
  move-file-to-trash("/Users/jousimies/Nextcloud/L.Personal.Galaxy/pictures/concrete-0.pdf")
  delete-file("/Users/jousimies/Nextcloud/L.Personal.Galaxy/pictures/concrete-0.pdf" t)
  (let* ((keyword-plist (org-drawio-keyword-string-to-plist)) (dio-input-dir (or (plist-get keyword-plist :input-dir) org-drawio-input-dir)) (dio-input (file-name-with-extension (plist-get keyword-plist :input) "drawio")) (dio-page (or (plist-get keyword-plist :page) org-drawio-page)) (dio-output-dir (or (plist-get keyword-plist :output-dir) org-drawio-output-dir)) (dio-output (or (plist-get keyword-plist :output) (file-name-with-extension dio-input "svg"))) (dio-output-svg (file-name-with-extension (concat (file-name-sans-extension dio-output) "-" dio-page) "svg")) (dio-output-pdf (file-name-with-extension dio-output-svg "pdf")) (_ (if (not (file-exists-p dio-output-dir)) (progn (make-directory dio-output-dir)))) (script (concat "draw.io -x " dio-input-dir "/" dio-input " -p " dio-page " -o " dio-output-dir "/" dio-output-pdf " >/dev/null 2>&1 && " "pdf2svg " dio-output-dir "/" dio-output-pdf " " dio-output-dir "/" dio-output-svg " >/dev/null 2>&1"))) (if (org-next-line-empty-p) (progn (end-of-line) (insert-char 10)) (while (s-starts-with? "#+" (org-current-line-string (next-line))))) (shell-command script "*org-drawio-out*" "*org-drawio-err*") (if (s-starts-with? "[[" (org-current-line-string)) (progn (kill-whole-line 0))) (insert (concat "[[" dio-output-dir "/" dio-output-svg "]]")) (delete-file (concat dio-output-dir "/" dio-output-pdf) t) (org-display-inline-images))
  (save-excursion (let* ((keyword-plist (org-drawio-keyword-string-to-plist)) (dio-input-dir (or (plist-get keyword-plist :input-dir) org-drawio-input-dir)) (dio-input (file-name-with-extension (plist-get keyword-plist :input) "drawio")) (dio-page (or (plist-get keyword-plist :page) org-drawio-page)) (dio-output-dir (or (plist-get keyword-plist :output-dir) org-drawio-output-dir)) (dio-output (or (plist-get keyword-plist :output) (file-name-with-extension dio-input "svg"))) (dio-output-svg (file-name-with-extension (concat (file-name-sans-extension dio-output) "-" dio-page) "svg")) (dio-output-pdf (file-name-with-extension dio-output-svg "pdf")) (_ (if (not (file-exists-p dio-output-dir)) (progn (make-directory dio-output-dir)))) (script (concat "draw.io -x " dio-input-dir "/" dio-input " -p " dio-page " -o " dio-output-dir "/" dio-output-pdf " >/dev/null 2>&1 && " "pdf2svg " dio-output-dir "/" dio-output-pdf " " dio-output-dir "/" dio-output-svg " >/dev/null 2>&1"))) (if (org-next-line-empty-p) (progn (end-of-line) (insert-char 10)) (while (s-starts-with? "#+" (org-current-line-string (next-line))))) (shell-command script "*org-drawio-out*" "*org-drawio-err*") (if (s-starts-with? "[[" (org-current-line-string)) (progn (kill-whole-line 0))) (insert (concat "[[" dio-output-dir "/" dio-output-svg "]]")) (delete-file (concat dio-output-dir "/" dio-output-pdf) t) (org-display-inline-images)))
  org-drawio-add()
  funcall-interactively(org-drawio-add)
  call-interactively(org-drawio-add record nil)
  command-execute(org-drawio-add record)
  execute-extended-command(nil "org-drawio-add" nil)
  funcall-interactively(execute-extended-command nil "org-drawio-add" nil)
  call-interactively(execute-extended-command nil nil)
  command-execute(execute-extended-command)

#+drawio: concrete.drawio
[[/Users/jousimies/Nextcloud/L.Personal.Galaxy/pictures/concrete-0.svg]]

有生成上面的链接,但是没有图。

检查下PATH中有没有 draw.io 和 pdf2svg 没有macOS,需要大家帮忙调试改进。

我在 macOS 上也试用了一下,发现两个问题:

  1. 生成的图片文件链接没有以 file: 开头,导致无法预览。手动改成 [[file:output.svg]] 后就可以预览了;
  2. 生成的图片希望可以做一下裁剪。下图是同一个 .drawio 文件用 org-imagine 生成(上)和用 org-drawio 生成(下)的效果对比。可以看到 org-drawio 生成的图片包含大量空白区域。

自己的 Elisp 水平实在有限,短期内可能无法直接贡献代码,只能提提问题。望见谅哈~

谢谢。我添加个file: 剪裁可以设定参数。但是,我想要固定大小的图片。我可以把剪裁做成参数。

(use-package org-drawio
  :load-path "~/.emacs.d/site-lisp/org-drawio"
  :commands (org-drawio-add
             org-drawio-open)
  :custom ((org-drawio-input-dir "./draws")
           (org-drawio-output-dir "./images")
           (org-drawio-output-page "0")
           ;; set to t, if you want to crop the image.
           (org-drawio-crop t)))

这种情况,我会去调整drawio的画布大小。

最好设置两个变量,让用户自己设置 command line 的路径,比依靠 PATH 读取要靠谱。

(defcustom pdf2vsg-cli ""
  "Define pdf2svg command line path."
  :type 'string)

(defcustom drawio-cli ""
  "Define draw.io command line path."
  :type 'string)

(concat drawio-cli " -x " dio-input-dir "/" dio-input " -p " dio-page
                           " -o " dio-output-dir "/" dio-output-pdf " >/dev/null 2>&1 && "
                           pdf2vsg-cli " " dio-output-dir "/" dio-output-pdf " "
                           dio-output-dir "/" dio-output-svg " >/dev/null 2>&1")

我自己改了下,供参考。

此外,用 shell-command 会卡住 Emacs,如果能改成 async 的就好了。

(async-start
       `(lambda ()
          (shell-command ,script "*org-drawio-out*" "*org-drawio-err*"))
       (lambda (_)
		 (message "Async execution completed.")))

执行完毕后的操作得改改。

你代码中用到了 s.el 中的函数,但是没有 require。

会报错,最好是 require 一下。

我记得 drawio 直接就可以导出成 svg,导出的结果会自动裁剪(但我是在 Ubuntu 里使用的,所以不确定 macos或其他系统里 drawio 是否需要先导出 pdf 再转 svg)

windows 上有点麻烦,需要指定 draw.io.exe 中的 .exe 后缀才找得到……

去掉 s.el 的依赖了。

可以直接转 svg,但是转出来的 svg 格式版本较高,emacs 里显示会出错,所以先转成 pdf 再转 svg,这样显示 OK。 等以后 emacs 支持更高版本的 svg 的时候,可以去掉中间 pdf 的过程。

会显示这个,图画里面的字体格式有时候会显示不出来。