2

(This may be a XY problem, more on the context at the end)

Is there any way of programmatically splitting a pdf based on the section titles? That is

from this pdf, produce 2 pdfs, where one contains everything up to the section called "XY", and the second contains all the rest.

I know how to split a pdf based on the page number, but is there anything more "semantical" available?

(In short, the original problem: the NSF wants to have the list of references in one document, and the description in another, and since I use LaTeX / pandoc to produce my document, it's simpler to just have everything in 1 document and then split it. Links don't matter, obviously.)

3

3 Answers 3

3
+150

I like using pdftk for this type of work.

If I create a basic document using Latex, it might look like this:

\documentclass{report}
\usepackage{blindtext} % For lorem ipsum text
\usepackage{hyperref} % Turns chapters/sections into bookmarks

\begin{document}

\chapter{First Chapter}
\Blindtext[10]

\section{Subsection of chapter 1}
\Blindtext[3]

\chapter{Second Chapter}
\Blindtext[20]

\chapter{Third Chapter}
\Blindtext

\pdfbookmark{Example bookmark}{A}
\Blindtext

\end{document}

When I run this through pdflatex, it generates an 13-page PDF with automatic bookmarks for each chapter, a deep bookmark for section 1.1, a manual bookmark later, and lots of lorem ipsum paragraphs.

Now that we have a pdf, we can use pdftk to get some insights about it. There's lots of data, so I reduced it down to the interesting section.

$ pdftk sample.pdf dump_data
InfoBegin
...
NumberOfPages: 13
BookmarkBegin
BookmarkTitle: First Chapter
BookmarkLevel: 1
BookmarkPageNumber: 1
BookmarkBegin
BookmarkTitle: Subsection of chapter 1
BookmarkLevel: 2
BookmarkPageNumber: 3
BookmarkBegin
BookmarkTitle: Second Chapter
BookmarkLevel: 1
BookmarkPageNumber: 5
BookmarkBegin
BookmarkTitle: Third Chapter
BookmarkLevel: 1
BookmarkPageNumber: 11
BookmarkBegin
BookmarkTitle: Example bookmark
BookmarkLevel: 1
BookmarkPageNumber: 12
PageMediaBegin
...

When we hit a BookmarkBegin tag, we know we have a bookmark. When we see BookmarkTitle: X we know that we have reached section X. There is a BookmarkPageNumber: associated with each bookmark. You can use that for your split. You could also use the BookmarkLevel to filter the level (chapter, section, subsection) you want your splits.


Let's say you wanted to split between Chapters 1 and 2. We can see from the data, that we need to add a split before page 4:

BookmarkTitle: Second Chapter
BookmarkPageNumber: 5

Now that we have our page number, we can use pdftk to do the split. This actually involves using cat on specific pages:

pdftk sample.pdf cat 1-4 output sample-1.pdf
pdftk sample.pdf cat 5-end output sample-2.pdf

Let's go for a more generic approach and script this for a pdf called sample.pdf. Let's split the PDF at each high-level bookmark (filtering out the section).

First, let's translate the bookmark tags into an easier-to-parse format, we could write something in awk (I'm going to save this as parser.awk)

#!/bin/awk -f
BEGIN {
    FS=": "
    OFS=";"
}
/^BookmarkBegin/ {
  if (this_level > 1) { # Only handle high-level sections
    next
  }
  if (this_page == "" || this_title == ""){
    next
  }
  if (last_page != "" && last_title != "") {
    print last_title, last_page, this_page-1
  }
  last_title=this_title
  last_page=this_page
  this_title=this_page=this_level=""
}
/^BookmarkTitle:/ {
    this_title=$2
}
/^BookmarkPageNumber:/ {
    this_page=$2
}
/^BookmarkLevel:/ {
    this_level=$2
}
END {
  print last_title, last_page, this_page-1
  print this_title, this_page, "end"
}

Here, I'm assuming that titles will not include a ;, so I'm using that as the output delimiter. This script outputs:

  1. the name of each section
  2. First page of the section
  3. Last page of the section.
$ pdftk sample.pdf dump_data | awk -f parser.awk
First Chapter;1;4
Second Chapter;5;10
Third Chapter;11;11
Example bookmark;12;end

Now let's iterate over each line and call pdftk with that in bash:

#!/bin/bash                                                                     
                                                                                
sequence=1                                                                      
                                                                                
pdftk sample.pdf dump_data | awk -f parser.awk | \                              
while IFS=";" \                                                                 
read -r title start end                                                         
do                                                                              
  pdftk sample.pdf cat "$start"-"$end" output sample-$sequence-"$title".pdf      
  sequence=$((sequence+1))                                                      
done                                                                                     

This leaves you with the following:

$ ls *.pdf
'sample-1-First Chapter.pdf'
'sample-2-Second Chapter.pdf'
'sample-3-Third Chapter.pdf'
'sample-4-Example bookmark.pdf'
sample.pdf

One limitation of this is that when there are several bookmarks on the same page or there are very few bookmarks, it gets a bit confused.

2
  • Thanks a lot! I am using pftk as well to split the file, and the way you identify the page number where the splits needs to occur is ingenious. I would have to try and write a macro that does all the work for me, but I'll see if I'll manage.
    – Clément
    Commented Mar 11, 2023 at 15:59
  • 1
    I updated the post to include a script that does the work for you. I hope it helps.
    – Stewart
    Commented Mar 12, 2023 at 10:03
1

I had used python libraries py_pdf_parser and pdfplumber to achieve something similar last year. On top of that, I used MathPix API (which is paid but minimal fees) to do final conversion to latex.

You can checkout my script here and see if it helps: https://github.com/sekerez/linear_algebra_done_right/blob/main/problems/extract_problems_from_pdf_book.py This script extracted all the problems in the book and neatly organized them in a data-structure based on the exercise number and problem number. It was amazing that this was actually possible using a few lines of code.

You'd have to adapt the script it to your use case. PDFs are very unwieldy to be processed by the machine since each PDF is very different. Hence, you'd need to handcraft your own script based on your PDF formatting.

1
  • Thanks a lot for sharing your solution. It seems very ingenious and well suited for your use, but it is a bit of an overkill for simply splitting a pdf in two, in my opinion.
    – Clément
    Commented Mar 11, 2023 at 15:58
0

Stewart's answer gave me pretty much all the tools, but I made two important edits to their solution:

  1. Use coherent pdf to preserve the table of content (pdftk just delete it),
  2. Make the command split the file in two, instead of splitting the file into as many files as chapters.

Re-using the parser.awk shared by Stewarts, assuming that the source is called source.pdf and that the chapter we want to use to split the document is called "Appendix", this gives:

pdftk source.pdf dump_data | \
awk -f parser.awk  | \
grep Appendix | \
{ 
    IFS=";" ; \
    read -r title start end; \
    ./cpdf source.pdf  1-"`expr $start - 1`" -o A.pdf; \
    ./cpdf source.pdf "$start"-"$end" -o B.pdf;
}

This

  1. Dump the metadata of the file,
  2. Extract the relevant bits (title ; page start ; page ends),
  3. Grab the relevant bit for the Appendix,
  4. Set the separator for read to be ;
  5. Store the title in a title variable, and the start page in start and end page in end,
  6. output all the pages from 1 to start - 1 (which corresponds to the page before the appendix begins) into a file called A.pdf,
  7. output the rest of the document into a file called B.pdf.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .