lib/pdf/image.ex

defmodule Pdf.Image do
  @moduledoc false

  defstruct bits: nil,
            width: nil,
            height: nil,
            color_type: nil,
            size: nil,
            data: nil,
            dictionary: nil

  import Pdf.Size

  @stream_start "\nstream\n"
  @stream_end "endstream\n"

  def new({:binary, image_data}, objects) do
    case identify_image(image_data) do
      :jpeg ->
        {Pdf.Images.JPEG.prepare_image(image_data), objects}

      :png ->
        Pdf.Images.PNG.prepare_image(image_data, objects)

      _else ->
        {:error, :image_format_not_recognised}
    end
  end

  def new(image_path, objects) do
    new({:binary, File.read!(image_path)}, objects)
  end

  def identify_image(<<255, 216, _rest::binary>>), do: :jpeg
  def identify_image(<<137, 80, 78, 71, 13, 10, 26, 10, _rest::binary>>), do: :png
  def identify_image(_), do: {:error, :image_format_not_recognised}

  def size(%__MODULE__{size: size, dictionary: dictionary}) do
    size_of(dictionary) + size + byte_size(@stream_start <> @stream_end)
  end

  def to_iolist(%__MODULE__{data: data, dictionary: dictionary}) do
    Pdf.Export.to_iolist(
      Enum.filter(
        [
          dictionary,
          @stream_start,
          data,
          @stream_end
        ],
        & &1
      )
    )
  end

  defimpl Pdf.Size do
    def size_of(%Pdf.Image{} = image), do: Pdf.Image.size(image)
  end

  defimpl Pdf.Export do
    def to_iolist(%Pdf.Image{} = image), do: Pdf.Image.to_iolist(image)
  end
end