Jekyll2021-10-02T16:19:31+09:00https://chirimenmonster.github.io/feed.xmlめもらんだむこれは単なる個人的なおぼえがきです。あってるかもしれないしまちがっているかもしれない。ほかのだれかの役にたつことがあったとしてもきっとそれは偶然です。
{"twitter"=>"chirimenspiral"}Django Rest Framework のテストでハマったこと (4)2021-10-02T16:00:00+09:002021-10-02T16:00:00+09:00https://chirimenmonster.github.io/2021/10/02/django-test-migrate<p>Django で既存データベースから inspectdb で作成した
models.my は <code class="language-plaintext highlighter-rouge">managed = False</code> となっている。
そのままだと test を実行したときに、
テスト用データベースにモデルに対応したテーブルが作成されない。</p>
<h2 id="inspectdb-による既存データベースの利用">inspectdb による既存データベースの利用</h2>
<p>既存のデータベースを Django で使用するとき、
データベースに対応したモデル定義を
<code class="language-plaintext highlighter-rouge">python managed.py inspectdb</code>
で取得することができる
(<a href="https://docs.djangoproject.com/en/3.2/howto/legacy-databases/">Integrating Django with a legacy database</a>,
<a href="https://qiita.com/okoppe8/items/82a9cc2bb99de05f8dbb">[Django]既存のデータベースを利用する方法</a>)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python managed inspectdb > models.py
</code></pre></div></div>
<p>このとき取得したモデル定義は、
<code class="language-plaintext highlighter-rouge">managed = False</code>
となっている。
既存データベースだから
Django
側からテーブル定義を変更しない、
ということでこういう設定なのだろう。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">managed</span> <span class="o">=</span> <span class="bp">False</span>
</code></pre></div></div>
<h2 id="テスト実行時のエラー-table-doesnt-exist">テスト実行時のエラー (Table doesn’t exist)</h2>
<p>サーバは特に支障なく動いていたのだが、
テストを実行する習慣をつけようとテストコードを書いて
<code class="language-plaintext highlighter-rouge">python manage.py test</code>
を実行すると、
“Table ‘test_XXX.XXX’ doesn’t exitst”
のエラーが。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>django.db.utils.ProgrammingError: (1146, "Table 'test_XXX.XXX' doesn't exist")
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">--keepdb</code> でデータベースの内容を確認すると、
モデル定義に対応したテーブルが作成されていない。</p>
<p>そもそも <code class="language-plaintext highlighter-rouge">migrations</code> が空なので、作成されていないのだ。</p>
<h2 id="makemigration-本番環境では不要-を実行">makemigration (本番環境では不要) を実行</h2>
<p><code class="language-plaintext highlighter-rouge">python manage.py makemigrations</code> を実行して、
<code class="language-plaintext highlighter-rouge">migrations</code> にファイル <code class="language-plaintext highlighter-rouge">0001_initial.py</code> ができていることを確認してから、
再度 <code class="language-plaintext highlighter-rouge">python manage.py test</code>。</p>
<p>“Table ‘test_XXX.XXX’ doesn’t exitst”</p>
<h2 id="モデル定義を-managed--true-にしてから-makemigration">モデル定義を managed = True にしてから makemigration</h2>
<p>これは
models.py
で
<code class="language-plaintext highlighter-rouge">managed = False</code>
のままになっていたことを忘れていたせい。</p>
<p><code class="language-plaintext highlighter-rouge">managed = True</code> に書き換えて <code class="language-plaintext highlighter-rouge">python manage.py makemigrations</code> 実行。
今度は <code class="language-plaintext highlighter-rouge">migrations</code> にファイル <code class="language-plaintext highlighter-rouge">0002_auto_XXXXXXXX_XXXX.py</code> が作成された。</p>
<p>再々度 <code class="language-plaintext highlighter-rouge">python manage.py test</code>。</p>
<p>“Table ‘test_XXX.XXX’ doesn’t exitst”。</p>
<p>やはりテーブルは作成されていない。</p>
<h2 id="解決-migrations-のファイルをクリアしてから-makemigration-をやり直し">[解決] migrations のファイルをクリアしてから makemigration をやり直し</h2>
<p>よくわからないが初回のマイグレーションファイル
<code class="language-plaintext highlighter-rouge">0001_initial.py</code>
で
<code class="language-plaintext highlighter-rouge">managed: False</code>
に設定されたモデルは、
後続のマイグレーションファイルで
<code class="language-plaintext highlighter-rouge">managed: True</code>
となっても無視されてしまうようだ。</p>
<p>マイグレーションファイルをすべて消去して、
<code class="language-plaintext highlighter-rouge">makemgigrations</code>
をやり直すとテスト実行時にテーブルが作成されるようになった。</p>
<h2 id="その他">その他</h2>
<p>かつて
<a href="https://stackoverflow.com/questions/7020966/how-to-create-table-during-django-tests-with-managed-false/7035002#7035002">DjangoTestSuiteRunner のサブクラスでテスト時に managed の設定を書き換える方法</a>
が提案されていたようだが、
<a href="https://stackoverflow.com/questions/36986369/testing-django-application-with-several-legacy-databases">後継の DiscoverRunner でやってみたけど機能しない</a>
ようである。</p>
<p><a href="https://www.ytyng.com/blog/django-unmanaged-model-unit-test/">モデルの _meta.managed を設定する方法</a>
も試してみたけど機能しなかった。</p>ChirimenDjango で既存データベースから inspectdb で作成した models.my は managed = False となっている。 そのままだと test を実行したときに、 テスト用データベースにモデルに対応したテーブルが作成されない。Django Rest Framework のテストでハマったこと (3)2021-10-01T22:00:00+09:002021-10-01T22:00:00+09:00https://chirimenmonster.github.io/2021/10/01/django-test-factoryboy-exclude<p>factory_boy の Faker() で、
取得した値を加工してから使用する話。</p>
<h2 id="faker-で取得した値の加工">Faker() で取得した値の加工</h2>
<h3 id="fakeraddress-で住所と建物名を取得する">Faker(‘address’) で住所と建物名を取得する</h3>
<p><code class="language-plaintext highlighter-rouge">Faker('address')</code> で住所を取得すると、
<code class="language-plaintext highlighter-rouge">'大阪府富津市細野33丁目16番13号 皇居外苑シャルム938'</code>
のように、
住所と建物名が空白で連結された文字列を取得できる
(建物名が定義されない場合もある)。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AddressFactory</span><span class="p">(</span><span class="n">factory</span><span class="p">.</span><span class="n">django</span><span class="p">.</span><span class="n">DjangoModelFactory</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">Address</span>
<span class="n">address</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'address'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
<span class="n">building</span> <span class="o">=</span> <span class="s">''</span> <span class="c1"># ← ここに建物名を設定したい
</span></code></pre></div></div>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># address のインスタンスを取得
</span><span class="n">address</span> <span class="o">=</span> <span class="n">AddressFactory</span><span class="p">()</span>
<span class="n">address</span><span class="p">.</span><span class="n">address</span> <span class="c1"># '大阪府富津市細野33丁目16番13号 皇居外苑シャルム938' ← 建物名まで含まれる
</span><span class="n">address</span><span class="p">.</span><span class="n">building</span> <span class="c1"># '' ← 設定していないので空文字列のまま
</span></code></pre></div></div>
<p>これを、
‘大阪府富津市細野33丁目16番13号`
と
‘皇居外苑シャルム938’
のように、
住所と建物名を分離したレコードとして設定したい。</p>
<h3 id="facker-の返す値は直接加工できない">Facker() の返す値は直接加工できない</h3>
<p>気分としては
<code class="language-plaintext highlighter-rouge">address, building = factory.Faker('address').split()</code>
というような感じで設定したいのだけれども、
<code class="language-plaintext highlighter-rouge">factory.Facker()</code> が返すのは文字列ではないのでエラーになる。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># factory.Facker() が返すのは文字列でないのでエラーになる
</span> <span class="n">address</span><span class="p">,</span> <span class="n">building</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'address'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">).</span><span class="n">split</span><span class="p">()</span>
</code></pre></div></div>
<h3 id="lazyattribute-の利用">LazyAttribute() の利用</h3>
<p>こういうときは
<code class="language-plaintext highlighter-rouge">LazyAttribute()</code>
を使用する。
引数に指定した lambda 関数に
インスタンス化されたオブジェクトが渡されるので、
別の変数、例えば <code class="language-plaintext highlighter-rouge">fulladdress</code> に
<code class="language-plaintext highlighter-rouge">Faker('address')</code> を設定するようにしておいて、
<code class="language-plaintext highlighter-rouge">LazyAttribute()</code> で値を取り出して加工した結果を目的の変数に格納する。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># 一旦別の変数で受けてから LazyAttr で加工する
</span> <span class="n">fulladdress</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'address'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
<span class="n">address</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">.</span><span class="n">fulladdress</span><span class="p">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">building</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">fulladdress</span><span class="p">.</span><span class="n">split</span><span class="p">()</span> <span class="o">+</span> <span class="p">[</span><span class="s">''</span><span class="p">])[</span><span class="mi">1</span><span class="p">])</span>
</code></pre></div></div>
<h3 id="exclude-で仮の変数を除外する">exclude で仮の変数を除外する</h3>
<p>このままだと、
追加した <code class="language-plaintext highlighter-rouge">fullladderss</code> は
<code class="language-plaintext highlighter-rouge">models.Address</code> で定義されていない変数なのでエラーになってしまう。
除外するには、
クラス <code class="language-plaintext highlighter-rouge">Meta</code> の <code class="language-plaintext highlighter-rouge">exclude</code> に <code class="language-plaintext highlighter-rouge">fulladdres</code> を指定すればよい。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AddressFactory</span><span class="p">(</span><span class="n">factory</span><span class="p">.</span><span class="n">django</span><span class="p">.</span><span class="n">DjangoModelFactory</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">Address</span>
<span class="n">exclude</span> <span class="o">=</span> <span class="p">(</span><span class="s">'fulladdress'</span><span class="p">,)</span> <span class="c1"># exclude で models.Address に含まれない変数を指定
</span> <span class="n">fulladdress</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'address'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
<span class="n">address</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">.</span><span class="n">fulladdress</span><span class="p">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">building</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">fulladdress</span><span class="p">.</span><span class="n">split</span><span class="p">()</span> <span class="o">+</span> <span class="p">[</span><span class="s">''</span><span class="p">])[</span><span class="mi">1</span><span class="p">])</span>
</code></pre></div></div>
<h2 id="名前の取得例">名前の取得例</h2>
<p>同様にして、
名前とフリガナのペアを取得し、
それぞれを別の変数に格納できる。
そのようにすると、漢字と読みが対応したレコードを作成できる
(個別に <code class="language-plaintext highlighter-rouge">factory.Faker()</code> で取得すると、漢字と読みの対応関係が崩れてしまう)。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PersonFactory</span><span class="p">(</span><span class="n">factory</span><span class="p">.</span><span class="n">django</span><span class="p">.</span><span class="n">DjangoModelFactory</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">Person</span>
<span class="n">exclude</span> <span class="o">=</span> <span class="p">(</span><span class="s">'firstname_pair'</span><span class="p">,</span> <span class="s">'lastname_pair'</span><span class="p">)</span>
<span class="n">firstname_pair</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'first_name_pair'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
<span class="n">lastname_pair</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'last_name_pair'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
<span class="n">lastname</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">:</span><span class="n">lastname_pair</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">lastname_kana</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">:</span><span class="n">lastname_pair</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">firstname</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">:</span><span class="n">firstname_pair</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">firstname_kana</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">LazyAttribute</span><span class="p">(</span><span class="k">lambda</span> <span class="n">o</span><span class="p">:</span> <span class="n">o</span><span class="p">:</span><span class="n">firstname_pair</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</code></pre></div></div>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">person</span> <span class="o">=</span> <span class="n">PersonFactory</span><span class="p">()</span>
<span class="n">person</span><span class="p">.</span><span class="n">firstname</span> <span class="c1"># '康弘'
</span><span class="n">person</span><span class="p">.</span><span class="n">firstname_kana</span> <span class="c1"># 'ヤスヒロ'
</span><span class="n">person</span><span class="p">.</span><span class="n">lastname</span> <span class="c1"># '近藤'
</span><span class="n">person</span><span class="p">.</span><span class="n">lastname_kana</span> <span class="c1"># 'コンドウ'
</span></code></pre></div></div>Chirimenfactory_boy の Faker() で、 取得した値を加工してから使用する話。Django Rest Framework のテストでハマったこと (2)2021-09-30T22:00:00+09:002021-09-30T22:00:00+09:00https://chirimenmonster.github.io/2021/09/30/django-test-factoryboy-locale<p>Django のテスト用データを作成するのによく用いられる
factory_boy
で locale を指定して日本語圏用のデータを利用する話。</p>
<h2 id="はじめに">はじめに</h2>
<p>Django のテストでテスト用のデータを作成するのに
<a href="https://factoryboy.readthedocs.io/en/stable/">factory_boy</a>
が便利である。</p>
<p>実際のテスト用のダミーデータの作成には
<a href="https://faker.readthedocs.io/en/master/">Faker</a>
が呼び出されるようになっていて、
locale を指定することで、
<a href="https://faker.readthedocs.io/en/master/locales/ja_JP.html">日本語用のデータセット</a>を使用することができる。</p>
<h2 id="引数での-locale-指定">引数での locale 指定</h2>
<p>factory_boy での locale 指定は
<code class="language-plaintext highlighter-rouge">Faker</code> のキーワード引数 <code class="language-plaintext highlighter-rouge">locale</code> で指定する。
次に示すのは公式ドキュメントでの例であるが、
<code class="language-plaintext highlighter-rouge">ja_JP</code> を指定すれば日本人名が得られる。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">UserFactory</span><span class="p">(</span><span class="n">factory</span><span class="p">.</span><span class="n">Factory</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">User</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'name'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'fr_FR'</span><span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">User</code> は Django の model で、別の箇所で定義されているものとする。</p>
<p>ここで <code class="language-plaintext highlighter-rouge">UserFactory</code> をインスタンス化すると、
プロパティ name にフランス locale (fr_FR) の人名がセットされる
(インスタンス化を行うたびに異なる値がセットされる)。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user</span> <span class="o">=</span> <span class="n">UserFactory</span><span class="p">()</span>
<span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="c1"># 'Jean Valjean'
</span></code></pre></div></div>
<p>locale が <code class="language-plaintext highlighter-rouge">ja_JP</code> であれば、
日本語圏の人名がセットされる。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">name</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'name'</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="s">'ja_JP'</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user</span> <span class="o">=</span> <span class="n">UserFactory</span><span class="p">()</span>
<span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="c1"># '前田 舞'
</span></code></pre></div></div>
<h2 id="クラスメソッド-override_default_locale">クラスメソッド override_default_locale</h2>
<p>locale を頻繁に切り替えるようなシステムでなければ、
毎回キーワード引数を指定するのではなく、
デフォルトを <code class="language-plaintext highlighter-rouge">ja_JP</code> に変更したい。</p>
<p>そう思って調べてみると、
<code class="language-plaintext highlighter-rouge">override_default_locale</code>
という、いかにもそれっぽいクラスメソッドが
<code class="language-plaintext highlighter-rouge">factory.Faker</code>
に用意されていた。</p>
<p>が、Factory を定義するユーザーコードで</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">faker</span><span class="p">.</span><span class="n">override_default_locale</span><span class="p">(</span><span class="s">'ja_JP'</span><span class="p">)</span>
</code></pre></div></div>
<p>を実行してもデフォルトロケールの変更は有効にならない。
model がインスタンス化されるところで
<code class="language-plaintext highlighter-rouge">override_default_locale</code>
が有効になっていないと駄目なようだ。</p>
<p>なので、さっきの例だと
<code class="language-plaintext highlighter-rouge">UserFactory()</code>
を実行するモジュールのところで実行してやる必要がある
(公式ドキュメントに記載されている)。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">.</span><span class="n">override_default_locale</span><span class="p">(</span><span class="s">'ja_JP'</span><span class="p">):</span>
<span class="n">UserFactory</span><span class="p">()</span>
</code></pre></div></div>
<p>これだと <code class="language-plaintext highlighter-rouge">UserFactroy</code> の呼び出し側で
factory_boy
をインポートする (<code class="language-plaintext highlighter-rouge">import factory</code>) ことになる、
(locale の設定のためだけにわざわざインポートすることになる)
ので、
コードの構成によってはあまりうれしくない。</p>
<h2 id="クラス変数-_default_locale">クラス変数 _DEFAULT_LOCALE</h2>
<p>どうしても Factory を定義するモジュールでデフォルト設定をしたい場合は、
非公式ではあるが <code class="language-plaintext highlighter-rouge">Faker._DEFAULT_LOCALE</code> を直接設定する方法がある。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">.</span><span class="n">_DEFAULT_LOCALE</span> <span class="o">=</span> <span class="s">'ja_JP'</span>
<span class="k">class</span> <span class="nc">UserFactory</span><span class="p">(</span><span class="n">factory</span><span class="p">.</span><span class="n">Factory</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">User</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">Faker</span><span class="p">(</span><span class="s">'name'</span><span class="p">)</span>
</code></pre></div></div>
<p>このようにすれば、
手軽にデフォルトの locale を変更できる。</p>ChirimenDjango のテスト用データを作成するのによく用いられる factory_boy で locale を指定して日本語圏用のデータを利用する話。Django Rest Framework のテストでハマったこと (1)2021-09-29T21:20:00+09:002021-09-29T21:20:00+09:00https://chirimenmonster.github.io/2021/09/29/django-test-reverse<p>Django Rest Framework のテストコードの例でよく出てくる
<code class="language-plaintext highlighter-rouge">reverse('user-list')</code>
というような記述。
ここに出てくる <code class="language-plaintext highlighter-rouge">user-list</code> の部分って、
何を書いたらいいの? って話。</p>
<h2 id="はじめに">はじめに</h2>
<p>Django Rest Framework でのテストの書き方を調べていたところ、
URL を取得するのに
<code class="language-plaintext highlighter-rouge">reverse('user-list')</code>
というような記述例をよく見かけた。</p>
<p><code class="language-plaintext highlighter-rouge">user</code> はモデルの名前だとして、その後の <code class="language-plaintext highlighter-rouge">-list</code> ってなんだろう?</p>
<p>手元で試すと <code class="language-plaintext highlighter-rouge">user</code> では動かなくて <code class="language-plaintext highlighter-rouge">user-list</code> だと動くのだけれども、
そもそも
<code class="language-plaintext highlighter-rouge">reverse()</code>
に何を指定すべきかよくわからないのだ。</p>
<h2 id="djangourlsreverse-のドキュメント">django.urls.reverse のドキュメント</h2>
<p>公式ドキュメント django.urls の
<a href="https://docs.djangoproject.com/en/3.2/ref/urlresolvers/#reverse">reverse()</a>
を見ると、</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>If you need to use something similar to the url template tag in your code, Django provides the following function:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
</code></pre></div></div>
<p>とあって、引数には
<strong>viewname</strong>
を指定するらしい。
<strong>viewname</strong>
は <code class="language-plaintext highlighter-rouge">path()</code> で指定した名前、
<code class="language-plaintext highlighter-rouge">urls.py</code> の <code class="language-plaintext highlighter-rouge">urlpatterns</code> で指定したやつのようだけれども、
自分のコードだと次のようになっていて、
<strong>viewname</strong> を明示的に指定していない。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">router</span> <span class="o">=</span> <span class="n">routers</span><span class="p">.</span><span class="n">DefaultRouter</span><span class="p">()</span>
<span class="n">router</span><span class="p">.</span><span class="n">register</span><span class="p">(</span><span class="sa">r</span><span class="s">'person'</span><span class="p">,</span> <span class="n">views</span><span class="p">.</span><span class="n">PersonViewSet</span><span class="p">)</span>
<span class="p">(</span><span class="n">略</span><span class="p">)</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s">'addressbook/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="n">urls</span><span class="p">)),</span>
<span class="n">path</span><span class="p">(</span><span class="s">'admin/'</span><span class="p">,</span> <span class="n">admin</span><span class="p">.</span><span class="n">site</span><span class="p">.</span><span class="n">urls</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div></div>
<p>上のコードだと <code class="language-plaintext highlighter-rouge">router.urls</code> をインクルードしているので、
routers (rest_framework.routers) について調べる必要がありそうだ。</p>
<h2 id="rest_frameworkrouters-のドキュメント">rest_framework.routers のドキュメント</h2>
<p>Django REST framework の公式ドキュメント
<a href="https://www.django-rest-framework.org/api-guide/routers/">Routers</a>
を参照すると</p>
<blockquote>
<p>The example above would generate the following URL patterns:</p>
<p>URL pattern: <code class="language-plaintext highlighter-rouge">^users/$</code> Name: <code class="language-plaintext highlighter-rouge">'user-list'</code><br />
URL pattern: <code class="language-plaintext highlighter-rouge">^users/{pk}/$</code> Name: <code class="language-plaintext highlighter-rouge">'user-detail'</code><br />
URL pattern: <code class="language-plaintext highlighter-rouge">^accounts/$</code> Name: <code class="language-plaintext highlighter-rouge">'account-list'</code><br />
URL pattern: <code class="language-plaintext highlighter-rouge">^accounts/{pk}/$</code> Name: <code class="language-plaintext highlighter-rouge">'account-detail'</code></p>
</blockquote>
<p>とあって、
サフィックス
<code class="language-plaintext highlighter-rouge">-list</code>, <code class="language-plaintext highlighter-rouge">-detail</code>
が router によって登録されていたことがわかる。</p>
<p><a href="https://www.django-rest-framework.org/api-guide/routers/#defaultrouter">DefaultRouter</a>
の場合の完全な表は次のようになっていて、
これで <code class="language-plaintext highlighter-rouge">reverse()</code> の引数に何を指定すればいいかがわかるようになった。</p>
<p><img src="/images/2021-09-29-django-rest-defaultrouter.png" alt="DefaultRouter" /></p>
<h2 id="おわりに">おわりに</h2>
<p>上記のことが、
当初調べていてもさっぱり検索に引っかかってこなかった。
探し方が悪いのかもしれないし、
慣れたら当たり前のようになって気にならなくなるのかもしれないけれど、
割と行き詰まった感があったので、
初心を忘れないうちに書き留めておく。</p>ChirimenDjango Rest Framework のテストコードの例でよく出てくる reverse('user-list') というような記述。 ここに出てくる user-list の部分って、 何を書いたらいいの? って話。Cloudflare を DDNS として利用する2021-09-28T21:00:00+09:002021-09-28T21:00:00+09:00https://chirimenmonster.github.io/2021/09/28/ubuntu-ddclient<p>外部から自宅サーバを操作するのには
Cloudflare で DDNS やるのが手っ取り早そうだった。</p>
<h2 id="はじめに">はじめに</h2>
<p>自宅に設置した Ubuntu サーバを外部から ssh できるようにするには
いろいろな方法があると思うのだけれど、</p>
<ul>
<li>グローバルIP (IPv4) は動的に割り当てられる (IPv6 は動的なのか? 要確認)</li>
<li>プロバイダから貸与されてるルータでポートフォワード設定は可能</li>
<li>ドメインは取得していて、ネームサーバを Cloudflare に置いている</li>
<li>外部に (仮想) サーバを借りていて、それなりに融通が利く</li>
</ul>
<p>という環境で、
あまり手間をかけないで実現するには、
ルータで ssh のポートを Ubuntu サーバに転送しておいて、
Cloudflare を DDNS サーバとして運用することで対応するのが簡単そうである。</p>
<p>ポートを公開すると、侵入を試みるアクセスが増えそうだけど、
転送許可するアドレスを、外部のサーバに限定しておけばよいだろう。</p>
<p>DDNS での更新については、
<a href="https://zenn.dev/akaregi/articles/4a0db32a4d40a7">Cloudflare を ddclient で DDNS 化する</a>
という記事を参考にした。</p>
<h2 id="cloudflare-での準備">Cloudflare での準備</h2>
<p>すでに Cloudflare にアカウントを持っていて、
自ドメインの DNS を運用しているという前提。</p>
<p>ddclient での操作には次のものが必要。</p>
<ul>
<li>アカウント名 (メールアドレス)</li>
<li>Global API Key (後述するように Cloudflare から取得)</li>
<li>操作対象のゾーン名 (ドメイン名)</li>
<li>登録対象の A レコード (ダミーの IP アドレスであらかじめ作成しておく)</li>
</ul>
<h3 id="cloudflare-の-global-api-key-を取得">Cloudflare の Global API Key を取得</h3>
<p>Cloudflare の Global API Key を取得しておく。
マイプロフィールの API トークンのページで取得できる。</p>
<p><img src="/images/2021-09-23-ddclient-cloudflare-apikey.png" alt="Cloudflare の Global API Key を取得" /></p>
<h2 id="ddclient-のインストールと設定">ddclient のインストールと設定</h2>
<h3 id="インストール">インストール</h3>
<p>記事
<a href="https://zenn.dev/akaregi/articles/4a0db32a4d40a7">Cloudflare を ddclient で DDNS 化する</a>
によれば
Ubuntu 20.04 LTS (Focal Fossa) の apt で提供されている ddclient 3.8.x では
現在の Cloudflare に対応できず ddclient 3.9.x が必要らしい。</p>
<p>なんてこった。</p>
<p>だが、先の記事では
<a href="https://launchpad.net/ubuntu/+source/ddclient">Launchpad</a>
から
Groovy 用の ddclient 3.9.1 の deb を <code class="language-plaintext highlighter-rouge">dpkg -i</code> でインストールすればよい、とある。
難易度は高くなさそうだ。</p>
<p>Groovy とは Ubuntu 20.10 (Groovy Gorilla) のことだろう。
Launchpad には Ubuntu 21.04 (Hirsute Hippo) 用の 3.9.1-7 と
Ubuntu 20.04 LTS (Focal Fossa) 用の 3.8.3-1.1ubuntu1 が提供されていて
Groovy 用は見当たらない。</p>
<p>Hirsute Hippo 用の
ddclient_3.9.1-7_all.deb
をダウンロードし、
<code class="language-plaintext highlighter-rouge">dpkg -i</code>
でインストールする。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>herobox$ sudo dpkg -i ddclient_3.9.1-7_all.deb
(略)
dpkg: dependency problems prevent configuration of ddclient:
ddclient depends on libdata-validate-ip-perl; however:
Package libdata-validate-ip-perl is not installed.
(略)
</code></pre></div></div>
<p>依存関係でパッケージ
libdata-validate-ip-perl
がないとのエラーが出る。</p>
<p><a href="https://github.com/ddclient/ddclient#ubuntu-style-rc-files-and-daemon-mode">GitHub: ddclient</a>
によれば、
以下のパッケージが必要とあるので、
それらを <code class="language-plaintext highlighter-rouge">apt install</code> でインストールしてから、
再度 ddclient のインストールを <code class="language-plaintext highlighter-rouge">dpkg -i</code> で実行する。</p>
<ul>
<li>libdata-validate-ip-perl</li>
<li>libio-socket-ssl-perl</li>
<li>libjson-pp-perl</li>
<li>libio-socket-inet6-perl (IPv6 を使う場合)</li>
</ul>
<h3 id="インストール時の初期設定">インストール時の初期設定</h3>
<p><code class="language-plaintext highlighter-rouge">dpkg -i</code> で ddclient のインストールを行うと、
続けて次のような設定 UI が表示される。
設定 UI ではすべての情報を入力できず、後でファイルを編集することになるので、
ここでは分かる範囲で入力すればよい。
後でやり直したいときは <code class="language-plaintext highlighter-rouge">dpkg-reconfigure ddclient_3.9.1-7_all.deb</code> とする。</p>
<p>最初の画面で、利用する DDNS サービスを選択する。
表示されている選択肢に cloudflare はないので、
<code class="language-plaintext highlighter-rouge">other</code>
を選ぶと次の画面で DDNS のプロトコルを選択できる。</p>
<p><img src="/images/2021-09-23-ddclient-configure.png" alt="インストール時の設定" /></p>
<p>プロトコルの選択画面で <code class="language-plaintext highlighter-rouge">cloudflare</code> を選択。</p>
<p><img src="/images/2021-09-23-ddclient-configure-protocol.png" alt="プロトコルの選択" /></p>
<p>続いて DDNS サーバーを入力する画面になるが、
先程選択したプロトコル <code class="language-plaintext highlighter-rouge">cloudflare</code> のデフォルトのサーバを使用するので空欄でよい。</p>
<p><img src="/images/2021-09-23-ddclient-configure-server.png" alt="DDNSサーバーの入力" /></p>
<p>以降、次の情報を入力するよう要求される。</p>
<ul>
<li>HTTP プロキシの設定 (使用しない)</li>
<li>ユーザー名 (cloudflare のアカウント名でメールアドレス)</li>
<li>パスワード (取得した Global API Key)</li>
<li>グローバル IP の確認方法 (NAT 経由なので web-based を選択)</li>
<li>IPアドレスの問い合わせ先 (どこでもよいがデフォルトが https://api.ipify.org/)</li>
<li>最短更新間隔 (デフォルトの5分 <code class="language-plaintext highlighter-rouge">5m</code> のままにしておく)</li>
<li>更新対象のホスト名</li>
</ul>
<p>設定が終わると
<code class="language-plaintext highlighter-rouge">/etc/ddclient.conf</code>
はこのようになる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf
protocol=cloudflare \
use=web, web=https://api.ipify.org/ \
login=[メールアドレス] \
password=[パスワード] \
[ホスト名]
</code></pre></div></div>
<h3 id="設定">設定</h3>
<p>ddclient の動作には <code class="language-plaintext highlighter-rouge">zone</code> の設定が必要
(cloudflare の場合)
なので、ファイルを編集して追加する。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf
protocol=cloudflare \
use=web, web=https://api.ipify.org/ \
zone=[ゾーン名] \
login=[メールアドレス] \
password=[パスワード] \
[ホスト名]
</code></pre></div></div>
<h3 id="動作確認">動作確認</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>herobox$ sudo ddclient -v
CONNECT: api.ipify.org
(略)
RECEIVE: xxx.xxx.xxx.xxx (api.ipify.org で確認したグローバル IP)
(略)
INFO: setting IP address to xxx.xxx.xxx.xxx for YYY.example.com (更新対象ホスト名)
UPDATE: updating YYY.example.com
CONNECT: api.cloudflare.com
(略)
SUCCESS: YYY.example.com -- Updated Successfully to xxx.xxx.xxx.xxx
</code></pre></div></div>
<h2 id="運用">運用</h2>
<p>daemon で運用するか、
cron で実行するかは後で考える。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://zenn.dev/akaregi/articles/4a0db32a4d40a7">Cloudflare を ddclient で DDNS 化する</a></li>
</ul>Chirimen外部から自宅サーバを操作するのには Cloudflare で DDNS やるのが手っ取り早そうだった。Ubuntu サーバのインストール2021-09-23T11:00:00+09:002021-09-23T11:00:00+09:00https://chirimenmonster.github.io/2021/09/23/ubuntu-install<p>QNAPでのDocker運用にポートフォワードが安定しないという致命的な弱点があったので、
小型PCを購入してLinuxサーバを運用することにした。</p>
<h2 id="小型pcの購入">小型PCの購入</h2>
<p>Linux サーバをインストールして
モニターレス、キーボードレスで常時稼働させるので、
小型で消費電力が少なく、できれば安い PC が望ましい。</p>
<p>Amazon で CHUWI HeroBox Pro という小型PCが2万5千円程度で売ってたので購入。
memory 8GB、SSD 256GB と、この価格帯にしてはかなりよい感じのスペックである。</p>
<p>ハードのレビューについては
<a href="https://www.thun-techblog.com/index.php/blog/chuwi-herobox-pro-review-celeron-n4500/">こちらの記事</a>
が、充実しているので紹介しておく (購入する前に見ておけばよかった)。</p>
<p>だいたい満足しているが、
ファンの音がちょっと気になるかな (先の記事によればファン制御の機能はないようだ)。</p>
<h3 id="参考">参考</h3>
<ul>
<li><a href="https://www.thun-techblog.com/index.php/blog/chuwi-herobox-pro-review-celeron-n4500/">CHUWI “HeroBox Pro”のレビュー!Jasper Lake世代Celeron搭載でコスパ最高の最新ミニPC!ただし…※</a></li>
</ul>
<h2 id="ubuntu-server-のインストール">Ubuntu server のインストール</h2>
<h3 id="インストールメディア作成とブート">インストールメディア作成とブート</h3>
<p>Ubuntu の ISO イメージ
(ubuntu-20.04.3-live-server-amd64.iso)
をダウンロードして、
手持ちの USB メモリでブートメディアを作成する。
Windows 10 でのブートメディアの作成には
<a href="https://rufus.ie/ja/">rufus</a>
というツールを使用。</p>
<p><img src="/images/2021-09-20-ubuntu-rufus.png" alt="rufusによるブートメディア作成" /></p>
<p>HeroBox Pro は起動時に ESC で BIOS 画面に、F7 で起動メディア選択メニューを出すことができる。
BIOS は AMI だった。
Quiet Boot を Enabled に、
Secure Boot を Disabled にしておく。</p>
<p><img src="/images/2021-09-20-ubuntu-bios-boot1.png" alt="Quiet Boot" />
<img src="/images/2021-09-20-ubuntu-bios-boot2.png" alt="Secure Boot" /></p>
<p>Ubuntu のインストーラを rufus でコピーした USB メモリを HeroBox Pro に挿して起動、
すればよいはずだったのだが、ちょっとハマりポイントがあった。</p>
<p>古い USB メモリを使用したせいか、
HeroBox 全面の USB 3.0 コネクタではブートメディアとして認識されず、
背面の USB 2.0 コネクタに挿さなければいけなかった。
新しめの USB メモリでは前面でちゃんと認識されたので、
どちらが悪いのかわからないが、組み合わせの問題のようだ。</p>
<p>LANケーブルも挿しておく。</p>
<h3 id="デフォルト設定でインストール">デフォルト設定でインストール</h3>
<p>画面の指示に従ってインストール。
ほとんどデフォルトのままで、
設定したのはキーボードの種類と初期アカウントくらい。</p>
<p>インストールが終わったら USB メモリを抜いて再起動。</p>
<h2 id="インストール後の設定">インストール後の設定</h2>
<h3 id="ネットワーク設定の変更">ネットワーク設定の変更</h3>
<p>デフォルト設定でインストールしたので
DHCP を使う設定になっている。</p>
<p>Ubuntu のネットワーク設定は <code class="language-plaintext highlighter-rouge">/etc/netplan</code> 以下にあって、
インストール直後の設定ファイルは
<code class="language-plaintext highlighter-rouge">00-installer-config.yaml</code>
のみで、中身は次のように dhcp4 を使ってインターフェースの設定を行うようになっている。
enp1s0 は HeroBox Pro のネットワークインターフェースの名前。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># This is the network config written by 'subiquity'</span>
<span class="na">network</span><span class="pi">:</span>
<span class="na">ethernets</span><span class="pi">:</span>
<span class="na">enp1s0</span><span class="pi">:</span>
<span class="na">dhcp4</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">2</span>
</code></pre></div></div>
<p>サーバ運用なので IP アドレスは静的に設定したい、
が、その他の設定、デフォルトゲートウェイなど、
は DHCP から設定を持ってきたいところ。</p>
<p>そこで、同じディレクトリに <code class="language-plaintext highlighter-rouge">99-config.yaml</code> という名前で、
次のようなファイルを作成する。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network</span><span class="pi">:</span>
<span class="na">version</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">renderer</span><span class="pi">:</span> <span class="s">networkd</span>
<span class="na">ethernets</span><span class="pi">:</span>
<span class="na">enp1s0</span><span class="pi">:</span>
<span class="na">addresses</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">192.168.0.11/24</span>
<span class="na">nameservers</span><span class="pi">:</span>
<span class="na">search</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">example.com</span>
</code></pre></div></div>
<p>2つの設定ファイルは、辞書順に読み込まれるので、
ネットワークインターフェース enp1s0 に対して
dhcp4 を有効にし、かつ、IP アドレスを静的に指定した状態になる。</p>
<p>ここで使ってるルータの DHCP は DNS の検索パスを提供してくれないので、
nameservers の search についても静的に設定を行っている。</p>
<p>設定は <code class="language-plaintext highlighter-rouge">netplan apply</code> で反映させることができる。
確認して意図通りになっていることを確認したら、
再起動の場合についても確認しておく。</p>
<p>結果は、
静的に指定した IP アドレスと、
DHCP から取得した IP アドレスの両方が設定された状態となった
(DHCP の方には secondary dynamic のフラグがついている)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>herobox$ ip address
(略)
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(略)
inet 192.168.0.11/24 brd 192.168.0.255 scope global enp1s0
valid_lft forever preferred_lft forever
inet 192.168.0.141/24 brd 192.168.0.255 scope global secondary dynamic enp1s0
valid_lft 80995sec preferred_lft 80995sec
(略)
</code></pre></div></div>
<p>不要な IP アドレスが余分に割り当てられた形となってしまったが、
DHCP クライアントとしての一貫性を保つためにはこの形がよいのかな。</p>
<h3 id="ホームディレクトリを-autofs-で-nfs-マウントする">ホームディレクトリを autofs で NFS マウントする</h3>
<p>NAS サーバにホームディレクトリがあるので、
autofs で NFS マウントする設定を行う。</p>
<p>autofs はインストールされていないので、
<code class="language-plaintext highlighter-rouge">apt install</code> で追加する。
NFS クライアントに必要な nfs-common も依存関係でインストールされる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>herobox$ sudo apt install autofs
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">/home</code> にマウントさせるので、
次のような内容で
<code class="language-plaintext highlighter-rouge">/etc/auto.master.d/home.autofs</code>
を作成する。
拡張子が <code class="language-plaintext highlighter-rouge">.autofs</code> であればよいので、
<code class="language-plaintext highlighter-rouge">home.autofs</code> である必要はないのだが、
マウントポイントがわかりやすいようにしておく。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home /etc/auto.home
</code></pre></div></div>
<p>1列目の <code class="language-plaintext highlighter-rouge">/home</code> はマウントポイントで、
2列目の <code class="language-plaintext highlighter-rouge">/etc/auto.home</code> はマップファイル名である。</p>
<p>マップファイル <code class="language-plaintext highlighter-rouge">/etc/auto.home</code> には次のように、
key と option、location を記述する。
key はマウントポイントに対するサブディレクトリ名、
options はマウントコマンドに対するオプション、
locatioon はリモートファイルシステムのパス (NFS の場合) である。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>foo -rw,soft,intr nas.example.com:/homes/foo
</code></pre></div></div>
<p>したがって、この例の場合、
<code class="language-plaintext highlighter-rouge">/home/foo</code> にアクセスした場合に
<code class="language-plaintext highlighter-rouge">mount -t nfs -o rw,soft,intr nas.example.com:/homes/foo</code>
相当が行われることになる。</p>
<p><code class="language-plaintext highlighter-rouge">/etc/init.d/autofs reload</code> で動作確認したら、
再起動でも確認する。</p>
<p>ホームディレクトリには設定済みの <code class="language-plaintext highlighter-rouge">.ssh</code> ディレクトリがあるので、
そのまま SSH 公開鍵認証で接続できるようになった。</p>ChirimenQNAPでのDocker運用にポートフォワードが安定しないという致命的な弱点があったので、 小型PCを購入してLinuxサーバを運用することにした。QNAP の Docker を使ってみる2021-09-16T21:20:00+09:002021-09-16T21:20:00+09:00https://chirimenmonster.github.io/2021/09/16/qnap-docker-vscode<p>QNAP で Docker が使用できるようなので使ってみた。
Docker Hub のイメージも利用できるし、
試してはいないが docker compose も使えるようなので、
思ったより柔軟性の高い運用ができそうだ。</p>
<h2 id="はじめに">はじめに</h2>
<p>古い NetBSD サーバからのデータの移行先として QNAP を設置し、
ファイルサーバ、データベースを移動した。
こうなってくると、常時稼働のサーバとして QNAP を運用したくなってくる。
過度に QNAP に特化した構成にするつもりはなかったのだが、
Docker 上で運用できるなら、ということで、
QNAP での Docker の使用感を試してみた。</p>
<h2 id="qnap-で-docker-を使用する準備">QNAP で Docker を使用する準備</h2>
<h3 id="container-station-をインストールする">Container Station をインストールする</h3>
<p>QNAP の App Center から Container Station をインストールすると、
Docker が使用できるようになる。</p>
<p>Container Station を起動するとこんな感じ。</p>
<p><img src="/images/2021-09-13-qnap-container-station.png" alt="Container Station" /></p>
<p>初回時には、
次のような LXC サポート終了通知 が表示されるが、
Docker を使用する際には関係ないので
「今後、このメッセージを表示しない」
にチェックを入れて OK して構わない。</p>
<p><img src="/images/2021-09-13-qnap-container-station-dialog-lxc.png" alt="LXCサポート終了通知" /></p>
<h2 id="qnap-で-docker-イメージを使用する">QNAP で Docker イメージを使用する</h2>
<h2 id="container-station-に用意されている-docker-イメージを使用する">Container Station に用意されている Docker イメージを使用する</h2>
<p>Container Station サイドメニューの
管理-作成
で Docker イメージが用意されているのが確認できる。</p>
<p><img src="/images/2021-09-13-qnap-container-station-create.png" alt="イメージの作成" /></p>
<p>各 Docker イメージの欄にある「インストール」ボタンをクリックすると、
Docker イメージをダウンロードしてコンテナを作成することができる。</p>
<p>Ubuntu:bionic の「インストール」ボタンをクリックすると、
図のようにコンテナのパラメータ設定ダイアログが表示される。</p>
<p><img src="/images/2021-09-14-qnap-container-create-ubuntu.png" alt="コンテナの作成" /></p>
<p>デフォルトでは「自動実行」が設定されており、
「作成」をクリックするとコンテナの実行まで行われる。</p>
<p>コンテナはサイドメニューの「概要」または「コンテナ」で確認できる。</p>
<p><img src="/images/2021-09-14-qnap-container-running-ubuntu.png" alt="Dockerコンテナの確認" /></p>
<p>コンテナの名称のところのリンクをクリックすると
コンソールが表示される。
何も表示されていない場合は、一度 Enter キーを押すと表示される。</p>
<p><img src="/images/2021-09-14-qnap-container-console-ubuntu.png" alt="Dockerコンテナのコンソール表示" /></p>
<p>Container Station の一覧に出てきている Ubuntu は
Bionic 18.04
で、やや古いが、後述するように Docker Hub のイメージを使用することもできるので問題はない。</p>
<h2 id="docker-hub-のイメージを使用する">Docker Hub のイメージを使用する</h2>
<p>サイドバーメニューの「イメージ」で
ウィンドウ右上の「プル」ボタンをクリックすると、
下のようなダイアログが表示され、
Docker Hub などの外部レジストリの Docker イメージを利用することができる。</p>
<p><img src="/images/2021-09-12-qnap-docker-pull-image.png" alt="Dockerイメージのプル" /></p>
<h2 id="リモートから-qnap-の-docker-を操作する">リモートから QNAP の Docker を操作する</h2>
<p>PC 上の Docker を使用して QNAP (Container Station) 上の Docker に接続し、操作することができる。
QNAP の Docker に接続する際は TLS クライアント認証が用いられ、
その証明書は Container Station からダウンロードしたものを使用する。</p>
<h3 id="container-station-から-docker-証明書をダウンロードする">Container Station から Docker 証明書をダウンロードする</h3>
<p>Docker 証明書は、リモートの (QNAP 上の) Docker との接続に用いる TLS 証明書である。
Container Station でルート証明書、クライアント証明書、秘密鍵のセットを生成し、ダウンロードする。</p>
<p>Container Station サイドメニュー
「管理」-「初期設定」
のタブ
「Docker証明書」
から証明書を「ダウンロード」する。</p>
<p><img src="/images/2021-09-12-qnap-docker-cert.png" alt="Docker証明書" /></p>
<h3 id="コマンドラインオプションまたは環境変数でサーバを指定する">コマンドラインオプションまたは環境変数でサーバを指定する</h3>
<h4 id="証明書を-docker-規定のフォルダに設置する">証明書を Docker 規定のフォルダに設置する</h4>
<p>リモートの Docker サーバをコマンドラインオプション、または環境変数で指定する場合
(dokcer context を使用しない場合)
には、
QNAP からダウンロードした <code class="language-plaintext highlighter-rouge">cert.zip</code> をユーザーのホームパス下のフォルダ <code class="language-plaintext highlighter-rouge">.docker</code>
すなわち <code class="language-plaintext highlighter-rouge">%HOMEPATH%\.docker</code> に展開する。
<code class="language-plaintext highlighter-rouge">cert.zip</code> には <code class="language-plaintext highlighter-rouge">ca.pem</code>, <code class="language-plaintext highlighter-rouge">cert.pem</code>, <code class="language-plaintext highlighter-rouge">key.pem</code> が含まれている。</p>
<h4 id="コマンドラインオプションで接続先サーバを指定して-docker-を実行する">コマンドラインオプションで接続先サーバを指定して docker を実行する</h4>
<p><code class="language-plaintext highlighter-rouge">docker -H=tcp://(QNAPのIPアドレス):2376' --tlsverify ps</code>
のようにしてリモート (QNAP) の docker を操作することができる。</p>
<p>接続先 <code class="language-plaintext highlighter-rouge">tcp://192.168.0.128:2376</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker -H='tcp://192.168.0.128:2376' --tlsverify ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
340403fce20e arm32v7/ubuntu:bionic-data-1 "bash" 3 hours ago Up 2 hours ubuntu-1
</code></pre></div></div>
<h4 id="環境変数で接続先サーバを指定して-docker-を実行する">環境変数で接続先サーバを指定して docker を実行する</h4>
<p>コマンドラインオプション <code class="language-plaintext highlighter-rouge">-H</code>, <code class="language-plaintext highlighter-rouge">--tlsverify</code> の代わりに、
環境変数 <code class="language-plaintext highlighter-rouge">DOCKER_HOST</code>, <code class="language-plaintext highlighter-rouge">DOCKER_TLS_VERIFY</code> を使用することもできる。</p>
<p>一つのサーバしか指定できないので、
ローカルの docker を使用したり、複数のサーバを使用する場合には向かない。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> $env:DOCKER_HOST='tcp://192.168.0.128:2376'
PS D:\test> $env:DOCKER_TLS_VERIFY=1
PS D:\test> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
340403fce20e arm32v7/ubuntu:bionic-data-1 "bash" 3 hours ago Up 2 hours ubuntu-1
</code></pre></div></div>
<h3 id="docker-context-で-qnap-の-docker-を指定する">Docker context で QNAP の Docker を指定する</h3>
<p>接続先サーバを切り替える必要がある場合には docker context が利用できる。
dockder context は操作対象の Docker サーバへの接続情報をあらかじめ登録しておき、
必要に応じて切り替えて接続する仕組みである。</p>
<p><code class="language-plaintext highlighter-rouge">docker context ls</code> で現在設定されているコンテキストの一覧を確認できる。
下の例では、<code class="language-plaintext highlighter-rouge">default</code> と <code class="language-plaintext highlighter-rouge">destktop-linux</code> の2つのコンテキストがすでに登録されていて、
コンテキスト名の後ろに <code class="language-plaintext highlighter-rouge">*</code> が表示されている <code class="language-plaintext highlighter-rouge">default</code> コンテキストが現在選択されていることがわかる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker context ls
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default * moby Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine swarm
desktop-linux moby npipe:////./pipe/dockerDesktopLinuxEngine
</code></pre></div></div>
<p>docker context では、証明書は各コンテキストの接続情報とともに保存されるので、
まず、QNAP からダウンロードした cert.zip を適当なフォルダ
(この例ではカレントフォルダに <code class="language-plaintext highlighter-rouge">tmp</code> フォルダを作成した)
に展開する。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\text> unzip -d tmp cert.zip
Archive: cert.zip
extracting: tmp/ca.pem
extracting: tmp/cert.pem
extracting: tmp/key.pem
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">docker context create</code> で適当な名前でコンテキストを作成する。
ここでは <code class="language-plaintext highlighter-rouge">qnap</code> を指定している。
オプション <code class="language-plaintext highlighter-rouge">--docker</code> の引数で接続のためのパラメータを指定する。
ここで必要なパラメータは <code class="language-plaintext highlighter-rouge">host</code>, <code class="language-plaintext highlighter-rouge">skip-tls-verify</code>, <code class="language-plaintext highlighter-rouge">ca</code>, <code class="language-plaintext highlighter-rouge">cert</code>, <code class="language-plaintext highlighter-rouge">key</code> である。
存在しないパラメータを指定するとエラーになる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker context create qnap --docker "host=tcp://192.168.0.128:2376,skip-tls-verify=false,ca=tmp\ca.pem,cert=tmp\cert.pem,key=tmp\key.pem"
qnap
Successfully created context "qnap"
</code></pre></div></div>
<p>作成済みのコンテキストの接続パラメータを更新するコマンドは <code class="language-plaintext highlighter-rouge">docker context update</code> である。
また、<code class="language-plaintext highlighter-rouge">docker context inspect</code> で指定したコンテキストのパラメータを確認できる。</p>
<p>コンテキストの切り替えは <code class="language-plaintext highlighter-rouge">docker context use</code> である。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker context use qnap
qnap
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker context list
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default moby Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine swarm
desktop-linux moby npipe:////./pipe/dockerDesktopLinuxEngine
qnap * moby tcp://192.168.0.128:2376
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
58a1d22b95e2 arm32v7/ubuntu:bionic "bash" 2 hours ago Up 2 hours ubuntu-1
</code></pre></div></div>
<h2 id="その他">その他</h2>
<h3 id="qnap-の-docker-のバージョン">QNAP の Docker のバージョン</h3>
<p>QNAP の Docker のバージョンを確認してみた。
Windows の Docker のバージョン 20.10.7 に対して
QNAP の Docker は 20.10.3 で、
最新のバージョンではないものの、それほど古くはないことが確認できる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> docker version
Client:
Cloud integration: 1.0.17
Version: 20.10.7
API version: 1.41
Go version: go1.16.4
Git commit: f0df350
Built: Wed Jun 2 12:00:56 2021
OS/Arch: windows/amd64
Context: qnap
Experimental: true
Server:
Engine:
Version: 20.10.3
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: 4c417df92a
Built: Fri Feb 26 04:03:02 2021
OS/Arch: linux/arm
Experimental: false
containerd:
Version: v1.4.3
GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b
runc:
Version: 1.0.0-rc93
GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec
docker-init:
Version: 0.19.0
GitCommit: de40ad0
</code></pre></div></div>
<h3 id="qnap-の-docker-証明書">QNAP の Docker 証明書</h3>
<p>QNAP からダウンロードした証明書、
ルート証明書 <code class="language-plaintext highlighter-rouge">ca.pem</code> と
クライアント証明書 <code class="language-plaintext highlighter-rouge">cert.pem</code>
の内容は次のようになっている。</p>
<h4 id="ルート証明書-capem">ルート証明書 ca.pem</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate:
Data:
Version: 3 (0x2)
Serial Number:
93:12:84:b8:b7:9b:97:3f:8d:20:cd:42:41:e1:82:d6
Signature Algorithm: sha256WithRSAEncryption
Issuer: O = QNAP
Validity
Not Before: Sep 11 13:11:00 2021 GMT
Not After : Aug 26 13:11:00 2024 GMT
Subject: O = QNAP
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
(略)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Key Agreement, Certificate Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
47:17:6A:A7:62:98:E6:6B:27:5E:AC:51:4A:68:16:30:06:98:6D:02
Signature Algorithm: sha256WithRSAEncryption
(略)
</code></pre></div></div>
<h4 id="クライアント証明書-certpem">クライアント証明書 cert.pem</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate:
Data:
Version: 3 (0x2)
Serial Number:
13:da:72:d8:13:e9:d2:2a:d5:44:de:19:ae:9c:c2:c3
Signature Algorithm: sha256WithRSAEncryption
Issuer: O = QNAP
Validity
Not Before: Sep 11 13:11:00 2021 GMT
Not After : Aug 26 13:11:00 2024 GMT
Subject: O = unknown, CN = client
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
(略)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Client Authentication, TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
keyid:47:17:6A:A7:62:98:E6:6B:27:5E:AC:51:4A:68:16:30:06:98:6D:02
Signature Algorithm: sha256WithRSAEncryption
(略)
</code></pre></div></div>
<h2 id="まとめ">まとめ</h2>
<p>QNAP の Docker は、最初の環境構築のところこそ、
QNAP 固有の操作が必要であるものの、
Docker Hub のイメージをそのまま利用でき、
クライアント PC からの Docker 操作もできるので、
家庭で使う限りでは十分な環境でないかと思う。</p>ChirimenQNAP で Docker が使用できるようなので使ってみた。 Docker Hub のイメージも利用できるし、 試してはいないが docker compose も使えるようなので、 思ったより柔軟性の高い運用ができそうだ。UNIX系OS (NetBSD) での Ghostscript 7.07 のコンパイル2021-09-05T14:30:00+09:002021-09-05T14:30:00+09:00https://chirimenmonster.github.io/2021/09/05/gs7-compile<p>UNIX系 (NetBSD) で
Ghostscript 7.07 をソースからコンパイルしてインストールし、
日本語フォントを利用できるようにするまでの手順のまとめ。</p>
<h2 id="はじめに">はじめに</h2>
<p><a href="/2021/08/22/gs7win.html">Windows 用 Ghostscript 7.07 の環境設定</a>
でも触れたように、日本語縦書きの機能を利用するために
Ghostscript 7.07 が必要とされる場面があるが、
古いソフトであるため、環境を構築することは困難になりつつある。</p>
<p>今後のために、現時点で入手可能な情報をもとにした
Ghostscript 7.07 のソースとその関連ファイルの入手法、
コンパイルの手順と設定方法について、記録を残しておく。</p>
<h3 id="参考">参考</h3>
<ul>
<li><a href="https://texwiki.texjp.org/?Ghostscript%207.07">TeX Wiki: Ghostscript 7.07</a></li>
<li><a href="https://www.uconst.org/blog/archives/38">以前に書いたページ</a></li>
</ul>
<h2 id="ダウンロード">ダウンロード</h2>
<ul>
<li><strong>Ghostscript 7.07 のソース</strong><br />
Sourceforge にあった Ghostscript のページ
(<a href="https://sourceforge.net/projects/ghostscript/files/gnu-gs/7.07/">gnu-gs</a>)
が削除されたため、7.07 は入手困難になっているが、7.07b が入手可能。
<ul>
<li><strong>ghostscript-7.07b.tar.gz</strong><br />
<a href="https://ftp.gnu.org/gnu/ghostscript/">GNU のサイト</a>,
<a href="https://ftp.yz.yamagata-u.ac.jp/pub/GNU/ghostscript/">山形大のミラー</a>
から入手できる。</li>
</ul>
</li>
<li><strong>パッチ</strong><br />
そのままだと多くの TrueType フォントでエラーとなってしまうので、
<a href="https://www.aihara.co.jp/~taiji/gyve/">山田泰司さんのページ</a>で紹介されているパッチが必要。
パッチの数が多いので、パッチのダウンロードもコンパイルの節に記述する。</li>
<li><strong>依存ライブラリ</strong> (jpeg, libpng, zlib)
<ul>
<li><strong>jpeg</strong><br />
jpegsrc.v6b.tar.gz が必要。
Sourceforge の libjpeg のページ
(<a href="https://sourceforge.net/projects/libjpeg/files/libjpeg/6b/">libjpeg</a>),
<a href="https://ftp.gnu.org/gnu/ghostscript/">GNU のサイト</a>,
<a href="https://ftp.yz.yamagata-u.ac.jp/pub/GNU/ghostscript/">山形大のミラー</a>
からダウンロードできる。</li>
<li><strong>libpng</strong><br />
最新の 1.6 系列ではコンパイルできない。
1.4系列であれば可能。
1.4系列の最終版
<a href="https://sourceforge.net/projects/libpng/files/libpng14/1.4.22/libpng-1.4.22.tar.gz/download">libpng-1.4.22.tar.gz</a>
が Sourceforge の libpng のページ
(<a href="https://sourceforge.net/projects/libpng/files/">libpng</a>)
からダウンロードできる。</li>
<li>zlib<br />
システム付属の zlib が使用できることが多い。
その場合は zlib のソースは不要。
NetBSD 9 の場合でも不要だった。
ソースからコンパイルする場合は、1.1 系列が必要。
zlib-1.1.3.tar.gz が
<a href="https://ftp.gnu.org/gnu/ghostscript/">GNU のサイト</a>,
<a href="https://ftp.yz.yamagata-u.ac.jp/pub/GNU/ghostscript/">山形大のミラー</a>
からダウンロードできる。</li>
</ul>
</li>
<li><strong>Ghostscript のフォント</strong><br />
Sourceforge の Ghostscript font のページ
(<a href="https://sourceforge.net/projects/gs-fonts/">gs-fonts</a>)
からダウンロードできる。
同ページには同内容でライセンスが異なるパッケージが用意されているので、
適切なものを使用することが望ましい。
<ul>
<li><a href="https://sourceforge.net/projects/gs-fonts/files/gs-fonts/8.11%20%28base%2035%2C%20GPL%29/ghostscript-fonts-std-8.11.tar.gz/download">ghostscript-fonts-std-8.11.tar.gz (GPL)</a></li>
<li><a href="https://sourceforge.net/projects/gs-fonts/files/gs-fonts/6.0%20%28misc%2C%20AFPL%29/ghostscript-fonts-other-6.0.tar.gz/download">ghostscript-fonts-other-6.0.tar.gz (AFPL)</a></li>
</ul>
</li>
<li><strong>CMap Resource</strong><br />
GitHub の Adobe Type tools にある
<a href="https://github.com/adobe-type-tools/cmap-resources">cmap-resources</a>
からダウンロードできる。
同<a href="https://github.com/adobe-type-tools/cmap-resources/releases">リリースページ</a>から、
“Source code (tar.gz)” をダウンロードすればよい。
現時点での最新版は
<a href="https://github.com/adobe-type-tools/cmap-resources/archive/refs/tags/20190730.tar.gz">cmap-resources-20190730.tar.gz</a>
(ダウンロードする際にファイル名が付与される)。</li>
</ul>
<h2 id="コンパイルとインストール">コンパイルとインストール</h2>
<h4 id="ghostscript-707b-のソースを展開する">ghostscript-7.07b のソースを展開する</h4>
<p>ダウンロードした ghostscript-7.07b.tar.gz を展開する。
ビルドやインストールに関する手順が
doc/Make.htm,
doc/Install.htm
に記述されているので、
一通り目を通しておく。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tar xzf ghostscript-7.07b.tar.gz
</code></pre></div></div>
<h4 id="ghostscript-707b-のソースにパッチを適用する">ghostscript-7.07b のソースにパッチを適用する</h4>
<p>素の ghostscript-7.07b のままだと、
使用できる日本語 TrueType フォントがあまりなかったりする。</p>
<p>対応可能な日本語 TrueType フォントを増やすためのパッチが
<a href="https://www.aihara.co.jp/~taiji/gyve/">山田泰司さんのページ</a>
で紹介されているので、パッチをダウンロードして
ghostscript-7.07b のソースにパッチを適用する。</p>
<h5 id="パッチファイルのダウンロード">パッチファイルのダウンロード</h5>
<p>必要なパッチファイルの数が多いので、
ダウンロードのコマンドを列挙したシェルスクリプトを準備して実行することにした。</p>
<p>パッチを当てる工程に合わせて、
ダウンロードしたパッチファイルは4つのディレクトリに分けて管理している。</p>
<p>今のところはすべてのパッチをダウンロードできているが、
フォルダ名のパスに tmp が含まれているのを見ると、
いつまでダウンロード可能かは心配である。</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#! /bin/sh</span>
<span class="o">(</span>
<span class="nb">mkdir </span>step1
<span class="nb">cd </span>step1
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/tagoh.jp_junk/ghostscript-7.07-bigposttable.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/tagoh.jp_junk/ghostscript-7.07-gsublookuptable.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/tagoh.jp_junk/ghostscript-7.07-coverage-glyphcount.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/fix_rename_font_gs_cidfn.ps.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/fix_cidfontname_Encoding_CIDToGIDMap_DW_W.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/modify_ps2pdfwr.patch
<span class="o">)</span>
<span class="o">(</span>
<span class="nb">mkdir </span>step2
<span class="nb">cd </span>step2
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-bigcmaptable.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-noglyph-gid0.patch
<span class="o">)</span>
<span class="o">(</span>
<span class="nb">mkdir </span>step3
<span class="nb">cd </span>step3
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/fix_cidfonttype2_Adobe-CNS1-4.patch
<span class="o">)</span>
<span class="o">(</span>
<span class="nb">mkdir </span>step4
<span class="nb">cd </span>step4
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-seekCFFtable.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-readFDSelect.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-write_GSubrs.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/ghostscript-7.07-nosyoffsetcheck.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/fix_cidfonttype2_Adobe-Japan1-6.patch
curl <span class="nt">-RO</span> https://www.aihara.co.jp/~taiji/gyve/tmp/modify_ps2pdfwr-20050921.patch
<span class="o">)</span>
</code></pre></div></div>
<h5 id="パッチファイルを適用する">パッチファイルを適用する</h5>
<p>ダウンロードしたパッチファイルを指定された順で適用する。</p>
<p>指定する patch のオプションや、
実行するディレクトリがパッチファイルによって異なるので、
こちらもあらかじめシェルスクリプトとして記述したものを実行するようにした。</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#! /bin/sh</span>
<span class="nv">target</span><span class="o">=</span>../ghostscript-7.07b
patch <span class="nt">-p1</span> <span class="nt">-d</span> <span class="nv">$target</span> < step1/ghostscript-7.07-bigposttable.patch
patch <span class="nt">-p1</span> <span class="nt">-d</span> <span class="nv">$target</span> < step1/ghostscript-7.07-gsublookuptable.patch
patch <span class="nt">-p1</span> <span class="nt">-d</span> <span class="nv">$target</span> < step1/ghostscript-7.07-coverage-glyphcount.patch
patch <span class="nt">-p1</span> <span class="nt">-d</span> <span class="nv">$target</span> < step1/fix_rename_font_gs_cidfn.ps.patch
patch <span class="nt">-p1</span> <span class="nt">-d</span> <span class="nv">$target</span> < step1/fix_cidfontname_Encoding_CIDToGIDMap_DW_W.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span>/lib < step1/modify_ps2pdfwr.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step2/ghostscript-7.07-bigcmaptable.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step2/ghostscript-7.07-noglyph-gid0.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step3/fix_cidfonttype2_Adobe-CNS1-4.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step4/ghostscript-7.07-seekCFFtable.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step4/ghostscript-7.07-readFDSelect.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step4/ghostscript-7.07-write_GSubrs.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step4/ghostscript-7.07-nosyoffsetcheck.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span> < step4/fix_cidfonttype2_Adobe-Japan1-6.patch
patch <span class="nt">-p0</span> <span class="nt">-d</span> <span class="nv">$target</span>/lib < step4/modify_ps2pdfwr-20050921.patch
</code></pre></div></div>
<h4 id="依存ライブラリのソースを展開する">依存ライブラリのソースを展開する</h4>
<p>NetBSD 9 (他の多くのシステムも) ではシステムの zlib が使用できるので、
jpeg と libpng のみソースを設置する。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd </span>ghostscript-7.07b
<span class="gp">$</span><span class="w"> </span><span class="nb">tar </span>xzf ../jpegsrc.v6b.tar.gz
<span class="gp">$</span><span class="w"> </span><span class="nb">mv </span>jpeg-6b jpeg
</code></pre></div></div>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cd </span>ghostscript-7.07b
<span class="nv">$ </span><span class="nb">tar </span>xzf ../libpng-1.4.22.tar.gz
<span class="nv">$ </span><span class="nb">mv </span>libpng-1.4.22 libpng
</code></pre></div></div>
<h4 id="設定のために-configure-を実行する">設定のために configure を実行する</h4>
<p>多くのフリーソフトと同様に、
ソースに同梱されている configure
を実行してコンパイルのための設定を行う。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./configure
</code></pre></div></div>
<p>デフォルトでは <code class="language-plaintext highlighter-rouge">/usr/local</code> 以下にインストールされる設定となっている。
NetBSD では pkg 管理下のアプリケーションは <code class="language-plaintext highlighter-rouge">/usr/pkg</code> 下にインストールされるので、
デフォルトのままで問題ないと思われるが、
アプリケーション管理ツールが <code class="language-plaintext highlighter-rouge">/usr/local</code> を使用するシステムの場合は、
競合を避けるために別のディレクトリを使用したほうがよいだろう。</p>
<p>confighure のオプション <code class="language-plaintext highlighter-rouge">--prefix=path</code> を指定すると、
デフォルトの <code class="language-plaintext highlighter-rouge">/usr/local</code> の代わりに <code class="language-plaintext highlighter-rouge">path</code> を使用することができる (<code class="language-plaintext highlighter-rouge">path</code> は任意)。</p>
<h4 id="コンパイル-make-の実行">コンパイル (make) の実行</h4>
<p>GNU make で make を実行する。</p>
<p>NetBSD のシステムに含まれている BSD make は仕様が異なるので動作しないようである。
NetBSD 環境なので gmake (GNU make) をインストールし、
gmake を実行する。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gmake
</code></pre></div></div>
<p>NetBSD 9.2 で X window system をインストールしていない環境下でコンパイルした結果。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ldd ./bin/gs
./bin/gs:
-lz.1 => /usr/lib/libz.so.1
-lc.12 => /usr/lib/libc.so.12
-lm.0 => /usr/lib/libm.so.0
</code></pre></div></div>
<p>zlib はシステムのものが動的リンクされており、
jpeg や libpng は静的リンクされている。</p>
<h4 id="インストール-make-install-の実行">インストール (make install) の実行</h4>
<p><code class="language-plaintext highlighter-rouge">gmake install</code> を実行してインストールを行う。
デフォルトの設定のままであれば
<code class="language-plaintext highlighter-rouge">/usr/local/bin</code> 下に実行ファルが、
<code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript</code> 下に他の必要ファイル群がインストールされる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gmake install
</code></pre></div></div>
<p>そのままだと、Resource の参照先が適切でないので修正する必要がある。
具体的には
<code class="language-plaintext highlighter-rouge">lib/gs_res.ps</code> (<code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/7.07/lib/gs_res.ps</code>)
の249–250行</p>
<div class="language-postscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">/FontResourceDir</span> <span class="s">(/Resource/Font/)</span> <span class="nf">readonly</span> <span class="nf">.forcedef</span> <span class="c1">% pssys'params is r-o</span>
<span class="nv">/GenericResourceDir</span> <span class="s">(/Resource/)</span> <span class="nf">readonly</span> <span class="nf">.forcedef</span> <span class="c1">% pssys'params is r-o</span>
</code></pre></div></div>
<p>の <code class="language-plaintext highlighter-rouge">/Resource/</code> (2箇所)を
<code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/Resource/</code>
のように修正する。</p>
<div class="language-postscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">/FontResourceDir</span> <span class="s">(/usr/local/share/ghostscript/Resource/Font/)</span> <span class="nf">readonly</span> <span class="nf">.forcedef</span> <span class="c1">% pssys'params is r-o</span>
<span class="nv">/GenericResourceDir</span> <span class="s">(/usr/local/share/ghostscript/Resource/)</span> <span class="nf">readonly</span> <span class="nf">.forcedef</span> <span class="c1">% pssys'params is r-o</span>
</code></pre></div></div>
<h3 id="標準フォントのインストール">標準フォントのインストール</h3>
<p>ダウンロードした標準フォント
ghostscript-fonts-std-8.11.tar.gz,
ghostscript-fonts-other-6.0.tar.gz
の内容を <code class="language-plaintext highlighter-rouge">/usr/local/ghostscript/fonts</code> に設置する。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo tar xzf ghostscript-fonts-std-8.11.tar.gz -C /usr/local/share/ghostscript
$ sudo tar xzf ghostscript-fonts-other-6.0.tar.gz -C /usr/local/share/ghostscript
</code></pre></div></div>
<p>ファイルのオーナーやパーミッションは環境に合わせて設定する。</p>
<h3 id="cmap-resource-のインストール">CMap Resource のインストール</h3>
<p>ダウンロードした cmap-resouorce ファイルを展開し、
<code class="language-plaintext highlighter-rouge">Adobe-Japan1-7</code> などのディレクトリの下にある
<code class="language-plaintext highlighter-rouge">CMap</code> ディレクトリを <code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/Resource</code> にコピーする。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unzip cmap-resources-20190730.zip
$ cd cmap-resources
$ sudo mkdir /usr/local/share/ghostscript/Resource
$ for d in Adobe-*; do sudo cp -pr $d/CMap /usr/local/share/ghostscript/Resource; done
</code></pre></div></div>
<p>ファイルのオーナーやパーミッションなどは環境や好みに合わせて設定する。</p>
<h2 id="環境設定">環境設定</h2>
<h3 id="ghostscript-の検索パス-search-path">ghostscript の検索パス (search path)</h3>
<p>TrueType フォントは ghostscript の検索パス (search path) から読み込まれる。
検索パスは <code class="language-plaintext highlighter-rouge">gs -h</code> で確認することができる。
デフォルトでは
<code class="language-plaintext highlighter-rouge">.</code>, <code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/7.07/lib</code>, <code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/font</code>
の順に検索される。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gs -h
GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA. All rights reserved.
(snip)
Search path:
. : /usr/local/share/ghostscript/7.07/lib :
/usr/local/share/ghostscript/fonts
For more information, see /usr/local/share/ghostscript/7.07/doc/Use.htm.
Report bugs to bug-gs@ghostscript.com, using the form in Bug-form.htm.
</code></pre></div></div>
<p>検索パスは、環境変数 <code class="language-plaintext highlighter-rouge">GS_LIB</code> やオプション <code class="language-plaintext highlighter-rouge">-I</code> で追加することができる。
追加したパスはカレントディレクトリ <code class="language-plaintext highlighter-rouge">.</code> の次に検索される。</p>
<p>環境変数 <code class="language-plaintext highlighter-rouge">GS_LIB</code> で指定する場合は次のようになる。
分離記号 <code class="language-plaintext highlighter-rouge">:</code> (UNIX系の場合) を用いて複数のディレクトリを指定することもできる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ GS_LIB=lib:fonts gs -h
(snip)
Search path:
. : lib : fonts : /usr/local/share/ghostscript/7.07/lib :
/usr/local/share/ghostscript/fonts
</code></pre></div></div>
<p>オプション <code class="language-plaintext highlighter-rouge">-I</code> で指定する場合は次のようになる。
オプション文字列 <code class="language-plaintext highlighter-rouge">-I</code> とパス文字列の間に空白を入れてはいけない。
また、<code class="language-plaintext highlighter-rouge">-h</code> で検索パスを確認する際は、
<code class="language-plaintext highlighter-rouge">-h</code> は <code class="language-plaintext highlighter-rouge">-I</code> より後に指定する必要がある。</p>
<p>複数のディレクトリを指定するには、
<code class="language-plaintext highlighter-rouge">-I</code> オプションを複数回使用するか、</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gs -Ilib -Ifonts -h
(snip)
Search path:
. : lib : fonts : /usr/local/share/ghostscript/7.07/lib :
/usr/local/share/ghostscript/fonts
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">GS_LIB</code> の場合と同様に分離記号 <code class="language-plaintext highlighter-rouge">:</code> を使用する方法がある。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gs -Ilib:fonts -h
(snip)
Search path:
. : lib : fonts : /usr/local/share/ghostscript/7.07/lib :
/usr/local/share/ghostscript/fonts
</code></pre></div></div>
<h3 id="日本語-truetype-フォントの設置と設定">日本語 TrueType フォントの設置と設定</h3>
<h4 id="フォントファイルの設置">フォントファイルの設置</h4>
<p>日本語 TrueType フォントファイルは
ghostscript の検索パスが通っているディレクトリ、
またはそのディレクトリからの相対パスで参照可能なサブディレクトリに設置する。</p>
<p>フォントファイルは絶対パスで指定することもできる。</p>
<p>オプション <code class="language-plaintext highlighter-rouge">-dSAFER</code> が指定されていると、
親ディレクトリへの参照 <code class="language-plaintext highlighter-rouge">..</code> はエラーになる。</p>
<h4 id="cidfnmap-の設定">CIDFnmap の設定</h4>
<p>日本語 TrueType フォントの設定を <code class="language-plaintext highlighter-rouge">CIDFnmap</code> で行う。
<code class="language-plaintext highlighter-rouge">CIDFnmap</code> は <code class="language-plaintext highlighter-rouge">/usr/local/share/ghostscript/7.07/lib</code> にあるが、
カレントディレクトリの <code class="language-plaintext highlighter-rouge">CIDFnmap</code> が優先される。</p>
<p>フォントの指定は次のようになる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%!
% IPA明朝・ゴシック
% 等幅は縦書きでも問題ない。
% プロポーショナルでは縦書き時にカーニングされてしまう。
/IPAMincho (ipam.ttc) 1 ; % IPA明朝 TTC (Ver.003.03)
/IPAPMincho (ipam.ttc) 2 ; % IPAP明朝 TTC (Ver.003.03)
/IPAGothic (ipag.ttc) 1 ; % IPAゴシック TTC (Ver.003.03)
/IPAPGothic (ipag.ttc) 2 ; % IPAPゴシック TTC (Ver.003.03)
% IPAex明朝・ゴシック
% 縦書き時に横書き用グリフが使用されてしまう。
/IPAexMincho (ipaexm.ttf) ; % IPAex明朝 (Ver.004.01)
/IPAexGothic (ipaexg.ttf) ; % IPAexゴシック (Ver.004.01)
% Windows 10 のMS明朝・ゴシック
% エラーで使用できない。古い (XP) のだとおそらく使える。
/MS-Mincho (msmincho.ttc) 1 ; % MS明朝
/MS-Gothic (msgothic.ttc) 1 ; % MSゴシック
% Windows 10 の BIZ UD シリーズ
% 等幅は縦書きでも問題ない。
% プロポーショナルでは縦書き時にカーニングが行われる。また、カッコの文字間がおかしい
/BIZ-UDMincho-Medium (BIZ-UDMinchoM.ttc) 1 ; % BIZ UD明朝 Medium
/BIZ-UDPMincho-Medium (BIZ-UDMinchoM.ttc) 2 ; % BIZ UDP明朝 Medium
/BIZ-UDGothic-Regular (BIZ-UDGothicR.ttc) 1 ; % BIZ UDゴシック Regular
/BIZ-UDPGothic-Regular (BIZ-UDGothicR.ttc) 2 ; % BIZ UDPゴシック Regular
/BIZ-UDGothic-Bold (BIZ-UDGothicB.ttc) 1 ; % BIZ UDゴシック Bold
/BIZ-UDPGothic-Bold (BIZ-UDGothicB.ttc) 2 ; % BIZ UDPゴシック Bold
% Windows 10 の UDデジタル教科書体
% 縦書き時に横書き用グリフが使用されてしまう。
/UDDigiKyokashoN-R (UDDigiKyokashoN-R.ttc) 1 ; % UDデジタル教科書体 N-R
/UDDigiKyokashoNP-R (UDDigiKyokashoN-R.ttc) 2 ; % UDデジタル教科書体 NP-R
/UDDigiKyokashoNK-R (UDDigiKyokashoN-R.ttc) 3 ; % UDデジタル教科書体 NK-R
% 古い商用 TrueType フォント (ヒラギノ)
% 縦書きでも問題ない。
/HiraginoMin-W3-90ms-RKSJ-H (DShirmn3.ttc) 2 ; % ヒラギノ明朝体3等幅
/HiraginoMin-W5-90ms-RKSJ-H (DShirmn5.ttc) 2 ; % ヒラギノ明朝体5等幅
/HiraginoMin-W7-90ms-RKSJ-H (DShirmn7.ttc) 2 ; % ヒラギノ明朝体7等幅
/HiraginoMin-W3-90msp-RKSJ-H (DShirmn3.ttc) 1 ; % ヒラギノ明朝体3
/HiraginoMin-W5-90msp-RKSJ-H (DShirmn5.ttc) 1 ; % ヒラギノ明朝体5
/HiraginoMin-W7-90msp-RKSJ-H (DShirmn7.ttc) 1 ; % ヒラギノ明朝体7
/HiraginoKaku-W3-90ms-RKSJ-H (DShirkg3.ttc) 2 ; % ヒラギノ角ゴ3等幅
/HiraginoKaku-W5-90ms-RKSJ-H (DShirkg5.ttc) 2 ; % ヒラギノ角ゴ5等幅
/HiraginoKaku-W7-90ms-RKSJ-H (DShirkg7.ttc) 2 ; % ヒラギノ角ゴ7等幅
/HiraginoKaku-W3-90msp-RKSJ-H (DShirkg3.ttc) 1 ; % ヒラギノ角ゴ3
/HiraginoKaku-W5-90msp-RKSJ-H (DShirkg5.ttc) 1 ; % ヒラギノ角ゴ5
/HiraginoKaku-W7-90msp-RKSJ-H (DShirkg7.ttc) 1 ; % ヒラギノ角ゴ7
/HiraginoGyoDS-W4-90ms-RKSJ-H (DShirgy4.ttc) 2 ; % ヒラギノ行書等幅
/HiraginoGyoDS-W4-90msp-RKSJ-H (DShirgy4.ttc) 1 ; % ヒラギノ行書
% 原ノ味フォント OTF
% 縦書き時の文字高さ(文字送り量)がおかしい。
/HaranoAjiMincho-Regular (HaranoAjiMincho-Regular.otf) ; % 原ノ味明朝
/HaranoAjiGothic-Medium (HaranoAjiGothic-Medium.otf) ; % 原ノ味ゴシック
/Ryumin-Light /IPAMincho ;
/GothicBBB-Medium /IPAGothic ;
</code></pre></div></div>
<h4 id="動作確認">動作確認</h4>
<p>上記の CIDFnmap をカレントディレクトリに、
日本語フォントファイルを <code class="language-plaintext highlighter-rouge">fonts</code> (<code class="language-plaintext highlighter-rouge">./fonts</code>)
に置いて動作確認を行った。</p>
<p>テスト用のファイル
<a href="https://gist.github.com/chirimenmonster/a00d9bcf743dee9169627103c0bff03d">fontexample.ps</a>
を gist に掲載しておく。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gs -Ifonts -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r150 -sOutputFile=result-p%d.png fontexample.ps
</code></pre></div></div>
<p><img src="/images/2021-09-05-gs-result-p1.png" alt="result-p1.png" />
<img src="/images/2021-09-05-gs-result-p2.png" alt="result-p2.png" /></p>ChirimenUNIX系 (NetBSD) で Ghostscript 7.07 をソースからコンパイルしてインストールし、 日本語フォントを利用できるようにするまでの手順のまとめ。Windows 用 Ghostscript 7.07 の環境設定2021-08-22T15:15:00+09:002021-08-22T15:15:00+09:00https://chirimenmonster.github.io/2021/08/22/gs7win<p>Windows で Ghostscript 7.07 をインストールして日本語を表示させるための環境を構築する手順のまとめ。
ファイルの入手方法から設定、動作確認まで。</p>
<h2 id="はじめに">はじめに</h2>
<p>Ghostscript 7.07 は 2003 年頃に発表された古いソフトである。
Ghostscript は 9 系列が 2010 年頃から利用でき、
現在は 9.54 となっている。
ところが、9.54 でも 7.07
の時点では利用できていた日本語縦書き機能の一部が
<a href="/2021/08/21/ghostscript-front-vertical.html">使えない</a>
ままとなっており、
用途によっては 7.07 の動作環境が必要とされる場合がある。</p>
<p>しかし、Ghostscript 7.07 はすでに開発が終了し使用が非推奨となっているため、
Windows 用の実行ファイルや、その他の動作に必要なファイルなどは入手自体が困難となってきているし、
当時の解説文書なども、リンクの多くが無効なものとなっている。
利用できるフォントとその設定方法についても、
そのままでは使えなくなってしまっているものが多い。</p>
<p>幸いにして、現時点でもある程度 Ghostscript 7.07 の実行環境の構築することができたので、
忘備録としてまとめを残すことにした。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://texwiki.texjp.org/?Ghostscript%207.07#k0bb1098">TeX Wiki: Ghostscript 7.07</a></li>
</ul>
<h2 id="ダウンロード">ダウンロード</h2>
<h3 id="windows-用-ghostscript-本体">Windows 用 Ghostscript 本体</h3>
<p>Windows 用 Ghostscript 7.07 は入手が困難になってきているが、
角藤版 Ghojstscript である
<a href="http://ftp.ring.gr.jp/pub/text/TeX/ptex-win32/gs/gs707w32full.zip">gs707w32full.zip</a>
が
<a href="http://ftp.ring.gr.jp/">ring server</a>
の
<a href="http://ftp.ring.gr.jp/pub/text/TeX/ptex-win32/gs/">/pub/text/TeX/ptex-win32/gs</a>
より入手できる (2021.8.22)。</p>
<h3 id="cmap-リソース">CMap リソース</h3>
<p>GitHub のリポジトリ
<a href="https://github.com/adobe-type-tools/cmap-resources">cmap-resource</a>
から、CMap リソースをダウンロードできる。</p>
<p>上記リポジトリのリリースページにある現時点 (2021.8.22) の最新版は
<a href="https://github.com/adobe-type-tools/cmap-resources/archive/refs/tags/20190730.zip">cmap-resources-20190730.zip</a>
である。</p>
<h3 id="truetype-フォント">TrueType フォント</h3>
<p>OSDN のページ
<a href="https://ja.osdn.net/projects/efont/">efont</a>
から
<a href="https://ja.osdn.net/projects/efont/releases/10087">さざなみフォント</a>
をダウンロードできる</p>
<p>さざなみフォントは必須ではないが、
後述するように Ghostscript 7 で利用可能な TrueType フォントは限られているため、
動作確認などのために確保しておくことが望ましい。</p>
<h2 id="インストール">インストール</h2>
<h3 id="ghostscript-のインストール">Ghostscript のインストール</h3>
<p>ダウンロードした gs707w32full.zip を展開し、
setupgs.exe
を実行する。</p>
<p>デフォルトでは C:\gs 下にインストールされる。</p>
<h3 id="cmap-ファイルの設置">CMap ファイルの設置</h3>
<p>フォルダ C:\gs\gs7.07\Resource\CMap を作成し、
ダウンロードした CMap リソースの zip ファイルを展開してできる
Adobe-Identity-0, Adobe-Japan1-7
などのフォルダの下にある CMap フォルダの中身をすべて
C:\gs\gs7.07\Resource\CMap
にコピーする。</p>
<p>元の各 CMap フォルダの中身が統合された状態になる。</p>
<p><img src="/images/2021-08-22-cmap.png" alt="CMap" /></p>
<h3 id="truetype-フォント-さざなみフォント-の設置">TrueType フォント (さざなみフォント) の設置</h3>
<h4 id="コピー先フォルダ">コピー先フォルダ</h4>
<p>gs の検索パスに含まれているフォルダのいずれかに、さざなみフォント
(sazanami-mincho.ttf, sazanami-gothic.ttf) をコピーする。
または、任意のフォルダにフォントファイルを設置してフォルダを環境変数
GS_LIB で指定する検索パスに追加するか、
フォントの指定時に絶対パスを指定するのでもよい。</p>
<p>検索パスには C:\Windows\Fonts が含まれているので、
Windows の
「すべてのユーザに対してフォントをインストール」
でインストールすれば、ファイル名のみでの指定が可能になる。</p>
<p>gs の検索パスは gswin32.exe, gswin32c.exe のオプション -h で
検索パスを確認できる。
GS_LIB で指定したフォルダは、カレントフォルダ <code class="language-plaintext highlighter-rouge">.</code> の次に挿入される。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> C:\gs\gs7.07\bin\gswin32c.exe -h
GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA. All rights reserved.
Usage: gs [switches] [file1.ps file2.ps ...]
(略)
Search path:
. ; C:\gs\gs7.07\lib ; C:\gs\gs7.07\kanji ; C:\gs\fonts ;
c:/gs/gs7.07/lib ; c:/gs/gs7.07/kanji ; c:/gs/fonts ; c:/winnt/fonts ;
c:/usr/sysfonts ; c:/windows/fonts ; c:/winnt35/fonts
(略)
</code></pre></div></div>
<h4 id="cidfnmap-の設定">CIDFnmap の設定</h4>
<p>c:\gs\gs7.07\lib にある CIDFnmap に,
明朝体 (Ryumin-Light) とゴシック体 (GothicBBB-Medium)
の定義を次のように記述する。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/Ryumin-Light (sazanami-mincho.ttf) ;
/GothicBBB-Medium (sazanami-gothic.ttf) ;
</code></pre></div></div>
<p>日本語を使用している Postscript ファイルのサンプルの多くが
Ryumin-Light と GothicBBB-Medium
を使用しているためであるが、
別の名前での定義を追加してもよい。</p>
<h2 id="ghostscript-の実行と確認">Ghostscript の実行と確認</h2>
<p>日本語フォントの表示は、gs 付属のサンプル article9.ps などで確認することができる。
articke.ps は C:\gs\gs7.07\kanji にあり、
C:\gs\gs7.07\kanji は gs の検索パスに含まれているので、
単に article.ps として読み込ませることができる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> C:\gs\gs7.07\bin\gswin32c.exe -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r150 -sOutputFile='article9.png' article9.ps
</code></pre></div></div>
<p><img src="/images/2021-08-22-gs-article9.png" alt="article.png" /></p>
<p>さざなみフォントが使用されていることが確認できる。
文字間隔がおかしかったり下にはみ出しているのは
article9.ps 側の問題である。</p>
<p>オプション -dWINKANJI を指定すると、
日本語フォントのレンダリングに Win32API を用いるようになる。
使用すると明朝体は MS明朝 で、ゴシック体は MSゴシックで レンダリングする結果となるが、
レンダリングに使用するフォントを変更できるのかどうかはわからなかった。</p>
<p>参考までに、-dWINKANJI を指定した場合の結果を次に示す。
CIDFnmap はさざなみフォント指定のままにしている。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS D:\test> C:\gs\gs7.07\bin\gswin32c.exe -dWINKANJI -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r150 -sOutputFile='article9.png' article9.ps
</code></pre></div></div>
<p><img src="/images/2021-08-22-gs-article9-winkanji.png" alt="article.png" /></p>
<p>CIDFnmap の設定と無関係に MS明朝, MSゴシック でレンダリングされていることが確認できる。</p>
<h2 id="ghostscript-707-で利用可能な-truetype-フォント">Ghostscript 7.07 で利用可能な TrueType フォント</h2>
<p>CIDFnmap の記載例には msmincho.ttc や msgothic.ttc が示されており、
少なくとも gs7.07 が公開された当時の環境では msmincho.ttc や msgothic.ttc
が利用できていたものと思われる。</p>
<p>しかし、Windows 10 付属の msmincho.ttc, msgothic.ttc
を指定すると次のようなエラーになって使用できない。
Windows 10 付属の TrueType フォントは駄目なようである。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Error: /undefinedresource in --findresource--
Operand stack:
48 GothicBBB-Medium-V Font GothicBBB-Medium-V (GothicBBB-Medium-V) 16 GothicBBB-Medium V V --dict:0/10(G)-- GothicBBB-Medium false GothicBBB-Medium GothicBBB-Medium Auto --nostringval-- 92166 --nostringval-- CMap 92166 CMap 92166
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1 3 %oparray_pop 1 3 %oparray_pop 1 3 %oparray_pop 1 3 %oparray_pop .runexec2 --nostringval--
--nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- 2 3 %oparray_pop 3 3 %oparray_pop --nostringval--
--nostringval-- 18 6 %oparray_pop --nostringval-- 21 7 %oparray_pop --nostringval-- --nostringval-- --nostringval-- --nostringval--
Dictionary stack:
--dict:1068/1123(ro)(G)-- --dict:0/20(G)-- --dict:82/200(L)-- --dict:17/17(ro)(G)-- --dict:28/50(ro)(G)-- --dict:12/40(G)-- --dict:0/10(L)-- --dict:16/24(ro)(G)--
Current allocation mode is local
Last OS error: No such file or directory
Current file position is 1344
GNU Ghostscript 7.07: Unrecoverable error, exit code 1
</code></pre></div></div>
<p>IPAフォントは現行のもの (ver.003.03) は駄目だったが、
手元に残っていた Ver.001 の IPA明朝、IPAゴシックであれば使用可能だった。
ただし、Ver.001 のIPAフォントの入手方法はわからなかった。</p>
<p>手持ちの古い市販 TrueType フォント (ヒラギノフォントTrueType ver4.1) は使えたので
新し目の TrueType フォントが使えないということかもしれない。</p>
<p>時期的には JIS X 0213 対応の前後だと思うが、関係は不明である。</p>
<h2 id="おわりに">おわりに</h2>
<p>Ghostscript 7.07 で日本語フォントを取り扱うための情報の多くがまだ残っているが、
実際にはファイルの入手が不能、または困難になっているため、
現時点で利用可能なものとしての情報を確認してまとめておく必要があった。</p>
<p>また、当時の主要なニーズ
(ベクターフォントでなんとか日本語のテキストが読めればよい)
に対して書かれた文書を読むと、
今とは前提や目的が大きく異なっているので、
あらためてまとめ直すことにも意義があると思われる。</p>
<p>自分の知識不足のため、深く掘り下げた調査を行うことはできなかったが、
現時点のマイルストーンとしてこの文書を残しておく。</p>ChirimenWindows で Ghostscript 7.07 をインストールして日本語を表示させるための環境を構築する手順のまとめ。 ファイルの入手方法から設定、動作確認まで。Ghostscript 9 で CID フォントを使った縦書きをやってみた2021-08-21T13:45:00+09:002021-08-21T13:45:00+09:00https://chirimenmonster.github.io/2021/08/21/ghostscript9-font-vertical<p>Ghostscript の縦書きサポートは 7.07 を最後に壊れたままになっていると言われているが、
それは TrueType フォントを使うときの話なのか、
CID フォントを使う場合でもそうなのかについては
よくわからなかったので、
最新の Ghostscript 9.54 で
確認してみた。</p>
<h2 id="はじめに">はじめに</h2>
<p>Ghostscript の縦書き日本語対応には問題がある。</p>
<p>TeX Wiki Ghostscript 7.07 における
<a href="https://texwiki.texjp.org/?Ghostscript%207.07#y494a6b5">参考:Ghostscript のバージョンと歴史的経緯</a>
によれば、
かつて gs-cjk プロジェクトにより作成された日本語化・CJK化対応パッチが
Ghostscript 7.07 の時点では本家に統合されていたものの、
その後 (Ghostscript 8 以降) は取り込まれずに、
Ghostscript のCJK対応は不十分なままとなっている、とある。</p>
<p>実際に手元で試してみると、
Ghostscript 7.07 で正しく日本語の縦書きが表示される Postcript ファイルが、
9系列では表示位置がずれるという結果が確認された。
縦書き時のグリフの原点 (グリフの上端中央が想定される)
が横書き時のまま (グリフの左下) になっているような挙動である。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">左が GS 7.07、右が 9.52 の出力、縦書きで位置ずれが発生してるのは、参照点が異なってるっぽい。8.x の頃にあった縦書きグリフ選択の問題は問題なさそう。位置ずれはGS側の問題かフォントに起因するのか、仕様としてはどっちもありで、前者の環境しか想定していない Postscript のコードがダメなのか <a href="https://t.co/yD7OyTm1kB">pic.twitter.com/yD7OyTm1kB</a></p>— Chirimen (@chirimenspiral) <a href="https://twitter.com/chirimenspiral/status/1403592800722034688?ref_src=twsrc%5Etfw">June 12, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ただし、ここで使用したのは TrueType フォントである。
本来 Postscript で使用を想定されていたフォントは CID keyed フォントなので、
単に TrueType フォント対応が不十分なだけ、ということも考えられる。
CID keyed フォントだと縦書き時の位置ずれなどの問題は生じないのかもしれない、
ということで確認してみることにした。</p>
<p>CID keyed フォント、だと長くてタイプが面倒なので、
特に誤解を招くような場合でなければ、単に CID フォント、と表現することにする。</p>
<h2 id="cid-フォント">CID フォント</h2>
<p>正直なところ、CIDフォントとはどういうものかはよく知らない。
Postscript で日本語などを取り扱う仕組みだと聞いたことがあるくらいで、
直接CIDフォントを取り扱ったことはない。
たぶんプリンタには内蔵されていたのだろう。
それに、現在はCIDフォントは使われていない
(<a href="https://www.itmedia.co.jp/news/articles/1812/26/news084.html">ITmedia: アドビ、CIDフォントのサポート終了へ, 2018.12.26</a>)
というような話もある。</p>
<p><a href="https://www.iwatafont.co.jp/news/img/about_font.pdf">フォントのしくみ (第3回 DTPの勉強会 狩野宏樹)</a>
によれば、
OpenTypeフォントのうち、
拡張子 .otf となっているものの中にCIDフォントとして使えるものがあるらしい。</p>
<p>フリーのCIDフォントはあまり見かけないが、
<a href="https://github.com/trueroad/HaranoAjiFonts">原ノ味フォント</a>
を見るとCIDフォントとして使用できるOpenTypeフォントのようなので、
今回は原ノ味フォントを検証に用いることにした。</p>
<h2 id="検証環境の準備">検証環境の準備</h2>
<h3 id="ghostscript-9-のインストール">Ghostscript 9 のインストール</h3>
<p>Ghostscript 公式の<a href="https://www.ghostscript.com/download/gsdnld.html">ダウンロードページ</a>の
Ghostscript AGPL Release のところから
Windows 用のインストーラー gs9540w64.exe をダウンロードしてインストールする。
特筆する点は特にない。</p>
<p>デフォルトだと C:\Program Files\gs\gs9.54.0 にインストールされるので、
その前提で以下の記述を行う。</p>
<h3 id="cidフォント-opentypeフォント-の設定">CIDフォント (OpenTypeフォント) の設定</h3>
<p>TeX Wiki: Ghostscript/Windows の
<a href="https://texwiki.texjp.org/?Ghostscript%2FWindows#dc743646">CID font</a>、
<a href="https://texwiki.texjp.org/?Ghostscript%2FWindows#j4509a9f">日本語 OpenType font</a>
の記述を参考に設定を行う。</p>
<h4 id="フォントファイルの設置">フォントファイルの設置</h4>
<p>ダウンロードしたCIDフォントのファイルを
C:\Program Files\gs\gs9.54.0\Resource\CIDFont
にコピーし、ファイル名は拡張子がないものに変更する。</p>
<p>HaranoAjiMincho-Regular.otf であれば
HaranoAjiMincho-Regular というようにである。</p>
<h4 id="cmap名付きのフォント定義">CMap名付きのフォント定義</h4>
<p>HaranoAjiMincho-Regular-UniJIS-UTF8-H のように、
CMap名付きでフォントにアクセスするための定義を行う。</p>
<p>定義は次のようになる。
この内容を HaranoAjiMincho-Regular-UniJIS-UTF8-H というファイル名で
C:\Program Files\gs\gs9.54.0\Resource\Font
に設置する。
定義の内容は TeX Live 2021 付属の gs (tlgs) での設定を参考に作成した。</p>
<div class="language-postscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">%!PS-Adobe-3.0 Resource-Font</span>
<span class="cs">%%DocumentNeededResources: UniJIS-UTF8-H (CMap)</span>
<span class="cs">%%IncludeResource: UniJIS-UTF8-H (CMap)</span>
<span class="cs">%%BeginResource: Font (HaranoAjiMincho-Regular-UniJIS-UTF8-H)</span>
<span class="s">(HaranoAjiMincho-Regular-UniJIS-UTF8-H)</span>
<span class="s">(UniJIS-UTF8-H)</span> <span class="nv">/CMap</span> <span class="nf">findresource</span>
<span class="p">[</span><span class="s">(HaranoAjiMincho-Regular)</span> <span class="nv">/CIDFont</span> <span class="nf">findresource</span><span class="p">]</span>
<span class="nf">composefont</span>
<span class="nb">pop</span>
<span class="cs">%%EndResource</span>
<span class="cs">%%EOF</span>
</code></pre></div></div>
<p>縦書き用は HaranoAjiMincho-Regular-UniJIS-UTF8-V というファイル名で
次の内容とした。
前述のファイルとの違いは、
フォント名末尾の -H が -V となっている部分のみである。</p>
<div class="language-postscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">%!PS-Adobe-3.0 Resource-Font</span>
<span class="cs">%%DocumentNeededResources: UniJIS-UTF8-V (CMap)</span>
<span class="cs">%%IncludeResource: UniJIS-UTF8-V (CMap)</span>
<span class="cs">%%BeginResource: Font (HaranoAjiMincho-Regular-UniJIS-UTF8-V)</span>
<span class="s">(HaranoAjiMincho-Regular-UniJIS-UTF8-V)</span>
<span class="s">(UniJIS-UTF8-V)</span> <span class="nv">/CMap</span> <span class="nf">findresource</span>
<span class="p">[</span><span class="s">(HaranoAjiMincho-Regular)</span> <span class="nv">/CIDFont</span> <span class="nf">findresource</span><span class="p">]</span>
<span class="nf">composefont</span>
<span class="nb">pop</span>
<span class="cs">%%EndResource</span>
<span class="cs">%%EOF</span>
</code></pre></div></div>
<h4 id="フォントフォルダ読み込みのための追加指定">フォントフォルダ読み込みのための追加指定</h4>
<p>現在の Ghostscript では環境変数 GS_LIB による読み込みパスの指定は不要とされているが、
追加したCIDフォントを使おうとすると、
下記のようなエラーがでてフォントファイルの読み込みに失敗した。
今回フォントを設置した CIDFont フォルダが
gs のデフォルトの読み込みパスに含まれていないと思われる。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Can't find (or can't open) font file %rom%Resource/Font/%rom%Resource/Font/HaranoAjiMincho-Re.
Can't find (or can't open) font file HaranoAjiMincho-Regular-UniJIS-UTF8-H.
Didn't find this font on the system!
Substituting font Courier for HaranoAjiMincho-Regular-UniJIS-UTF8-H.
</code></pre></div></div>
<p>オプション -I を使用して
<code class="language-plaintext highlighter-rouge">-I 'C:\Program Files\gs\gs9.54.0\Resource\CIDFont'</code>
のようにすれば CIDFont フォルダを gs の読み込みパスに追加できる。</p>
<h2 id="縦書き指定時の挙動確認">縦書き指定時の挙動確認</h2>
<h3 id="postscript-ファイル">Postscript ファイル</h3>
<p>動作確認用に、次のようなファイルを UTF-8 で test.ps として作成した。
原ノ味フォントを使用して、横書きと縦書きの出力をそれぞれ行っている。
current point の移動を確認するため、
文字列の描画の前後で current point の位置を赤丸として描画している。</p>
<div class="language-postscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">%!PS</span>
<span class="c1">% current point 描画用のコマンド定義</span>
<span class="nv">/DrawCurrentPoint</span> <span class="p">{</span>
<span class="nf">currentrgbcolor</span>
<span class="mf">1</span> <span class="mf">0</span> <span class="mf">0</span> <span class="nb">setrgbcolor</span>
<span class="nb">currentpoint</span> <span class="nb">currentpoint</span> <span class="mf">4</span> <span class="mf">0</span> <span class="mf">360</span> <span class="nb">arc</span> <span class="nb">fill</span> <span class="nb">moveto</span>
<span class="nb">setrgbcolor</span>
<span class="p">}</span> <span class="nb">def</span>
<span class="c1">% 背景を白で描画</span>
<span class="nf">currentrgbcolor</span> <span class="mf">1</span> <span class="nb">setgray</span>
<span class="mf">0</span> <span class="mf">0</span> <span class="mf">960</span> <span class="mf">540</span> <span class="nf">rectfill</span>
<span class="nb">setrgbcolor</span>
<span class="c1">% 横書きの確認</span>
<span class="mf">100</span> <span class="mf">360</span> <span class="nb">moveto</span>
<span class="nf">DrawCurrentPoint</span>
<span class="nv">/HaranoAjiMincho-Regular-UniJIS-UTF8-H</span> <span class="nb">findfont</span> <span class="mf">72</span> <span class="nb">scalefont</span> <span class="nb">setfont</span>
<span class="s">(原ノ味明朝―H。)</span> <span class="nb">show</span>
<span class="nf">DrawCurrentPoint</span>
<span class="c1">% 縦書きの確認</span>
<span class="mf">100</span> <span class="mf">180</span> <span class="nb">moveto</span>
<span class="nf">DrawCurrentPoint</span>
<span class="nv">/HaranoAjiMincho-Regular-UniJIS-UTF8-V</span> <span class="nb">findfont</span> <span class="mf">72</span> <span class="nb">scalefont</span> <span class="nb">setfont</span>
<span class="s">(原ノ味明朝―V。)</span> <span class="nb">show</span>
<span class="nf">DrawCurrentPoint</span>
<span class="nb">showpage</span>
</code></pre></div></div>
<h3 id="コマンドラインオプション">コマンドラインオプション</h3>
<p>gs は次のようにして実行して 960 x 540 の PNG ファイルを作成した。
解像度を 72 dpi (Postscript のデフォルト解像度と同じ) で指定しているので、
ピクセル単位での位置と Postscript での座標が一致する (原点と正負の向きは異なる) ようになっている。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>& 'C:\Program Files\gs\gs9.54.0\bin\gswin64c.exe' -I 'C:\Program Files\gs\gs9.54.0\Resource\CIDFont' -dBATCH -dNOPAUSE -sDEVICE='pngalpha' -g960x540 -r72 -sOutputFile='result.png' test.ps
</code></pre></div></div>
<h3 id="実行結果">実行結果</h3>
<p>実行結果は次のようになった。
上が横書きを指定した場合、
下が縦書きを指定した場合である。</p>
<p><img src="/images/2021-08-21-gs-result.png" alt="実行結果" /></p>
<p>残念ながら、縦書きフォントを指定しても、
横書きのままであった。
ダッシュや句点などに縦書き用のグリフが選択されているので、
全く横書きのままというわけでもない。</p>
<h2 id="まとめ">まとめ</h2>
<p>CID keyed フォントを使用した場合であれば
Ghostscript の縦書きが使用に耐えうるかどうかを原ノ味フォントを使って試してみた。</p>
<p>結果は、縦書きグリフは選択されるものの、
グリフの原点や文字送りの方向は横書きのままで、
使用できるようなものではなかった
(ちなみに TrueType フォントであれば位置ずれはあるものの縦書きにはなる)。</p>
<p>作成したフォントの定義ファイルが間違っていたのか、
使用したフォントの問題か、
Ghostscript 側の問題であるかの切り分けはできていない。</p>ChirimenGhostscript の縦書きサポートは 7.07 を最後に壊れたままになっていると言われているが、 それは TrueType フォントを使うときの話なのか、 CID フォントを使う場合でもそうなのかについては よくわからなかったので、 最新の Ghostscript 9.54 で 確認してみた。