andrewtimberlake/elixir-pdf

Question: Get number of pages

Opened this issue · 4 comments

First of all thanks for this amazing library. I chooed it over html based solutions. It's has everything for my purposes but I wonder if is there any way to get the total number of pages?. I'm rendering an invoice with items. so I have a kind of recursive methods to know when I have to add a new page (:continue.. remaining etc.) however I'm not sure how I could calculate the total of pages that I will add, before render the table. I'm thinking to calculate the total using the size of the data. If do you have any Idea I really appreciate another approach. thanks!

Hey @andrewtimberlake thanks for the quick answer, basically I render the header and the footer in each page, for example if the invoice is too long It could have many pages, so I'm showing the header/fotter in whole pages and I would like to show in the footer something like page 1 from 3 , page 2 from 3, etc.

So when I render the invoice items I have two functions with pattern matching; one for :complete and the other one for the remaining. Something like these:

  defp add_table({pdf, :complete}, _invoice), do: pdf

  defp add_table({pdf, {:continue, _} = remaining}, invoice) do
    {pdf, grid} =
      pdf
      |> Pdf.add_page(:a4)
      |> add_header(invoice)
      |> Pdf.table({50, 560}, {500, 300}, remaining, @table_opts)

    pdf = add_footer(pdf, invoice)
    add_table({pdf, grid}, invoice)
  end

  defp add_header(pdf, invoice) do
  # ...
  end

  defp add_footer(pdf, invoice) do
    %{width: width, height: _height} = Pdf.size(pdf)

    page_number = "#{Pdf.page_number(pdf)}"k
    total_pages = 3 # How calculate this?

    |> Pdf.text_wrap!({20, 100}, {width - 40, 20}, "Page #{page_number} of #{total_pages}", align: :center)
  end

I know it is hacky, but I have this function in my pdf_helpers module.
It will traverse the list of pages and execute the given function with arity 3, the pdf, current page number and the total number of pages. I execute this at the end of the PDF generation.

example

add_page_numbers(pdf, fn pdf, page, pages ->
    Pdf.text_at(pdf, {100,100}, "#{page} of {pages}")
end)

code:

      defp add_page_numbers(pdf, render_fn) when is_function(render_fn, 3) do
        document = :sys.get_state(pdf)
        pages = length(document.pages) + 1
        current_page = document.current

        new_pages =
          document.pages
          |> Enum.reverse()
          |> Enum.with_index(1)
          |> Enum.map(fn {page, pageno} ->
            :sys.replace_state(pdf, fn state -> Map.put(state, :current, page) end)
            render_fn.(pdf, pageno, pages)
            :sys.get_state(pdf).current
          end)
          |> Enum.reverse()

        :sys.replace_state(pdf, fn state ->
          %{state | current: current_page, pages: new_pages}
        end)

        render_fn.(pdf, pages, pages)
      end

      defp add_page_numbers(pdf, render_fn) when is_function(render_fn) do
        raise ArgumentError, "add_page_numbers takes a render function with arity 3!"
      end

      defp add_page_numbers(pdf, coords) do
        add_page_numbers(pdf, fn pdf, page, pages ->
          Pdf.text_at(pdf, coords, "#{page}/#{pages}")
        end)
      end

Thanks @Hermanverschooten I’ll look at how to incorporate something like this into the library.