2009/05/10

PDFCreatorの日本語問題-PDF情報編

「PDFCreatorの文字化け問題-修正箇所発見」のやすさんのコメント欄からの記事になります。

PDFCreatorは家使いなのでそこまで使いきっている感じでもなく、これまで私は素通りしていましたが。PDFCreatorは印刷時のPDF情報を設定する際、日本語が混じっているとその部分はスルーする、という現象があるのですね。日本語を入れるとはじかれる、無視される…。

私の場合、家にはVB6.0環境がないので、とりあえず、やすさんのコメント欄とソースコードを頼りにロジックのみ考えてみました。

ちなみに問題のモジュールを含む本家オリジナルのソースはこちら

(1)ReplaceEncodingCharsについて

やすさんの修正案のコードの全貌。

Public Function ReplaceEncodingChars(Str1 As String) As String
'---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---
On Error GoTo ErrPtnr_OnError
'---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---
50010  Dim i As Integer, tStr As String
50011  Dim bStr() As Byte
50020  tStr = ""
50030  ' First we look for oct encoding chars
50040  For i = 127 To 255
50050   Str1 = Replace$(Str1, "\" & Oct$(i), Chr$(i))
50060  Next i
50070  ReplaceEncodingChars = Str1
50080  ' Second we look for hex encoding chars
50090  If Len(Str1) >= 4 Then
50100   If Mid$(Str1, 1, 1) = "<" And Mid$(Str1, Len(Str1), 1) = ">" Then
50110    If Len(Str1) Mod 2 = 0 Then
50111     ReDim bStr((Len(Str1) - 2) / 2)
50120     For i = 2 To Len(Str1) - 1 Step 2
50130      If IsNumeric("&H" & Mid$(Str1, i, 2)) = True Then
50140        If CByte("&H" & Mid$(Str1, i, 2)) > 255 Then
50150          Exit Function
50160         Else
'50170     tStr = tStr & Chr$(CByte("&H" & Mid$(Str1, i, 2)))
50170          bStr((i - 2) / 2) = CByte("&H" & Mid$(Str1, i, 2))
50180        End If
50190       Else
50200        Exit Function
50210      End If
50220     Next i
50221     tStr = StrConv(bStr, vbUnicode)
50230    End If
50240   End If
50250  End If
50260  If Len(tStr) > 0 Then
50270   ReplaceEncodingChars = tStr
50280  End If
'---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---
Exit Function
ErrPtnr_OnError:
'---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---
End Function

ReplaceEncodingCharsの役割としては、

  1. 引数には、バイナリの文字列が渡される。
     たとえば「<82E282B782B382F182B182F182CE82F182CD8142>」こんな文字列を引き渡される。
  2. 返り値としては、日本語の文字列を返せばOK。

という関数なわけですね?(コードから推測…)

だとすると、やすさんの修正案通り、バイトの配列を一端作る流れが妥当だと私も思います。

いまのところ、敢えて修正案を出すなら

50130      If IsNumeric("&H" & Mid$(Str1, i, 2)) = True Then
50140        If CByte("&H" & Mid$(Str1, i, 2)) > 255 Then
50150          Exit Function
50160         Else

あたりの分岐はコメントアウトしちゃっていいんじゃないの?と思ったくらいでしょうか。「なんかもー、どうせバイト変換できないときに抜けちゃうなら、エラー処理でいいんじゃないの?」と思うので…。

それにしてもオリジナルのコードは明らかにおかしいですよね。2桁ずつゲットして文字変換されても、マルチバイトな言語では明らかに困るだろう。(;´Д`)

(2)EncodeCharsについて

やすさんの修正案のコードの全貌。

Public Function EncodeChars(ByVal CodePage As eCodePage, ByVal Str1 As String) As String ' UTF-16, UTF-8 conversion
'---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---
On Error GoTo ErrPtnr_OnError
'---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---
50010  Dim i As Long, tStr As String, Size As Long, buffer() As Byte, c As Long, tL As Long
50020
50030  If LenB(Str1) = 0 Then
50040   Exit Function
50050  End If
50061  Select Case CodePage
        Case eCodePage.CP_NoEncoding
50080    EncodeChars = "(" & Str1 & ")"
50090   Case eCodePage.CP_UTF8
50100    For i = 1 To Len(Str1)
50110     c = AscW(Mid$(Str1, i, 1))
50121     Select Case c
           Case 0 To &H7F&
50140       tStr = tStr & String(2 - Len(Hex(c)), "0") & Hex(c)
50150      Case &H80& To &H7FF&
50160       tL = &HC0& Or ((c And &H3FC0&) \ &H40&)
50170       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)
50180       tL = &H80& Or (c And &H3F&)
50190       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)
50200      Case &H800& To &HFFFF&
50210       tL = &HE0& Or ((c And &HF000&) \ &H1000&)
50220       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)
50230       tL = &H80& Or ((c And &HFC0&) \ &H40&)
50240       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)
50250       tL = &H80& Or (c And &H3F&)
50260       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)
50270     End Select
50280    Next i
50290    EncodeChars = "<BFBBEF" & tStr & ">"
50540   Case eCodePage.CP_UTF16
50550    For i = 1 To Len(Str1)
50560     c = AscW(Mid$(Str1, i, 1))
'50570     tStr = tStr & String(4 - Len(Hex(c)), "0") & Hex(c)
50570     tStr = tStr & Right("0000" & Hex(c), 4)
50580    Next i
50590    EncodeChars = "<FEFF" & tStr & ">"
50600  End Select
'---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---
Exit Function
ErrPtnr_OnError:

'---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---
End Function

引数でエンコーディング、及び文字列を渡して、引数で決めたエンコーディングの16進を返すファンクションですね(eCodePageはEnumで、CP_NoEncoding/CP_UTF8/CP_UTF16の三択です)。

AscWが負の値のとき(たとえば「髙」とか)、オリジナルのコードだとHex(c)に「FFFF9AD9」などアタマ4桁に余計なコードがついて16進数を返せなくなくなりますね。36行目を

50560 c = CLng("&H" & Hex(AscW(Mid$(Str1, i, 1))))

このように修正すれば、どうにか…、という感じですけど。

やすさんのRight関数を使っての4桁取得が妥当ですよね。エレガントだし、これはこれで完成っぽいですね。


やすさんへ

いろいろネタ的には初めて尽くしで、すごく勉強になりました!ありがとうございます。

(1)の方は、根本的にこれ以上のロジックが私のアタマで浮かぶか怪しいですが、しばらく考えてみます。

もともとどこかでbyteの配列をゲットしているチャンスがあるんだから、そのままどーにかならなかったの?とか思わなくもありませんが。そこまで手を広げていくと、収拾つかなくなっていきそうですよね…。

いやー、それにしてもこうしてみてみると、つくづくと面白いですね!(←だんだんノってきた) +。:.゚ヽ(*´∀`)ノ゚.:。+゚

4 件のコメント:

やす さんのコメント...

こんばんわ、じょに さん。
PDFCreatorも昔(0.9.2?)と比べると、Vista対応のためか、かなり変更されているようですが、ところどころ?と思う処理が残ってますね。
修正しだすと、止め処なく修正が必要になりそうなので、そこはグッとガマンしています。
これからも じょに さんの記事を楽しく拝見させて頂きます。

じょに さんのコメント...

Vista対応ですか。PDFCreatorは、このままVB6.0で目指すということなんでしょうかねー。

ところで日本語対応ですが、やすさんの修正案を反映させて動かしてみたところ、
タイトル等はキレイに日本語表示でフォームに入るようになりましたが、
いざ[保存]ボタンを押すと、保存できませんよね?
[保存]を押した後、イマイチどこで抜けちゃうのか追いきれず & PDFについての知識もかなり浅いので、その辺の知識も漁る必要がありそうな予感が…。
.NetではActiveReportを使ったり、iText.NETをいじったりしたことはあるものの、そのテのもので「お任せ」状態で実は「そもそも」がわかっていなかったりするので。
道のりは遠い…。(^^;)

やす さんのコメント...

じょに さん、こんにちは。
日本語で保存できないとのこと、こちらでは問題なく保存できているのですが?
もしかしたら modPDF.ReplaceEncodingChars で8進数指定の考慮(修正)漏れが見つかったので、文字に依存しているのかもしれません。こちらの修正は20行程度追加してしまいました。
どのような文字を指定した場合に保存できないのか教えて頂いたら調べてみます。

一時的ですが、修正版を置きました。
https://www.webfile.jp/dl.php?i=533572&s=64da0d2645003a937846

PDFCreator_0516.zip 834,618byte
(MD5 : 91ae1b27a964be1cfdbd111b286e445f)
Version 0.9.8_090516.zip 4,211,740byte
(MD5 : db8944afaa2e672b31d9f66a8a473060)

じょに さんのコメント...

やすさん、こんばんわ。
前回の修正のみでは、タイトルに日本語は表示されるものの、「保存」をクリックすると、もう一度、最初の画面が再表示され、保存できなかったのです。日本語を少しでも混ぜて[保存]をクリック→再表示するだけ、の永久ループだったわけですね。アルファベット英数のみの文字列にするまでこのループからは抜け出せませんでした。
でも今回やすさんがアップロードしてくれたexeだと確かにうまく動きますね(DLしました。ありがとうございます)。すごーい!!
修正コードも例によってエディタで確認しました。
8進数の変換部分をを全面的に書き変えたわけですね。現状、引数のStr1に入るバリエーションが把握しきれていなくて、イマヒトツ、ピンときていなかった私はこの部分から目をそらしていたわけですが…。(^^;
保存できない永久ループの原因はここにあったのですね。
元ネタと修正案の差は、入り口と出口の関係がよくわかっていないのでピンときていないところがあるのですが(修正案はUnicodeで変換しているところがポイント???)、平日、またちょっとずつ見比べて見たいと思います。(・・)ゞ