Category Archives: 未分類

2024-09-17 ,

org-modeでエクスポートしたHTMLから図番を消す方法

と言うわけで昨日の続きなのですが、captionから図番を消す方法について。

昨日紹介した "#+CUSTOM_TRANSLATION: "Figure %d:" "" という指定で図番を消すのは、指定方法が間接的すぎてイマイチだなという話でした。もっと直接的に「図番を消せ」と指定したい所です。

なので今度は #+OPTIONS: fignum:nil という指定で消せるようにしてみます。

#+OPTIONS: fignum:nil

これは何かの写真です。

#+CAPTION: 何かの写真
[[file:example-1.jpg]]

これは別の写真です。

#+CAPTION: 別の写真
[[file:example-2.jpg]]

といってもやることは結局翻訳を書き替えるだけです。

そもそも図番を消すのになぜ翻訳を書き替えているのかというと、ox-html.elでこの図番の文字列を生成している部分が非常に入り組んだ場所にあって、advice等で動作を修正しづらいからという理由があります。

;; ox-html.elより
(defun org-html-paragraph (paragraph contents info)
  "Transcode a PARAGRAPH element from Org to HTML.
CONTENTS is the contents of the paragraph, as a string.  INFO is
the plist used as a communication channel."
  (let* ((parent (org-element-parent paragraph))
         (parent-type (org-element-type parent))
         (style '((footnote-definition " class=\"footpara\"")
                  (org-data " class=\"footpara\"")))
         (attributes (org-html--make-attribute-string
                      (org-export-read-attribute :attr_html paragraph)))
         (extra (or (cadr (assq parent-type style)) "")))
    (cond
     ((and (eq parent-type 'item)
           (not (org-export-get-previous-element paragraph info))
           (let ((followers (org-export-get-next-element paragraph info 2)))
             (and (not (cdr followers))
                  (org-element-type-p (car followers) '(nil plain-list)))))
      ;; First paragraph in an item has no tag if it is alone or
      ;; followed, at most, by a sub-list.
      contents)
     ((org-html-standalone-image-p paragraph info)
      ;; Standalone image.
      (let ((caption
             (let ((raw (org-export-data
                         (org-export-get-caption paragraph) info))
                   (org-html-standalone-image-predicate
                    #'org-html--has-caption-p))
               (if (not (org-string-nw-p raw)) raw
                 (concat "<span class=\"figure-number\">"
                         (format (org-html--translate "Figure %d:" info) ;;★★ここ!!★★ (org-html--translateはorg-export-translateを呼んでるだけです)
                                 (org-export-get-ordinal
                                  (org-element-map paragraph 'link
                                    #'identity info t)
                                  info nil #'org-html-standalone-image-p))
                         " </span>"
                         raw))))
            (label (org-html--reference paragraph info)))
        (org-html--wrap-image contents info caption label)))
     ;; Regular paragraph.
     (t (format "<p%s%s>\n%s</p>"
                (if (org-string-nw-p attributes)
                    (concat " " attributes) "")
                extra contents)))))

paragraphのtranscodeを行う関数の奥深くに埋め込まれてしまっているんですね。もっと関数を細かく分けようよ……などと言っても仕方がありません(Emacs Lispでは良くあることです)。なのでそこから呼び出されているorg-html--translate(org-export-translate)の動作を変えることを考えたわけです。

最初は次のようにしてうまく行くことを確かめました。

(defun my-org-html--translate:no-figure-number (s info)
  (when (and (stringp s) (string= s "Figure %d:"))
    ""))
(advice-add 'org-html--translate :before-until ;;←nilを返したら元の関数を呼ぶ指定
            'my-org-html--translate:no-figure-number)

これだと常に図番が消えてしまうので、何か切り替える方法が必要です。必要かどうかは文書によって変わるのでバッファ内オプションで指定出来るのが望ましいです。

それならいっそのこと翻訳全般をバッファ内オプションでカスタマイズ出来るようにしてはどうか? と思い作成したのが昨日のコードでした。まぁ、ちょっとやり過ぎだったみたいです。

なので昨日のコードをベースにして、オプションの指定方法とその反映部分を修正してみましょう。

まずはエクスポートオプション fignum を追加します。全てのバックエンドに共通するオプションはorg-export-options-alist変数に格納されています。その定義は次のようになっています。

;; ox.elより
(defconst org-export-options-alist
  '((:title "TITLE" nil nil parse)
    (:date "DATE" nil nil parse)
    (:author "AUTHOR" nil user-full-name parse)
    ...
    (:creator "CREATOR" nil org-export-creator-string)
    (:headline-levels nil "H" org-export-headline-levels)
    (:preserve-breaks nil "\\n" org-export-preserve-breaks)
    (:section-numbers nil "num" org-export-with-section-numbers)
    ...

ここに登録しておくと、エクスポート時に自動的にplistの形で全オプションの値を集めてくれます。alistのキー(:title等)は後からplistのキーとして使うキーワードです。2番目の文字列は #+TITLE: のような形のオプションで使います。3番目の文字列は #+OPTIONS: H:5 のような形のオプションで使います。4番目はデフォルト値、5番目は複数のオプションが指定されたときにどうするかを指定します。

今回は #+OPTIONS: fignum:nil のように指定させたいので (:figure-number nil "fignum" t) のような要素をorg-export-options-alistに追加すれば良いでしょう。

(setf (alist-get :figure-number org-export-options-alist)
      '(nil "fignum" t))

こういう時私はいつもsetfとalist-getを使用しています。なんでalist-setみたいなものが無いんでしょうね?

後は翻訳辞書を書き替えるだけです。

今回私はマニュアルのエクスポートプロセスを見てから少し実験した上で、org-export-filter-options-functionsに登録する関数でその書き替え処理を行うことにしました。理由は、この段階になるとオプションが収集し終わっていること、そしてトランスコードが始まる前であることです。加えて、その後一貫してカレントバッファが一時コピーであることも確認しました(ソースコードを追った上での確認はしていないので、そうならないケースがあったらスミマセン)。

(add-to-list 'org-export-filter-options-functions
             'org-figure-number-filter-options)

(defun org-figure-number-filter-options (options _backend &rest _rest)
  (unless (plist-get options :figure-number)
    (org-figure-number-override-dictionary options))
  options)

翻訳辞書(org-export-dictionary)の一時的な変更はローカル変数化することで実現しています。少し実験した限り、エクスポート処理中は元のorg-modeバッファをコピーした一時バッファが常にカレントバッファになっているようだったので、それで十分かなと思いました(間違っていたらスミマセン)。

(defun org-figure-number-override-dictionary (options)
  (setq-local
   org-export-dictionary
   (nconc
    (org-figure-number-make-dictionary
     ;; #+LANGUAGE:の指定がある場合にも対応。
     (or (plist-get options :language) org-export-default-language))
    org-export-dictionary)))

(defun org-figure-number-make-dictionary (lang)
  (list
   (list
    "Figure %d:"
    (list lang :default ""))))

というわけで最終的には次のようになります。

;;; org-figure-number.el --- Remove figure numbers   -*- lexical-binding: t; -*-

;; init.el:
;; (with-eval-after-load "ox" (require 'org-figure-number))

(require 'cl-lib)
(require 'ox)

(defun org-figure-number-make-dictionary (lang)
  (list
   (list
    "Figure %d:"
    (list lang :default ""))))

(defun org-figure-number-override-dictionary (options)
  (setq-local
   org-export-dictionary
   (nconc
    (org-figure-number-make-dictionary
     (or (plist-get options :language) org-export-default-language))
    org-export-dictionary)))

(defun org-figure-number-filter-options (options _backend &rest _rest)
  (unless (plist-get options :figure-number)
    (org-figure-number-override-dictionary options))
  options)

(defun org-figure-number-setup ()
  (setf (alist-get :figure-number org-export-options-alist)
        '(nil "fignum" t))
  (add-to-list 'org-export-filter-options-functions
               'org-figure-number-filter-options))

(org-figure-number-setup)

(provide 'org-figure-number)

と、ここまで書いてふと気がついたのですが、他のバックエンドではどうなっているのかな? と。……ox-latex.elなんかだと図番はまた別の方法で生成されているみたいですね。そういえばTeXって処理系が連番振るものでしたね……。

というわけで、以上はHTMLでエクスポートする際の話でした。

2024-09-16 ,

org-modeでエクスポート時の翻訳をバッファ内オプションで変更する

「エクスポート時の翻訳ってなんじゃ?」とお思いの方もいると思いますが、org-modeには人間が読むための短い文字列を英語以外の言語へ翻訳するための仕組みがあります。詳しくはorg-export-dictionary変数を見るのが手っ取り早いと思います。 "Author""著者""Date""日付""Figure %d:""図%d: ""Listing""ソースコード" 等々、色々定義されています。そしてそういった訳がイマイチしっくりこないという事はありませんか? それも文書によって適したものは変わってくることもあります。そこで今回はこの翻訳をバッファ内のオプションでカスタマイズする方法を用意してみました。

前準備:

  1. 末尾掲載のorg-custom-translation.elをload-pathが通っているところに配置。
  2. init.elに次のコードを追加。
(with-eval-after-load "ox"
  (require 'org-custom-translation))

使い方:

単純に置き換える例。

#+TITLE: むかしのはなし
#+AUTHOR: おおむかしのかたりべ
#+CUSTOM_TRANSLATION: ja Author さくしゃ
#+CUSTOM_TRANSLATION: ja Created つくったにちじ

むかしむかしあるところにおじいさんとおばあさんがいました。

めでたしめでたし。

図番を消す例。次のようにすれば「図1:」のような番号を消すことも出来ます。

#+CUSTOM_TRANSLATION: "Figure %d:" ""

これは何かの写真です。

#+CAPTION: 何かの写真
[[file:example-1.jpg]]

これは別の写真です。

#+CAPTION: 別の写真
[[file:example-2.jpg]]

いや、本当はこの図番を消すために作ったのですが、いざ作り終えてみると正直この方法で消すのはイマイチかなぁ、と。もしorg-modeの更新で "Figure %d:" の部分が変わってしまったら効果が無くなってしまいますからね。例えば ":" の部分は別途付け加えるようになるとか。

なので後でもうちょっと違うやり方を考えてみようと思いますが、せっかく作ったのでここに残しておきます。

;;; org-custom-translation.el --- Customize export translations -*- lexical-binding: t; -*-

;; Copyright (C) 2024 AKIYAMA Kouhei

;; Author: AKIYAMA Kouhei <misohena@gmail.com>
;; Keywords: 

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

;; This program 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 General Public License for more details.

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

;;; Commentary:

;; Change the translation dictionary used during export from the
;; in-buffer options.

;; Add the export option "#+CUSTOM_TRANSLATION:" to temporarily change
;; org-export-dictionary during export.

;; * Preparation
;; Put the following in your init.el.
;;   (with-eval-after-load "ox"
;;     (require 'org-custom-translation))

;; * Option Syntax
;; #+CUSTOM_TRANSLATION: [<language>] <src> <dst>

;; * Examples
;; Translate to Japanese romanization.
;; #+CUSTOM_TRANSLATION: ja Author Sakusha
;; #+CUSTOM_TRANSLATION: ja Date Hizuke

;; Remove figure number.
;; #+CUSTOM_TRANSLATION: "Figure %d:" ""

;;; Code:

(require 'cl-lib)
(require 'ox)

(defun org-custom-translation-split-option-value (str)
  (cl-loop with index = 0
           while (progn
                   (string-match
                    " *\\(\\(\"\\([^\"]*\\)\"\\)\\|\\([^ \t\"]+\\)\\)\\|"
                    str index)
                   (match-beginning 1))
           collect (or (match-string 3 str)
                       (match-string 4 str))
           do (setq index (match-end 0))))

(defun org-custom-translation-make-dictionary (lines current-language)
  (cl-loop for line in lines
           for values = (org-custom-translation-split-option-value line)
           when (>= (length values) 2)
           collect (let ((lang (if (>= (length values) 3)
                                   (pop values)
                                 current-language))
                         (src (car values))
                         (dst (cadr values)))
                     (list src (list lang :default dst)))))

(defun org-custom-translation-override-dictionary (options)
  (setq-local
   org-export-dictionary
   (append
    (org-custom-translation-make-dictionary
     ;;(cdar (org-collect-keywords '("CUSTOM_TRANSLATION")))
     (when-let ((lines-str (plist-get options :custom-translation)))
       (split-string lines-str "\n"))
     (or (plist-get options :language) org-export-default-language))
    org-export-dictionary)))

(defun org-custom-translation-filter-options (options _backend &rest _rest)
  (org-custom-translation-override-dictionary options)
  options)

(defun org-custom-translation-add-export-option ()
  (setf (alist-get :custom-translation org-export-options-alist)
        '("CUSTOM_TRANSLATION" nil nil newline)))

(defun org-custom-translation-setup ()
  (org-custom-translation-add-export-option)
  (add-to-list 'org-export-filter-options-functions
               'org-custom-translation-filter-options))

(org-custom-translation-setup)

(provide 'org-custom-translation)
;;; org-custom-translation.el ends here

基本的な処理は全て org-export-filter-options-functions を経由して呼び出される org-custom-translation-filter-options で行っています。

org-export-filter-options-functions は本来オプションをフィルタするためのものですが、ここで CUSTOM_TRANSLATION オプションの値に応じて org-export-dictionary を変更してしまいます。ここが呼ばれる時のカレントバッファは元のorg-modeバッファをコピーした一時的なバッファのようなので、ローカル変数として設定してしまいます。後は自然に新しい訳が使われるようになります。

2024-09-13

Emacs 30.0.91を試す(MS-Windows)

たまにはpretestの段階で触ってみる。

Windows版のバイナリは既にある。仕事が速い。

https://alpha.gnu.org/gnu/emacs/pretest/windows/emacs-30/

ネイティブコンパイルの設定は以前と同じでOKだった(一部のdllファイルはすでに含まれていた)。MSYS2は最近pacman -Suyしたので最新のはず(国内のミラーって無くなってたのね)。

lib/gdk-pixbuf-2.0(画像ローダーdll)はコピーしなくてもSVG内のimage要素が表示されるみたい。環境変数PATHをSystem32だけにしたりmsys64ディレクトリを一時的にリネームしたりしても表示されるので、密かに画像ローダーが見つけられてしまっているわけでも無さそう。喜ばしいことではあるけど何でだろう。「librsvg decode image」でGoogle検索したらLibrsvg will use Rust-only image decoders starting on 2.58.0 - Federico's Blogというのが出てきた。ひょっとしてこれのおかげ? 去年の12月の記事だし、emacs-30.0.91.zipに入っているlibrsvgのバージョンも2.58.0になっている。bmpも表示できなくなっているので多分間違いない(対応しているのはJPEG、PNG、GIF、WebPのみ:Do not load images with gdk-pixbuf; use Rust loaders instead (!904) · マージリクエスト · GNOME / librsvg · GitLab)。

.emacs.dを29と分けるため、runemacs.exeへのショートカットに --init-directory= オプションを含めて ~/.emacs.d.30 を指すようにした(私はいつもrunemacs.exeへのショートカットをスタートメニューにemacs-xx.xという名前で入れていて、「Ctrl+ESC em RET」でEmacsを起動している。バージョンアップするときはいつもこのショートカットを入れ替える作業をしている)。29と一緒だとやはりバイトコンパイル済みのelispに問題が生じる。

ソースコード(https://alpha.gnu.org/gnu/emacs/pretest/emacs-30.0.91.tar.xz)をダウンロードしてfind-function-C-source-directoryがそれのsrcディレクトリを指すようにする。

image-diredが色々変更されているのでそれに追従する。私のカスタマイズと衝突しているので修正。まずは29から30へのlisp/imageディレクトリのdiffを取って変更点を理解する。image-dired-insert-thumbnail関数の引数が一つ減っているので、とりあえずそこだけ対応したらエラーは出なくなった。他にも何かあるかもしれない。

w32image-create-thumbnailという関数が追加された。image-diredはサムネイル作成用のプログラム(ImageMagickやGraphicsMagick)が存在しない場合はこの関数を使うようになった。パフォーマンスはどちらが良いのか分からない。0.05秒のタイマーで繰り返しているのは気になる。試しに (setq image-dired-cmd-create-thumbnail-program "hoge") などと存在しないプログラムを指定することで使ってみた。縦横比を無視して正方形のサムネイルが生成されてしまう。ExifのOrientationも考慮されなかった。パフォーマンスはそう大きく変わらないように感じたが、多分サムネイル生成部分以外が遅そうなので後で調べてみる。

(追記:パフォーマンス以前にw32image-create-thumbnailを使ってサムネイルを生成するimage-dired-thumb-queue-run関数の後半はタイマーの使い方が間違っていてサムネイルが全部出来るまで操作不能になってしまう。直るまで使わない方が良い)

Windowsでは、convert.exeがImageMagickのものかSystem32のものかを /? オプションを付けて実際に実行して判別するコードが追加された。これがqueue-runで呼び出されているのはパフォーマンス的に良くないと思う。というかいい加減convertなんてやめてmagickコマンドを使えば良いのに。

サムネイルのファイル名をファイル内容の先頭4096バイトのSHA-1にできる設定が追加されているが、これはどうなんだろう。どうもファイルを移動してもサムネイルが使い回せることが狙いのようだが、先頭4096バイトがたまたま同一な別の画像というのは普通にあり得るのではないだろうか。特にbmpのような圧縮しない形式においては。