Skip to content

Commit

Permalink
Merge pull request #190 from cfcurtis/userunit
Browse files Browse the repository at this point in the history
Adding support for different UserUnit
  • Loading branch information
cfcurtis authored Jul 24, 2023
2 parents 6f4762c + 7399288 commit 79943f5
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 69 deletions.
28 changes: 28 additions & 0 deletions pdfstitcher/layerfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def convert_layer_props(self):
convert the line properties from the GUI to what the PDF needs
"""
self.pdf_line_props = {}

for layer, lp in self.line_props.items():
w = 1
clp = {}
Expand All @@ -219,6 +220,29 @@ def convert_layer_props(self):
clp["k"] = clp["K"]

self.pdf_line_props[layer] = clp
self.pdf_line_props["user_unit"] = 1

def adjust_user_unit(self, user_unit):
"""
Updates the width and dash pattern to match the user unit.
"""
# Don't do anything if it's already the same
if self.pdf_line_props["user_unit"] == user_unit:
return

scale = self.pdf_line_props["user_unit"] / user_unit

for layer, lp in self.pdf_line_props.items():
if layer == "user_unit":
continue

if "w" in lp.keys():
self.pdf_line_props[layer]["w"][0] *= scale

if "d" in lp.keys():
self.pdf_line_props[layer]["d"][0] = [d * scale for d in lp["d"][0]]

self.pdf_line_props["user_unit"] = user_unit

def override_state(self, commands, line_props, transparency=False):
"""
Expand Down Expand Up @@ -491,6 +515,10 @@ def filter_content(self, page, current_layer_name="", do_filter=False):
else:
self.processed_objects.add(obid)

# Adjust the user unit if necessary
if "/UserUnit" in page.keys():
self.adjust_user_unit(page.UserUnit)

# the page is either an actual page, or a form xobject
is_page = isinstance(page, pikepdf.Page)

Expand Down
4 changes: 2 additions & 2 deletions pdfstitcher/pagefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ def run(self):

if "/UserUnit" in self.in_doc.pages[-1].keys():
new_doc.pages[-1].UserUnit = self.in_doc.pages[-1].UserUnit
user_unit = self.in_doc.pages[-1].UserUnit
user_unit = float(self.in_doc.pages[-1].UserUnit)

if self.margin:
# if margins were added, expand the new page boxes
margin = Config.general["units"].units_to_px(self.margin / user_unit)
margin = Config.general["units"].units_to_px(self.margin, user_unit)
new_page = new_doc.pages[-1]
media_box = [
float(new_page.MediaBox[0]) - margin,
Expand Down
129 changes: 79 additions & 50 deletions pdfstitcher/tile_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(
horizontal_align=SW_ALIGN_H.LEFT,
):
self.in_doc = in_doc
self.user_unit = 1

if isinstance(page_range, str):
self.page_range = utils.parse_page_range(page_range)
Expand Down Expand Up @@ -179,7 +180,7 @@ def build_pagelist(self, new_doc: pikepdf.Pdf, trim: list) -> tuple:

# get a pointer to the reference page and parse out the width and height
ref_page = self.in_doc.pages[p - 1]
ref_width, ref_height = utils.get_page_dims(ref_page, page_rot)
ref_width, ref_height = utils.get_page_dims(ref_page, page_rot, self.user_unit)

different_size = set()

Expand Down Expand Up @@ -224,8 +225,8 @@ def build_pagelist(self, new_doc: pikepdf.Pdf, trim: list) -> tuple:
in_trim[2] - rtrim[2],
in_trim[3] - rtrim[3],
]
# get the input page height and width
p_width, p_height = utils.get_page_dims(in_doc_page, page_rot)

p_width, p_height = utils.get_page_dims(in_doc_page, page_rot, self.user_unit)
pw.append(p_width)
ph.append(p_height)
page_names.append(pagekey)
Expand All @@ -240,6 +241,16 @@ def build_pagelist(self, new_doc: pikepdf.Pdf, trim: list) -> tuple:
# magic sauce to copy the info to the new document as an XOBject
content_dict[pagekey] = new_doc.copy_foreign(new_page.as_form_xobject())

# scale the form xobject by the target user unit
if self.user_unit != 1:
if "/Matrix" in content_dict[pagekey].keys():
xobj_matrix = pikepdf.PdfMatrix(content_dict[pagekey].Matrix)
else:
xobj_matrix = pikepdf.PdfMatrix.identity()
content_dict[pagekey].Matrix = xobj_matrix.scaled(
1 / self.user_unit, 1 / self.user_unit
).shorthand

else:
# blank page, use the reference for sizes and such
page_names.append(None)
Expand Down Expand Up @@ -340,6 +351,62 @@ def calc_rows_cols(self, n_tiles: int) -> bool:
else:
return self.cols * self.rows - n_tiles < self.cols

