2019年09月22日

LuaHPDF + wxWidgets で日本語 PDF

 「LuaHPDF:日本語の PDF 文書を作る」のつづき。Mac, Windows のクロスプラットフォームにしようと思って、また一苦労した。

 先日のコードでは、フォントのファイルパスを /Library/Fonts/Microsoft/MS Gothic.ttf とハードコーディングしている。これを何とかできないか、せめて「フォント名」を指定して、そこからフォントファイルを探すようにできないかと考えた。しかし、Windows ではこれが非常に難儀だった。どうやら、公式の API は存在しないようで、レジストリを自力で探すか、フォント名からデータを取得してファイルの内容と突き合わせるか、非公開の API を使うか、そんな方法しかないらしい。

 今作成しているプログラムでは、フォントは「MSゴシック」固定でも特に問題はないので、下のように対応することにした。フォントのファイルパスはハードコーディングしておき、見つからなければ埋め込み無しの「MS-Gothic」を使う。

-- プラットフォーム名。"Mac", "Windows" などになる
local platform = string.match(wx.wxGetOsDescription(), "^(%S*)")
local pdf = hpdf.New()
local page = hpdf.AddPage(pdf)
local height = hpdf.Page_GetHeight(page)
local width = hpdf.Page_GetWidth(page)
hpdf.UseJPEncodings(pdf)
local font_name
--  MS ゴシックの ttf/ttc があれば使う、なければ UseJPFonts の MS-Gothic を使う
if platform == "Mac" then
  font_name = hpdf.LoadTTFontFromFile(pdf, "/Library/Fonts/Microsoft/MS Gothic.ttf", 1)
elseif platform == "Windows" then
  font_name = hpdf.LoadTTFontFromFile2(pdf, "c:\\Windows\\Fonts\\msgothic.ttc", 0, 1)
end
if font_name == nil or font_name == "" then
  hpdf.UseJPFonts(pdf)
  font_name = "MS-Gothic"
end

 もう一つ、地味に面倒なのが、UTF-8 文字列を Shift-JIS 文字列に変換する方法。UTF-8 と Shift-JIS の文字の並びには何の関連性もないので、表を引くしかない。車輪の再発明は避けて、それぞれの OS の機能を素直に使う。Lua の関数として実装した。

/*  UTF-8 string to CP932 (Windows Shift-JIS) string  */
static int
utf8_to_sjis(lua_State *L)
{
    size_t len;
    const char *utf8 = lua_tolstring(L, -1, &len);
    if (utf8 == NULL)
        luaL_error(L, "Cannot get string");
    
#if defined(WIN32)
    size_t widelen, sjislen;
    wchar_t *wide;
    char *sjis;
    widelen = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
    wide = calloc(widelen + 1, sizeof(wchar_t));
    if (wide == NULL)
        goto error_alloc;
    if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, widelen + 1) == 0) {
        free(wide);
        goto error_conv;
    }
    sjislen = WideCharToMultiByte(CP_THREAD_ACP, 0, wide, -1, NULL, 0, NULL, NULL);
    sjis = calloc(sjislen + 1, sizeof(char));
    if (sjis == NULL) {
        free(wide);
        goto error_alloc;
    }
    if (WideCharToMultiByte(CP_THREAD_ACP, 0, wide, widelen + 1, sjis, sjislen + 1, NULL, NULL) == 0) {
        free(wide);
        free(sjis);
        goto error_conv;
    }
    lua_pop(L, 1);
    lua_pushlstring(L, sjis, sjislen);
    free(wide);
    free(sjis);
    return 1;
#elif defined(__APPLE__)
    CFStringRef ref = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)utf8, len, kCFStringEncodingUTF8, false);
    if (ref == (CFStringRef)0)
        goto error_conv;
    size_t widelen = CFStringGetLength(ref);
    char *sjis = (char *)calloc(widelen * 2 + 1, sizeof(char));
    if (sjis == NULL) {
        CFRelease(ref);
        goto error_alloc;
    }
    if (!CFStringGetCString(ref, sjis, widelen * 2 + 1, kCFStringEncodingDOSJapanese)) {
        CFRelease(ref);
        free(sjis);
        goto error_conv;
    }
    lua_pop(L, 1);
    lua_pushlstring(L, sjis, strlen(sjis));
    free(sjis);
    CFRelease(ref);
    return 1;
