A powerful and easy-to-use Elixir library for extracting text and metadata from PDF files.
PdfExtractor leverages Python's pdfplumber library through seamless integration to provide
robust PDF text extraction capabilities. It supports both file-based and binary-based operations,
making it suitable for various use cases from local file processing to web-based PDF handling.
Features
- π Extract text from single or multiple PDF pages
- π Area-based extraction using bounding boxes
- π Extract words together with their position on the page
- π Extract tables as rows of cells
- π Open encrypted PDFs with a password
- π Work with PDF data directly from memory (e.g., HTTP downloads)
- π Get PDF metadata like title, author, creation date
- π Leverages Python's powerful
pdfplumberlibrary - π Simple and intuitive API
- β Comprehensive test coverage
- π Full documentation
Installation
Add pdf_extractor to your list of dependencies in mix.exs:
def deps do
[
{:pdf_extractor, "~> 0.9.0"}
]
endThen start it in your application start function:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
PdfExtractor,
...
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endUsage
Extract text from specific regions using bounding boxes {x0, y0, x1, y1}:
pages = %{
0 => {0, 0, 300, 200}, # Top-left area of page 0
1 => [
{200, 300, 600, 500}, # Bottom-right area of page 1
{0, 0, 200, 250}, # Top-left area of page 1
]
}
PdfExtractor.extract_text("path/to/document.pdf", pages)Return Format
The function returns a map where keys are page numbers and values are the extracted text:
%{
0 => "Text from page 0...",
1 => ["Text from page 1 (first area)...", "Text from page 1 (second area)..."],
2 => "Text from page 2..."
}Keeping the Layout
Pass layout: true to pad the text with spaces and blank lines so it keeps its position on the page.
Columns of statements and tables stay aligned instead of being flattened into single lines:
PdfExtractor.extract_text("path/to/document.pdf", 0, layout: true)Encrypted Files
Every function takes a :password option for encrypted PDFs. Without it, or with a wrong one,
the function returns {:error, exception}:
PdfExtractor.extract_text("path/to/document.pdf", [], password: "secret")Tables
Use extract_tables/3 to get the tables of each page as rows of cells. By default the cell borders
come from the lines drawn on the page; use the :text strategy for tables drawn without lines:
PdfExtractor.extract_tables("path/to/document.pdf", 0)
# {:ok, %{0 => [[["Item", "Quantity", "Price"], ["Work", "1", "1 500,00 β¬"]]]}}
PdfExtractor.extract_tables("path/to/document.pdf", 0,
vertical_strategy: :text,
horizontal_strategy: :text
)Words With Positions
Use extract_words/3 to get every word with its bounding box in PDF points, measured from the top left
corner of the page. It takes the same pages and areas as extract_text/3:
PdfExtractor.extract_words("path/to/document.pdf", 0)
# {:ok,
# %{
# 0 => [
# %{text: "Text", left: 48.0, right: 67.99875, top: 26.64224999999999, bottom: 36.39224999999999},
# %{text: "Example", left: 70.734375, right: 110.757, top: 26.64224999999999, bottom: 36.39224999999999},
# ...
# ]
# }}License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built on top of the excellent pdfplumber Python library
- Uses pythonx for seamless Python integration
Summary
Functions
Returns a specification to start this module under a supervisor.
Extracts metadata from a PDF file info trailers. Typically includes "CreationDate", "ModDate", "Producer", et cetera.
Extracts metadata from PDF binary data. Similar to extract_metadata/1 but works with PDF data in memory instead of
files.
Extracts tables from PDF pages.
Extracts tables from PDF binary data. See extract_tables/3 for the shape of each table and its
options, and extract_text/3 for details on how to specify pages and areas.
Extracts text from PDF pages.
Extracts text from PDF binary data. See extract_text/2 for details on how to specify pages, areas, and options.
Extracts words from PDF pages together with their position on the page.
Extracts words from PDF binary data. See extract_words/3 for the shape of each word and extract_text/3
for details on how to specify pages, areas, and options.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Extracts metadata from a PDF file info trailers. Typically includes "CreationDate", "ModDate", "Producer", et cetera.
Examples
iex> PdfExtractor.extract_metadata("priv/fixtures/frankenstein-mary-shelley.pdf")
{:ok, %{"CreationDate" => "D:20260403141150", "Creator" => "PDFium", "Producer" => "PDFium"}}
Extracts metadata from PDF binary data. Similar to extract_metadata/1 but works with PDF data in memory instead of
files.
Examples
iex> content = File.read!("priv/fixtures/frankenstein-mary-shelley.pdf")
...> PdfExtractor.extract_metadata_from_binary(content)
{:ok, %{"CreationDate" => "D:20260403141150", "Creator" => "PDFium", "Producer" => "PDFium"}}
Extracts tables from PDF pages.
Each page maps to the list of tables found on it. A table is a list of rows and a row a list of
cells, read left to right and top to bottom. A cell is nil when the table has a cell there but no
text could be assigned to it.
See extract_text/3 for details on how to specify pages, areas, and options.
Options
Besides :timeout and :name, this function accepts how the edges of the tables are found:
:vertical_strategy- how to find the columns (default::lines):horizontal_strategy- how to find the rows (default::lines)
Each strategy is one of:
:lines- use the lines drawn on the page, including the sides of rectangles, as cell borders:lines_strict- like:linesbut ignore the sides of rectangles:text- infer the borders from the alignment of the words, for tables drawn without lines
Examples
Extract the tables of every page.
iex> PdfExtractor.extract_tables("priv/fixtures/fatura.pdf")
{:ok, %{0 => [[["Item", "Quantidade", "Avaliar", "Quantia"]]], 1 => []}}Extract only the tables inside an area.
iex> PdfExtractor.extract_tables("priv/fixtures/fatura.pdf", %{0 => {0, 180, 595, 260}})
{:ok, %{0 => [[["Item", "Quantidade", "Avaliar", "Quantia"]]]}}Find the columns from the alignment of the words instead of the drawn lines.
iex> PdfExtractor.extract_tables("priv/fixtures/fatura.pdf", 0,
...> vertical_strategy: :text,
...> horizontal_strategy: :lines
...> )
{:ok,
%{
0 => [
[
[nil, nil, "devedor:", "", "1 52", "5,59 β¬"],
[nil, nil, "", "", "", ""],
["I", "tem Quantidad", "e", "Avaliar", "", "Quantia"]
]
]
}}
Extracts tables from PDF binary data. See extract_tables/3 for the shape of each table and its
options, and extract_text/3 for details on how to specify pages and areas.
Examples
iex> content = File.read!("priv/fixtures/fatura.pdf")
...> PdfExtractor.extract_tables_from_binary(content, 0)
{:ok, %{0 => [[["Item", "Quantidade", "Avaliar", "Quantia"]]]}}
Extracts text from PDF pages.
It supports extracting from single pages, multiple pages, and specific areas within pages.
Page Numbers
- Integer: Extract from single page (e.g.,
0for first page) - List: Extract from multiple pages (e.g.,
[0, 1, 2]) - Empty list
[]: Extract from all pages (default)
Areas Format
Areas are specified as a map where keys are page numbers and values are bounding boxes:
- Single area:
%{0 => {x0, y0, x1, y1}} - Multiple areas:
%{0 => [{x0, y0, x1, y1}, {x2, y2, x3, y3}]} - Mixed:
%{0 => {x0, y0, x1, y1}, 1 => [{x2, y2, x3, y3}, {x4, y4, x5, y5}]}
Options
All extraction functions accept these options:
:timeout- timeout in milliseconds for the GenServer call (default:#{@default_timeout}):name- name or PID of the server to call (default:PdfExtractor):password- the password of an encrypted PDF. Without it, or with a wrong one, extraction returns an error (default:nil)
extract_text/3 and extract_text_from_binary/3 also accept:
:layout- whentrue, keeps the position of the text on the page by padding it with spaces and blank lines, so columns stay aligned. Useful for statements and tables that the default extraction flattens into single lines (default:false)
Examples
Extract text from all pages.
iex> PdfExtractor.extract_text("priv/fixtures/fatura.pdf")
{:ok,
%{
0 =>
"Text Example Bill FATURA\n# 2025010002\nData: Jun 21, 2025\nProjeto de lei para:\nSaldo devedor: 1 525,59 β¬\nElixir Company\nItem Quantidade Avaliar Quantia\nTrabalho 1 1 500,00 β¬ 1 500,00 β¬\nMais trabalho 1 25,59 β¬ 25,59 β¬\nSubtotal: 1 525,59 β¬\nImposto (0%): 0,00 β¬\nTotal: 1 525,59 β¬",
1 =>
"β\nReceipt Payment part Account / Payable to\nCH4431999123000889012\nβ\nMax Muster & SΓΆhne\nAccount / Payable to\nCH4431999123000889012 Musterstrasse 123\nMax Muster & SΓΆhne 8000 Seldwyla\nMusterstrasse 123\n8000 Seldwyla\nReference\n210000000003139471430009017\nReference\n210000000003139471430009017\nAdditional information\nBestellung vom 15.10.2020\nPayable by (name/address)\nSimon Muster\nPayable by (name/address)\nMusterstrasse 1\nCurrency Amount\nSimon Muster\n8000 Seldwyla\nCHF 1 949.75 Musterstrasse 1\n8000 Seldwyla\nCurrency Amount\nCHF 1 949.75\nAcceptance point"
}}Extract text from only some pages.
iex> PdfExtractor.extract_text("priv/fixtures/fatura.pdf", [0])
{:ok,
%{
0 =>
"Text Example Bill FATURA\n# 2025010002\nData: Jun 21, 2025\nProjeto de lei para:\nSaldo devedor: 1 525,59 β¬\nElixir Company\nItem Quantidade Avaliar Quantia\nTrabalho 1 1 500,00 β¬ 1 500,00 β¬\nMais trabalho 1 25,59 β¬ 25,59 β¬\nSubtotal: 1 525,59 β¬\nImposto (0%): 0,00 β¬\nTotal: 1 525,59 β¬"
}}Extract only the titles in the book.
iex> PdfExtractor.extract_text("priv/fixtures/frankenstein-mary-shelley.pdf", %{
...> 1 => {0, 0, 612, 190},
...> 2 => {0, 0, 612, 190},
...> 8 => {0, 0, 612, 190},
...> 12 => {0, 0, 612, 190},
...> 13 => {0, 0, 612, 190}
...> })
{:ok,
%{
1 => "Frankenstein",
2 => "Introduction",
8 => "Preface",
12 => "Frankenstein",
13 => "Letter I"
}}Extract multiple areas from a single page.
iex> PdfExtractor.extract_text("priv/fixtures/frankenstein-mary-shelley.pdf", %{
...> 13 => [{0, 200, 600, 300}, {0, 400, 612, 440}]
...> })
{:ok,
%{
13 => [
"To Mrs. Saville, England.\nSt. Petersburgh, Dec. 11th, 17 β.",
"I am already far north of London; and as I walk in the streets of\nPetersburgh, I feel a cold northern breeze play upon my cheeks, which"
]
}}Extract with a custom timeout for large files.
iex> PdfExtractor.extract_text("priv/fixtures/frankenstein-mary-shelley.pdf", 11,
...> timeout: 30_000
...> )
{:ok,
%{
11 =>
"βDid I request thee, Maker, from my clay\nTo mould me Man, did I solicit thee\nFrom darkness to promote me?β\nP L , X, 743 β 45\nARADISE OST"
}}Open an encrypted file with its password.
iex> PdfExtractor.extract_text("priv/fixtures/fatura-encrypted.pdf", %{0 => {0, 0, 300, 200}},
...> password: "secret"
...> )
{:ok, %{0 => "Text Example Bill\nProjeto de lei para:\nElixir Company"}}Keep the layout of the page so the columns of a table stay aligned.
iex> PdfExtractor.extract_text("priv/fixtures/fatura.pdf", %{0 => {0, 220, 595, 250}})
{:ok, %{0 => "Trabalho 1 1 500,00 β¬ 1 500,00 β¬\nMais trabalho 1 25,59 β¬ 25,59 β¬"}}
iex> PdfExtractor.extract_text("priv/fixtures/fatura.pdf", %{0 => {0, 220, 595, 250}},
...> layout: true
...> )
{:ok,
%{
0 =>
" Trabalho 1 1 500,00 β¬ 1 500,00 β¬\n Mais trabalho 1 25,59 β¬ 25,59 β¬ "
}}
Extracts text from PDF binary data. See extract_text/2 for details on how to specify pages, areas, and options.
This function allows you to extract text from PDF data that's already in memory, such as data downloaded from a URL or received via an API. This avoids the need to write the PDF to the filesystem.
Examples
Extract text from all pages.
iex> content = File.read!("priv/fixtures/fatura.pdf")
...> PdfExtractor.extract_text_from_binary(content)
{:ok,
%{
0 =>
"Text Example Bill FATURA\n# 2025010002\nData: Jun 21, 2025\nProjeto de lei para:\nSaldo devedor: 1 525,59 β¬\nElixir Company\nItem Quantidade Avaliar Quantia\nTrabalho 1 1 500,00 β¬ 1 500,00 β¬\nMais trabalho 1 25,59 β¬ 25,59 β¬\nSubtotal: 1 525,59 β¬\nImposto (0%): 0,00 β¬\nTotal: 1 525,59 β¬",
1 =>
"β\nReceipt Payment part Account / Payable to\nCH4431999123000889012\nβ\nMax Muster & SΓΆhne\nAccount / Payable to\nCH4431999123000889012 Musterstrasse 123\nMax Muster & SΓΆhne 8000 Seldwyla\nMusterstrasse 123\n8000 Seldwyla\nReference\n210000000003139471430009017\nReference\n210000000003139471430009017\nAdditional information\nBestellung vom 15.10.2020\nPayable by (name/address)\nSimon Muster\nPayable by (name/address)\nMusterstrasse 1\nCurrency Amount\nSimon Muster\n8000 Seldwyla\nCHF 1 949.75 Musterstrasse 1\n8000 Seldwyla\nCurrency Amount\nCHF 1 949.75\nAcceptance point"
}}Extract text from only some pages.
iex> content = File.read!("priv/fixtures/fatura.pdf")
...> PdfExtractor.extract_text_from_binary(content, [0])
{:ok,
%{
0 =>
"Text Example Bill FATURA\n# 2025010002\nData: Jun 21, 2025\nProjeto de lei para:\nSaldo devedor: 1 525,59 β¬\nElixir Company\nItem Quantidade Avaliar Quantia\nTrabalho 1 1 500,00 β¬ 1 500,00 β¬\nMais trabalho 1 25,59 β¬ 25,59 β¬\nSubtotal: 1 525,59 β¬\nImposto (0%): 0,00 β¬\nTotal: 1 525,59 β¬"
}}Extract only the titles in the book.
iex> content = File.read!("priv/fixtures/frankenstein-mary-shelley.pdf")
...>
...> PdfExtractor.extract_text_from_binary(content, %{
...> 1 => {0, 0, 612, 190},
...> 2 => {0, 0, 612, 190},
...> 8 => {0, 0, 612, 190},
...> 12 => {0, 0, 612, 190},
...> 13 => {0, 0, 612, 190}
...> })
{:ok,
%{
1 => "Frankenstein",
2 => "Introduction",
8 => "Preface",
12 => "Frankenstein",
13 => "Letter I"
}}Extract multiple areas from a single page.
iex> content = File.read!("priv/fixtures/frankenstein-mary-shelley.pdf")
...>
...> PdfExtractor.extract_text_from_binary(content, %{
...> 13 => [{0, 200, 600, 300}, {0, 400, 612, 440}]
...> })
{:ok,
%{
13 => [
"To Mrs. Saville, England.\nSt. Petersburgh, Dec. 11th, 17 β.",
"I am already far north of London; and as I walk in the streets of\nPetersburgh, I feel a cold northern breeze play upon my cheeks, which"
]
}}
Extracts words from PDF pages together with their position on the page.
Each word is a map with its :text and the edges of its bounding box, :left, :right, :top and
:bottom, in PDF points measured from the top left corner of the page. Words come in the order
pdfplumber reads them, which is the same order extract_text/3 uses to build its text.
See extract_text/3 for details on how to specify pages, areas, and options.
Examples
Extract the words of a single page.
iex> {:ok, %{0 => words}} = PdfExtractor.extract_words("priv/fixtures/fatura.pdf", 0)
...> Enum.take(words, 3)
[
%{
text: "Text",
left: 48.0,
right: 67.99875,
top: 26.64224999999999,
bottom: 36.39224999999999
},
%{
text: "Example",
left: 70.734375,
right: 110.757,
top: 26.64224999999999,
bottom: 36.39224999999999
},
%{
text: "Bill",
left: 113.49609375,
right: 128.63240625,
top: 26.64224999999999,
bottom: 36.39224999999999
}
]Extract only the words inside an area.
iex> PdfExtractor.extract_words("priv/fixtures/frankenstein-mary-shelley.pdf", %{
...> 1 => {0, 0, 612, 190}
...> })
{:ok,
%{
1 => [
%{
text: "Frankenstein",
left: 76.99218429199217,
right: 177.95707306012164,
top: 102.2249957406251,
bottom: 132.22499449062502
}
]
}}
Extracts words from PDF binary data. See extract_words/3 for the shape of each word and extract_text/3
for details on how to specify pages, areas, and options.
Examples
Extract only the words inside an area.
iex> content = File.read!("priv/fixtures/frankenstein-mary-shelley.pdf")
...> PdfExtractor.extract_words_from_binary(content, %{1 => {0, 0, 612, 190}})
{:ok,
%{
1 => [
%{
text: "Frankenstein",
left: 76.99218429199217,
right: 177.95707306012164,
top: 102.2249957406251,
bottom: 132.22499449062502
}
]
}}