home · contact · privacy
Start page counts at 0 rather than 1, avoiding code confusion.
authorPlom Heller <plom@plomlompom.com>
Mon, 2 Nov 2026 20:32:56 +0000 (21:32 +0100)
committerPlom Heller <plom@plomlompom.com>
Mon, 2 Nov 2026 20:32:56 +0000 (21:32 +0100)
README.txt
src/bookmaker/misc.py

index f27bf8424a38af4b50b010c2095b5c8cae77e519..f1925288fb22d3fa2cfce12f6ebe5aeb0978408d 100644 (file)
@@ -31,14 +31,14 @@ OVERVIEW OF TARGET USAGE:
 By cropping with -c and studying the results, define the areas of the input PDF's pages you want visible.  Then, with--nup4, map those areas onto 4 input pages per 1 output page, arranged in such a way that double-sided print-out of those output pages can be cut, folded, and bound (helped by addition of stencils for small incisions to carry rubber bands or the like) into a small A6 book.  Each unit of 8 pages from the input PDF is mapped by --nup4 onto two pages representing two sides of a (no-tumble-duplex-printed) A4 paper:
 
                       +-------=-------+                     __________________
- (front)  (back)      | 4 | 1 = 2 | 3 |             4      /=|===|============
+ (front)  (back)      | 3 | 0 = 1 | 2 |             3      /=|===|============
 +-------=-------+ ==> +-------=-------+ ===>  _/|\_ v      >=|===|============
-| 4 | 1 = 2 | 3 |                            /  |  \_      \=|===|============
-|-------=-------|     +-------=-------+ 1-> | 2 | 3 |      | \   / <- cut out!
-| 8 | 5 = 6 | 7 | ==> | 8 | 5 = 6 | 7 |     | _/ \_ |      |  \ |
-+-------=-------+     +-------=-------+     |/     \|      |   \| (p. 5)
+| 3 | 0 = 1 | 2 |                            /  |  \_      \=|===|============
+|-------=-------|     +-------=-------+ 0-> | 1 | 2 |      | \   / <- cut out!
+| 7 | 4 = 5 | 6 | ==> | 7 | 4 = 5 | 6 |     | _/ \_ |      |  \ |
++-------=-------+     +-------=-------+     |/     \|      |   \| (p. 4)
 
-To turn this paper into a small 8-pages book, first cut it into two A5 papers along its horizontal middle.  Fold both A5's by their vertical middles, with pages 2-3 and 7-6 on the folds' insides.  You now have two 4-page A6 "books" of pages 1-4 and pages 5-8.  Fold both closed and (counter-intuitively) stack the second one on top of the first one (creating a temporary page order of 5,6,7,8,1,2,3,4).  This reveals a small stencil on the top left of page 5 – cut it out, with all other pages folded and aligned under it, creating a small notch in the upper "inner" corner of all pages.  Turn around the stack to find a mirror stencil on the bottom and repeat the cutting.  Each page now has cuts on top and bottom of its inner margins into which a rubber band can be hooked, or through which a string may be looped and tied, to bind the page's inner margins into a kind of book spine.  You may now swap the order of the 4-page books back into a proper final page order (of 1,2,3,4,5,6,7,8) and repeat the whole process for each further --nup4 output paper.
+To turn this paper into a small 8-pages book, first cut it into two A5 papers along its horizontal middle.  Fold both A5's by their vertical middles, with pages 1-2 and 6-5 on the folds' insides.  You now have two 4-page A6 "books" of pages 0-3 and pages 4-7.  Fold both closed and (counter-intuitively) stack the second one on top of the first one (creating a temporary page order of 4,5,6,7,0,1,2,3).  This reveals a small stencil on the top left of page 4 – cut it out, with all other pages folded and aligned under it, creating a small notch in the upper "inner" corner of all pages.  Turn around the stack to find a mirror stencil on the bottom and repeat the cutting.  Each page now has cuts on top and bottom of its inner margins into which a rubber band can be hooked, or through which a string may be looped and tied, to bind the page's inner margins into a kind of book spine.  You may now swap the order of the 4-page books back into a proper final page order (of 0,1,2,3,4,5,6,7) and repeat the whole process for each further --nup4 output paper.
 
 COMMAND EXAMPLES:
 
@@ -49,7 +49,7 @@ Produce OUTPUT.pdf containing all pages of (inclusive) page number range 3-7 fro
     bookmaker.py -i INPUT.pdf --page_range 3-7 -o OUTPUT.pdf
 
 Produce COMBINED.pdf from A.pdf's first 7 pages, B.pdf's pages except its first two, and all pages of C.pdf:
