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;}