Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8899

PDFの文字列オブジェクトの詳細仕様をAdobeReaderDCで確認してみた

$
0
0

PDFファイル内部のリテラル文字列表現について

今回の対象は、丸括弧でくくられた文字列オブジェクトです。
こちらの解説が分かりやすいと思います。
なので、下記では説明をさぼっています。

確認環境

ヘッダに%PDF-1.4を指定して、Adobe Reader DC (ver 2019.021.20056)で動作を確認。

PDFの作成は
https://qiita.com/kob58im/items/8474bfd37a5bd464172c
をもとに実施。

確認したこと:エスケープシーケンス

\075のようにエスケープ文字\と直後に続く1~3桁の8進数で、任意の1byteを表現できる仕様がある。
公式の7.3.4.2章の例では、(\53)\53\053とみなされ、+になるとあるが、直後が終端以外だとどうなるかが明記されていないように見える。1

試した結果:

PDFファイル上の文字列:(Test\075Test\75Test\758Test\75)

表示された文字列:Test=Test=Test=8Test=

結論:3文字以内に0~7以外が来たら、その時点で8進数として解釈するようである。2
なお、8bitを超えるbitは無視するとの仕様記載があり、\505\105とみなされる。

リテラル文字列を解析するサンプルソース

こんな感じでよいはず。

※注意:作成中のツールのコードから切り出してきたのでコンパイル確認はしていません。

publicstaticbyte[]ReadString(Streamst){byte[]ret=newbyte[0];intnCount=0;// Count of open '('boolescape=false;intb=st.ReadByte();if(b!='('){if(b>=0){// EOFでなければ1文字戻すst.Position--;}returnnull;}nCount++;while(nCount>0){b=st.ReadByte();if(b<0){// EOFreturnnull;}if(!escape){switch(b){case'(':nCount++;ArrayAppend(refret,(byte)b);break;case')':nCount--;if(nCount>0){ArrayAppend(refret,(byte)b);}break;case'\\':escape=true;break;default:ArrayAppend(refret,(byte)b);break;}}else{escape=false;if('0'<=b&&b<='7'){// \000 - \777 (8bitの最大値は\377だが、overflowを無視することとあるので\777を受け取ることとする)// (\53a) の扱いが明確には定義されていないが、(\53)を\053と扱えとあるので、(\53a)は"+a"とみなすこととするintb2=st.ReadByte();if(b2<'0'||'7'<b2){// out of range// 1byte戻して、とれたとこまでを解釈して次の文字に移るst.Position--;ArrayAppend(refret,OctNum(0,0,b));continue;}intb3=st.ReadByte();if(b3<'0'||'7'<b3){// 1byte戻して、とれたとこまでを解釈して次の文字に移るst.Position--;ArrayAppend(refret,OctNum(0,b,b2));continue;}ArrayAppend(refret,OctNum(b,b2,b3));}else{switch(b){case't':ArrayAppend(refret,(byte)'\t');break;case'r':ArrayAppend(refret,(byte)'\r');break;case'n':ArrayAppend(refret,(byte)'\n');break;case'b':ArrayAppend(refret,(byte)'\b');break;case'f':ArrayAppend(refret,(byte)'\f');break;case'\r':// '\r'を読み捨てる{intb2=st.ReadByte();// "\r\n"を読み捨てるif(b2!='\n'){st.Position--;}// '\r'だけだったら1byte戻す}break;case'\n':break;// '\n'を読み捨てるdefault:ArrayAppend(refret,(byte)b);break;}}}}returnret;}staticbyteOctNum(intb,intb2,intb3){return(byte)(((b-'0')<<6)|((b2-'0')<<3)|(b3-'0'));}staticvoidArrayAppend<T>(refT[]a,TnewItem){intn=a.Length;Array.Resize(refa,n+1);a[n]=newItem;}

おまけ:16進文字列

<>で16進文字列をくくると、同様に文字列オブジェクトとして扱われる。

PDFファイル上の文字列:<35353 53 5 353\n 5353\t5355>に対し、
表示された文字列:555555555Pとなった。
(35 35 35 35 35 35 35 35 35 50 と解釈されている。)
間に空白文字があっても2桁ずつ取るわけではないので注意が必要。

また、1文字だけでも成立するようである。

PDFファイル上の文字列:<A> (0x0A = Lf)
表示された文字列: (表現できない文字として四角形が描画された。)

0文字のケースについて
<>に対しては当然ながら表示はされず、Adobe Reader DC上、開いてズームイン/アウトなどの操作をしてもエラーとはならなかったため、おそらく有効なデータ(空文字列)として扱われていると思う。

16進文字列を解析するサンプルソース

publicstaticbyte[]ReadHexString(Streamst){byte[]ret=newbyte[0];intb=st.ReadByte();if(b!='<'){if(b>=0){st.Position--;}// EOFでなければ1文字戻すreturnnull;}while(true){do{b=st.ReadByte();}while(IsWhiteSpace(b));// skip whitespacesif(b=='>'){returnret;}intupperNibble=AsHexDigit(b);if(upperNibble<0){returnnull;}do{b=st.ReadByte();}while(IsWhiteSpace(b));// skip whitespacesif(b=='>'){// 奇数桁で終わっている場合は末尾に'0'を補填する( = (上位nibble<<4) + 0)ArrayAppend(refret,(byte)(upperNibble<<4));returnret;}intlowerNibble=AsHexDigit(b);if(lowerNibble<0){returnnull;}ArrayAppend(refret,(byte)((upperNibble<<4)|lowerNibble));}}staticboolIsWhiteSpace(intc){return(c==0||c==9||c==10||c==12||c==13||c==32);}staticboolIsHexDigit(intc){return(('0'<=c&&c<='9')||('A'<=c&&c<='F')||('a'<=c&&c<='f'));}staticintAsHexDigit(intc){if('0'<=c&&c<='9'){returnc-'0';}if('A'<=c&&c<='F'){return(c-'A')+10;}if('a'<=c&&c<='f'){return(c-'a')+10;}return-1;}

  1. 1~3桁という記載はあるが、3桁になるようにleading 0をつけろという記載もある。 

  2. Acrobat Reader DCでの結果であり、3rd party製のツールがどこまでまじめにやっているかは不明。 


Viewing all articles
Browse latest Browse all 8899

Trending Articles