PdfElixide.Document.to_docx/2, PdfElixide.Document.to_pptx/2 and
PdfElixide.Document.to_xlsx/2 convert a PDF to a Word, PowerPoint or Excel
file. PdfElixide.Office.to_pdf/1 converts such a file to a PDF. Both
directions work on bytes: an export returns the Office file as a binary, and
an import takes one and returns the PDF as a binary.
alias PdfElixide.Document
alias PdfElixide.Office
doc = Document.open!("path/to/report.pdf")
File.write!("report.docx", Document.to_docx!(doc, mode: :flow))
:ok = Document.close(doc)
pdf = Office.to_pdf!(File.read!("path/to/slides.pptx"))
File.write!("slides.pdf", pdf)The mode passed to the export decides how the pages are laid out. It also decides whether the file can be converted back to PDF with its pages intact — see Round trips.
Choosing a mode
Every export takes a :mode:
:layoutplaces each run of text where it sits on the PDF page, as its own positioned frame (DOCX), text box (PPTX) or shape (XLSX). The result looks like the source, but editing it means moving dozens of small boxes, and a long document produces a file that is slow to open.:flowgroups the text into paragraphs (DOCX), text on slides (PPTX) or cells (XLSX) that flow like an ordinary document. The result is easy to edit and to search, but positions on the page are not kept.:auto, the default, uses:layoutfor short documents and:flowfor long ones.
Give :layout or :flow explicitly when the output must not depend on how
many pages the source has — in a pipeline whose consumer expects one shape, or
in a test.
:flow is also the only mode that writes the document's title, author,
subject and keywords into the Office file's properties.
What an export carries
- Text, in its font and size, and in
:layoutat its position. - Images, in every format and mode.
- Embedded fonts, copied into the Office file so that it shows the source's typefaces on a machine that does not have them installed.
- One section, slide or worksheet per page, with one exception: a very
long document exported to PPTX in
:flow— which:autochooses for long documents — may have several pages combined onto one slide. Passmode: :layoutto keep one slide per page.
What it does not carry:
- Tables arrive as text, not as Word or PowerPoint tables. Use
PdfElixide.Document.tables/1when the table structure is what you need. - Vector drawings — lines, boxes, charts drawn as paths — are
approximated at best in
:layoutand dropped in:flow.
Encrypted and damaged documents
An encrypted document must be authenticated before it can be exported. Until then, every export returns an error rather than a file with empty pages:
doc = Document.open!("path/to/protected.pdf")
Document.to_docx(doc)
#=> {:error, %PdfElixide.Error{reason: :encrypted,
# message: "the document is encrypted; authenticate before exporting to DOCX"}}
{:ok, true} = Document.authenticate(doc, "secret")
{:ok, docx} = Document.to_docx(doc)
:ok = Document.close(doc)Opening with Document.open(path, password: "secret") has the same effect.
A page whose object cannot be read at all fails the export, as it fails the
other whole-document converters — see the "When a page cannot be read" section
of PdfElixide.Document. Content that is damaged inside a readable page does
not: that page is exported with whatever could be read from it, possibly
nothing, and the call still succeeds.
Converting Office files to PDF
PdfElixide.Office.to_pdf/1 returns a new PDF's bytes, which can be opened
as a document or an editor, or written out as they are.
The format is detected from the contents rather than a filename. See
PdfElixide.Office for the accepted variants and
PdfElixide.Office.to_pdf/1 for its error contract. That contract makes an
upload boundary straightforward:
case Office.to_pdf(upload) do
{:ok, pdf} -> {:ok, pdf}
{:error, %PdfElixide.Error{reason: :unsupported}} -> {:error, :unsupported_file}
{:error, %PdfElixide.Error{} = error} -> {:error, error}
endA document that does not set its own page size is laid out on US Letter pages.
Round trips
A multi-page DOCX whose text sits in positioned frames converts to PDF on a
single page, with the text of every page drawn over it. That is exactly
what :layout writes, and what :auto writes for a short document, so a
PDF → DOCX → PDF round trip needs :flow:
source = Document.open!("path/to/report.pdf")
docx = Document.to_docx!(source, mode: :flow)
:ok = Document.close(source)
back = Document.from_binary!(Office.to_pdf!(docx))
:ok = Document.close(back)A :flow DOCX comes back with one page per source page, except that a last
page with no text on it can be lost; blank pages earlier in the document are
kept.
XLSX exports keep their pages through a round trip in either mode, and PPTX exports do too unless a very long document had pages combined onto shared slides. Nothing about the round trip is lossless: the second PDF is rebuilt from what the Office file carried — see What an export carries.
Fonts
When importing an Office file to PDF, text is set in its embedded fonts or in a standard PDF font. For characters outside the Latin-1 range that those fonts do not cover, the import looks for a fallback font file at fixed paths and uses the first one that exists. It does not ask the operating system where fonts are installed. For most scripts, the paths are:
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf/usr/share/fonts/TTF/DejaVuSans.ttf/Library/Fonts/DejaVuSans.ttf/usr/share/fonts/truetype/freefont/FreeSans.ttf/usr/share/fonts/gnu-free/FreeSans.ttf/usr/share/fonts/chromeos/noto/NotoSans-Regular.ttf/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf/usr/share/fonts/chromeos/croscore/Arimo-Regular.ttf/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf
and for Chinese, Japanese and Korean:
/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf/usr/share/fonts/android/DroidSansFallbackFull.ttf/usr/share/fonts/opentype/ipafont-gothic/ipag.ttf/usr/share/fonts/chromeos/ko-nanum/NanumGothic.ttf/usr/share/fonts/opentype/unifont/unifont.otf
When none of these files exists, those characters come out as missing-glyph
boxes, so the same file can convert differently on two machines. On Linux,
install a font package that puts one of the files above in place, and check
the path with ls. On macOS, only DejaVu Sans copied to /Library/Fonts is
found; a font installed for one user goes to ~/Library/Fonts, which is not
searched, and none of the Chinese, Japanese or Korean paths exists. On Windows
none of these paths exists. There, and for Chinese, Japanese or Korean text on
macOS, the only way to convert such text is for the Office file to embed its
fonts.
The search runs once, the first time a conversion needs a fallback font, and its result is kept until the VM restarts. That includes finding nothing, so a font installed while the node is running is not used until it is restarted.
Memory and concurrency
An export converts the whole document in one call and returns the file as a
single binary, which briefly exists twice — see the "Whole-document extraction
and memory" section of PdfElixide.Document. An import briefly holds two
copies of the Office file while it reads it, and two copies of the PDF bytes as
it returns them.
An import's memory follows the size of the file's contents once unzipped, not
the size of the upload. Each part of the package may unpack to as much as 512
MiB, so a file of well under a megabyte can take hundreds of megabytes to
convert. Summing the sizes :zip.list_dir/2 reports before calling
PdfElixide.Office.to_pdf/1 lets a service reject a well-formed package whose
declared uncompressed size exceeds its limit. Those sizes are supplied by the
file, and a crafted file can declare less than it holds. For such a file, the
512 MiB limit per part is the only bound. A service converting untrusted uploads
should budget memory against that limit and limit how many conversions run at
once.
Exports read the document the way the other extractors do, so many processes can export from one open document concurrently. They extract the document's text, so The /ActualText hazard applies to them too. Imports take no document handle, so they run independently of each other.