-    bookmaker.py -i A.pdf -p start-7 -i B.pdf -p 3-end -i C.pdf -o COMBINED.pdf
+    bookmaker.py -i A.pdf -p start-6 -i B.pdf -p 2-end -i C.pdf -o COMBINED.pdf
 
 Crop each page 5cm from the left, 10cm from the bottom, 2cm from the right, and 0cm from the top:
     bookmaker.py -i INPUT.pdf -o OUTPUT.pdf --crops "5,10,2,0"
@@ -77,7 +77,7 @@ Same --nup4, but draw lines marking printable-region margins, page quarters, spi
 
 FURTHER NOTES:
 
-For arguments like -p, page numbers are assumed to start with 1 (not 0, which is treated as an invalid page number value).
+For arguments like -p, page numbers are assumed to start with 0 (i.e. first page at 0, second page at 1 …).
 
 If -p defines a page range beyond the source document's last page, the remaining pages will be created blank.
 
index 89b1717faa260008450cad33eee46eaa24e6b65a..a291bf76d78caeb908a09f5c3c6bce06693613eb 100755 (executable)
@@ -73,13 +73,11 @@ class PageRange:
                 except ValueError:
                     self._fail('carries value neither integer, nor '
                                f'"{_TOK_RANGE_START}", nor "{_TOK_RANGE_END}"')
-                if int(token) < 1:
-                    self._fail('carries page number <1')
             str_start, str_end = self._code.split('-')
             if str_end not in {'', _TOK_RANGE_END}:
                 self.end = int(str_end)
             if str_start not in {'', _TOK_RANGE_START}:
-                self.start = int(str_start) - 1
+                self.start = int(str_start)
 
     def _fail(
             self,
@@ -533,8 +531,8 @@ def _validate_args_syntax(
         if args.rotate_page:
             arg_char = 'r'
             for r in args.rotate_page:
-                if r < 1:
-                    raise MsgFail('value must not be <1: {r}')
+                if r < 0:
+                    raise MsgFail('value must not be <0: {r}')
     except MsgFail as e:
         raise ArgFail(arg_char, e.msg) from e
     try:
@@ -584,16 +582,16 @@ class Book:
                               f'cannot interpret {path} as PDF file') from e
             page_range = (args_page_range[i] if len(args_page_range) > i
                           else PageRange(None))
-            page_range.to_max(len(reader.pages))
+            page_range.to_max(len(reader.pages)-1)
             for old_page_num in page_range.ranging():
-                new_page_num += 1
                 page = (Page.new_blank() if old_page_num >= len(reader.pages)
                         else Page(reader.pages[old_page_num]))
                 self.pages += [page]
                 if do_analyze:
                     page.draw_borders((0.75, 0.0, 0.0))
-                print(f'-i, -p: read in {path} page number {old_page_num+1} '
+                print(f'-i, -p: read in {path} page number {old_page_num} '
                       f'as new page {new_page_num}')
+                new_page_num += 1
         self._writer = PdfWriter()
 
     def __len__(
@@ -627,7 +625,7 @@ class Book:
             ) -> None:
         'For .pages numbered in args_rotate_page, rotate by 90°.'
         for rotate_page in args_rotate_page or []:
-            page = self.pages[rotate_page - 1]
+            page = self.pages[rotate_page]
             page.translate(tx=-A4_WIDTH/2, ty=-A4_HEIGHT/2)
             page.rotate(-90)
             page.translate(tx=A4_WIDTH/2, ty=A4_HEIGHT/2)
@@ -690,14 +688,14 @@ class Book:
             for c_string in args_crops:
                 range_code, crops = split_crops_string(c_string)
                 page_range = PageRange(range_code)
-                page_range.to_max(len(self))
+                page_range.to_max(len(self)-1)
                 prefix = '-c, -t' if args_symmetry else '-c'
                 suffix = ((' (but alternating left and right crop '
                           'between even and odd pages)') if args_symmetry
                           else '')
                 page_crop = PageCrop(*(float(crop)
                                        for crop in crops.split(',')))
-                print(f'{prefix}: to pages {page_range.start + 1}:'
+                print(f'{prefix}: to pages {page_range.start}:'
                       f'{page_range.end} applying crop: '
                       f'{page_crop.format_in_cm}{suffix}')
                 for idx in page_range.ranging():
@@ -727,7 +725,7 @@ class Book:
             if arg_analyze:
                 page.draw_borders((0.0, 0.0, 0.75))
             page.add_to_writer(self._writer)
-            print(f'built page number {i+1} (of {len(self)})')
+            print(f'built page number {i} (of {len(self)})')
 
     def build_nup4_output(
             self,
@@ -764,8 +762,8 @@ class Book:
         for page in self.pages:
             page.nup4_transforms(nup4_geometry, nup4_i)
             new_page.merge_page(page)
-            page_count += 1
             print(f'merged page number {page_count} (of {len(self)})')
+            page_count += 1
             nup4_i += 1
             if nup4_i > 3:
                 new_page.ornate_nup4(is_front_page, nup4_geometry, arg_analyze)