defmodule Pdf.Stream do @moduledoc false defstruct compress: 6, size: 0, dictionary: %{}, content: [] import Pdf.Size import Pdf.Utils alias Pdf.Dictionary @stream_start "\nstream\n" @stream_end "endstream" def new(opts \\ []), do: init(opts, %__MODULE__{}) defp init([], stream), do: stream defp init([{:compress, true} | tail], stream), do: init(tail, %{stream | compress: 6}) defp init([{:compress, compress} | tail], stream), do: init(tail, %{stream | compress: compress}) defp init([{:dictionary, dictionary} | tail], stream), do: init(tail, %{stream | dictionary: dictionary}) defp init([_ | tail], stream), do: init(tail, stream) def push(stream, {:command, _} = command) do # +1 because we are adding a newline size = size_of(command) + 1 %{stream | size: stream.size + size, content: ["\n", command | stream.content]} end def push(stream, command), do: push(stream, c(command)) def set(stream, content) when is_binary(content) do size = size_of(content) %{stream | size: size, content: [content]} end def to_iolist(%{compress: false} = stream) do dictionary = Dictionary.new(Map.merge(stream.dictionary, %{"Length" => stream.size})) Pdf.Export.to_iolist([ dictionary, @stream_start, Enum.reverse(stream.content), @stream_end ]) end def to_iolist(%{compress: level} = stream) do compressed = stream.content |> Enum.reverse() |> Pdf.Export.to_iolist() |> compress(level) dictionary = Dictionary.new( Map.merge(stream.dictionary, %{ "Length" => byte_size(compressed), "Filter" => n("FlateDecode") }) ) Pdf.Export.to_iolist([ dictionary, @stream_start, compressed, @stream_end ]) end defp compress(iodata, level) do z = :zlib.open() :zlib.deflateInit(z, level) compressed = :zlib.deflate(z, iodata, :finish) :zlib.deflateEnd(z) IO.iodata_to_binary(compressed) end defimpl Pdf.Export do def to_iolist(stream), do: Pdf.Stream.to_iolist(stream) end end