2009/05/10

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

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

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

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

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

(1)ReplaceEncodingCharsについて

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

  1. Public Function ReplaceEncodingChars(Str1 As StringAs String  
  2. '---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---  
  3. On Error GoTo ErrPtnr_OnError  
  4. '---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---  
  5. 50010  Dim i As Integer, tStr As String  
  6. 50011  Dim bStr() As Byte  
  7. 50020  tStr = ""  
  8. 50030  ' First we look for oct encoding chars  
  9. 50040  For i = 127 To 255  
  10. 50050   Str1 = Replace$(Str1, "\" & Oct$(i), Chr$(i))  
  11. 50060  Next i  
  12. 50070  ReplaceEncodingChars = Str1  
  13. 50080  ' Second we look for hex encoding chars  
  14. 50090  If Len(Str1) >= 4 Then  
  15. 50100   If Mid$(Str1, 1, 1) = "<" And Mid$(Str1, Len(Str1), 1) = ">" Then  
  16. 50110    If Len(Str1) Mod 2 = 0 Then  
  17. 50111     ReDim bStr((Len(Str1) - 2) / 2)  
  18. 50120     For i = 2 To Len(Str1) - 1 Step 2  
  19. 50130      If IsNumeric("&H" & Mid$(Str1, i, 2)) = True Then  
  20. 50140        If CByte("&H" & Mid$(Str1, i, 2)) > 255 Then  
  21. 50150          Exit Function  
  22. 50160         Else  
  23. '50170     tStr = tStr & Chr$(CByte("&H" & Mid$(Str1, i, 2)))  
  24. 50170          bStr((i - 2) / 2) = CByte("&H" & Mid$(Str1, i, 2))  
  25. 50180        End If  
  26. 50190       Else  
  27. 50200        Exit Function  
  28. 50210      End If  
  29. 50220     Next i  
  30. 50221     tStr = StrConv(bStr, vbUnicode)  
  31. 50230    End If  
  32. 50240   End If  
  33. 50250  End If  
  34. 50260  If Len(tStr) > 0 Then  
  35. 50270   ReplaceEncodingChars = tStr  
  36. 50280  End If  
  37. '---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---  
  38. Exit Function  
  39. ErrPtnr_OnError:  
  40. '---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---  
  41. End Function  

ReplaceEncodingCharsの役割としては、

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

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

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

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

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

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

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

(2)EncodeCharsについて

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

  1. Public Function EncodeChars(ByVal CodePage As eCodePage, ByVal Str1 As StringAs String ' UTF-16, UTF-8 conversion  
  2. '---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---  
  3. On Error GoTo ErrPtnr_OnError  
  4. '---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---  
  5. 50010  Dim i As Long, tStr As String, Size As Long, buffer() As Byte, c As Long, tL As Long  
  6. 50020  
  7. 50030  If LenB(Str1) = 0 Then  
  8. 50040   Exit Function  
  9. 50050  End If  
  10. 50061  Select Case CodePage  
  11.         Case eCodePage.CP_NoEncoding  
  12. 50080    EncodeChars = "(" & Str1 & ")"  
  13. 50090   Case eCodePage.CP_UTF8  
  14. 50100    For i = 1 To Len(Str1)  
  15. 50110     c = AscW(Mid$(Str1, i, 1))  
  16. 50121     Select Case c  
  17.            Case 0 To &H7F&  
  18. 50140       tStr = tStr & String(2 - Len(Hex(c)), "0") & Hex(c)  
  19. 50150      Case &H80& To &H7FF&  
  20. 50160       tL = &HC0& Or ((c And &H3FC0&) \ &H40&)  
  21. 50170       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)  
  22. 50180       tL = &H80& Or (c And &H3F&)  
  23. 50190       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)  
  24. 50200      Case &H800& To &HFFFF&  
  25. 50210       tL = &HE0& Or ((c And &HF000&) \ &H1000&)  
  26. 50220       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)  
  27. 50230       tL = &H80& Or ((c And &HFC0&) \ &H40&)  
  28. 50240       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)  
  29. 50250       tL = &H80& Or (c And &H3F&)  
  30. 50260       tStr = tStr & String(2 - Len(Hex(tL)), "0") & Hex(tL)  
  31. 50270     End Select  
  32. 50280    Next i  
  33. 50290    EncodeChars = "<BFBBEF" & tStr & ">"  
  34. 50540   Case eCodePage.CP_UTF16  
  35. 50550    For i = 1 To Len(Str1)  
  36. 50560     c = AscW(Mid$(Str1, i, 1))  
  37. '50570     tStr = tStr & String(4 - Len(Hex(c)), "0") & Hex(c)  
  38. 50570     tStr = tStr & Right("0000" & Hex(c), 4)  
  39. 50580    Next i  
  40. 50590    EncodeChars = "<FEFF" & tStr & ">"  
  41. 50600  End Select  
  42. '---ErrPtnr-OnError-START--- DO NOT MODIFY ! ---  
  43. Exit Function  
  44. ErrPtnr_OnError:  
  45.   
  46. '---ErrPtnr-OnError-END--- DO NOT MODIFY ! ---  
  47. 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で変換しているところがポイント???)、平日、またちょっとずつ見比べて見たいと思います。(・・)ゞ