How can I drop metadata fields (e.g., PageLabel fields) from PDFs? - pdf

I have used pdftk to change the "Info" metadata associated with a PDF. I currently have several PDFs with extraneous page labels and I cannot figure how to drop them. This is what I am currently doing:
$ pdftk example_orig.pdf dump_data output page_labels.orig
$ grep -v PageLabel page_labels.orig > page_labels.new
$ pdftk example_orig.pdf update_info page_labels.new output example_new.pdf
This does not remove the PageLabel* metadata which can be verified with:
$ pdftk example_orig.pdf dump_data | grep PageLabel
How can I programmatically remove this metadata from the PDF? It would be nice to do with with pdftk but if there another tool or way to do this on GNU/Linux, that would also work for me.
I need this because I am using LaTeX Beamer to generate presentations with the \setbeameroption{show notes on second screen} option which generates a double-width PDF for showing notes on a second screen. Unfortunately, there seems to be a bug in pgfpages which results in incorrect and extraneous PageLabels in these files (example). If I generate a slides only PDF, it will generates the correct PageLabels (example). Since I can generate a correct set of PageLabels, one solution would be to replace the pagelabels in the first examples with those in the second. That said, since there are extra pagelabels in the first example, I would need to remove them first.

Using a text editor to remove PDF metadata
If it is the first time you edit a PDF, make a backup copy first.
Open your PDF with a text editor that can handle binary blobs. vim -b will be fine.
Locate the /Info dictionary. Overwrite all the entries you do not want any more completely with blanks (an entry consists of /Key names plus the (some values) following them).
Be careful to not use more spaces than there were characters initially. Otherwise your xref table (ToC of PDF objects will be invalidated, and some viewers will indicate the PDF as corrupted).
For additional measure, locate the /XML string in your PDF. It should show you where your XMP/XML metadata section is (not all PDFs have them). Locate all the key values (not the <something keys>!) in there which you want to remove. Again, just overwrite them with blanks and be careful not to change the total length (neither longer, nor shorter).
In case your PDF does not make the /Info dictionary accessible, transform it with the help of qpdf.
Use this command:
qpdf --qdf --object-streams=disable orig.pdf qdf---orig.pdf
Apply the procedure outlined above. (The qdf---orig.pdf now should be much better suited for
Re-compact your edited file:
qpdf qdf---orig.pdf edited---orig.pdf
Done! Enjoy your edited---orig.pdf. Check if it has all the data removed:
pdfinfo -meta edited---orig.pdf
Update
After looking at the sample PDF files provided, it became clear to me that the /PageLabel key is not part of the /Info dictionary (PDF's Document Information Dictionary), but of the /Root object.
That's probably one reason why pdftk was unable to update it with the method the OP described.
The other reason is the following: the PDF which the OP quoted as containing the correct page labels does in fact contain incorrect ones!
Logical Page No. | Page Label
-----------------+------------
1 | 1
2 | 2
3 | 2
4 | 2
5 | 2
6 | 4
The other PDF (which supposedly contains extraneous page labels) is incorrect in a different way:
Logical Page No. | Page Label
-----------------+------------
1 | 1
2 | 1
3 | 2
4 | 2
5 | 2
6 | 4
My original advice about how to manually edit the classical metadata of a PDF remains valid. For the case of editing page labels you can apply the same method with a slight variation.
In the case of the OP's example files, the complication comes into play: the /Root object is not directly accessible, because it is hidden inside a compressed object stream (PDF object type /ObjStm). That means one has to decompress it with the help of qpdf first:
Use qpdf:
qpdf --qdf --object-streams=disable example_presentation-NOTES.pdf q-notes.pdf
Open the resulting file in binary mode with vim:
vim -b q-notes.pdf
Locate the 1 0 obj marker for the beginning of the /Root object, containing a dictionary named /PageLabels.
(a) To disable page labels altogether, just replace the /PageLabels string by /Pagelabels, using a lowercase 'l' (PDF is case sensitive, and will no longer recognize the keyword; you yourself could at some other time restore the original version should you need it.)
(b) To edit the page labels, first see how the consecutive labels for pages 1--6 are being referred to as
<feff0031>
[....]
<feff0032>
[....]
<feff0032>
[....]
<feff0032>
[....]
<feff0033>
[....]
<feff0034>
(These values are in BOM-marked hex, meaning 1, 2, 2, 2, 3, 4...)
Edit these values to read:
<feff0031>
[....]
<feff0032>
[....]
<feff0033>
[....]
<feff0034>
[....]
<feff0035>
[....]
<feff0036>
Save the file and run qpdf again in order to re-compress the PDF:
qpdf q-notes.pdf notes.pdf
These now hopefully are the page labels the OP is looking for....
Since the OP seems to be familiar with editing pdftk's output of dump_data output, he can possibly edit the output and use update_data to apply the fix to the PDF without needing to resort to qpdf and vim.
Update 2:
User #Iserni posted a very good, short and working answer, which limits itself to one command, pdftk, which the OP seems to be familiar with already, plus sed -- not needing to use a text editor to open the PDF, and not introducing an additional utility qpdf like my answer did.
Unfortunately #Iserni deleted it again after a comment of mine. I think his answer deserves to get the bounty and I call you to vote to "undelete" his answer!
So temporarily, I'll include a copy of #Iserni's answer here, until his is undeleted again:
Not sure if I correctly understood the problem. You can try with a butcher's solution: brute force replace the /PageLabels block with a different one which will not be recognized.
# Get a readable/writable PDF
pdftk file1.pdf output temp.pdf uncompress
# Mangle the PDF. Keep same length
sed -e 's|^/PageLabels|/BageLapels|g' < temp.pdf > mangled.pdf
# Recompress
pdftk mangled.pdf output final.pdf compress
# Remove temp file
rm -f temp.pdf mangled.pdf

Not sure if I correctly understood the problem. You can try with a butcher's solution: brute force replace the /PageLabels block with a different one which will not be recognized.
# Get a readable/writable PDF
pdftk file1.pdf output temp.pdf uncompress
# Mangle the PDF. Keep same length
sed -e 's|^/PageLabels|/BageLapels|g' < temp.pdf > mangled.pdf
# Recompress
pdftk mangled.pdf output final.pdf compress
rm -f temp.pdf mangled.pdf

Related

How to identify the pdf object in raw pdf file?

I want to remove certain objects using programs.
Using cpdf I can get the objects, if I can somehow identify the objects that I want to delete, then I should be able to modify pdf files with programs.
$ cpdf in.pdf -output-json -output-json-parse-content-streams -o out.json
$ cpdf -j out.json -o out.pdf
However, I can not find out the object corresponding to my target text. For example, text search does not work on a raw pdf file. What is the best way to identify the target object of a text?
EDIT: Here is a test pdf. Please remove XYZ from the top of each page. Note that the test is a significant simplification of the real pdf file. So the solution should not be so simple so that it can not be applied to real complicated pdf files.
curl -s https://i.stack.imgur.com/whsnm.gif | tail -c +43 > test.pdf
The output of cpdf -output-json -output-json-parse-content-streams may or may not contain text which is recognisable to you. This depends on the font encodings in use, and the way in which text is layed out. In your file, for example, the painting of the string "XYZ" is represented as
[ "\u0000;\u0000<\u0000=", "Tj" ]
This is a string representing three codepoints indexing into the font. Cpdf presently has no way to show you what actual text this corresponds to; a future version will.
So I don't think your task can be done via cpdf -output-json in the general case, or indeed in this specific case.

How to decode PDF file and encode it back?

My overall goal is to make some PDF files conform to the PDF/A standard for archival purposes. They fail one requirement, namely that some glyph mappings map to 0, which they should not.
My usual strategy was to use an old software called "Pdfedit" that could decode PDF-Files, all the byte-streams would then be human-readable, edit the relevant part of the PDF containing the glyph mappings, and open the file with Adobe Acrobat that automatically re-encoded it.
Now I have some PDFs that cause "Pdfedit" to crash upon opening. I tried using PDF-Parser but its output cannot be re-encoded by Adobe Acrobat.
Also, the relevant parts used to look like this decoded:
/CMapType 2 def
1 begincodespacerange
<00><04>
endcodespacerange
5 beginbfchar
<00><0000>
<01><0000>
<02><263A>
<03><0000>
<04><0000>
endbfchar
endcmap
But now I use the following command python3 pdf-parser.py -f -n /path/to/file.pdf > dump.txt and inside dump.txt the relevant part looks like this:
b'/CMapType 2 def\n1 begincodespacerange\n<00><04>\nendcodespacerange\n5 beginbfchar\n<00><0000>\n<01><0000>\n<02><263A>\n<03><0000>\n<04><0000>\nendbfchar\nendcmap\nCMapName currentdict/CMap defineresource pop end end'
So it is a bytestring and any linebreak is rendered literally as \n. The txt file that contains this cannot be interpreted as a PDF by Adobe Acrobat.
I have now also realized that many elements such as %%EOF are delimited by ''.
The true issue is how to get an Acrobat-readable output from pdf-parser.py, as the shell-command > does not work and stdout in the shell is also faulty.
I will try out a few things but could really need some help on this!
Answering my own question in case this is relevant for someone down the line.
Didier Stevens, the dev behind the pdf-parser, answered that his tool is not made for this. He recommended qpdf instead.
That was indeed the solution. Make sure you use the flag --stream-data=uncompress so that compressed parts are also accessible in the output. The command to use with qpdf is:
qpdf old_file.pdf --stream-data=uncompress --decode-level=all new_file.txt
You can output new_file also as .pdf. In any case you will be able to open it in the text editor. Once you're done applying the changes you wish to apply, you can change the ending to pdf and process it further with acrobat or any other conversion program.

How can I remove the last three pages of a set of PDF files using pdf-stapler?

I have a large set of PDF files, each of which contains three unnecessary pages at the end. I was originally trying to create a solution based on an answer I found on askUbuntu and another StackOverflow question, but pdf-stapler apparently doesn't support the "r" notation that pdftk did, e.g. if I try pdf-stapler del myFile.pdf r4-r1 outputFiles/myFile.pdf or pdf-stapler cat myFile.pdf 1-r4 outputFiles/myFile.pdf, I get an error saying "Invalid range: r4-r1" for the first option and an output file with only page 1 for the second option.
Is there a way to do this with pdf-stapler?
The qpdf CLI command should be available in the Fedora repositories. It supports the "r" notation, so removing the last three pages from a file should work like this:
qpdf --pages myFile.pdf 1-r4 -- myFile.pdf outputFiles/myFile.pdf

How to annotate PS or PDF from (Linux) command line without losing quality?

Is there any command line tool for Linux that will allow me to annotate a PS or PDF file with text or a particular font, color, and size with no loss of quality? I have tried ImageMagick's convert, and the resulting PDF is of pretty poor quality.
I have a template originally authored in Adobe Illustrator, and I would like to generate PDFs from it with names in certain places. I have a huge list of names, so I would like to do this in a batch (not interactively).
If anyone has any ideas I'd appreciate hearing them.
Thanks,
Carl
Another way to accomplish this would be to hack the postscript file itself. It used to be that AI files were postscript files, and you could modify them directly; I don't know if that's true anymore. So you may have to export it.
For simplicity, I assume there's a single page. Therefore, at the very end there will be a single call to showpage (perhaps through another name). Any drawing commands performed before showpage will show up on the page.
You may need to reinitialize the graphics state (initgraphics), as the rest of the document may have left it all funny, expecting showpage to clean up before anyone notices.
To place text, you'll need to set a new font (the old one was invalidated by initgraphics) measure the location in points (72 points/inch, 28.3465 points/cm).
/Palatino-Roman 17 selectfont %so much prettier than Times
x y moveto
(new text) show
To do the merging, you can use perl: emit the beginning of the document as a HERE-document, construct some text-writing lines by program, emit the tail of the document. Here's an example of generating postscript with PERL
Or you can take data from the command-line (with ghostscript) by using the -- option ($gs -q -- program.ps arg1 arg2 ... argn). These arguments are accessible to the program through an array named /ARGUMENTS.
So, say you have a nice graphic of a scary clown holding a blank sign about 1 inch wide, 3 inches tall, top left corner at 4 inches from the left, 4 inches from the bottom. You can insert this code into the ps program, just before showpage.
initgraphics
/Palatino-Roman 12 selectfont
4 72 mul 4 72 mul moveto
ARGUMENTS {
gsave show grestore 0 -14 rmoveto
} forall
Now you can make him say funny things ($gs -- clown.ps "On a dark," "and stormy night...").
I think it's better to create PDF form and fill it with pdftk fill_form in batch:
$ pdftk form.pdf fill_form data.fdf output out.pdf flatten
Form data should be in Forms Data Format (it's just XML file with field names and values specified).
Note the flatten command. It is required to convert filled form to plain document.
Another way is to create set of PDF documents "with names in certain places" and transparent background, and pdftk stamp each of them over the template:
$ pdftk template.pdf stamp words.pdf output out.pdf

Rendering the whole media box of a pdf page into a png file using ghostscript

I'm trying to render Pdfs pages into png files using Ghostscript v9.02. For that purpose I'm using the following command line:
gswin32c.exe -sDEVICE=png16m -o outputFile%d.png mypdf.pdf
This is working fine when the pdf crop box is the same as the media box, but if the crop box is smaller than the media box, only the media box is displayed and the border of the pdf page is lost.
I know usually pdf viewers only display the crop box but I need to be able to see the whole media page in my png file.
Ghostscript documentation says that per default the media box of a document is rendered, but this does not work in my case.
As anyone an idea how I could achieve rendering the whole media box using ghostscript?Could it be that for png file device, only the crop box is rendered? Am I maybe forgetting a specific command?
For example, this pdf contains some registration marks outside of the crop box, which are not present in the output png file. Some more information about this pdf:
media box:
width: 667
height: 908 pts
crop box:
width: 640
height: 851
OK, now that revers has re-stated his problem into that he is looking for "generic code", let me try again.
The problem with a "generic code" is that there are many "legal" formal representations of "CropBox" statements which could appear in a PDF. All of the following are possible and correct and set the same values for the page's CropBox:
/CropBox[10 20 500 700]
/CropBox[ 10 20 500 700 ]
/CropBox[10 20 500 700 ]
/CropBox [10 20 500 700]
/CropBox [ 10 20 500 700 ]
/CropBox [ 10.00 20.0000 500.0 700 ]
/CropBox [
10
20
500
700
]
The same is true for ArtBox, TrimBox, BleedBox, CropBox and MediaBox. Therefor you need to "normalize" the *Box representation inside the PDF source code if you want to edit it.
First Step: "Normalize" the PDF source code
Here is how you do that:
Download qpdf for your OS platform.
Run this command on your input PDF:
qpdf --qdf input.pdf output.pdf
The output.pdf now will have a kind of normalized structure (similar to the last example given above), and it will be easier to edit, even with a stream editor like sed.
Second Step: Remove all superfluous *Box statements
Next, you need to know that the only essential *Box is MediaBox. This one MUST be present, the others are optional (in a certain prioritized way). If the others are missing, they default to the same values as MediaBox. Therefor, in order to achieve your goal, we can simply delete all code that is related to them. We'll do it with the help of sed.
That tool is normally installed on all Linux systems -- on Windows download and install it from gnuwin32.sf.net. (Don't forget to install the named "dependencies" should you decide to use the .zip file instead of the Setup .exe).
Now run this command:
sed.exe -i.bak -e "/CropBox/,/]/s#.# #g" output.pdf
Here is what this command is supposed to do:
-i.bak tells sed to edit the original file inline, but to also create a backup file with a.bak suffix (in case something goes wrong).
/CropBox/ states the first address line to be processed by sed.
/]/ states the last address line to be processed by sed.
s tells sed to do substitutions for all lines from first to last addressed line.
#.# #g tells sed which kind of substitution to do: replace each arbitrary character ('.') in the address space by blanks (''), globally ('g').
We substitute all characters by blanks (instead of by 'nothing', i.e. deleting them) because otherwise we'd get complaints about "PDF file corruption", since the object reference counting and the stream lengths would have changed.
Third step: run your Ghostscript command
You know that already well enough:
gswin32c.exe -sDEVICE=png16m -o outputImage_%03d.png output.pdf
All the three steps from above can easily be scripted, which I'll leave to you for your own pleasure.
First, let's get rid of a misunderstanding. You wrote:
"This is working fine when the pdf crop box is the same as the media box, but if the crop box is smaller than the media box, only the media box is displayed and the border of the pdf page is lost."
That's not correct. If the CropBox is smaller than the MediaBox, then only the CropBox should be displayed (not the MediaBox). And that is exactly how it was designed to work. This is the whole idea behind the CropBox concept...
At the moment I cannot think of a solution that works automatically for each PDF and all possibly values that can be there (unless you want to use payware).
To manually process the PDF you linked to:
Open the PDF in a good text editor (one that doesn't mess with existing EOL conventions, and doesn't complain about binary parts in the file).
Search for all spots in the file that contain the /CropBox keyword.
Since you have only one page in the PDF, it should find only one spot.
This could read like /CropBox [12.3456 78.9012 345.67 890.123456].
Now edit this part, carefully avoiding to add to (or lose from) the number of already existing characters:
Set the value to your wanted one: /CropBox [0.00000 0.00000 667.00 908.000000]. (You can use spaces instead of my .0000.. parts, but if I do, the SO editor will eat them and you'll not see what I originally typed...)
Save the file under a new name.
A PDF viewer should now show the full MediaBox (as of your specification).
When you convert the new file with Ghostscript to PNG, the bigger page will be visible.