Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add merge feature #194

Merged
merged 13 commits into from
Dec 21, 2022
51 changes: 47 additions & 4 deletions lib/chromic_pdf/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,36 @@ defmodule ChromicPDF.API do
Browser,
CaptureScreenshot,
GhostscriptPool,
OutputOptions,
PDFOptions,
PDFAOptions,
PrintToPDF
}

@spec print_to_pdf(
ChromicPDF.Supervisor.services(),
ChromicPDF.source() | ChromicPDF.source_and_options(),
ChromicPDF.source()
| ChromicPDF.source_and_options()
| [ChromicPDF.source() | ChromicPDF.source_and_options()],
[ChromicPDF.pdf_option() | ChromicPDF.export_option()]
) :: ChromicPDF.export_return()
def print_to_pdf(services, sources, opts) when is_list(sources) and is_list(opts) do
with_tmp_dir(fn tmp_dir ->
sources =
Enum.map(sources, fn
%{opts: source_opts} = source -> %{source | opts: Keyword.merge(source_opts, opts)}
source -> source
xaviRodri marked this conversation as resolved.
Show resolved Hide resolved
end)

pdf_path_list = Enum.map(sources, &print_tmp(services, &1, tmp_dir, opts))

merge_tmp_path = Path.join(tmp_dir, random_file_name(".pdf"))

:ok = GhostscriptPool.merge(services.ghostscript_pool, pdf_path_list, opts, merge_tmp_path)

OutputOptions.feed_file_into_output(merge_tmp_path, opts)
end)
end

def print_to_pdf(services, %{source: source, opts: opts}, overrides)
when tuple_size(source) == 2 and is_list(opts) and is_list(overrides) do
print_to_pdf(services, source, Keyword.merge(opts, overrides))
Expand Down Expand Up @@ -48,10 +68,22 @@ defmodule ChromicPDF.API do
with_telemetry(protocol, opts, fn ->
services.browser
|> Browser.run_protocol(Map.fetch!(@export_protocols, protocol), opts)
|> PDFOptions.feed_chrome_data_into_output(opts)
|> OutputOptions.feed_chrome_data_into_output(opts)
end)
end

defp print_tmp(services, %{source: source, opts: opts}, tmp_dir, _opts),
do: print_tmp(services, source, tmp_dir, opts)

defp print_tmp(services, source, tmp_dir, opts) when tuple_size(source) == 2 do
tmp_path = Path.join(tmp_dir, random_file_name(".pdf"))
opts = Keyword.put(opts, :output, tmp_path)

chrome_export(services, :print_to_pdf, source, opts)

tmp_path
end

@spec convert_to_pdfa(ChromicPDF.Supervisor.services(), ChromicPDF.path(), [
ChromicPDF.pdfa_option() | ChromicPDF.export_option()
]) ::
Expand Down Expand Up @@ -83,12 +115,23 @@ defmodule ChromicPDF.API do
end)
end

@spec merge(ChromicPDF.Supervisor.services(), list(ChromicPDF.path()), keyword()) ::
ChromicPDF.export_return()
def merge(services, pdf_path_list, opts) do
with_tmp_dir(fn tmp_dir ->
tmp_path = Path.join(tmp_dir, random_file_name(".pdf"))

:ok = GhostscriptPool.merge(services.ghostscript_pool, pdf_path_list, opts, tmp_path)
OutputOptions.feed_file_into_output(tmp_path, opts)
end)
end
xaviRodri marked this conversation as resolved.
Show resolved Hide resolved

defp do_convert_to_pdfa(services, pdf_path, opts, tmp_dir) do
pdfa_path = Path.join(tmp_dir, random_file_name(".pdf"))

with_telemetry(:convert_to_pdfa, opts, fn ->
:ok = GhostscriptPool.convert(services.ghostscript_pool, pdf_path, opts, pdfa_path)
PDFAOptions.feed_ghostscript_file_into_output(pdfa_path, opts)
OutputOptions.feed_file_into_output(pdfa_path, opts)
end)
end
end
52 changes: 52 additions & 0 deletions lib/chromic_pdf/api/output_option.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: Apache-2.0

defmodule ChromicPDF.OutputOptions do
@moduledoc false

import ChromicPDF.Utils
alias ChromicPDF.ChromeError

def feed_file_into_output(pdf_path, opts) do
case Keyword.get(opts, :output) do
path when is_binary(path) ->
File.cp!(pdf_path, path)
:ok

fun when is_function(fun, 1) ->
{:ok, fun.(pdf_path)}

nil ->
data =
pdf_path
|> File.read!()
|> Base.encode64()

{:ok, data}
end
end

def feed_chrome_data_into_output({:error, error}, opts) do
raise ChromeError, error: error, opts: opts
end

def feed_chrome_data_into_output({:ok, data}, opts) do
case Keyword.get(opts, :output) do
path when is_binary(path) ->
File.write!(path, Base.decode64!(data))
:ok

fun when is_function(fun, 1) ->
result_from_callback =
with_tmp_dir(fn tmp_dir ->
path = Path.join(tmp_dir, random_file_name(".pdf"))
File.write!(path, Base.decode64!(data))
fun.(path)
end)

