github.com/pdfcpu/pdfcpu@v0.11.1/pkg/pdfcpu/booklet_test.go (about)

     1  /*
     2  Copyright 2024 The pdfcpu Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  package pdfcpu
    17  
    18  import (
    19  	"fmt"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  type pageOrderResults struct {
    25  	id                 string
    26  	nup                int
    27  	pageCount          int
    28  	expectedPageOrder  []int
    29  	papersize          string
    30  	bookletType        string
    31  	binding            string
    32  	useSignatures      bool
    33  	nPagesPerSignature int
    34  }
    35  
    36  var bookletTestCases = []pageOrderResults{
    37  	{
    38  		id:        "2up",
    39  		nup:       2,
    40  		pageCount: 16,
    41  		expectedPageOrder: []int{
    42  			16, 1,
    43  			15, 2,
    44  			14, 3,
    45  			13, 4,
    46  			12, 5,
    47  			11, 6,
    48  			10, 7,
    49  			9, 8,
    50  		},
    51  		papersize:   "A6",
    52  		bookletType: "booklet",
    53  		binding:     "long",
    54  	},
    55  	{
    56  		id:        "2up with trailing blank pages",
    57  		nup:       2,
    58  		pageCount: 10,
    59  		expectedPageOrder: []int{
    60  			0, 1,
    61  			0, 2,
    62  			10, 3,
    63  			9, 4,
    64  			8, 5,
    65  			7, 6,
    66  		},
    67  		papersize:   "A6",
    68  		bookletType: "booklet",
    69  		binding:     "long",
    70  	},
    71  	// basic booklet sidefold test cases
    72  	{
    73  		id:        "booklet portrait long edge",
    74  		nup:       4,
    75  		pageCount: 16,
    76  		expectedPageOrder: []int{
    77  			16, 1, 3, 14,
    78  			2, 15, 13, 4,
    79  			12, 5, 7, 10,
    80  			6, 11, 9, 8,
    81  		},
    82  		papersize:   "A5", // portrait, long-edge binding
    83  		bookletType: "booklet",
    84  		binding:     "long",
    85  	},
    86  	{
    87  		id:        "booklet landscape short edge",
    88  		nup:       4,
    89  		pageCount: 8,
    90  		expectedPageOrder: []int{
    91  			8, 1, 3, 6,
    92  			4, 5, 7, 2, // this is ordered differently from the portrait layout (because of differences in how duplexing works)
    93  		},
    94  		papersize:   "A5L", // landscape, short-edge binding
    95  		bookletType: "booklet",
    96  		binding:     "short",
    97  	},
    98  	// basic booklet topfold test cases
    99  	{
   100  		id:        "booklet topfold portrait",
   101  		nup:       4,
   102  		pageCount: 16,
   103  		expectedPageOrder: []int{
   104  			16, 3, 1, 14,
   105  			4, 15, 13, 2,
   106  			12, 7, 5, 10,
   107  			8, 11, 9, 6,
   108  		},
   109  		papersize:   "A5", // portrait, short-edge binding
   110  		bookletType: "booklet",
   111  		binding:     "short",
   112  	},
   113  	{
   114  		id:        "booklet topfold landscape",
   115  		nup:       4,
   116  		pageCount: 8,
   117  		expectedPageOrder: []int{
   118  			8, 3, 1, 6,
   119  			2, 5, 7, 4, // this is 180degrees flipped from the portrait layout (because of differences in how duplexing works)
   120  		},
   121  		papersize:   "A5L", // landscape, long-edge binding
   122  		bookletType: "booklet",
   123  		binding:     "long",
   124  	},
   125  	// advanced booklet sidefold test cases
   126  	{
   127  		id:        "advanced portrait long edge",
   128  		nup:       4,
   129  		pageCount: 8,
   130  		expectedPageOrder: []int{
   131  			8, 1, 5, 4,
   132  			2, 7, 3, 6,
   133  		},
   134  		papersize:   "A5", // portrait, long-edge binding
   135  		bookletType: "bookletadvanced",
   136  		binding:     "long",
   137  	},
   138  	{
   139  		id:        "advanced landscape short edge",
   140  		nup:       4,
   141  		pageCount: 8,
   142  		expectedPageOrder: []int{
   143  			8, 1, 5, 4,
   144  			6, 3, 7, 2, // this is ordered differently from the portrait layout (because of differences in how duplexing works)
   145  		},
   146  		papersize:   "A5L", // landscape, short-edge binding
   147  		bookletType: "bookletadvanced",
   148  		binding:     "short",
   149  	},
   150  	// 6up test
   151  	{
   152  		id:        "6up",
   153  		nup:       6,
   154  		pageCount: 12,
   155  		expectedPageOrder: []int{
   156  			12, 1, 10, 3, 8, 5,
   157  			2, 11, 4, 9, 6, 7,
   158  		},
   159  		papersize:   "A6", // portrait, long-edge binding
   160  		bookletType: "booklet",
   161  		binding:     "long",
   162  	},
   163  	{
   164  		id:        "6up multisheet",
   165  		nup:       6,
   166  		pageCount: 24,
   167  		expectedPageOrder: []int{
   168  			24, 1, 22, 3, 20, 5,
   169  			2, 23, 4, 21, 6, 19,
   170  			18, 7, 16, 9, 14, 11,
   171  			8, 17, 10, 15, 12, 13,
   172  		},
   173  		papersize:   "A6", // portrait, long-edge binding
   174  		bookletType: "booklet",
   175  		binding:     "long",
   176  	},
   177  	// 8up test
   178  	{
   179  		id:        "8up portrait long edge",
   180  		nup:       8,
   181  		pageCount: 32,
   182  		expectedPageOrder: []int{
   183  			1, 30, 32, 3, 5, 26, 28, 7,
   184  			29, 2, 4, 31, 25, 6, 8, 27,
   185  			9, 22, 24, 11, 13, 18, 20, 15,
   186  			21, 10, 12, 23, 17, 14, 16, 19,
   187  		},
   188  		papersize:   "A6",
   189  		bookletType: "booklet",
   190  		binding:     "long",
   191  	},
   192  	{
   193  		id:        "8up portrait short edge",
   194  		nup:       8,
   195  		pageCount: 16,
   196  		expectedPageOrder: []int{
   197  			16, 1, 14, 3, 12, 5, 10, 7,
   198  			2, 15, 4, 13, 6, 11, 8, 9,
   199  		},
   200  		papersize:   "A6",
   201  		bookletType: "booklet",
   202  		binding:     "short",
   203  	},
   204  	{
   205  		id:        "8up landscape short edge",
   206  		nup:       8,
   207  		pageCount: 16,
   208  		expectedPageOrder: []int{
   209  			16, 1, 14, 3, 12, 5, 10, 7,
   210  			2, 15, 4, 13, 6, 11, 8, 9,
   211  		},
   212  		papersize:   "A6L",
   213  		bookletType: "booklet",
   214  		binding:     "short",
   215  	},
   216  	{
   217  		id:        "8up landscape long edge",
   218  		nup:       8,
   219  		pageCount: 16,
   220  		expectedPageOrder: []int{
   221  			1, 14, 16, 3, 5, 10, 12, 7,
   222  			13, 2, 4, 15, 9, 6, 8, 11,
   223  		},
   224  		papersize:   "A6L",
   225  		bookletType: "booklet",
   226  		binding:     "long",
   227  	},
   228  	// perfect bound
   229  	{
   230  		id:        "perfect bound 2up",
   231  		nup:       2,
   232  		pageCount: 8,
   233  		expectedPageOrder: []int{
   234  			1, 3,
   235  			2, 4,
   236  			5, 7,
   237  			6, 8,
   238  		},
   239  		papersize:   "A6", // portrait, long-edge binding
   240  		bookletType: "perfectbound",
   241  		binding:     "long",
   242  	},
   243  	{
   244  		id:        "perfect bound 4up",
   245  		nup:       4,
   246  		pageCount: 16,
   247  		expectedPageOrder: []int{
   248  			1, 3, 5, 7,
   249  			4, 2, 8, 6,
   250  			9, 11, 13, 15,
   251  			12, 10, 16, 14,
   252  		},
   253  		papersize:   "A6", // portrait, long-edge binding
   254  		bookletType: "perfectbound",
   255  		binding:     "long",
   256  	},
   257  	{
   258  		id:        "perfect bound 4up landscape short-edge",
   259  		nup:       4,
   260  		pageCount: 16,
   261  		expectedPageOrder: []int{
   262  			1, 3, 5, 7,
   263  			6, 8, 2, 4,
   264  			9, 11, 13, 15,
   265  			14, 16, 10, 12,
   266  		},
   267  		papersize:   "A6L", // landscape, short-edge binding
   268  		bookletType: "perfectbound",
   269  		binding:     "short",
   270  	},
   271  	{
   272  		id:        "perfect bound 8up",
   273  		nup:       8,
   274  		pageCount: 16,
   275  		expectedPageOrder: []int{
   276  			1, 3, 5, 7, 9, 11, 13, 15,
   277  			4, 2, 8, 6, 12, 10, 16, 14,
   278  		},
   279  		papersize:   "A6", // portrait, long-edge binding
   280  		bookletType: "perfectbound",
   281  		binding:     "long",
   282  	},
   283  	// signatures
   284  	{
   285  		id:        "signatures 2up",
   286  		nup:       2,
   287  		pageCount: 16,
   288  		expectedPageOrder: []int{
   289  			12, 1, // signature 1
   290  			11, 2,
   291  			10, 3,
   292  			9, 4,
   293  			8, 5,
   294  			7, 6,
   295  			16, 13, // signature 2, incomplete
   296  			15, 14,
   297  		},
   298  		papersize:          "A6",
   299  		bookletType:        "booklet",
   300  		binding:            "long",
   301  		useSignatures:      true,
   302  		nPagesPerSignature: 12,
   303  	},
   304  	{
   305  		id:        "signatures 4up",
   306  		nup:       4,
   307  		pageCount: 24,
   308  		expectedPageOrder: []int{
   309  			16, 1, 3, 14, // signature 1
   310  			2, 15, 13, 4,
   311  			12, 5, 7, 10,
   312  			6, 11, 9, 8,
   313  			24, 17, 19, 22, // signature 2, incomplete
   314  			18, 23, 21, 20,
   315  		},
   316  		papersize:          "A5",
   317  		bookletType:        "booklet",
   318  		binding:            "long",
   319  		useSignatures:      true,
   320  		nPagesPerSignature: 16,
   321  	},
   322  	{
   323  		id:        "signatures 2up with trailing blank pages",
   324  		nup:       2,
   325  		pageCount: 18,
   326  		expectedPageOrder: []int{
   327  			12, 1, // signature 1
   328  			11, 2,
   329  			10, 3,
   330  			9, 4,
   331  			8, 5,
   332  			7, 6,
   333  			0, 13, // signature 2, incomplete, with blanks
   334  			0, 14,
   335  			18, 15,
   336  			17, 16,
   337  		},
   338  		papersize:          "A6",
   339  		bookletType:        "booklet",
   340  		binding:            "long",
   341  		useSignatures:      true,
   342  		nPagesPerSignature: 12,
   343  	},
   344  }
   345  
   346  func TestBookletPageOrder(t *testing.T) {
   347  	for _, test := range bookletTestCases {
   348  		t.Run(test.id, func(tt *testing.T) {
   349  			desc := fmt.Sprintf("papersize:%s, btype:%s, binding: %s", test.papersize, test.bookletType, test.binding)
   350  			if test.useSignatures {
   351  				desc += fmt.Sprintf(", multifolio:on, foliosize:%d", test.nPagesPerSignature/4)
   352  			}
   353  			nup, err := PDFBookletConfig(test.nup, desc, nil)
   354  			if err != nil {
   355  				tt.Fatal(err)
   356  			}
   357  			pageNumbers := make(map[int]bool)
   358  			for i := 0; i < test.pageCount; i++ {
   359  				pageNumbers[i+1] = true
   360  			}
   361  			pageOrder := make([]int, len(test.expectedPageOrder))
   362  			out := GetBookletOrdering(pageNumbers, nup)
   363  			if len(test.expectedPageOrder) != len(out) {
   364  				tt.Fatalf("page order output has the wrong length, expected %d but got %d", len(test.expectedPageOrder), len(out))
   365  			}
   366  			for i, p := range out {
   367  				pageOrder[i] = p.Number
   368  			}
   369  			for i, expected := range test.expectedPageOrder {
   370  				if pageOrder[i] != expected {
   371  					tt.Fatal("incorrect page order\nexpected:", arrayToString(test.expectedPageOrder), "\n     got:", arrayToString(pageOrder))
   372  				}
   373  			}
   374  		})
   375  	}
   376  }
   377  
   378  func arrayToString(arr []int) string {
   379  	out := make([]string, len(arr))
   380  	for i, n := range arr {
   381  		out[i] = fmt.Sprintf("%02d", n)
   382  	}
   383  	return fmt.Sprintf("[%s]", strings.Join(out, " "))
   384  }