Skip to content

Commit

Permalink
Stitch two pages with rotations
Browse files Browse the repository at this point in the history
  • Loading branch information
angsch committed Mar 28, 2021
1 parent a4b408d commit 9c22893
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
8 changes: 8 additions & 0 deletions data/menu.ui
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ along with PDF Arranger. If not, see <http://www.gnu.org/licenses/>.
<attribute name="label" translatable="yes">_Split Pages</attribute>
<attribute name="action">win.split</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Stitch Pages</attribute>
<attribute name="action">win.stitch</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Cu_t</attribute>
<attribute name="action">win.cut</attribute>
Expand Down Expand Up @@ -327,6 +331,10 @@ along with PDF Arranger. If not, see <http://www.gnu.org/licenses/>.
<attribute name="label" translatable="yes">_Split Pages</attribute>
<attribute name="action">win.split</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Stitch Pages</attribute>
<attribute name="action">win.stitch</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Insert Blan_k Page</attribute>
<attribute name="action">win.insert-blank-page</attribute>
Expand Down
93 changes: 93 additions & 0 deletions pdfarranger/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,99 @@ def _mediabox(page, crop):
return [x1_new, y1_new, x2_new, y2_new]


def _rshift(lrotate, rrotate, lsize, rsize):
wl, hl = lsize
wr, hr = rsize
shift = {
# lrotate : rrotate : offset of right page
0: { 0 : [wl, 0],
1 : [wl, hr],
2 : [wl + wr, hr],
3 : [wl + wr, 0]},
1: { 0 : [wr, wl],
1 : [0, wl],
2 : [0, hr + wl],
3 : [hl, wl + wr]},
2: { 0 : [0, hr],
1 : [0, -wl + hr],
2 : [-wl, 0],
3 : [-wl + wr, hr]},
3: { 0 : [0, -hl + wr],
1 : [hr, 0],
2 : [hl, -wl],
3 : [0, -wl]}
}
return shift[lrotate][rrotate]

def _lshift(lrotate, lsize):
w, h = lsize
shift = {
0 : [0, 0],
1 : [0, h],
2 : [w, h],
3 : [w, 0]
}
return shift[lrotate]

def create_stitched_page(tmpdir, input_files, pages):
"""
Stitch two pages vertically and save the result as a temporary PDF file.
"""
f = pikepdf.Pdf.new()
content_dict = pikepdf.Dictionary({})
content_txt = ''
rotation_matrices = {0 : [1, 0, 0, 1], # 0 degrees
1 : [0, -1, 1, 0], # 270 degrees
2 : [-1, 0, 0, -1], # 180 degrees
3 : [0, 1, -1, 0]} # 90 degrees
pdf_input = [pikepdf.open(p.copyname, password=p.password) for p in input_files]

width = 0
height = None
lsize = [0, 0]
lrotate = None
for count, cur_page in enumerate(pages, start = 1):
current_page = pdf_input[cur_page.nfile - 1].pages[cur_page.npage - 1]
angle = cur_page.angle
angle0 = current_page.Rotate if '/Rotate' in current_page else 0
rotate_times = int(round(((angle + angle0) % 360) / 90) % 4)
_, _, w, h = [float(x) for x in current_page.MediaBox]
# TODO: support crop
if rotate_times == 1 or rotate_times == 3:
# Swap width and height
w, h = h, w
if count == 1: # left page
lsize = [w, h]
height = h
lrotate = rotate_times
shift = _lshift(lrotate, lsize)
else: # right page
shift = _rshift(lrotate, rotate_times, lsize, [w, h])
# Multiply rotation matrices.
rotate_times = (rotate_times - lrotate + 4) % 4

R = rotation_matrices[rotate_times]
new_page = f.copy_foreign(current_page)
pagekey = '/Page{0}'.format(count)
content_dict[pagekey] = pikepdf.Page(new_page).as_form_xobject()
width += w
content_txt += 'q {} {} {} {} {} {} cm {} Do Q'.format(R[0], R[1], R[2], R[3], shift[0], shift[1], pagekey)

# Create new page.
newmediabox = [0, 0, width, height]
newpage = pikepdf.Dictionary(
Type=pikepdf.Name.Page,
MediaBox=newmediabox,
Resources=pikepdf.Dictionary(XObject=content_dict),
Contents=pikepdf.Stream(f, content_txt.encode())
)
fd, filename = tempfile.mkstemp(suffix=".pdf", dir=tmpdir)
os.close(fd)
f.pages.append(newpage)
f.save(filename)
return filename


_report_pikepdf_err = True


Expand Down
24 changes: 24 additions & 0 deletions pdfarranger/pdfarranger.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def __create_actions(self):
('undo', self.undomanager.undo),
('redo', self.undomanager.redo),
('split', self.split_pages),
('stitch', self.stitch_pages),
('metadata', self.edit_metadata),
('cut', self.on_action_cut),
('copy', self.on_action_copy),
Expand Down Expand Up @@ -1507,6 +1508,7 @@ def iv_selection_changed_event(self, _iconview=None, move_cursor_event=False):
("cut", ne),
("copy", ne),
("split", ne),
("stitch", self.stitch_available(selection)),
("select-same-file", ne),
("select-same-format", ne),
("crop-white-borders", ne),
Expand Down Expand Up @@ -1716,6 +1718,15 @@ def split_pages(self, _action, _parameter, _unknown):
model.set_value(iterator, 0, page)
self.iv_selection_changed_event()

def stitch_pages(self, _action, _parameter, _unknown):
"""Stich the two selected pages"""
selection = self.iconview.get_selected_items()
pages = [row[0] for row in self.model if row.path in selection]
adder = PageAdder(self)
filename = exporter.create_stitched_page(self.tmp_dir, self.pdfqueue, pages)
adder.addpages(filename)
adder.commit(select_added=True, add_to_undomanager=True)

def edit_metadata(self, _action, _parameter, _unknown):
if metadata.edit(self.metadata, self.pdfqueue, self.window):
self.set_unsaved(True)
Expand Down Expand Up @@ -1773,6 +1784,19 @@ def duplicate(self, _action, _parameter, _unknown):
model.insert_after(iterator, [page, page.description()])
self.iv_selection_changed_event()

def stitch_available(self, selection):
"""Determine whether two pages with matching heights are selected."""
if len(selection) != 2:
return False

model = self.iconview.get_model()
selected_pages = []
for path in selection:
it = model.get_iter(path)
selected_pages.append(model.get_value(it, 0))
left_page, right_page = selected_pages
same_height = left_page.height_in_points() == right_page.height_in_points()
return same_height

@staticmethod
def reverse_order_available(selection):
Expand Down

0 comments on commit 9c22893

Please sign in to comment.