【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

FlateDecodeと差分圧縮

PDFで/Filter /FlateDecodeが指定されているストリームはdeflate圧縮されています。.NETのDeflateStreamで展開しようとしてもエラーになりますが、最初の2バイトを飛ばすと読み込めます。

/DecodeParmsが指定されていると更に差分圧縮されています。/XRefやPNGなどで使用されているようです。

このようなストリームを展開するため、DeflateStreamをラップしたクラスを作成しました。パブリックドメインで置いておきます。ちなみに差分圧縮つながりでADPCMをいじったことがあります。 ⇒ id:n7shi:20090329

public class PdfDeflateStream : Stream
{
    private DeflateStream ds;
    int columns, position, rowpos;
    byte[] prev, rows;

    public PdfDeflateStream(Stream s, int columns)
    {
        this.columns = columns;
        if (columns > 0)
        {
            prev = new byte[columns];
            rows = new byte[columns];
            rowpos = rows.Length;
        }
        s.ReadByte();
        s.ReadByte();
        ds = new DeflateStream(s, CompressionMode.Decompress);
    }

    public override long Length
    {
        get { throw new NotImplementedException(); }
    }
    public override bool CanRead { get { return true; } }
    public override bool CanWrite { get { return false; } }
    public override bool CanSeek { get { return false; } }
    public override void Flush() { }

    public override long Position
    {
        get { return position; }
        set { throw new NotImplementedException(); }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int ret = 0;
        if (columns == 0)
            ret = ds.Read(buffer, offset, count);
        else
        {
            while (ret < count)
            {
                if (rowpos >= rows.Length)
                {
                    if (ds.ReadByte() != 2)
                        throw new Exception("unknown predictor type");
                    Array.Copy(rows, prev, rows.Length);
                    int len = ds.Read(rows, offset, rows.Length);
                    if (len < rows.Length) break;
                    for (int i = 0; i < rows.Length; i++)
                        rows[i] += prev[i];
                    rowpos = 0;
                }
                int rlen = Math.Min(count - ret, rows.Length - rowpos);
                Array.Copy(rows, rowpos, buffer, ret, rlen);
                ret += rlen;
                rowpos += rlen;
            }
        }
        position += ret;
        return ret;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }
}