Skip to content

Commit

Permalink
Merge pull request #837 from SuffolkLITLab/make-pages-odd-or-even
Browse files Browse the repository at this point in the history
When using the as_pdf() method of a bundle, add parameter to ensure an odd or even number of pages
  • Loading branch information
nonprofittechy authored Mar 26, 2024
2 parents b8ae585 + 01f83e3 commit 55a6ef6
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 1 deletion.
64 changes: 63 additions & 1 deletion docassemble/AssemblyLine/al_document.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
import os
import mimetypes
from typing import Any, Dict, List, Union, Callable, Optional
from typing import Any, Dict, List, Literal, Union, Callable, Optional
from docassemble.base.util import (
Address,
LatitudeLongitude,
Expand Down Expand Up @@ -34,6 +34,7 @@
from math import floor
import subprocess
from collections import ChainMap
import pikepdf

__all__ = [
"ALAddendumField",
Expand Down Expand Up @@ -180,6 +181,50 @@ def table_row(title: str, button_htmls: List[str] = []) -> str:
return html


def pdf_page_parity(pdf_path: str) -> Literal["even", "odd"]:
"""
Count the number of pages in the PDF and
return "even" if it is divisible by 2 and "odd"
if it is not divisible by 2.
Args:
pdf_path (str): Path to the PDF in the filesystem
Returns:
Literal["even", "odd"]: The parity of the number of pages in the PDF
"""
with pikepdf.open(pdf_path) as pdf:
num_pages = len(pdf.pages)
if num_pages % 2 == 0:
return "even"
return "odd"


def add_blank_page(pdf_path: str) -> None:
"""
Add a blank page to the end of a PDF.
Args:
pdf_path (str): Path to the PDF in the filesystem
"""
# Load the PDF
with pikepdf.open(pdf_path, allow_overwriting_input=True) as pdf:
# Retrieve the last page
last_page = pdf.pages[-1]

# Extract the size of the last page
media_box = last_page.MediaBox

# Create a new blank page with the same dimensions as the last page
blank_page = pikepdf.Page(pikepdf.Dictionary(MediaBox=media_box))

# Add the blank page to the end of the PDF
pdf.pages.append(blank_page)

# Overwrite the original PDF with the modified version
pdf.save(pdf_path)


class ALAddendumField(DAObject):
"""
Represents a field with attributes determining its display in an addendum, typically for PDF templates.
Expand Down Expand Up @@ -1441,6 +1486,7 @@ class ALDocumentBundle(DAList):
enabled (bool, optional): Determines if the bundle is active. Defaults to True.
auto_gather (bool, optional): Automatically gathers attributes. Defaults to False.
gathered (bool, optional): Specifies if attributes have been gathered. Defaults to True.
default_parity (Optional[Literal["even", "odd"]]): Default parity to enforce on the PDF. Defaults to None.
Examples:
Given three documents: `Cover page`, `Main motion form`, and `Notice of Interpreter Request`,
Expand Down Expand Up @@ -1481,6 +1527,7 @@ def as_pdf(
refresh: bool = True,
pdfa: bool = False,
append_matching_suffix: bool = True,
ensure_parity: Optional[Literal["even", "odd"]] = None,
) -> Optional[DAFile]:
"""
Returns a consolidated PDF of all enabled documents in the bundle.
Expand All @@ -1491,6 +1538,8 @@ def as_pdf(
pdfa (bool): If True, generates a PDF/A compliant document, defaults to False.
append_matching_suffix (bool): Flag to determine if matching suffix should be appended to file name, default is True.
Used primarily to enhance automated tests.
ensure_parity (Optional[Literal["even", "odd"]]): Ensures the number of pages in the PDF is even or odd. If omitted,
no parity is enforced. Defaults to None.
Returns:
Optional[DAFile]: Combined PDF file or None if no documents are enabled.
Expand Down Expand Up @@ -1533,6 +1582,19 @@ def as_pdf(
)
pdf.title = self.title
setattr(self.cache, safe_key, pdf)

if hasattr(self, "default_parity") and not ensure_parity:
ensure_parity = self.default_parity

if ensure_parity not in [None, "even", "odd"]:
raise ValueError("ensure_parity must be either 'even', 'odd' or None")

if ensure_parity: # Check for odd/even requirement
if pdf_page_parity(pdf.path()) == ensure_parity:
return pdf
else:
add_blank_page(pdf.path())

return pdf

def __str__(self) -> str:
Expand Down
79 changes: 79 additions & 0 deletions docassemble/AssemblyLine/data/questions/test_bundle_parity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
include:
- assembly_line.yml
---
objects:
- the_doc: ALDocument.using(
title="The document",
filename="the_document",
enabled=True,
has_addendum=False
)
---
objects:
- even_bundle: ALDocumentBundle.using(
title="Even bundle",
filename="the_bundle",
elements=[
the_doc
],
default_parity="even",
enabled=True
)
- odd_bundle: ALDocumentBundle.using(
title="Even bundle",
filename="the_bundle",
elements=[
the_doc,
the_doc
],
enabled=True
)
- bundle_with_default: ALDocumentBundle.using(
title="Even bundle",
filename="the_bundle",
elements=[
the_doc
],
default_parity="even",
enabled=True
)

---
objects:
- bundle_of_bundles: ALDocumentBundle.using(
title="Bundle of bundles",
filename="the_bundle",
elements=[
even_bundle,
bundle_with_default
],
enabled=True
)

---
attachment:
variable name: the_doc[i]
content: |
Test content
---
mandatory: True
question: |
About to make a PDF
continue button field: the_field
---
mandatory: True
question: |
Download
subquestion: |
# Should be even
${ even_bundle.as_pdf(ensure_parity="even") }
# should be odd
${ odd_bundle.as_pdf(ensure_parity="odd") }
# should be even
${ bundle_with_default.as_pdf() }
# Should be even
${ bundle_of_bundles.as_pdf() }

0 comments on commit 55a6ef6

Please sign in to comment.