Current section
Files
Jump to
Current section
Files
lib/chromic_pdf/pdfa/ghostscript_worker.ex
defmodule ChromicPDF.GhostscriptWorker do
@moduledoc false
use GenServer
require EEx
import ChromicPDF.Utils
@ghostscript Application.get_env(:chromic_pdf, :ghostscript, ChromicPDF.GhostscriptImpl)
# ------------- API ----------------
def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end
def convert(pid, pdf_path, params, output_path) do
GenServer.call(pid, {:convert, pdf_path, params, output_path})
end
# --------- Implementation ---------
@pdfinfo_ps Path.expand("../assets/pdfinfo.ps", __ENV__.file)
@eci_icc Path.expand("../assets/eciRGB_v2.icc", __ENV__.file)
@psdef_ps Path.expand("../assets/PDFA_def.ps.eex", __ENV__.file)
@external_resource @psdef_ps
@pdfinfo_keys %{
"__knowninfoTitle" => :title,
"__knowninfoAuthor" => :author,
"__knowninfoSubject" => :subject,
"__knowninfoKeywords" => :keywords,
"__knowninfoCreator" => :creator,
"__knowninfoCreationDate" => :creation_date,
"__knowninfoModDate" => :mod_date,
"__knowninfoTrapped" => :trapped
}
EEx.function_from_file(:defp, :render_pdfa_def_ps, @psdef_ps, [:assigns])
@impl GenServer
def init(_) do
{:ok, nil}
end
@impl GenServer
def handle_call({:convert, pdf_path, params, output_path}, _from, state) do
pdf_path = Path.expand(pdf_path)
pdf_with_fonts = Path.join(Path.dirname(output_path), random_file_name(".pdf"))
pdfa_def_ps_path = Path.join(Path.dirname(output_path), random_file_name(".ps"))
create_pdfa_def_ps!(pdf_path, params, pdfa_def_ps_path)
create_pdf_with_fonts!(pdf_path, pdf_with_fonts)
convert_to_pdfa!(pdf_with_fonts, params, pdfa_def_ps_path, output_path)
{:reply, :ok, state}
end
defp create_pdfa_def_ps!(pdf_path, params, pdfa_def_ps_path) do
info = Keyword.get(params, :info, %{})
pdfa_def_ext = Keyword.get(params, :pdfa_def_ext)
rendered =
pdf_path
|> pdfinfo()
|> Map.merge(info)
|> Enum.into(%{}, &cast_info_value/1)
|> Map.put(:eci_icc, @eci_icc)
|> Map.put(:pdfa_def_ext, pdfa_def_ext)
|> render_pdfa_def_ps()
File.write!(pdfa_def_ps_path, rendered)
end
defp create_pdf_with_fonts!(pdf_path, pdf_with_fonts) do
:ok = @ghostscript.embed_fonts(pdf_path, pdf_with_fonts)
end
defp convert_to_pdfa!(pdf_with_fonts, params, pdfa_def_ps_path, output_path) do
pdfa_version =
params
|> Keyword.get(:pdfa_version, 3)
|> to_string()
:ok =
@ghostscript.convert_to_pdfa(
pdf_with_fonts,
pdfa_version,
@eci_icc,
pdfa_def_ps_path,
output_path
)
end
defp pdfinfo(pdf_path) do
infos_from_file =
pdf_path
|> @ghostscript.run_postscript(@pdfinfo_ps)
|> String.trim()
|> String.split("\n")
|> Enum.map(&parse_info_line/1)
|> Enum.reject(&is_nil/1)
|> Enum.into(%{})
Enum.into(
@pdfinfo_keys,
%{},
fn {ext, int} -> {int, Map.get(infos_from_file, ext, "")} end
)
end
defp parse_info_line(line) do
if String.contains?(line, ": ") do
[key, value] = String.split(line, ": ", parts: 2)
if Map.has_key?(@pdfinfo_keys, key) do
{key, value}
end
end
end
defp cast_info_value({:trapped, value}) do
cast =
case String.downcase(value) do
"/true" -> "/True"
"/false" -> "/False"
_ -> nil
end
{:trapped, cast}
end
defp cast_info_value({key, %DateTime{} = value}) do
{key, to_postscript_date(value)}
end
defp cast_info_value(other), do: other
end