Skip to content

Commit 0b0f158

Browse files
author
Steve Canny
committed
docs: document shrink-to-fit analysis
1 parent 8150d17 commit 0b0f158

File tree

4 files changed

+563
-2
lines changed

4 files changed

+563
-2
lines changed

README.rst

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
.. image:: https://travis-ci.org/scanny/python-pptx.svg?branch=master
2-
:target: https://travis-ci.org/scanny/python-pptx
1+
.. raw:: html
2+
3+
<p>
4+
<a href="https://travis-ci.org/scanny/python-pptx" >
5+
<img style="margin: 0 0 0 0"
6+
src="https://travis-ci.org/scanny/python-pptx.svg?branch=master"/>
7+
</a>
8+
</p>
39

410
*python-pptx* is a Python library for creating and updating PowerPoint (.pptx)
511
files.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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

Comments
 (0)