{:ok, result_from_callback}

nil ->
{:ok, data}
end
end
end
xaviRodri marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 0 additions & 27 deletions lib/chromic_pdf/api/pdf_options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
defmodule ChromicPDF.PDFOptions do
@moduledoc false

import ChromicPDF.Utils
require EEx
alias ChromicPDF.ChromeError

def prepare_export_options(source, opts) do
opts
Expand Down Expand Up @@ -93,29 +91,4 @@ defmodule ChromicPDF.PDFOptions do
end)
end)
end

def feed_chrome_data_into_output({:error, error}, opts) do
raise ChromeError, error: error, opts: opts
end

def feed_chrome_data_into_output({:ok, data}, opts) do
case Keyword.get(opts, :output) do
path when is_binary(path) ->
File.write!(path, Base.decode64!(data))
:ok

fun when is_function(fun, 1) ->
result_from_callback =
with_tmp_dir(fn tmp_dir ->
path = Path.join(tmp_dir, random_file_name(".pdf"))
File.write!(path, Base.decode64!(data))
fun.(path)
end)

{:ok, result_from_callback}

nil ->
{:ok, data}
end
end
end
24 changes: 0 additions & 24 deletions lib/chromic_pdf/api/pdfa_options.ex

This file was deleted.

1 change: 1 addition & 0 deletions lib/chromic_pdf/pdfa/ghostscript.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ defmodule ChromicPDF.Ghostscript do
pdfa_def_ps_path :: binary(),
output_path :: binary()
) :: :ok
@callback merge(pdf_path_list :: list(binary()), output_path :: binary()) :: :ok
end
10 changes: 10 additions & 0 deletions lib/chromic_pdf/pdfa/ghostscript_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ defmodule ChromicPDF.GhostscriptImpl do
:ok
end

@impl ChromicPDF.Ghostscript
def merge(pdf_path_list, output_path) do
ghostscript_cmd!(
["-dNOPAUSE", "-sDEVICE=pdfwrite", "-sOUTPUTFILE=#{output_path}", "-dBATCH"] ++
pdf_path_list
)

:ok
end

defp ghostscript_cmd!(args) do
args =
args
Expand Down
8 changes: 8 additions & 0 deletions lib/chromic_pdf/pdfa/ghostscript_pool.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ defmodule ChromicPDF.GhostscriptPool do
end)
end

# Merges multiple PDF files using Ghostscript.
@spec merge(pid(), list(binary()), keyword(), binary()) :: :ok
def merge(pool, pdf_path_list, params, output_path) do
NimblePool.checkout!(pool, :checkout, fn _from, _worker_state ->
{GhostscriptWorker.merge(pdf_path_list, params, output_path), :ok}
end)
end

# ------------ Callbacks -----------

@impl NimblePool
Expand Down
9 changes: 9 additions & 0 deletions lib/chromic_pdf/pdfa/ghostscript_worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ defmodule ChromicPDF.GhostscriptWorker do
:ok
end

@spec merge(list(binary()), keyword(), binary()) :: :ok
def merge(pdf_path_list, _params, output_path) do
pdf_path_list = Enum.map(pdf_path_list, &Path.expand/1)
xaviRodri marked this conversation as resolved.
Show resolved Hide resolved

@ghostscript.merge(pdf_path_list, output_path)

:ok
end

EEx.function_from_file(:defp, :render_pdfa_def_ps, @psdef_ps, [:assigns])

defp create_pdfa_def_ps!(pdf_path, params, pdfa_def_ps_path) do
Expand Down
23 changes: 22 additions & 1 deletion lib/chromic_pdf/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ defmodule ChromicPDF.Supervisor do
ChromicPDF.print_to_pdf({:url, "http:///example.net"}, wait_for: wait_for)
'''
@spec print_to_pdf(
input :: source() | source_and_options(),
input :: source() | source_and_options() | [source() | source_and_options()],
opts :: [pdf_option() | export_option()]
) :: export_return()
def print_to_pdf(input, opts \\ []) do
Expand Down Expand Up @@ -619,6 +619,27 @@ defmodule ChromicPDF.Supervisor do
def print_to_pdfa(input, opts \\ []) do
with_services(__MODULE__, &API.print_to_pdfa(&1, input, opts))
end

@doc """
Merges multiple PDF files.

It accepts an `output` option like `print_to_pdf/2` in order to choose
between the blob output or writing to a file.

Will raise if any of the given files is not found.

## Example

{:ok, blob} = ChromicPDF.merge(["sample-1.pdf", "sample-2.pdf"])

## Example with PDF file output

:ok = ChromicPDF.merge(["sample-1.pdf", "sample-2.pdf"], output: "combined-sample.pdf")
"""
@spec merge(pdf_path_list :: list(path()), opts :: keyword()) :: export_return()
def merge(pdf_path_list, opts \\ []) do
with_services(__MODULE__, &API.merge(&1, pdf_path_list, opts))
end
end
end
end