def grid_position(self, tile_i: int) -> tuple[int, int]:
"""Determines the placement of the tile in the grid, returning a tuple of (row, col)"""
if self.col_major:
c = math.floor(tile_i / self.rows)
r = tile_i % self.rows
else:
r = math.floor(tile_i / self.cols)
c = tile_i % self.cols

if self.right_to_left:
c = self.cols - c - 1

if self.bottom_to_top:
r = self.rows - r - 1

return r, c

def calc_shift(self, horizontal_space: float, vertical_space: float) -> tuple[float, float]:
"""
Calculates the shift needed to align the tile in the grid.
Returns a tuple of (shift_right, shift_up).
Only used if a tile is smaller than the grid space.
"""
if self.horizontal_align is SW_ALIGN_H.LEFT:
shift_right = 0
elif self.horizontal_align is SW_ALIGN_H.MID:
shift_right = round(horizontal_space / 2)
elif self.horizontal_align is SW_ALIGN_H.RIGHT:
shift_right = round(horizontal_space)
if self.vertical_align is SW_ALIGN_V.BOTTOM:
shift_up = 0
elif self.vertical_align is SW_ALIGN_V.MID:
shift_up = round(vertical_space / 2)
elif self.vertical_align is SW_ALIGN_V.TOP:
shift_up = round(vertical_space)

# invert shift if we are rotating
if self.rotation == SW_ROTATION.CLOCKWISE:
shift_up *= -1
elif self.rotation == SW_ROTATION.COUNTERCLOCKWISE:
shift_right *= -1
elif self.rotation == SW_ROTATION.TURNAROUND:
shift_right *= -1
shift_up *= -1

return shift_right, shift_up

def set_user_unit(self):
"""
Find the maximum user_unit defined in the document, then use this for the new document.
"""
for p in self.page_range:
page = self.in_doc.pages[p - 1]
if "/UserUnit" in page.keys() and page.UserUnit > self.user_unit:
self.user_unit = float(page.UserUnit)

