「LuaHPDF:日本語の PDF 文書を作る」のつづき。Mac, Windows のクロスプラットフォームにしようと思って、また一苦労した。
先日のコードでは、フォントのファイルパスを /Library/Fonts/Microsoft/MS Gothic.ttf
とハードコーディングしている。これを何とかできないか、せめて「フォント名」を指定して、そこからフォントファイルを探すようにできないかと考えた。しかし、Windows ではこれが非常に難儀だった。どうやら、公式の API は存在しないようで、レジストリを自力で探すか、フォント名からデータを取得してファイルの内容と突き合わせるか、非公開の API を使うか、そんな方法しかないらしい。
- 「フォント名からフォントファイル名(*.ttf)を取得するには?」
- 「Get a font filename based on the font handle (HFONT)」
- 「C#, Powershell, undocumeted WinApi function GetFontResourceInfoW」
今作成しているプログラムでは、フォントは「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;
}