How to scale the page content of PDF files (with open source commandline tools)?

That is, how to change the margins of PDF files while keeping the physical page size the same. This article is about solutions that work on Linux, though some might also work on Windows and Mac.

There are multiple solution alternatives, but only the first is reported to work out well. Let’s start:

Alternative 1: pdfScale.sh, using ghostscript (works best!)

Since Thomas enabled this to work (see comments), this is the single best-working alternative in this list. For the latest and most comfortable implementation, use the tavinus pdfScale software. It is very elaborate, including both functions for scaling the content, for resizing the page, and for both.

The history behind that piece of software is really funny, actually. Haven’t seen something like this yet: software emerging “by itself” from a non-working 7 lines of code to an elaborate command-line utility over the course of 10 years. Here’s that history, to give credit where credit is due:

  1. ReproRog, in this comp.lang.postscript message from December 2007. (not working yet)
  2. Helge Blischke, adding to it in this comp.lang.postscript message. (not working yet)
  3. Myself, providing it as an incomplete solution in this post. (not working yet)
  4. Thomas, getting it to work in the first comment below.
  5. crooney, extending it into a script in the comments below.
  6. Walt, improving the script in the comments below.
  7. Håkon Hægland, improving the script further in this StackOverflow answer.
  8. user2697605, improving the script further in this StackOverflow answer.
  9. Michael J. Cole, again improving the script over the previous version and providing it in this Github gist (also, see his comment below and hist StackOverflow answer).
  10. Gustavo Arnosti Neves, using Michael J. Cole’s version and extending it in the tavinus pdfScale Github repository and working on it a lot, creating an elaborate solution. See also his comment below.

Woah. Nice little demo of the power of open source collaboration 🙂

This solution has no obvious drawbacks. It is quite elegant, includes freely adjustable margins, vector fonts and images, and avoids the GUI hassles. The same instruction should also work with Postscript (PS) files. The core Ghostscript command used in all of the above scripts is this:

gs
  -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  -dCompatibilityLevel="1.3" -dPDFSETTINGS="/printer" \
  -dSubsetFonts=true -dEmbedAllFonts=true \
  -sPAPERSIZE=a4 -sOutputFile="out.pdf" \
  -c "<</BeginPage{0.9 0.9 scale 29.75 42.1 translate}>> setpagedevice" \
  -f in.pdf

This example scales the page content to 90%, and to compensate, moves it right and up by 5% each to keep its center the same. The 5% has to be expressed in absolute measures of the DIN A4 page we’re using here, using PostScript points as units (which are 1/72 inches). So a DIN A4 page is 595×842 pps (width×height) in Ghostscript, and 5% of this is the 29.75×42.1 pps you see in the command. Now you know how to calculate these numbers for other scales and page sizes …

Oh and, the “\” line ends in the command above are just for line continuation in bash. Remove them when making a single-line command of course.

Edit 2012-08-31: Integrated corrections and information from Thomas’ comments below.

Edit 2014-12-27: Integrated a link to Michael’s nicely bundled Github gist version, from the comments below.

Edit 2017-06-18: Added tavinus’ version, and history of how it all happened.

Alternative 2: using pstops

The task can be performed with pstops (whereof psnup is a simplified frontend).

  1. Convert your PDF file to a PS file, by printing to a file in Adobe Reader, or by using pdf2ps (Ghostscript-based) or pdftops (xpdf-based).
  2. Use pstops to adjust the marginspstops -p a4 "L@.9(1cm,1cm)" in.ps out.psOn mounting pages: here, the the task is to mount two A4 pages in A5 format on one A4 page, guaranteeing page margins of 3cm at left and right and 2cm at top and bottom. We need a width of 150mm and a height of 257mm. To scale 297mm (A4 height) to 150mm, use factor 0.505. Such n-up mounting together with freestyle adjustment of margins is not possible ith psnup, which has a simpler user interface.pstops -p a4 "2:0L@.505(18cm,2cm)+1L@.505(18cm,14.85cm)" in.ps out.ps
  3. Convert the PS file back to PDF by using pdftops.

The problem with psnup (from PSUtils Release 1 Patchlevel 17) and also of its frontend psnup is that it converts fonts to bitmap fonts (Adobe Type 3). This can be detected as rastered fonts when viewing the PDF file with Adobe Reader. It generates somewhat lower print quality, but is still acceptable. What is not acceptable (with respect to file size) is that pstops converts the whole file to an image if it has no idea how to treat it.

