Skip to content

Commit

Permalink
Finalize extended booklet cmd as contributed by Adam Greenhall
Browse files Browse the repository at this point in the history
  • Loading branch information
hhrutter committed Feb 25, 2024
2 parents d3e607d + a893411 commit cfd7627
Show file tree
Hide file tree
Showing 26 changed files with 1,039 additions and 123 deletions.
82 changes: 46 additions & 36 deletions cmd/pdfcpu/process.go
Expand Up @@ -39,8 +39,6 @@ import (
"github.com/pkg/errors"
)

var errInvalidBookletID = errors.New("pdfcpu: booklet: n: one of 2, 4")

func hasPDFExtension(filename string) bool {
return strings.HasSuffix(strings.ToLower(filename), ".pdf")
}
Expand Down Expand Up @@ -1211,34 +1209,51 @@ func processRotateCommand(conf *model.Configuration) {
process(cli.RotateCommand(inFile, outFile, rotation, selectedPages, conf))
}

func parseAfterNUpDetails(nup *model.NUp, argInd int, filenameOut string) []string {
if nup.PageGrid {
cols, err := strconv.Atoi(flag.Arg(argInd))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
rows, err := strconv.Atoi(flag.Arg(argInd + 1))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
if err = pdfcpu.ParseNUpGridDefinition(cols, rows, nup); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
func parseForGrid(nup *model.NUp, argInd *int) {
cols, err := strconv.Atoi(flag.Arg(*argInd))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
rows, err := strconv.Atoi(flag.Arg(*argInd + 1))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
if err = pdfcpu.ParseNUpGridDefinition(cols, rows, nup); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
*argInd += 2
}

func parseForNUp(nup *model.NUp, argInd *int, nUpValues []int) {
n, err := strconv.Atoi(flag.Arg(*argInd))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
if !types.IntMemberOf(n, nUpValues) {
ss := make([]string, len(nUpValues))
for i, v := range nUpValues {
ss[i] = strconv.Itoa(v)
}
argInd += 2
err := errors.Errorf("pdfcpu: n must be one of %s", strings.Join(ss, ", "))
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
if err = pdfcpu.ParseNUpValue(n, nup); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
*argInd++
}

func parseAfterNUpDetails(nup *model.NUp, argInd int, nUpValues []int, filenameOut string) []string {
if nup.PageGrid {
parseForGrid(nup, &argInd)
} else {
n, err := strconv.Atoi(flag.Arg(argInd))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
if err = pdfcpu.ParseNUpValue(n, nup); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
argInd++
parseForNUp(nup, &argInd, nUpValues)
}

filenameIn := flag.Arg(argInd)
Expand Down Expand Up @@ -1307,7 +1322,7 @@ func processNUpCommand(conf *model.Configuration) {
// pdfcpu nup outFile n inFile|imageFiles...
// If no optional 'description' argument provided use default nup configuration.

inFiles := parseAfterNUpDetails(nup, argInd, outFile)
inFiles := parseAfterNUpDetails(nup, argInd, pdfcpu.NUpValues, outFile)
process(cli.NUpCommand(inFiles, outFile, pages, nup, conf))
}

Expand Down Expand Up @@ -1345,7 +1360,7 @@ func processGridCommand(conf *model.Configuration) {
// pdfcpu grid outFile m n inFile|imageFiles...
// If no optional 'description' argument provided use default nup configuration.

inFiles := parseAfterNUpDetails(nup, argInd, outFile)
inFiles := parseAfterNUpDetails(nup, argInd, nil, outFile)
process(cli.NUpCommand(inFiles, outFile, pages, nup, conf))
}

Expand Down Expand Up @@ -1383,12 +1398,7 @@ func processBookletCommand(conf *model.Configuration) {
// pdfcpu booklet outFile n inFile|imageFiles...
// If no optional 'description' argument provided use default nup configuration.

inFiles := parseAfterNUpDetails(nup, argInd, outFile)
n := nup.Grid.Width * nup.Grid.Height
if n != 2 && n != 4 {
fmt.Fprintf(os.Stderr, "%s\n", errInvalidBookletID)
os.Exit(1)
}
inFiles := parseAfterNUpDetails(nup, argInd, pdfcpu.NUpValuesForBooklets, outFile)
process(cli.BookletCommand(inFiles, outFile, pages, nup, conf))
}

Expand Down
90 changes: 67 additions & 23 deletions cmd/pdfcpu/usage.go
Expand Up @@ -664,34 +664,62 @@ Examples: pdfcpu nup out.pdf 4 in.pdf
pages ... for inFile only, please refer to "pdfcpu selectedpages"
description ... dimensions, formsize, border, margin
outFile ... output PDF file
n ... booklet style (2 or 4)
n ... booklet style (2, 4, 6, 8)
inFile ... input PDF file
imageFiles ... input image file(s)
There are two styles of booklet, depending on your page/input and sheet/output size:
There are several styles of booklet, depending on your page/input and sheet/output size,
the edge along which your booklet will be bound,
and your preferred method for creating the booklet.
n=2: Two of your pages fit on one side of a sheet (eg statement on letter, A5 on A4)
For assembly instructions for each type, see: https://pdfcpu.io/generate/booklet
n=2: This is the simplest case and the most common for those printing at home.
Two of your pages fit on one side of a sheet (eg statement on letter, A5 on A4)
Assemble by printing on both sides (odd pages on the front and even pages on the back) and folding down the middle.
A variant of n=2 is a technique to bind your own hardback book.
It works best when the source PDF holding your book content has at least 128 pages.
You bind your paper in eight sheet folios each making up 32 pages of your book.
Each sheet is going to make four pages of your book, gets printed on both sides and folded in half.
A variant of n=2 is multifolio, a technique to bind your own hardback book.
This technique makes the most sense when your book has at least 128 pages.
For example, you can bind your paper in eight sheet folios (also known as signatures), with each folio containing 32 pages of your book.
For such a multi folio booklet set 'multifolio:on' and play around with 'foliosize' which defaults to 8.
n=4: Four of your pages fit on one side of a sheet (eg statement on ledger, A5 on A3, A6 on A4)
Assemble by printing on both sides, then cutting the sheets horizontally.
The sets of pages on the bottom of the sheet are rotated so that the cut side of the
paper is on the bottom of the booklet for every page. After cutting, place the bottom
set of pages after the top set of pages in the booklet. Then fold the half sheets.
n=4: Four of your pages fit on one side of a sheet (eg statement on ledger, A5 on A3, A6 on A4).
When printing 4-up, your booklet can be bound either along the long-edge (for portrait this is the left side of the paper, for landscape the top)
or the short-edge (for portrait this is the top of the paper, for landscape the left side).
Using a different binding will change the ordering of the pages on the sheet.
You can set long or short-edge with the 'binding' option.
In 4-up printing, the sets of pages on the bottom of the sheet are rotated so that the cut side of the
paper is on the bottom of the booklet for every page (for the default portrait, long-edge binding case.
Similar rotation logic applies for the other three orientations).
Having the cut edge always on bottom makes for more uniform pages within the book and less work in trimming.
The btype=advanced is a special method for assembling, only for 4-up booklets.
Printers that are used to collating first and then cutting may prefer this method.
n=6: Six of your pages fit on one side of a sheet. This produces an unusual sized booklet.
Only available for portrait, long-edge orientation.
n=8: Eight of your pages fit on one side of a sheet (eg A6 on A3).
Only available for portrait, long-edge orientation.
Perfect binding is a special type of booklet. The main difference is that the binding is glued into a spine,
meaning that the pages are cut along the binding and not folded as in the other forms of booklet.
This results in a different page ordering on the sheet than the other methods. If you intend to perfect bind your booklet,
use btype=perfectbound.
portrait landscape
Possible values for n: 2 ... 1x2 2x1
Possible values for n: 2 ... 1x2 --
4 ... 2x2 2x2
6 ... 2x3 --
8 ... 2x4 --
<description> is a comma separated configuration string containing these optional entries:
(defaults: "dim:595 842, formsize:A4, border:off, guides:off, margin:0")
(defaults: "dim:595 842, formsize:A4, btype: booklet, binding: long, multifolio: false, border:off, guides:off, margin:0")
dimensions: (width,height) of the output sheet in given display unit eg. '400 200'
formsize: The output sheet size, eg. A4, Letter, Legal...
Expand All @@ -700,6 +728,8 @@ set of pages after the top set of pages in the booklet. Then fold the half sheet
Only one of dimensions or format is allowed.
Please refer to "pdfcpu paper" for a comprehensive list of defined paper sizes.
"papersize" is also accepted.
btype: The method for arranging pages into a booklet. (booklet, bookletadvanced, perfectbound)
binding: The edge of the paper which has the binding. (long, short)
multifolio: Generate multi folio booklet (on/off, true/false, t/f) for n=2 and PDF input only.
foliosize: folio size for multi folio booklets only (default:8)
border: Print border (on/off, true/false, t/f)
Expand All @@ -710,18 +740,32 @@ set of pages after the top set of pages in the booklet. Then fold the half sheet
All configuration string parameters support completion.
Examples: pdfcpu booklet -- "formsize:Letter" out.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheet side (4 per sheet, back and front) onto out.pdf
Examples:
pdfcpu booklet -- "formsize:Ledger" out.pdf 4 in.pdf"
Arrange pages of in.pdf 4 per sheet side (8 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:Letter" out.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheet side (4 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:A4" out.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheet side (4 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:Ledger" out.pdf 4 in.pdf
Arrange pages of in.pdf 4 per sheet side (8 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:Ledger" out.pdf 6 in.pdf
Arrange pages of in.pdf 6 per sheet side (12 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:A3" out.pdf 8 in.pdf
Arrange pages of in.pdf 8 per sheet side (16 per sheet, back and front) onto out.pdf
pdfcpu booklet -- "formsize:A4, multifolio:on" hardbackbook.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheetside as sequence of folios covering 4*foliosize pages each.
See also: https://www.instructables.com/How-to-bind-your-own-Hardback-Book/
pdfcpu booklet -- "formsize:A3, binding:short" out.pdf 4 in.pdf
Arrange pages of in.pdf 4 per sheet side, with short-edge binding onto out.pdf
pdfcpu booklet -- "formsize:A4, multifolio:on" hardbackbook.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheetside as sequence of folios covering 4*foliosize pages each.
See also: https://www.instructables.com/How-to-bind-your-own-Hardback-Book/
pdfcpu booklet -- "formsize:A4, btype:perfectbound" out.pdf 2 in.pdf
Arrange pages of in.pdf 2 per sheet side, arranged for perfect binding, onto out.pdf
pdfcpu booklet -- "formsize:A3, btype:bookletadvanced" out.pdf 4 in.pdf
Arrange pages of in.pdf 4 per sheet side, arranged for advanced binding, onto out.pdf
`

usageGrid = "usage: pdfcpu grid [-p(ages) selectedPages] -- [description] outFile m n inFile|imageFiles..." + generalFlags
Expand Down
85 changes: 85 additions & 0 deletions pkg/api/test/booklet_test.go
Expand Up @@ -151,6 +151,91 @@ func TestBooklet(t *testing.T) {
false,
},

// more nup
{"TestBookletFromPDF_2up_perfectbound",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLetter_2Up_perfectbound.pdf"),
[]string{"1-24"},
"p:LetterP, g:on, btype:perfectbound",
"points",
2,
false,
},
{"TestBookletFromPDF_6up",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_6Up.pdf"),
[]string{"1-24"},
"p:LedgerP, g:on",
"points",
6,
false,
},
{"TestBookletFromPDF_8up",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_8Up.pdf"),
[]string{"1-32"},
"p:LedgerP, g:on",
"points",
8,
false,
},

// misc orientations and booklet types on 4-up
{"TestBookletFromPDF_4up_portrait_short",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_portrait_short.pdf"),
[]string{"1-24"},
"p:LedgerP, g:on, binding:short",
"points",
4,
false,
},
{"TestBookletFromPDF_4up_landscape_long",
[]string{filepath.Join(inDir, "bookletTestLandscape.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_landscape_long.pdf"),
[]string{"1-24"},
"p:LedgerL, g:on",
"points",
4,
false,
},
{"TestBookletFromPDF_4up_landscape_short",
[]string{filepath.Join(inDir, "bookletTestLandscape.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_landscape_short.pdf"),
[]string{"1-24"},
"p:LedgerL, g:on, binding:short",
"points",
4,
false,
},
{"TestBookletFromPDF_4up-portrait_long_advanced",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_portrait_long_advanced.pdf"),
[]string{"1-24"},
"p:LedgerP, g:on, btype:bookletadvanced",
"points",
4,
false,
},
{"TestBookletFromPDF_4up_landscape_short_advanced",
[]string{filepath.Join(inDir, "bookletTestLandscape.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_landscape_short_advanced.pdf"),
[]string{"1-24"},
"p:LedgerL, g:on, binding:short, btype:bookletadvanced",
"points",
4,
false,
},
{"TestBookletFromPDF_4up_perfectbound",
[]string{filepath.Join(inDir, "bookletTest.pdf")},
filepath.Join(outDir, "BookletFromPDFLedger_4Up_perfectbound.pdf"),
[]string{"1-24"},
"p:LedgerP, g:on, btype:perfectbound",
"points",
4,
false,
},

// 2-up multi folio booklet from PDF on A4 using 8 sheets per folio
// using the default foliosize:8
// Here we print 2 complete folios (2 x 8 sheets) + 1 partial folio
Expand Down

0 comments on commit cfd7627

Please sign in to comment.