lib/exiffer/png.ex

defmodule Exiffer.PNG do
  @moduledoc """
  Documentation for `Exiffer.PNG`.
  """

  require Logger

  alias Exiffer.{Binary, Buffer}
  alias Exiffer.PNG.Chunk
  import Exiffer.Logging, only: [integer: 1]

  @enforce_keys ~w(chunks)a
  defstruct ~w(chunks)a

  @magic <<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>>

  def magic, do: @magic

  def new(%{data: <<@magic, _rest::binary>>} = buffer) do
    buffer = Buffer.skip(buffer, byte_size(@magic))
    Logger.debug "PNG.new/1"
    Binary.set_byte_order(:big)
    {buffer, chunks} = chunks(buffer, [])
    {%__MODULE__{chunks: chunks}, buffer}
  end

  defp chunks(%{data: <<>>} = buffer, chunks), do: {buffer, Enum.reverse(chunks)}

  defp chunks(%{data: <<length_binary::binary-size(4), type::binary-size(4), _rest::binary>>} = buffer, chunks) do
    Logger.debug "Reading chunk at #{integer(buffer.position)}"
    length = Binary.to_integer(length_binary)
    buffer = Buffer.skip(buffer, 8)
    {<<data::binary-size(length)>>, buffer} = Buffer.consume(buffer, length)
    {<<_crc::binary-size(4)>>, buffer} = Buffer.consume(buffer, 4)
    chunk = Chunk.new(type, data)
    chunks(buffer, [chunk | chunks])
  end

  def binary(%__MODULE__{} = png) do
    Logger.info "Exiffer.PNG creating binary"
    Exiffer.Serialize.binary(png.chunks)
  end

  def text(%__MODULE__{} = png) do
    Exiffer.Serialize.text(png.chunks)
  end

  def write(%__MODULE__{} = png, io_device) do
    Logger.info "Exiffer.PNG writing binary"
    :ok = IO.binwrite(io_device, @magic)
    :ok = Exiffer.Serialize.write(png.chunks, io_device)
  end

  defimpl Exiffer.Serialize do
    alias Exiffer.PNG

    def write(%PNG{} = png, io_device) do
      PNG.write(png, io_device)
    end

    def binary(png) do
      PNG.binary(png)
    end

    def text(%PNG{} = png) do
      PNG.text(png)
    end
  end
end