#endif
error_conv:
    luaL_error(L, "Cannot convert");
error_alloc:
    luaL_error(L, "Cannot get string");
    return 0;
}
posted by toshinagata at 12:03| 日記

2019年09月21日

LuaHPDF:日本語の PDF 文書を作る

 「Lua で PDF 作成:LuaHPDF」のつづき。日本語を含む PDF 文書を作るには、次の関数を使う。

  • hpdf.UseJPFonts(): 日本語の「MS明朝」「MSゴシック」「MSP明朝」「MSPゴシック」の4つのフォントを使えるようにする。
  • hpdf.UseJPEncodings(): 日本語の「90ms-RKSJ-H」「90ms-RKSJ-V」「90msp-RKSJ-H」「EUC-H」「EUC-V」エンコーディングを使えるようにする。
package.cpath = "./?.dylib;" .. package.cpath
--  "mysampleあいうえお" をシフトJISで表記
local s = "mysample\\130\\160\\130\\162\\130\\164\\130\\166\\130\\168"
hpdf = require "hpdf"
local pdf = hpdf.New()
local page = hpdf.AddPage(pdf)
local height = hpdf.Page_GetHeight(page)
local width = hpdf.Page_GetWidth(page)
hpdf.UseJPEncodings(pdf)
hpdf.UseJPFonts(pdf)
local font = hpdf.GetFont(pdf, "MS-Gothic", "90ms-RKSJ-H")
hpdf.Page_SetFontAndSize(page, font, 24)
hpdf.Page_BeginText(page)
hpdf.Page_TextOut(page, 60, height - 60, s)
hpdf.Page_EndText(page)
hpdf.SaveToFile(pdf, "jp_from_haru.pdf")
hpdf.Free(pdf)

 できました。

20190921-1.png

 Windows 以外の環境だと「MS……」というフォントは標準搭載ではない。私の環境では、Mac でも Microsoft Office が入れてあるので、「MS……」が後からインストールされている。「MS……」が入っていない環境ではどう見えるか。VirtualBox に Debian を入れて、チェックしてみた。表示しているのは、ファイルビューアーの Evince。小文字の m のスペーシングがおかしい。

20190921-2.png

 代替フォントで何が使われているかは、「プロパティ」で見ることができる。DroidSansFallback だそうです。

20190921-3.png

 埋め込みなしで「MS……」を指定して PDF を作成するのは、「使う人がほぼ必ず『MS……』フォントをインストールしている」という環境では実用的と言える。だけど、そもそも PDF は「どういう環境でも同じように表示できる」ことを期待して使うことが多い。ということは、haruPDF で文書を作成する場合でも、フォントを明示的に指定して、埋め込む方がいい。

 「MSゴシック」を明示して埋め込んでみた。hpdf.UseJPFonts() をやめて、代わりに hpdf.LoadTTFontFromFile() を使う。

package.cpath = "./?.dylib;" .. package.cpath
hpdf = require "hpdf"
--  "mysampleあいうえお" をシフトJISで表記
local s = "mysample\\130\\160\\130\\162\\130\\164\\130\\166\\130\\168"
local pdf = hpdf.New()
local page = hpdf.AddPage(pdf)
local height = hpdf.Page_GetHeight(page)
local width = hpdf.Page_GetWidth(page)
hpdf.UseJPEncodings(pdf)
--hpdf.UseJPFonts(pdf)
--font_name = "MS-Gothic"
local font_name = hpdf.LoadTTFontFromFile(pdf, "/Library/Fonts/Microsoft/MS Gothic.ttf", 1)
local font = hpdf.GetFont(pdf, font_name, "90ms-RKSJ-H")
hpdf.Page_SetFontAndSize(page, font, 24)
hpdf.Page_BeginText(page)
hpdf.Page_TextOut(page, 60, height - 60, s)
hpdf.Page_EndText(page)
hpdf.SaveToFile(pdf, "../jp_from_haru2.pdf")
hpdf.Free(pdf)
print("../jp_from_haru2.pdf created")

 Debian で表示してみた。うまくいきました。

20190921-4.png

 「プロパティ」で確認する。フォントが埋め込みされている。

