-
Notifications
You must be signed in to change notification settings - Fork 13
implementing replace_picture() #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
31ea782
8ac31cd
4d8f199
37e9b1b
ef48d76
032e65a
ffda58f
e8e043e
7abbeb2
6576e08
9ec849e
a8a17e4
e629891
e8cdc97
d316fd3
d0a5208
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from pathlib import Path | ||
from typing import List | ||
|
||
from pptx import Presentation | ||
from pptx.shapes.autoshape import BaseShape | ||
from pptx.slide import Slide | ||
|
||
example_file_path = Path("./data/example01.pptx").resolve() | ||
prs = Presentation(example_file_path) | ||
print(prs) | ||
|
||
|
||
def get_shape_info(shape: BaseShape) -> dict: | ||
return { | ||
"name": shape.name, | ||
"text": shape.text if shape.has_text_frame else None, | ||
"width": shape.width, | ||
"height": shape.height, | ||
"id": shape.shape_id, | ||
"x": shape.left, | ||
"y": shape.top, | ||
"type": type(shape), | ||
} | ||
|
||
|
||
slide_info = [get_shape_info(shape) for shape in prs.slides[0].shapes] | ||
print(slide_info) | ||
|
||
|
||
def replace_by_image(slide: Slide, name: str, img_path: Path, *, do_not_scale: bool = False) -> List[BaseShape]: | ||
shapes_to_replace = [shape for shape in slide.shapes if hasattr(shape, "text") and shape.text == name] | ||
print(len(shapes_to_replace)) | ||
new_image_shapes = [] | ||
for old_shape in shapes_to_replace: | ||
shape_info = get_shape_info(old_shape) | ||
print(shape_info) | ||
img_file = open(img_path, "rb") | ||
slide_shapes = old_shape._parent | ||
img_shape = slide_shapes.add_picture( | ||
img_file, | ||
old_shape.left, | ||
old_shape.top, | ||
) | ||
old_aspect_ratio = old_shape.width / old_shape.height | ||
new_aspect_ratio = img_shape.width / img_shape.height | ||
if img_shape.height <= old_shape.height and img_shape.width <= old_shape.width and not do_not_scale: | ||
if old_aspect_ratio >= new_aspect_ratio: | ||
img_shape.width = old_shape.width | ||
img_shape.height = int(img_shape.width / new_aspect_ratio) | ||
else: | ||
img_shape.height = old_shape.height | ||
img_shape.width = int(img_shape.height * new_aspect_ratio) | ||
img_shape.top += int((old_shape.height - img_shape.height) / 2) | ||
img_shape.left += int((old_shape.width - img_shape.width) / 2) | ||
new_image_shapes.append(img_shape) | ||
slide_shapes.element.remove(old_shape.element) | ||
return new_image_shapes | ||
|
||
|
||
added_img_shapes = replace_by_image(prs.slides[0], "#logo", Path("./playground/pptx_icon.png")) | ||
assert len(added_img_shapes) == 1 | ||
added_img_shapes = replace_by_image(prs.slides[0], "#logo", Path("./playground/pptx_icon.png")) | ||
assert len(added_img_shapes) == 0 | ||
prs.save(Path("./playground") / example_file_path.name) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -30,14 +30,47 @@ def replace_text(self, label: str, text: str, *, scope=None) -> None: | |||||
""" | ||||||
pass | ||||||
|
||||||
def replace_picture(self, label: str, filename: _Pathlike) -> None: | ||||||
def replace_picture(self, label: str, filename: _Pathlike, *, do_not_scale_up: bool = False) -> None: | ||||||
"""Replaces rectangle placeholders on one or many slides. | ||||||
|
||||||
Args: | ||||||
label (str): label of the placeholder (without curly braces) | ||||||
filename (path-like): path to an image file | ||||||
do_not_scale_up (bool): deactivates that the image is enlarged (default: False) | ||||||
""" | ||||||
pass | ||||||
shapes_to_replace = self._find_shapes(label) | ||||||
if not shapes_to_replace: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an Exception. Don't let errors pass silently! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a good idea to handle the problem on a higher level. I will replace raise ValueError(f"The label '{label}' can't be found in the template.") |
||||||
return | ||||||
if isinstance(filename, str): | ||||||
filename = pathlib.Path(filename) | ||||||
if not filename.is_file(): | ||||||
raise FileNotFoundError(f"The file does not exist: {filename}") | ||||||
img_file = open(filename, "rb") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
old_shape: BaseShape | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would maybe type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already use |
||||||
for old_shape in shapes_to_replace: | ||||||
slide_shapes = old_shape._parent | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big fan of using the internals of the pptx library. But if there is really no better way... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems there is currently no other solution: Could be mentionend in a code comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx for linking the resource. |
||||||
img_shape = slide_shapes.add_picture( | ||||||
image_file=img_file, | ||||||
left=old_shape.left, | ||||||
top=old_shape.top, | ||||||
) | ||||||
# Scaling the image if `do_not_scale == False`: | ||||||
if img_shape.height <= old_shape.height and img_shape.width <= old_shape.width and not do_not_scale_up: | ||||||
old_aspect_ratio = old_shape.width / old_shape.height | ||||||
new_aspect_ratio = img_shape.width / img_shape.height | ||||||
if old_aspect_ratio >= new_aspect_ratio: | ||||||
img_shape.width = old_shape.width | ||||||
img_shape.height = int(img_shape.width / new_aspect_ratio) | ||||||
else: | ||||||
img_shape.height = old_shape.height | ||||||
img_shape.width = int(img_shape.height * new_aspect_ratio) | ||||||
# Centering the image at the extent of the placeholder: | ||||||
img_shape.top += int((old_shape.height - img_shape.height) / 2) | ||||||
img_shape.left += int((old_shape.width - img_shape.width) / 2) | ||||||
del slide_shapes[slide_shapes.index(old_shape)] | ||||||
# Removing shapes is performed at the lxml level. | ||||||
# The `element` attribute contains an instance of `lxml.etree._Element`. | ||||||
slide_shapes.element.remove(old_shape.element) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yikes! We should really not deal with xml ourselves. We should open a RP in pptx to get this feature implemented. For the time being, we should either 1) isolate this in a private method marked deprecated to prevent excessive use, or 2) decorate the pptx presentation object. |
||||||
|
||||||
def replace_table(self, label: str, data) -> None: | ||||||
"""Replaces rectangle placeholders on one or many slides. | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for not committing the dependency lock file? I would only not do this if there is a problem...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we use requirements.txt and dev_requirements.txt for listing dependencies a Pipfile would duplicate the requirements.