Skip to content

Commit

Permalink
Fix booklet cmd parsing, clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
adamgreenhall authored and hhrutter committed Feb 25, 2024
1 parent 1d5da77 commit a893411
Show file tree
Hide file tree
Showing 28 changed files with 326 additions and 118 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
42 changes: 9 additions & 33 deletions cmd/pdfcpu/usage.go
Expand Up @@ -662,7 +662,9 @@ Examples: pdfcpu nup out.pdf 4 in.pdf
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 prefered method for creating the booklet:
and your preferred method for creating the booklet.
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)
Expand All @@ -685,41 +687,15 @@ paper is on the bottom of the booklet for every page (for the default portrait,
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.
For the default binding method (btype=booklet) with 4-up printing, assemble by:
- print on both sides
- cut the sheets in half
- arrange the stacks of half sheets for collation in the following order: top half sheet 1, bottom half sheet 1, top half sheet 2, ...
- collate the stacks into individual sets of booklets
- fold, bind, and trim (if desired)
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.
Assemble by:
- print on both sides
- collate the whole sheets
- cut each of the collated sets in half
- place the bottom half (un-rotated) under the top half. This will produce a correctly ordered booklet.
- procede to fold, bind, and trim (if desired)
n=6: Six of your pages fit on one side of a sheet. This produces an unusual sized booklet. Assemble by:
- print on both sides
- cut the sheets in thrids horizontally
- arrange the sheet stacks for collation: moving top to bottom, then by sheet (ie top third sheet 1, middle third sheet 1, bottom third sheet 1, top third sheet 2, ...)
- collate the stacks into individual sets of booklets
- fold, bind, and trim (if desired)
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).
Pages are arranged similar to 4-up with btype=booklet (but without the rotation). Assemble by:
- printing on both sides
- print on both sides
- cut the sheets in half horizontally and then cutting those half-sheets in half vertically
- arrange the sheet stacks for collation: moving left to right, then top to bottom, then by sheet (ie top-left sheet 1, top-right sheet 1, middle-left sheet 1, ...)
- collate the stacks into individual sets of booklets
- fold, bind, and trim (if desired)
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,
Expand Down Expand Up @@ -759,16 +735,16 @@ 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
pdfcpu booklet -- "formsize:Ledger" out.pdf 4 in.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"
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"
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:A3, binding:short" out.pdf 4 in.pdf"
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
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
21 changes: 13 additions & 8 deletions pkg/pdfcpu/booklet.go
Expand Up @@ -20,17 +20,18 @@ import (
"bytes"
"fmt"
"os"
"strconv"
"strings"

"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/draw"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
"github.com/pkg/errors"
)

var (
errInvalidBookletGridID = errors.New("pdfcpu booklet: n must be one of 2, 4, 6, 8")
errInvalidBookletAdvanced = errors.New("pdfcpu booklet advanced cannot have binding along the top (portrait short-edge, landscape long-edge). use plain booklet instead.")
)
var errInvalidBookletAdvanced = errors.New("pdfcpu booklet advanced cannot have binding along the top (portrait short-edge, landscape long-edge). use plain booklet instead.")

var NUpValuesForBooklets = []int{2, 4, 6, 8}

// DefaultBookletConfig returns the default configuration for a booklet
func DefaultBookletConfig() *model.NUp {
Expand All @@ -57,15 +58,19 @@ func PDFBookletConfig(val int, desc string, conf *model.Configuration) (*model.N
return nil, err
}
}
if !types.IntMemberOf(val, NUpValuesForBooklets) {
ss := make([]string, len(NUpValuesForBooklets))
for i, v := range NUpValuesForBooklets {
ss[i] = strconv.Itoa(v)
}
return nil, errors.Errorf("pdfcpu: n must be one of %s", strings.Join(ss, ", "))
}
if err := ParseNUpValue(val, nup); err != nil {
return nil, err
}
if !(val == 2 || val == 4 || val == 6 || val == 8) {
return nup, errInvalidBookletGridID
}
// 6up and 8up special cases
if nup.IsBooklet() && val > 4 && nup.IsTopFoldBinding() {
// you can't top fold a 6up with 3 rows
// You can't top fold a 6up with 3 rows.
// TODO: support this for 8up
return nup, fmt.Errorf("pdfcpu booklet: n>4 must have binding on side (portrait long-edge or landscape short-edge)")
}
Expand Down
15 changes: 15 additions & 0 deletions pkg/pdfcpu/booklet_test.go
@@ -1,3 +1,18 @@
/*
Copyright 2024 The pdfcpu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pdfcpu

import (
Expand Down

0 comments on commit a893411

Please sign in to comment.