20190921-5.png

 残念ながら、Mac に標準でインストールされている日本語フォントは、うまく埋め込みできなかった。ヒラギノ系も Osaka も "Unsupported ttf format." というエラーが出る。ヒラギノ系は OpenType なのでしょうがないとしても、TrueType の Osaka は読めて欲しかった。

posted by toshinagata at 16:50| 日記

2019年09月14日

Lua で PDF 作成:LuaHPDF

 wxWidgets アプリを Lua で記述する wxLuaApp。これで PDF を作成したいと思ったのだが、wxWidgets には PDF 関連の API がない。誰か作ってるでしょ、と「Lua PDF」で検索すると、「Lua 関連の PDF 文書」が大量にヒットして、なかなか有益な情報にたどりつかない。PDF 関連の情報をネット検索で探すのは、案外面倒なんですね。

 「Lua PDF create」で探して出てきた、LuaHPDF が良さげだったので、試してみた。

20190914-1.png

 「Haru Free PDF Library」の Lua バインディングらしい。そうか、このライブラリは前に一度調べたことがあったな。オリジナルの作者が日本人なので、日本語対応がちゃんとしている。これは大事なポイントです。

20190914-2.png

 使い方の方針を決めておかないといけない。今回は、Lua スクリプトから見える場所に hpdf.dylib (Mac) または hpdf.dll (Windows) を置いて、hpdf = require "hpdf" で読み込むようにする。動的ライブラリは1個だけにしたいので、libHaru は静的にリンクすることにする。

 まず libHaru と luaHPDF をダウンロードする。

$ curl -L https://github.com/libharu/libharu/archive/RELEASE_2_3_0.zip >libharu-RELEASE_2_3_0.zip
$ unzip libharu-RELEASE_2_3_0.zip
$ curl -L https://github.com/jung-kurt/luahpdf/archive/master.zip >luahpdf-master.zip
$ unzip luahpdf-master.zip

 ここで大事なお知らせ。libHaru は libpng, zlib に依存します。Mac では、zlib はシステム標準のものを使う。libpng は X11 関連のライブラリにあるが、インストールされていない可能性もあるので、自前でビルドして静的リンクする。Windows では、両方自前でビルドすることにする。libHaru, luaHPDF と同じ階層に、libpng_zlib というディレクトリを作って、その中に zlib, libpng をインストールする。

$ mkdir libpng_zlib
$ cd libpng_zlib
$ curl -L https://download.sourceforge.net/libpng/libpng-1.6.37.tar.gz >libpng-1.6.37.tar.gz
$ tar xzf libpng-1.6.37.tar.gz
$ curl -L https://www.zlib.net/zlib-1.2.11.tar.gz >zlib-1.2.11.tar.gz
$ tar xzf zlib-1.2.11.tar.gz

 Mac 用のビルド。libpng を libpng_zlib/build-osx にインストールする。SDK として /Developer/SDKs/MacOSX10.6.sdk を指定する。--disable-shared--enable-static を指定して、静的ライブラリのみ作成する。

cd libpng-1.6.37
$ CFLAGS="-isysroot /Developer/SDKs/MacOSX10.6.sdk" CPPFLAGS="$CFLAGS" ./configure --prefix=$PWD/../build-osx --disable-shared --enable-static
$ make
$ make install

 libHaru のビルド。これも静的ライブラリのみ作成する。

$ cd ../../libharu-RELEASE_2_3_0
$ export SDK=/Developer/SDKs/MacOSX10.6.sdk; CFLAGS="-isysroot $SDK" ./configure --prefix=$PWD/build-osx --disable-shared --enable-static --with-sysroot=$SDK --with-png=$PWD/../libpng_zlib/build-osx --with-zlib=$SDK/usr
$ make
$ make install

 luaHPDF のビルド。ファイル1個だけなので、Makefile を書くより手打ちの方が早い。LuaJIT は、wxLuaApp の中でビルドしたものをリンクする。

$ cd ../luahpdf-master
$ gcc -isysroot /Developer/SDKs/MacOSX10.6.sdk -I../wxLuaApp/LuaJIT-2.0.5/src -I../libpng_zlib/build-osx/include -I../libharu-RELEASE_2_3_0/build-osx/include -Wall -O2 -fomit-frame-pointer -fPIC -c -o hpdf.o hpdf.c
$ gcc -shared -fPIC -isysroot /Developer/SDKs/MacOSX10.6.sdk -o hpdf.dylib hpdf.o -L../wxLuaApp/build-xcode/build/lib -L../libpng_zlib/build-osx/lib -L../libharu-RELEASE_2_3_0/build-osx/lib -lhpdf -lz -lpng -lm -lluajit-5.1

 できた!

