|
| 1 | + |
| 2 | +Text - Auto-fit text to shape |
| 3 | +============================= |
| 4 | + |
| 5 | +An AutoShape has a text frame, referred to in the PowerPoint UI as the |
| 6 | +shape's *Text Box*. One of the settings provided is *Autofit*, which can be |
| 7 | +one of "Do not autofit", "Resize text to fit shape", or "Resize shape to fit |
| 8 | +text". The scope of this analysis is how best to implement the "Resize text |
| 9 | +to fit shape" behavior. |
| 10 | + |
| 11 | +A robust implementation would be complex and would lead the project outside |
| 12 | +the currently intended scope. In particular, because the shape size, text |
| 13 | +content, the "full" point size of the text, and the autofit and wrap settings |
| 14 | +of the textframe all interact to determine the proper "fit" of adjusted text, |
| 15 | +all events that could change the state of any of these five factors would |
| 16 | +need to be coupled to an "update" method. There would also need to be at |
| 17 | +least two "fitting" algorithms, one for when wrap was turned on and another |
| 18 | +for when it was off. |
| 19 | + |
| 20 | +The initial solution we'll pursue is a special-purpose method on TextFrame |
| 21 | +that reduces the permutations of these variables to one and places |
| 22 | +responsibility on the developer to call it at the appropriate time. The |
| 23 | +method will calculate based on the current text and shape size, set wrap on, |
| 24 | +and set auto_size to fit text to the shape. If any of the variables change, |
| 25 | +the developer will be responsible for re-calling the method. |
| 26 | + |
| 27 | + |
| 28 | +Current constraints |
| 29 | +------------------- |
| 30 | + |
| 31 | +:meth:`.TextFrame.autofit_text` |
| 32 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 33 | + |
| 34 | +* User must manually set all shape text to a uniform 12pt default full-size |
| 35 | + font point size. |
| 36 | + |
| 37 | + + This is intended to be done before the call, but might work okay if done |
| 38 | + after too, as long as it matches the default 12pt. |
| 39 | + |
| 40 | +* Only 12pt "full-size" is supported. There is no mechanism to specify other |
| 41 | + sizes. |
| 42 | +* Unconditionally sets autofit and wrap. |
| 43 | +* Path to font file must be provided manually. |
| 44 | +* User must manually set the font typeface of all shape text to match the |
| 45 | + provided font file. |
| 46 | + |
| 47 | + |
| 48 | +Incremental enhancements |
| 49 | +------------------------ |
| 50 | + |
| 51 | +* **.fit_text() or .autofit_text().** Two related methods are used to fit |
| 52 | + text in a shape using different approaches. ``TextFrame.autofit_text()`` |
| 53 | + uses the `TEXT_TO_FIT_SHAPE` autofit setting to shrink a full-size |
| 54 | + paragraph of text. Later edits to that text using PowerPoint will re-fit |
| 55 | + the text automatically, up to the original (full-size) font size. |
| 56 | + |
| 57 | + ``TextFrame.fit_text()`` takes the approach of simply setting the font size |
| 58 | + for all text in the shape to the best-fit size. No automatic resizing |
| 59 | + occurs on later edits in PowerPoint, although the user can switch on |
| 60 | + auto-fit for that text box, perhaps after setting the full-size point size |
| 61 | + to their preferred size. |
| 62 | + |
| 63 | +* **Specified full point size.** Allow the point size maximum to be |
| 64 | + specified; defaults to 18pt. All text in the shape is set to this size |
| 65 | + before calculating the best-fit based on that size. |
| 66 | + |
| 67 | +* **Specified font.** In the process, this specifies the font to use, |
| 68 | + although it may require a tuple specifying the type family name as well as |
| 69 | + the bold and italic states, plus a file path. |
| 70 | + |
| 71 | +* **Auto-locate installed font file.** Search for and use the appropriate |
| 72 | + locally-installed font file corresponding to the selected typeface. On |
| 73 | + Windows, the font directory can be located using a registry key, and is |
| 74 | + perhaps often `C:\Windows\Fonts`. However the font filename is not the same |
| 75 | + as the UI typeface name, so some mapping would be required, including |
| 76 | + detecting whether bold and/or italic were specified. |
| 77 | + |
| 78 | +* **Accommodate line breaks.** Either from `<a:br>` elements or multiple |
| 79 | + paragraphs. Would involve ending lines at a break, other than the last |
| 80 | + paragraph. |
| 81 | + |
| 82 | +* **Add line space reduction.** PowerPoint always reduces line spacing by as |
| 83 | + much as 20% to maximize the font size used. Add this factor into the |
| 84 | + calculation to improve the exact match likelihood for font scale and |
| 85 | + lnSpcReduction and thereby reduce occurence of "jumping" of text to a new |
| 86 | + size on edit. |
| 87 | + |
| 88 | + |
| 89 | +Candidate Protocol |
| 90 | +------------------ |
| 91 | + |
| 92 | +Shape size and text are set before calling |
| 93 | +:meth:`.TextFrame.autofit_text` or :meth:`.TextFrame.fit_text`:: |
| 94 | + |
| 95 | + >>> shape.width, shape.height = cx, cy |
| 96 | + >>> shape.text = 'Lorem ipsum .. post facto.' |
| 97 | + >>> text_frame = shape.text_frame |
| 98 | + >>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale |
| 99 | + (None, False, None) |
| 100 | + |
| 101 | +Calling :meth:`TextFrame.autofit_text` turns on auto fit (text to shape), |
| 102 | +switches on word wrap, and calculates the best-fit font scaling factor:: |
| 103 | + |
| 104 | + >>> text_frame.autofit_text() |
| 105 | + >>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale |
| 106 | + (TEXT_TO_FIT_SHAPE (2), True, 55.0) |
| 107 | + |
| 108 | +Calling :meth:`TextFrame.fit_text` produces the same sized text, but autofit |
| 109 | +is not turned on. Rather the actual font size for text in the shape is set to |
| 110 | +the calculated "best fit" point size:: |
| 111 | + |
| 112 | + >>> text_frame.fit_text() |
| 113 | + >>> text_frame.auto_size, text_frame.word_wrap, text_frame._font_scale |
| 114 | + (None, True, None) |
| 115 | + >>> text_frame.paragraphs[0].font.size.pt |
| 116 | + 10 |
| 117 | + |
| 118 | + |
| 119 | +Proposed |pp| behavior |
| 120 | +---------------------- |
| 121 | + |
| 122 | +The :meth:`TextFrame.fit_text` method produces the following side effects: |
| 123 | + |
| 124 | +* :attr:`TextFrame.auto_size` is set to |
| 125 | + :attr:`MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE` |
| 126 | +* :attr:`TextFrame.word_wrap` is set to |True|. |
| 127 | +* A suitable value is calculated for `<a:normAutofit fontScale="?"/>`. The |
| 128 | + `fontScale` attribute is set to this value and the `lnSpcReduction` |
| 129 | + attribute is removed, if present. |
| 130 | + |
| 131 | +The operation can be undone by assigning |None|, :attr:`MSO_AUTO_SIZE.NONE`, |
| 132 | +or :attr:`MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT` to `TextFrame.auto_size`. |
| 133 | + |
| 134 | + |
| 135 | +PowerPoint behavior |
| 136 | +------------------- |
| 137 | + |
| 138 | +* PowerPoint shrinks text in whole-number font sizes. |
| 139 | + |
| 140 | +* The behavior interacts with *Wrap text in shape*. The behavior we want here |
| 141 | + is only when wrap is turned on. When wrap is off, only height and manual |
| 142 | + line breaks are taken into account. Long lines simply extend outside the |
| 143 | + box. |
| 144 | + |
| 145 | +* When assigning a font size to a shape, PowerPoint applies that font size at |
| 146 | + the run level, adding a `sz` attribute to the `<a:rPr>` element for every |
| 147 | + content child of every `<a:p>` element in the shape. The sentinel |
| 148 | + `<a:endParaRPr>` element also gets a `sz` attribute set to that size, but |
| 149 | + only in the last paragraph, it appears. |
| 150 | + |
| 151 | + |
| 152 | +XML specimens |
| 153 | +------------- |
| 154 | + |
| 155 | +.. highlight:: xml |
| 156 | + |
| 157 | +``<p:txBody>`` for default new textbox:: |
| 158 | + |
| 159 | + <p:txBody> |
| 160 | + <a:bodyPr wrap="none"> |
| 161 | + <a:spAutoFit/> <!-- fit shape to text --> |
| 162 | + </a:bodyPr> |
| 163 | + <a:lstStyle/> |
| 164 | + <a:p/> |
| 165 | + </p:txBody> |
| 166 | + |
| 167 | +8" x 0.5" text box, default margins, defaulting to 18pt "full-size" text, |
| 168 | +auto-reduced to 10pt. ``<a:t>`` element text wrapped for compact display:: |
| 169 | + |
| 170 | + <p:txBody> |
| 171 | + <a:bodyPr wrap="square" rtlCol="0"> |
| 172 | + <a:normAutofit fontScale="55000" lnSpcReduction="20000"/> |
| 173 | + </a:bodyPr> |
| 174 | + <a:lstStyle/> |
| 175 | + <a:p> |
| 176 | + <a:r> |
| 177 | + <a:rPr lang="en-US" dirty="0" smtClean="0"/> |
| 178 | + <a:t>The art and craft of designing typefaces is called type design. |
| 179 | + Designers of typefaces are called type designers and are often |
| 180 | + employed by type foundries. In digital typography, type |
| 181 | + designers are sometimes also called font developers or font |
| 182 | + designers.</a:t> |
| 183 | + </a:r> |
| 184 | + <a:endParaRPr lang="en-US" dirty="0"/> |
| 185 | + </a:p> |
| 186 | + </p:txBody> |
| 187 | + |
| 188 | + |
| 189 | +Related Schema Definitions |
| 190 | +-------------------------- |
| 191 | + |
| 192 | +:: |
| 193 | + |
| 194 | + <xsd:complexType name="CT_TextBody"> |
| 195 | + <xsd:sequence> |
| 196 | + <xsd:element name="bodyPr" type="CT_TextBodyProperties"/> |
| 197 | + <xsd:element name="lstStyle" type="CT_TextListStyle" minOccurs="0"/> |
| 198 | + <xsd:element name="p" type="CT_TextParagraph" maxOccurs="unbounded"/> |
| 199 | + </xsd:sequence> |
| 200 | + </xsd:complexType> |
| 201 | + |
| 202 | + <xsd:complexType name="CT_TextBodyProperties"> <!-- denormalized --> |
| 203 | + <xsd:sequence> |
| 204 | + <xsd:element name="prstTxWarp" type="CT_PresetTextShape" minOccurs="0"/> |
| 205 | + <xsd:choice minOccurs="0"> <!-- EG_TextAutofit --> |
| 206 | + <xsd:element name="noAutofit" type="CT_TextNoAutofit"/> |
| 207 | + <xsd:element name="normAutofit" type="CT_TextNormalAutofit"/> |
| 208 | + <xsd:element name="spAutoFit" type="CT_TextShapeAutofit"/> |
| 209 | + </xsd:choice> |
| 210 | + <xsd:element name="scene3d" type="CT_Scene3D" minOccurs="0"/> |
| 211 | + <xsd:choice minOccurs="0"> <!-- EG_Text3D --> |
| 212 | + <xsd:element name="sp3d" type="CT_Shape3D"/> |
| 213 | + <xsd:element name="flatTx" type="CT_FlatText"/> |
| 214 | + </xsd:choice> |
| 215 | + <xsd:element name="extLst" type="CT_OfficeArtExtensionList" minOccurs="0"/> |
| 216 | + </xsd:sequence> |
| 217 | + <xsd:attribute name="rot" type="ST_Angle"/> |
| 218 | + <xsd:attribute name="spcFirstLastPara" type="xsd:boolean"/> |
| 219 | + <xsd:attribute name="vertOverflow" type="ST_TextVertOverflowType"/> |
| 220 | + <xsd:attribute name="horzOverflow" type="ST_TextHorzOverflowType"/> |
| 221 | + <xsd:attribute name="vert" type="ST_TextVerticalType"/> |
| 222 | + <xsd:attribute name="wrap" type="ST_TextWrappingType"/> |
| 223 | + <xsd:attribute name="lIns" type="ST_Coordinate32"/> |
| 224 | + <xsd:attribute name="tIns" type="ST_Coordinate32"/> |
| 225 | + <xsd:attribute name="rIns" type="ST_Coordinate32"/> |
| 226 | + <xsd:attribute name="bIns" type="ST_Coordinate32"/> |
| 227 | + <xsd:attribute name="numCol" type="ST_TextColumnCount"/> |
| 228 | + <xsd:attribute name="spcCol" type="ST_PositiveCoordinate32"/> |
| 229 | + <xsd:attribute name="rtlCol" type="xsd:boolean"/> |
| 230 | + <xsd:attribute name="fromWordArt" type="xsd:boolean"/> |
| 231 | + <xsd:attribute name="anchor" type="ST_TextAnchoringType"/> |
| 232 | + <xsd:attribute name="anchorCtr" type="xsd:boolean"/> |
| 233 | + <xsd:attribute name="forceAA" type="xsd:boolean"/> |
| 234 | + <xsd:attribute name="upright" type="xsd:boolean" default="false"/> |
| 235 | + <xsd:attribute name="compatLnSpc" type="xsd:boolean"/> |
| 236 | + </xsd:complexType> |
| 237 | + |
| 238 | + <xsd:complexType name="CT_TextNormalAutofit"> |
| 239 | + <xsd:attribute name="fontScale" type="ST_TextFontScalePercentOrPercentString" |
| 240 | + use="optional" default="100%"/> |
| 241 | + <xsd:attribute name="lnSpcReduction" type="ST_TextSpacingPercentOrPercentString" |
| 242 | + use="optional" default="0%"/> |
| 243 | + </xsd:complexType> |
| 244 | + |
| 245 | + <xsd:complexType name="CT_TextShapeAutofit"/> |
| 246 | + |
| 247 | + <xsd:complexType name="CT_TextNoAutofit"/> |
| 248 | + |
| 249 | + <xsd:simpleType name="ST_TextFontScalePercentOrPercentString"> |
| 250 | + <xsd:union memberTypes="ST_TextFontScalePercent s:ST_Percentage"/> |
| 251 | + </xsd:simpleType> |
| 252 | + |
| 253 | + <xsd:simpleType name="ST_TextFontScalePercent"> |
| 254 | + <xsd:restriction base="ST_PercentageDecimal"> |
| 255 | + <xsd:minInclusive value="1000"/> |
| 256 | + <xsd:maxInclusive value="100000"/> |
| 257 | + </xsd:restriction> |
| 258 | + </xsd:simpleType> |
| 259 | + |
| 260 | + <xsd:simpleType name="ST_Percentage"> <!-- s:ST_Percentage --> |
| 261 | + <xsd:restriction base="xsd:string"> |
| 262 | + <xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/> |
| 263 | + </xsd:restriction> |
| 264 | + </xsd:simpleType> |
0 commit comments