To debug pstops and psnup output, you can use the -b option, which will mark out the original pages’ borders.

Alternative 3: Using Adobe Reader and printing the “current view”

Another alternative:

  1. Use Adobe Reader to print-to-file your page centered on a large sheet of paper, without scaling. This results in huge page margins, which we will reduce as desired in the other steps.
  2. Use ps2pdf to re-destill the PS file to PDF.
  3. Open the new PDF file in Adobe Reader and zoom and move so that you have an appropriate view, with your desired page margins.
  4. Use the “print current view” option in Adobe Reader to print exactly that view (which is, just what you see on the screen from the document right now).
  5. Again, use ps2pdf to re-destill the PS file to PDF.

The disadvantage of this technique is of course that margins cannot be chosen exactly.

But, this technique also allows to perform n-up mounting, should you need it. Say you start with a two-page document and want it 2-up mounted on one page. Then generate one page for each input page, using teh above technique: the first output page with a huge right margin, the second with a huge left margin. The margins should be large enough to contain the complete other page, respectively. You can now mount these output pages into one by overlaying them on top of each other using pdftk with the background option.

Alternative 4: Using Adobe Reader and printer margins

This has not yet been worked out, but might be possible. When printing (to a file or otherwise) Adobe Reader will fit the pages into the printable area of the selected printer. Now the idea would be to choose the special printer “Custom …”. This allows you to enter a lp print command. Per the lp documentation, CUPS lp understands options to set the margins. For 2cm at top and bottom and 3cm at left and right, use this command:

/usr/bin/lp -o page-top=57 -o page-bottom=57 -o page-left=85 -o page-right=85

However, this currently does not work out, for an unknown reason. It does not change anything, i.e. the document is printed as if you selected “Page scaling: none” instead of “Page scaling: fit to printable area”. If this command is not possible, another alternative would be to set up a printer definition with exactly the margins you desire, in a way that lets you change these margins easily.

If you just need “larger” margins around your page (without exact measures), you can do the following:

  1. Print the file with Adobe Reader to a PS file. Use the option “Page scaling: fit o printable area”. Try several different printers including the “Custom …” special printer to find one that adds margins of the same size all around the page. This step will generate a PS file with larger margins than the original PDF file had, even though this is not correctly shown in the preview of Adobe Reader 8.1.1.
  2. Convert the file to a PDF file by using ps2pdf.
  3. Repeat from step 1 with your new PDF file until the margins are large enough for you.

This method has the obvious disadvantage of not allowing to specify the margins exactly, but at least the file retains vector fonts and graphics (unlike whenusing pstops, see above).

If you need the margin adjustents in combination with Adobe Readers n-up printing (e.g. 2 pages per sheet), and if you can adjust the margins in your source file (before generating the initial PDF), you can do the following: adust the margins in the original file so that, after the n-up scaling, these margins together with the selected printer’s margins, result in the margins you desire. When printing one two pages A4 on one sheet A4 with the special prnter “Custom …” in Adobe Reader 8.1.1, the following margins are used (measured in the output A4 page):

  • left 5,25mm
  • right 13,71mm
  • top 6,59mm
  • bottom 6,59mm (probably)

Posted

in

,

by

Tags:

Comments