def run(
self,
rows=None,
Expand Down Expand Up @@ -378,14 +445,9 @@ def run(
# initialize a new document
new_doc = utils.init_new_doc(self.in_doc)

# get the user unit from the first page (either 1 or 10, if it's a huge page)
user_unit = 1
first_page = self.in_doc.pages[self.page_range[0] - 1]
if "/UserUnit" in first_page.keys():
user_unit = float(first_page.UserUnit)

# define the trim in pdf units, then build the page list
px_trim = [Config.general["units"].units_to_px(t / user_unit) for t in self.trim]
self.set_user_unit()
px_trim = [Config.general["units"].units_to_px(t, self.user_unit) for t in self.trim]
content_dict, pw, ph, page_names = self.build_pagelist(new_doc, px_trim)
n_tiles = len(page_names)
if not self.calc_rows_cols(n_tiles):
Expand Down Expand Up @@ -423,15 +485,16 @@ def run(
page_box_defined = False

# create a new document with a page big enough to contain all the tiled pages, plus requested margin
margin = Config.general["units"].units_to_px(self.margin / user_unit)
first_page = self.in_doc.pages[self.page_range[0] - 1]
margin = Config.general["units"].units_to_px(self.margin, self.user_unit)
media_box = [
float(first_page.MediaBox[0]),
float(first_page.MediaBox[1]),
width + 2 * margin,
height + 2 * margin,
]

utils.print_media_box(media_box)
utils.print_media_box(media_box, self.user_unit)

# TODO: Refactor this giant loop into two functions (scale to fit and no scaling)
i = 0
Expand All @@ -443,19 +506,7 @@ def run(
if not page_names[i]:
continue

if self.col_major:
c = math.floor(i / self.rows)
r = i % self.rows
else:
r = math.floor(i / self.cols)
c = i % self.cols

if self.right_to_left:
c = self.cols - c - 1

if self.bottom_to_top:
r = self.rows - r - 1

r, c = self.grid_position(i)
scale_factor = 1

if page_box_defined:
Expand Down Expand Up @@ -492,30 +543,8 @@ def run(
horizontal_space = col_width[c] - pw[i]
vertical_space = row_height[r] - ph[i]

# calculate shift
if self.horizontal_align is SW_ALIGN_H.LEFT:
shift_right = 0
elif self.horizontal_align is SW_ALIGN_H.MID:
shift_right = round(horizontal_space / 2)
elif self.horizontal_align is SW_ALIGN_H.RIGHT:
shift_right = round(horizontal_space)
if self.vertical_align is SW_ALIGN_V.BOTTOM:
shift_up = 0
elif self.vertical_align is SW_ALIGN_V.MID:
shift_up = round(vertical_space / 2)
elif self.vertical_align is SW_ALIGN_V.TOP:
shift_up = round(vertical_space)

# invert shift if we are rotating
if self.rotation == SW_ROTATION.CLOCKWISE:
shift_up *= -1
elif self.rotation == SW_ROTATION.COUNTERCLOCKWISE:
shift_right *= -1
elif self.rotation == SW_ROTATION.TURNAROUND:
shift_right *= -1
shift_up *= -1

# apply shift
shift_right, shift_up = self.calc_shift(horizontal_space, vertical_space)
x0 += shift_right
y0 += shift_up

Expand Down Expand Up @@ -559,8 +588,8 @@ def run(
Resources=pikepdf.Dictionary(XObject=content_dict),
Contents=pikepdf.Stream(new_doc, content_txt.encode()),
)
if user_unit != 1:
tiled_page.UserUnit = user_unit
if self.user_unit != 1:
tiled_page.UserUnit = self.user_unit

new_doc.pages.append(tiled_page)

Expand Down
38 changes: 24 additions & 14 deletions pdfstitcher/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,27 @@ def __str__(self):
elif self == UNITS.POINTS:
return _("pt")

def units_to_px(self, val):
def units_to_px(self, val: float, user_unit: float = 1):
"""
Converts from current units to pixels.
"""
if self == UNITS.INCHES:
return val * 72
return val * 72 / user_unit
elif self == UNITS.CENTIMETERS:
return val * 72 / 2.54
return val * 72 / user_unit / 2.54
elif self == UNITS.POINTS:
return val
return val / user_unit

def px_to_units(self, val):
def px_to_units(self, val: float, user_unit: float = 1):
"""
Converts from pixels to current units.
"""
if self == UNITS.INCHES:
return val / 72
return user_unit * val / 72
elif self == UNITS.CENTIMETERS:
return val / 72 * 2.54
return user_unit * val / 72 * 2.54
elif self == UNITS.POINTS:
return val
return user_unit * val


def unit_representer(dumper, data):
Expand Down Expand Up @@ -303,11 +303,13 @@ def init_new_doc(pdf):
return new_doc


def get_page_dims(page, global_rotation=0):
def get_page_dims(
page, global_rotation: float = 0, target_user_unit: float = 1
) -> tuple[float, float]:
"""
Helper function to calculate the page dimensions
Returns width, height as observed by the user
(taking rotation into account)
(taking rotation and UserUnit into account)
"""
# The mediabox is typically specified as
# [lower left x, lower left y, upper left x, upper left y],
Expand All @@ -316,6 +318,14 @@ def get_page_dims(page, global_rotation=0):
page_width = float(abs(mbox[2] - mbox[0]))
page_height = float(abs(mbox[3] - mbox[1]))

page_uu = 1
if "/UserUnit" in page.keys():
page_uu = float(page.UserUnit)

# scale according to the page and target user units
page_width *= page_uu / target_user_unit
page_height *= page_uu / target_user_unit

# global_rotation is defined by the document root, but
# may be overridden on a specific page
if "/Rotate" in page.keys():
Expand All @@ -330,7 +340,7 @@ def get_page_dims(page, global_rotation=0):
return page_width, page_height


def print_media_box(media_box, user_unit=1):
def print_media_box(media_box, user_unit: float = 1) -> None:
"""
Display the media box in the requested units.
Also checks to see if the size exceeds Adobe's max size.
Expand All @@ -342,7 +352,7 @@ def print_media_box(media_box, user_unit=1):
print(62 * "*")
print(
_("Warning! Output is larger than {} {}, may not open correctly.").format(
round(Config.general["units"].px_to_units(MAX_SIZE_PX)),
round(Config.general["units"].px_to_units(MAX_SIZE_PX, user_unit)),
Config.general["units"],
)
)
Expand All @@ -351,8 +361,8 @@ def print_media_box(media_box, user_unit=1):
print(
_("Output size:")
+ " {:0.2f} x {:0.2f} {}".format(
user_unit * Config.general["units"].px_to_units(width),
user_unit * Config.general["units"].px_to_units(height),
Config.general["units"].px_to_units(width, user_unit),
Config.general["units"].px_to_units(height, user_unit),
Config.general["units"],
)
)
Expand Down
12 changes: 9 additions & 3 deletions tests/gs_test.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# env /bin/bash
echo "Running gs on all pdfs in tests folder"
for pdf in *.pdf; do
pdfs=`ls *.pdf`
if [[ $# -eq 0 ]] ; then
echo "Running gs on all pdfs in tests folder"
else
pdfs="$@"
fi

for pdf in $pdfs; do
echo "Checking $pdf..."
gs -dNOPAUSE -dBATCH -sDEVICE=nullpage "$pdf" | grep -i 'warn\|err'
done
echo "Done"
echo "Done"
Binary file added tests/large-canvas-no-editing-preserved.pdf
Binary file not shown.
Binary file added tests/large-canvas.pdf
Binary file not shown.
Binary file added tests/userunit_10_2page.pdf
Binary file not shown.
Binary file added tests/userunit_mixed.pdf
Binary file not shown.

0 comments on commit 79943f5

Please sign in to comment.