# Stream encoding

Pdf streams can be encoded using one or more filters specified by pdf specification. The encoding/decoding algorithm is called a filter. There are two types of filters

- ASCII filters - enable decoding of arbitrary 8-bit binary data that has been encoded as ASCII text

- (de)compression filters - enable decoding of data that has been compressed

There is a set of filters which every pdf viewer must implement. It is up to the viewer how it handles an unknown filter.

## Xpdf filters

Xpdf viewer implements all decoding filters from pdf specification v1.5. However it does not implement any encoding filters (it does not need them).

Simple xpdf stream hierarchy

Simple xpdf stream hierarchy

Xpdf stream design is good but can be improved. The design resulted into many objects that are tightly coupled together (see picture). They can be decoupled using which also makes objects more reusable.

Finally, the unclear implementation makes it difficult to use and very difficult to extend and it lacks almost any fault tolerance. We use xpdf filters (because it is hardwired into xpdf parsing) only to decode data but we design our own encoding filters.

## Encoding filter design

Our implementation is based on simple observation that we don't have to bother with stream data encoding until we actualy need to write them to the file. In the meantime we can use unencoded data in their raw format. This way we can prevent from useless encoding/decoding when modifying data several times.

Therefore we have moved whole encoding to the code writing code path which is under responsibility of IPdfWriter class implementation which uses FilterStreamWriter class to provide proper implemenation for data encoding. OldStylePdfWriter (current implementation of the IPdfWriter) uses FilterStreamWriter factory interface (see below) to get proper FilterStreamWriter implementation which is used for stream object writing.

To provide easy-to-use and transparent interface we are using the following hierarchy and class relations:

FilterStreamWriter

- provides factory method getInstance which returns proper filter writer instance based on the given stream object
- stream writers can be registered in runtime by registerFilterStreamWriter method
- if getInstance is not able to find any suitable writer for a given object, it will try default writer
- if even default doesn't support given object, it will fall back to the NullFilterStreamWriter which writes raw data without any filters.

Default filter stream writer is useful for give-at-least some filter if the required one is not available (e.g. LZWDecode can be replaced by FlateDecode or stack of filters can be replaced by only one of them).

Finally, each filter stream implementation is required to provide:

- supportObject - it understands how to write filtered data specified by object.
- compress - write data from given object to the stream. Note that this method can use different filters than specified in the object, but it definitely has to write valid object (e.g. stack of filters can be reduced or one filter can be replaced by another).

This design is flexible, easily extensible as we only have to implement the new filter implemenation (FilterStreamWriter descendants) and register them with the FilterStreamWriter::registerFilterStreamWriter method. Each FilterStreamWriter implementation knows which filters it is able to provide and annouces this information by the FilterStreamWriter::supportObject method. Finally PdfWriter will simply pick up writer which supports current object and writes its content by the FilterStreamWriter::compress method.