Text - Auto-fit text to shape

An AutoShape has a text frame, referred to in the PowerPoint UI as the shape’s Text Box. One of the settings provided is Autofit, which can be one of “Do not autofit”, “Resize text to fit shape”, or “Resize shape to fit text”. The scope of this analysis is how best to implement the “Resize text to fit shape” behavior.

A robust implementation would be complex and would lead the project outside the currently intended scope. In particular, because the shape size, text content, the “full” point size of the text, and the autofit and wrap settings of the text frame all interact to determine the proper “fit” of adjusted text, all events that could change the state of any of these five factors would need to be coupled to an “update” method. There would also need to be at least two “fitting” algorithms, one for when wrap was turned on and another for when it was off.

The initial solution we’ll pursue is a special-purpose method on TextFrame that reduces the permutations of these variables to one and places responsibility on the developer to call it at the appropriate time. The method will calculate based on the current text and shape size, set wrap on, and set auto_size to fit text to the shape. If any of the variables change, the developer will be responsible for re-calling the method.

Current constraints

TextFrame.autofit_text()

  • User must manually set all shape text to a uniform 12pt default full-size font point size.

    • This is intended to be done before the call, but might work okay if done after too, as long as it matches the default 12pt.

  • Only 12pt “full-size” is supported. There is no mechanism to specify other sizes.

  • Unconditionally sets autofit and wrap.

  • Path to font file must be provided manually.

  • User must manually set the font typeface of all shape text to match the provided font file.

Incremental enhancements

  • .fit_text() or .autofit_text(). Two related methods are used to fit text in a shape using different approaches. TextFrame.autofit_text() uses the TEXT_TO_FIT_SHAPE autofit setting to shrink a full-size paragraph of text. Later edits to that text using PowerPoint will re-fit the text automatically, up to the original (full-size) font size.

    TextFrame.fit_text() takes the approach of simply setting the font size for all text in the shape to the best-fit size. No automatic resizing occurs on later edits in PowerPoint, although the user can switch on auto-fit for that text box, perhaps after setting the full-size point size to their preferred size.

  • Specified full point size. Allow the point size maximum to be specified; defaults to 18pt. All text in the shape is set to this size before calculating the best-fit based on that size.

  • Specified font. In the process, this specifies the font to use, although it may require a tuple specifying the type family name as well as the bold and italic states, plus a file path.

  • Auto-locate installed font file. Search for and use the appropriate locally-installed font file corresponding to the selected typeface. On Windows, the font directory can be located using a registry key, and is perhaps often C:WindowsFonts. However the font filename is not the same as the UI typeface name, so some mapping would be required, including detecting whether bold and/or italic were specified.

  • Accommodate line breaks. Either from <a:br> elements or multiple paragraphs. Would involve ending lines at a break, other than the last paragraph.

  • Add line space reduction. PowerPoint always reduces line spacing by as much as 20% to maximize the font size used. Add this factor into the calculation to improve the exact match likelihood for font scale and lnSpcReduction and thereby reduce occurence of “jumping” of text to a new size on edit.

Candidate Protocol

Shape size and text are set before calling TextFrame.autofit_text() or TextFrame.fit_text():

>>> shape.width, shape.height = cx, cy
>>> shape.text = 'Lorem ipsum .. post facto.'
>>> text_frame = shape.text_frame
>>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale
(None, False, None)

Calling TextFrame.autofit_text() turns on auto fit (text to shape), switches on word wrap, and calculates the best-fit font scaling factor:

>>> text_frame.autofit_text()
>>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale
(TEXT_TO_FIT_SHAPE (2), True, 55.0)

Calling TextFrame.fit_text() produces the same sized text, but autofit is not turned on. Rather the actual font size for text in the shape is set to the calculated “best fit” point size:

>>> text_frame.fit_text()
>>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale
(None, True, None)
>>> text_frame.paragraphs[0].font.size.pt
10

Proposed python-pptx behavior

The TextFrame.fit_text() method produces the following side effects:

  • TextFrame.auto_size is set to MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE

  • TextFrame.word_wrap is set to True.

  • A suitable value is calculated for <a:normAutofit fontScale=”?”/>. The fontScale attribute is set to this value and the lnSpcReduction attribute is removed, if present.

The operation can be undone by assigning None, MSO_AUTO_SIZE.NONE, or MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT to TextFrame.auto_size.

PowerPoint behavior

  • PowerPoint shrinks text in whole-number font sizes.

  • The behavior interacts with Wrap text in shape. The behavior we want here is only when wrap is turned on. When wrap is off, only height and manual line breaks are taken into account. Long lines simply extend outside the box.

  • When assigning a font size to a shape, PowerPoint applies that font size at the run level, adding a sz attribute to the <a:rPr> element for every content child of every <a:p> element in the shape. The sentinel <a:endParaRPr> element also gets a sz attribute set to that size, but only in the last paragraph, it appears.

XML specimens

<p:txBody> for default new textbox:

<p:txBody>
  <a:bodyPr wrap="none">
    <a:spAutoFit/>  <!-- fit shape to text -->
  </a:bodyPr>
  <a:lstStyle/>
  <a:p/>
</p:txBody>

8” x 0.5” text box, default margins, defaulting to 18pt “full-size” text, auto-reduced to 10pt. <a:t> element text wrapped for compact display:

<p:txBody>
  <a:bodyPr wrap="square" rtlCol="0">
    <a:normAutofit fontScale="55000" lnSpcReduction="20000"/>
  </a:bodyPr>
  <a:lstStyle/>
  <a:p>
    <a:r>
      <a:rPr lang="en-US" dirty="0" smtClean="0"/>
      <a:t>The art and craft of designing typefaces is called type design.
           Designers of typefaces are called type designers and are often
           employed by type foundries. In digital typography, type
           designers are sometimes also called font developers or font
           designers.</a:t>
    </a:r>
    <a:endParaRPr lang="en-US" dirty="0"/>
  </a:p>
</p:txBody>