28 responses to “How to scale the page content of PDF files (with open source commandline tools)?”

  1. Thomas

    Thanks a lot! The Alternative 1 works for me. You need to change the BeginPage section to this:
    “<</BeginPage{0.9 0.9 scale 29.75 42.1 translate}>> setpagedevice”
    It scales the page down by 10%. The size of an a4 page is 595×842 in ghostscript. If you scale it down by 10% you need to adjust the content with the translate command by 5% right and 5% up so it will be 5-5% places on the edges of the paper.

  2. Thomas, great that you could work this thing out! I’m gonna correct it in the article. Thank you!

  3. Sonia

    Would love to be able to do this on a Windows machine!

  4. Sonia: It appears that GhostScript is also available for Windows. There are some peculiarities for using GhostScript on Windows so that you probably have to adapt a bit the syntax of the gs command from above.

    Wish you good luck with that. If you’re successful, welcome to tell here how you did it.

  5. crooney

    I realize I’m late to this party, but it’s a high google result and I found the first solution excellent. so I whipped up a script that takes a filename and a scaling factor, (1.1 is 10% bigger, .9 10% smaller) (unix/linux, obviously):

    #! /bin/sh

    #idea from http://ma.juii.net/blog/scale-page-content-of-pdf-files

    IN=`echo $1 | sed -e “s/\(\.pdf\)$//”`
    OUT=${IN}.out.pdf
    IN=${IN}.pdf

    SCALE=$2

    if [ “x$SCALE” = “x” ] ; then
    SCALE=1.1
    fi

    V=`echo “( 1.0 – $SCALE ) * 29.75 * 10” | bc –`
    H=`echo “( 1.0 – $SCALE ) * 42.1 * 10″ | bc –`

    echo $IN $OUT $SCALE $V $H

    gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER -dCompatibilityLevel=”1.5″ \
    -dPDFSETTINGS=”/printer” -dSubsetFonts=true -dEmbedAllFonts=true \
    -sPAPERSIZE=a4 -sOutputFile=$OUT \
    -c “<> setpagedevice” \
    -f $IN

  6. crooney

    d’ohh. html problems. i’ll try again

    #! /bin/sh

    #idea from http://ma.juii.net/blog/scale-page-content-of-pdf-files

    IN=`echo $1 | sed -e “s/\(\.pdf\)$//”`
    OUT=${IN}.out.pdf
    IN=${IN}.pdf

    SCALE=$2

    if [ “x$SCALE” = “x” ] ; then
    SCALE=1.1
    fi

    V=`echo “( 1.0 – $SCALE ) * 29.75 * 10” | bc –`
    H=`echo “( 1.0 – $SCALE ) * 42.1 * 10″ | bc –`

    echo $IN $OUT $SCALE $V $H

    gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER -dCompatibilityLevel=”1.5″ \
    -dPDFSETTINGS=”/printer” -dSubsetFonts=true -dEmbedAllFonts=true \
    -sPAPERSIZE=a4 -sOutputFile=$OUT \
    -c “<</BeginPage{ $SCALE $SCALE scale $V $H translate}>> setpagedevice” \
    -f $IN

  7. crooney, thanks for the contribution, much appreciated! I know code sucks in my comment section 😉 but you’re welcome to put it into a Github gist (for example) and link it here.

  8. Walt

    I also found this thread via Google (thanks!), and I also hacked together a script, which may go a bit beyond the previous one:

    ##############################################################

    #!/bin/bash

    # pdfScale.sh
    #
    # Scale PDF to specified percentage of original size.
    # Ref: http://ma.juii.net/blog/scale-page-content-of-pdf-files.

    SCALE=0.95 # scaling factor (0.95 = 95%, e.g.)

    # Validate args.
    [ $# -eq 1 ] || { echo "***ERROR: Usage pdfScale.sh <inFile>.pdf"; exit 99; }
    INFILEPDF="$1"
    [[ "$INFILEPDF" =~ ^..*\.pdf$ ]] || { echo "***ERROR: Usage pdfScale.sh <inFile>.pdf"; exit 99; }
    OUTFILEPDF=$(echo "$INFILEPDF" | sed -e s/\.pdf$// -)=SCALED.pdf

    # Get width/height in postscript points (1/72-inch), via ImageMagick identify command.
    # (Alternatively, could use Poppler pdfinfo command; or grep/sed the PDF by hand.)
    IDENTIFY=($(identify $INFILEPDF 2>/dev/null)) # bash array
    [ $? -ne 0 ] &GEOMETRY=($(echo ${IDENTIFY[2]} | tr "x" " ")) # bash array — $IDENTIFY[2] is of the form PGWIDTHxPGHEIGHT
    PGWIDTH=${GEOMETRY[0]}; PGHEIGHT=${GEOMETRY[1]}

    # Compute translation factors (to center page.
    XTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGWIDTH" | bc)
    YTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGHEIGHT" | bc)

    # Do it.
    gs \
    -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
    -dCompatibilityLevel="1.5" -dPDFSETTINGS="/printer" \
    -dSubsetFonts=true -dEmbedAllFonts=true \
    -dDEVICEWIDTH=$PGWIDTH -dDEVICEWIDTH=$PGHEIGHT \
    -sOutputFile="$OUTFILEPDF" \
    -c "<</BeginPage{$SCALE $SCALE scale $XTRANS $YTRANS translate}>> setpagedevice" \
    -f "$INFILEPDF"

  9. MichaelCole

    Awesome script. Added dependency checking and fixed a color error for my pdf.

    See https://gist.github.com/MichaelJCole/86e4968dbfc13256228a

  10. Thanks a lot, Michael. I’ve updated the post to include a link to your gist version.

    I like the little script, it’s such a funny piece of software: as of now, it’s written over 7 years by 7 people … almost like “written by the Internet itself” 😀

  11. alejandro

    In Mac OS X I got this error:

    GPL Ghostscript 9.10: Set UseCIEColor for UseDeviceIndependentColor to work properly.
    Unrecoverable error: stackunderflow in .setdistillerparams

    I looked for a solution and I had to add this flag to the command:

    -dUseCIEColor

  12. Aston

    Above scripts work only for downsizing, I have tried to upsize the pdf and its not working… the actual dimension of the pdf never increases…

  13. Anonymous

    Hello Everyone,
    Where do I place this script? I’m sending a print job that’s converted to a PDF and then printed by an HP Laserjet. How does the job know to read the script to do the resizing?
    Can anyone help with that?
    Thanks
    CJ

  14. CJ, we don’t use the script within a print job pipeline (perhaps that can work also, but I don’t know how). In your case, just print your print job to a PDF file first, call the script in a terminal to convert that PDF file to another one with resized content, and print that resulting PDF file to your printer. If you don’t have to do this for every single print job, it will be alright.

  15. Anonymous

    Thanks for your prompt reply Matt. However, I will printing these pdf files in batch jobs. So, calling a script for every single job will be arduous labor, and messy if scripted.
    Are you familiar with PCL commands, is it possible to create a pcl job to set the margin for the printer upon or prior to receipt of the print job. Also where are those pcl commands entered? I’m familiar with ppd files that are part of the printer, but don’t know if that will be where these pcl commands can be entered to alter the margins.
    Thanks
    CJ

  16. Can’t you just use the “Scale: 0-100%” option of your printer driver then? Under Linux, most applications show that option under “Page Setup → Layout → Scale” of the print dialog. If your printer driver does not have it, try a different one that speaks PCL.

    The post above was dealing with how to scale in the PDF file, not during printing. But now that I say it, setting that scale option and printing to a PDF file would be another solution for that problem. I guess that option did not exist back in 2008 then …

    For a way too set custom margins during printing, see “Alternative 4” in the post above for the custom print command. Did not work for me back then, but maybe it’s a good starting point.

  17. Thanks a lot for this script, and Michael’s improvements.

  18. karpi

    If you want to print some selection on one page scaled up to exact value (for example 2 times),
    you can use method mentioned above ‘Using Adobe Reader and printing the “current view”‘ and:
    -> manually set exact zoom (for example 200%) to ‘Select & Zoom’ toolbar
    -> carefully use hand tool to center view
    -> call print dialog and set ‘Current view’ and ‘Fit to Printable Area’
    -> look to print Preview, and under the dimensioned page note real Zoom info (here 179% – for example)
    -> exit print dialog and enter corrected zoom to view (200*(200/179)=223%) – unfortunately this float value is rounded to whole 1%
    -> call print dialog again, now Zoom value is what you want (199%)

  19. Hi frands!
    I have “forked” @MichaelJCole’s gist into a repository and added a bunch off suff.
    Here: https://github.com/tavinus/pdfScale
    Main reason is because I wanted a Makefile there as well. But then again, the readme.md come as a bonus.
    So I changed a few bits and added a lot of cli handling stuff like help, verbose, etc. I may still change it a bit as I will be using it in production. You can read the commit messages if you like.
    I was able to run it on MacOS X, Debian/Ubuntu/Mint* and CentOS/RHEL/Amazon*. So it should run pretty much on any POSIX environment. I have a feeling we can still change stuff to be bash built-in.

    Any feedback is welcome!
    Thanks for providing all the hard logic!
    Cheers!

  20. doWhileTrue

    Terrific post, thanks.
    On Ubuntu 14.04 I had to use the additional switch -dUseCIEColor to prevent an error (Alternative 1)

  21. Gerald

    Thank you very much for your hint 1. I would never find this script without your blog.
    I got often papers with a format smaller than A4 (so much for standardisation) and to safe some paper sheets, I print 2 pages on one sheet. For now on, I scale the papers a little bit and than I do:
    pdfnup –nup 2×1 –suffix ‘2×1’ new_nobel_prize_winning_paper.pdf
    This is more comfortable to my eyes.

  22. Chit my new engagement

    http://amira.ablogs.relayblog.com/?post-presley

    zeez free porn amy king porn free iphone vagi cam porn 4 cubes porn site free twin asian porn

  23. My contemporary folio

    http://rhiannon.goblog.allproblog.com/?entry-alexia

    free latest iphone porn moms a cheater porn porn free clips daily chode penis porn picture porn site founded in arizona

  24. Frank

    I’m using VeryUtils PDF Page Resizer Command Line, it works great for me,

    https://veryutils.com/pdf-page-resizer-command-line

  25. Jaime

    I think that the “translate” values in your “core Ghostscript command” are incorrect. When I run “pdfScale.sh –gs-call -s 0.9 in.pdf”, it shows the translate values as being “33.055225 46.777310” (these values are your values divided by 0.9, so I assume that the translate values are “scaled” before they
    are used). HTH.

  26. vladd

    #! /bin/bash
    # Writen by Gorbatchev Vladimir

    # Image to print must to be located by a gimp or another editors.
    # With the script it is possible to resize the image and composite
    # to a4 canvas

    if [[ $# > 0 && -e $1 ]]; then
    in=$1
    ext=$(echo $1|sed ‘s/.*\.//’)
    if [[ $ext == “pnm” || $ext == “pdf” || $ext == “png” ]]; then
    fname=$(echo $1|sed -e “s/\.[^.]*$//”)

    if [[ $ext == “pnm” ]]; then
    rm -f *.resized.pdf blank.pnm resized.png
    elif [[ $ext == “pdf” ]]; then
    rm -f *.pnm *.resized.pdf blank.pnm resized.png
    elif [[ $ext == “png” ]]; then
    rm -f *.pnm *.resized.pdf blank.pnm resized.png
    fi

    if [[ $ext == “pdf” || $ext == “png” ]]; then
    convert -density 300 -quality 100 $in $fname.pnm
    sync
    fi

    # Image original size
    w_in=$(convert $fname.pnm -print “%w” /dev/null)
    h_in=$(convert $fname.pnm -print “%h” /dev/null)

    if [[ $# == 1 ]]; then
    w=$(echo “$w_in” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    h=$(echo “$h_in” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    echo “$in w=$w h=$h”
    exit 0
    fi

    if [[ $# 3 ]]; then
    echo “Usage: $0 ”
    exit 99
    else

    Xscale=$2
    Yscale=$3

    # Is X coordinate integer number?
    w1=$(echo “$Xscale” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    w2=$(echo “$Xscale” | bc -l|awk ‘{printf “%.20f\n”,$1}’)
    w1=$(echo “$w1*10^20” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    w2=$(echo “$w2*10^20” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    # Is Y coordinate integer number?
    y1=$(echo “$Yscale” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    y2=$(echo “$Yscale” | bc -l|awk ‘{printf “%.20f\n”,$1}’)
    y1=$(echo “$y1*10^20” | bc -l|awk ‘{printf “%.0f\n”,$1}’)
    y2=$(echo “$y2*10^20” | bc -l|awk ‘{printf “%.0f\n”,$1}’)

    if [[ $w1 < $w2 || $y1 /dev/null
    convert -size 2479×3508 canvas:white blank.pnm
    convert blank.pnm \( resized.png +repage \) -geometry +200+200 -composite resized.png
    convert -density 300 -quality 100 resized.png $fname.resized.pdf
    exit 0
    fi

    fi
    else
    echo “You can enter integer number to add to coordinate or float number”
    echo “to multiply coordinate to resize the image”; perl -e ‘printf “\n”‘
    echo “Examples:”
    echo “vladd@debian:~/tmp$ ./fScale a.pdf 4 -5”
    echo “w=1292+(4) h=947+(-5)”
    echo “a.pdf w=1296 h=942”
    echo “vladd@debian:~/tmp$ ./fScale a.pdf 1.003 0.996”
    echo “w=1292*(1.003) h=947*(0.996)”
    echo “a.pdf w=1296 h=943”
    echo “or”
    echo “inFile not .pdf .png or .pnm or inFile not exists”
    exit 99
    fi

  27. vladd

    The code above have mistakes because to site copy – paste

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.