$ ls -l *.dylib
-rwxr-xr-x  1 nagata   staff  1176140  8 29 23:28 hpdf.dylib

 試してみる。

package.cpath = "./?.dylib;" .. package.cpath
hpdf = require "hpdf"
local pdf = hpdf.New()
local page = hpdf.AddPage(pdf)
local height = hpdf.Page_GetHeight(page)
local width = hpdf.Page_GetWidth(page)
local font = hpdf.GetFont(pdf, "Helvetica")
hpdf.Page_SetFontAndSize(page, font, 24)
hpdf.Page_BeginText(page)
hpdf.Page_TextOut(page, 60, height - 60, "Hello from Haru")
hpdf.Page_EndText(page)
hpdf.SaveToFile(pdf, "hello.pdf")
hpdf.Free(pdf)

 できました。

20190914-3.png

 次は Windows (64 bit) のビルド。zlib でちょっと手こずった。

$ cd path/to/libpng_zlib
$ cd zlib-1.2.11
$ CHOST=x86_64-w64-mingw32 ./configure --prefix=$PWD/../build-win --static
...
Please use win32/Makefile.gcc instead.

 えー、大きなお世話じゃ。win32/Makefile.gcc は 32bit 限定なので、64bit 版はビルドできない。仕方がないので、configure に手を入れた。

  MINGW* | mingw*)
# temporary bypass  次の3行をコメントアウト。
        #rm -f $test.[co] $test $test$shared_ext
        #echo "Please use win32/Makefile.gcc instead." | tee -a configure.log
        #leave 1

 再トライ。今度はうまくいった。

$ CHOST=x86_64-w64-mingw32 ./configure --prefix=$PWD/../build-win --static
$ make
$ make install

 次は libpng。CFLAGSCPPFLAGS を両方指定しないといけない。気づくのに時間がかかった…

$ cd ../libpng-1.6.34
$ CFLAGS="-L$PWD/../build-win/lib" CPPFLAGS="-I$PWD/../build-win/include" ./configure --prefix=$PWD/../build-win --host=x86_64-w64-mingw32 --disable-shared --enable-static
$ make
$ make install

 libHaru は特に問題なし。

$ cd ../../libharu-RELEASE_2_3_0
$ ./configure --prefix=$PWD/build-win --host=x86_64-w64-mingw32 --disable-shared --enable-static --with-png=$PWD/../libpng_zlib/build-win --with-zlib=$PWD/../libpng_zlib/build-win
$ make
$ make install

 luaHPDF のビルド。前に書いた通り、fopen をマルチバイト対応のものに置き換える必要があるので、win_fopen.o を作成してリンクする。

$ x86_64-w64-mingw32-gcc -I../wxLuaApp/LuaJIT-2.0.5/src -I../libpng_zlib/build-win/include -I../libharu-RELEASE_2_3_0/build-win/include -Wall -O2 -fomit-frame-pointer -fPIC -c -o hpdf.o hpdf.c
$ x86_64-w64-mingw32-gcc -I../wxLuaApp/LuaJIT-2.0.5/src -I../libpng_zlib/build-win/include -I../libharu-RELEASE_2_3_0/build-win/include -Wall -O2 -fomit-frame-pointer -fPIC -c -o win_fopen.o win_fopen.c
$ x86_64-w64-mingw32-gcc -shared -fPIC -o hpdf.dll hpdf.o win_fopen.o -L../wxLuaApp/build-win/build/lib -L../libpng_zlib/build-win/lib -L../libharu-RELEASE_2_3_0/build-win/lib -lhpdf -lpng -lz -lm -llua51

 できた。

$ ls -l *.dll
-rwxr-xr-x  1 nagata   staff  1926112  9 11 21:57 hpdf.dll

 日本語 PDF を作成するには、Lua の文字列を Shift-JIS (CP932 = Microsoft Code Page 932) に変換する必要がある。libHaru は日本語エンコーディングとして CP932 か EUC しか対応してないためである(libharu:Encodings)。この件は後日。

posted by toshinagata at 22:44| 日記
email.png
Powered by さくらのブログ