scanny / python-pptx

Create Open XML PowerPoint documents in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

text_frame.fit_text crashes if no wrapped representation fits in the width of the shape

rianwouters opened this issue · comments

I have a shape with with w and height h.
The size of the first word in the text when rendered with the maximum allowed text size is bigger than w.
In that case, text_fit crashes with the stack trace below, allthough there is a smaller text size that fits.

Exception: cannot unpack non-iterable NoneType object
Traceback (most recent call last):
  File "site-packages\pptx\text\text.py", line 239, 
in _best_fit_font_size
    return TextFitter.best_fit_font_size(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "site-packages\pptx\text\layout.py", line 27, in best_fit_font_size
    return text_fitter._best_fit_font_size(max_size)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "site-packages\pptx\text\layout.py", line 36, in _best_fit_font_size
    return sizes.find_max(predicate)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "site-packages\pptx\text\layout.py", line 130, in find_max
    if predicate(self.value):
       ^^^^^^^^^^^^^^^^^^^^^
  File "site-packages\pptx\text\layout.py", line 79, in predicate
    text_lines = self._wrap_lines(self._line_source, point_size)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "site-packages\pptx\text\layout.py", line 107, in _wrap_lines
    text, remainder = self._break_line(line_source, point_size)

As a user (not a python-pptx developer) it strikes me to fix this would be difficult:

It would require python-pptx to ingest font metrics for all the fonts and compute the dimensions of every string in every run that gets added. Even if the metrics were available it's got to slow the code down tremendously.

@rianwouters your stack trace is incomplete. In particular the exception is missing. Also, make sure to use triple-backtick "fences" when adding code to a post.

self._break_line(line_source, point_size) returns None en therefore _wrap_lines fails to unpack it.

I believe what should happen is the following:

def predicate(point_size) in layout.py@73 (defined in _fits_inside_predicate) should return false when the text cannot be wrapped to fit because the smallest biggest does not fit within the available width.

Simple fix would be to have a try..except around _wrap_lines to catch this exception and return false.

Alternatively, _wrap_lines needs a way of expressin in the return value that the text cannot be wrapped to fit as requested, for example by returning None in such case.
the predicate then needs

if !text_lines:
 return False

_wrap_lines would need something like:

broken_line = self._break_line(line_source, point_size)
if !broken_line:
   return None
text, remainder = broken_line

@MartinPacker This is almost what this code is doing ;-)