ruby_pptx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +83 -0
- data/LICENSE +21 -0
- data/NOTICE +48 -0
- data/README.md +272 -0
- data/lib/ruby_pptx/action.rb +120 -0
- data/lib/ruby_pptx/autoshape_spec.rb +337 -0
- data/lib/ruby_pptx/builder.rb +228 -0
- data/lib/ruby_pptx/chart/categories.rb +208 -0
- data/lib/ruby_pptx/chart/chart.rb +443 -0
- data/lib/ruby_pptx/chart/combo.rb +230 -0
- data/lib/ruby_pptx/chart/data.rb +147 -0
- data/lib/ruby_pptx/chart/format.rb +647 -0
- data/lib/ruby_pptx/chart/workbook_writer.rb +212 -0
- data/lib/ruby_pptx/chart/xml_writer.rb +953 -0
- data/lib/ruby_pptx/chart/xy_data.rb +182 -0
- data/lib/ruby_pptx/core_ext.rb +12 -0
- data/lib/ruby_pptx/dml/color.rb +155 -0
- data/lib/ruby_pptx/dml/effect.rb +33 -0
- data/lib/ruby_pptx/dml/fill.rb +234 -0
- data/lib/ruby_pptx/element_proxy.rb +53 -0
- data/lib/ruby_pptx/enum/action.rb +46 -0
- data/lib/ruby_pptx/enum/base.rb +139 -0
- data/lib/ruby_pptx/enum/chart.rb +278 -0
- data/lib/ruby_pptx/enum/dml.rb +237 -0
- data/lib/ruby_pptx/enum/lang.rb +441 -0
- data/lib/ruby_pptx/enum/prog_id.rb +38 -0
- data/lib/ruby_pptx/enum/shapes.rb +514 -0
- data/lib/ruby_pptx/enum/text.rb +108 -0
- data/lib/ruby_pptx/errors.rb +15 -0
- data/lib/ruby_pptx/geometry.rb +89 -0
- data/lib/ruby_pptx/image.rb +347 -0
- data/lib/ruby_pptx/length.rb +120 -0
- data/lib/ruby_pptx/media.rb +64 -0
- data/lib/ruby_pptx/numeric_lengths.rb +30 -0
- data/lib/ruby_pptx/opc/constants.rb +205 -0
- data/lib/ruby_pptx/opc/oxml.rb +99 -0
- data/lib/ruby_pptx/opc/pack_uri.rb +146 -0
- data/lib/ruby_pptx/opc/package.rb +482 -0
- data/lib/ruby_pptx/opc/serialized.rb +229 -0
- data/lib/ruby_pptx/opc/spec.rb +37 -0
- data/lib/ruby_pptx/oxml/action.rb +43 -0
- data/lib/ruby_pptx/oxml/chart.rb +785 -0
- data/lib/ruby_pptx/oxml/content_model.rb +186 -0
- data/lib/ruby_pptx/oxml/core_properties.rb +145 -0
- data/lib/ruby_pptx/oxml/dml/color.rb +115 -0
- data/lib/ruby_pptx/oxml/dml/fill.rb +137 -0
- data/lib/ruby_pptx/oxml/element.rb +329 -0
- data/lib/ruby_pptx/oxml/ns.rb +115 -0
- data/lib/ruby_pptx/oxml/presentation.rb +110 -0
- data/lib/ruby_pptx/oxml/section.rb +65 -0
- data/lib/ruby_pptx/oxml/shapes/autoshape.rb +310 -0
- data/lib/ruby_pptx/oxml/shapes/groupshape.rb +217 -0
- data/lib/ruby_pptx/oxml/shapes/other.rb +405 -0
- data/lib/ruby_pptx/oxml/shapes/shared.rb +292 -0
- data/lib/ruby_pptx/oxml/simple_types.rb +563 -0
- data/lib/ruby_pptx/oxml/slide.rb +290 -0
- data/lib/ruby_pptx/oxml/table.rb +253 -0
- data/lib/ruby_pptx/oxml/text.rb +324 -0
- data/lib/ruby_pptx/oxml/theme.rb +93 -0
- data/lib/ruby_pptx/package.rb +139 -0
- data/lib/ruby_pptx/parts/chart.rb +75 -0
- data/lib/ruby_pptx/parts/core_properties.rb +40 -0
- data/lib/ruby_pptx/parts/embedded_package.rb +43 -0
- data/lib/ruby_pptx/parts/image.rb +55 -0
- data/lib/ruby_pptx/parts/media.rb +17 -0
- data/lib/ruby_pptx/parts/presentation.rb +79 -0
- data/lib/ruby_pptx/parts/slide.rb +221 -0
- data/lib/ruby_pptx/parts/theme.rb +26 -0
- data/lib/ruby_pptx/pattern_matching.rb +50 -0
- data/lib/ruby_pptx/presentation.rb +128 -0
- data/lib/ruby_pptx/refinements.rb +21 -0
- data/lib/ruby_pptx/section.rb +130 -0
- data/lib/ruby_pptx/shapes/adjustments.rb +83 -0
- data/lib/ruby_pptx/shapes/authoring.rb +110 -0
- data/lib/ruby_pptx/shapes/base.rb +138 -0
- data/lib/ruby_pptx/shapes/freeform.rb +141 -0
- data/lib/ruby_pptx/shapes/placeholder.rb +184 -0
- data/lib/ruby_pptx/shapes/shape.rb +337 -0
- data/lib/ruby_pptx/shapes/shape_tree.rb +634 -0
- data/lib/ruby_pptx/sliceable.rb +34 -0
- data/lib/ruby_pptx/slide.rb +430 -0
- data/lib/ruby_pptx/table.rb +316 -0
- data/lib/ruby_pptx/table_paging.rb +91 -0
- data/lib/ruby_pptx/templates/default.pptx +0 -0
- data/lib/ruby_pptx/templates/docx-icon.emf +0 -0
- data/lib/ruby_pptx/templates/generic-icon.emf +0 -0
- data/lib/ruby_pptx/templates/media-speaker.png +0 -0
- data/lib/ruby_pptx/templates/notes.xml +23 -0
- data/lib/ruby_pptx/templates/notesMaster.xml +352 -0
- data/lib/ruby_pptx/templates/pptx-icon.emf +0 -0
- data/lib/ruby_pptx/templates/slideMaster.xml +277 -0
- data/lib/ruby_pptx/templates/theme.xml +321 -0
- data/lib/ruby_pptx/templates/xlsx-icon.emf +0 -0
- data/lib/ruby_pptx/text/fitter.rb +72 -0
- data/lib/ruby_pptx/text/font_metrics.rb +221 -0
- data/lib/ruby_pptx/text/text.rb +393 -0
- data/lib/ruby_pptx/theme.rb +98 -0
- data/lib/ruby_pptx/version.rb +5 -0
- data/lib/ruby_pptx.rb +90 -0
- metadata +174 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
|
2
|
+
<a:theme
|
|
3
|
+
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
4
|
+
name="Office Theme"
|
|
5
|
+
>
|
|
6
|
+
<a:themeElements>
|
|
7
|
+
<a:clrScheme name="Office">
|
|
8
|
+
<a:dk1>
|
|
9
|
+
<a:sysClr val="windowText" lastClr="000000"/>
|
|
10
|
+
</a:dk1>
|
|
11
|
+
<a:lt1>
|
|
12
|
+
<a:sysClr val="window" lastClr="FFFFFF"/>
|
|
13
|
+
</a:lt1>
|
|
14
|
+
<a:dk2>
|
|
15
|
+
<a:srgbClr val="1F497D"/>
|
|
16
|
+
</a:dk2>
|
|
17
|
+
<a:lt2>
|
|
18
|
+
<a:srgbClr val="EEECE1"/>
|
|
19
|
+
</a:lt2>
|
|
20
|
+
<a:accent1>
|
|
21
|
+
<a:srgbClr val="4F81BD"/>
|
|
22
|
+
</a:accent1>
|
|
23
|
+
<a:accent2>
|
|
24
|
+
<a:srgbClr val="C0504D"/>
|
|
25
|
+
</a:accent2>
|
|
26
|
+
<a:accent3>
|
|
27
|
+
<a:srgbClr val="9BBB59"/>
|
|
28
|
+
</a:accent3>
|
|
29
|
+
<a:accent4>
|
|
30
|
+
<a:srgbClr val="8064A2"/>
|
|
31
|
+
</a:accent4>
|
|
32
|
+
<a:accent5>
|
|
33
|
+
<a:srgbClr val="4BACC6"/>
|
|
34
|
+
</a:accent5>
|
|
35
|
+
<a:accent6>
|
|
36
|
+
<a:srgbClr val="F79646"/>
|
|
37
|
+
</a:accent6>
|
|
38
|
+
<a:hlink>
|
|
39
|
+
<a:srgbClr val="0000FF"/>
|
|
40
|
+
</a:hlink>
|
|
41
|
+
<a:folHlink>
|
|
42
|
+
<a:srgbClr val="800080"/>
|
|
43
|
+
</a:folHlink>
|
|
44
|
+
</a:clrScheme>
|
|
45
|
+
<a:fontScheme name="Office">
|
|
46
|
+
<a:majorFont>
|
|
47
|
+
<a:latin typeface="Calibri"/>
|
|
48
|
+
<a:ea typeface=""/>
|
|
49
|
+
<a:cs typeface=""/>
|
|
50
|
+
<a:font script="Jpan" typeface="MS Pゴシック"/>
|
|
51
|
+
<a:font script="Hang" typeface="맑은 고딕"/>
|
|
52
|
+
<a:font script="Hans" typeface="宋体"/>
|
|
53
|
+
<a:font script="Hant" typeface="新細明體"/>
|
|
54
|
+
<a:font script="Arab" typeface="Times New Roman"/>
|
|
55
|
+
<a:font script="Hebr" typeface="Times New Roman"/>
|
|
56
|
+
<a:font script="Thai" typeface="Angsana New"/>
|
|
57
|
+
<a:font script="Ethi" typeface="Nyala"/>
|
|
58
|
+
<a:font script="Beng" typeface="Vrinda"/>
|
|
59
|
+
<a:font script="Gujr" typeface="Shruti"/>
|
|
60
|
+
<a:font script="Khmr" typeface="MoolBoran"/>
|
|
61
|
+
<a:font script="Knda" typeface="Tunga"/>
|
|
62
|
+
<a:font script="Guru" typeface="Raavi"/>
|
|
63
|
+
<a:font script="Cans" typeface="Euphemia"/>
|
|
64
|
+
<a:font script="Cher" typeface="Plantagenet Cherokee"/>
|
|
65
|
+
<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
|
|
66
|
+
<a:font script="Tibt" typeface="Microsoft Himalaya"/>
|
|
67
|
+
<a:font script="Thaa" typeface="MV Boli"/>
|
|
68
|
+
<a:font script="Deva" typeface="Mangal"/>
|
|
69
|
+
<a:font script="Telu" typeface="Gautami"/>
|
|
70
|
+
<a:font script="Taml" typeface="Latha"/>
|
|
71
|
+
<a:font script="Syrc" typeface="Estrangelo Edessa"/>
|
|
72
|
+
<a:font script="Orya" typeface="Kalinga"/>
|
|
73
|
+
<a:font script="Mlym" typeface="Kartika"/>
|
|
74
|
+
<a:font script="Laoo" typeface="DokChampa"/>
|
|
75
|
+
<a:font script="Sinh" typeface="Iskoola Pota"/>
|
|
76
|
+
<a:font script="Mong" typeface="Mongolian Baiti"/>
|
|
77
|
+
<a:font script="Viet" typeface="Times New Roman"/>
|
|
78
|
+
<a:font script="Uigh" typeface="Microsoft Uighur"/>
|
|
79
|
+
<a:font script="Geor" typeface="Sylfaen"/>
|
|
80
|
+
</a:majorFont>
|
|
81
|
+
<a:minorFont>
|
|
82
|
+
<a:latin typeface="Calibri"/>
|
|
83
|
+
<a:ea typeface=""/>
|
|
84
|
+
<a:cs typeface=""/>
|
|
85
|
+
<a:font script="Jpan" typeface="MS Pゴシック"/>
|
|
86
|
+
<a:font script="Hang" typeface="맑은 고딕"/>
|
|
87
|
+
<a:font script="Hans" typeface="宋体"/>
|
|
88
|
+
<a:font script="Hant" typeface="新細明體"/>
|
|
89
|
+
<a:font script="Arab" typeface="Arial"/>
|
|
90
|
+
<a:font script="Hebr" typeface="Arial"/>
|
|
91
|
+
<a:font script="Thai" typeface="Cordia New"/>
|
|
92
|
+
<a:font script="Ethi" typeface="Nyala"/>
|
|
93
|
+
<a:font script="Beng" typeface="Vrinda"/>
|
|
94
|
+
<a:font script="Gujr" typeface="Shruti"/>
|
|
95
|
+
<a:font script="Khmr" typeface="DaunPenh"/>
|
|
96
|
+
<a:font script="Knda" typeface="Tunga"/>
|
|
97
|
+
<a:font script="Guru" typeface="Raavi"/>
|
|
98
|
+
<a:font script="Cans" typeface="Euphemia"/>
|
|
99
|
+
<a:font script="Cher" typeface="Plantagenet Cherokee"/>
|
|
100
|
+
<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
|
|
101
|
+
<a:font script="Tibt" typeface="Microsoft Himalaya"/>
|
|
102
|
+
<a:font script="Thaa" typeface="MV Boli"/>
|
|
103
|
+
<a:font script="Deva" typeface="Mangal"/>
|
|
104
|
+
<a:font script="Telu" typeface="Gautami"/>
|
|
105
|
+
<a:font script="Taml" typeface="Latha"/>
|
|
106
|
+
<a:font script="Syrc" typeface="Estrangelo Edessa"/>
|
|
107
|
+
<a:font script="Orya" typeface="Kalinga"/>
|
|
108
|
+
<a:font script="Mlym" typeface="Kartika"/>
|
|
109
|
+
<a:font script="Laoo" typeface="DokChampa"/>
|
|
110
|
+
<a:font script="Sinh" typeface="Iskoola Pota"/>
|
|
111
|
+
<a:font script="Mong" typeface="Mongolian Baiti"/>
|
|
112
|
+
<a:font script="Viet" typeface="Arial"/>
|
|
113
|
+
<a:font script="Uigh" typeface="Microsoft Uighur"/>
|
|
114
|
+
<a:font script="Geor" typeface="Sylfaen"/>
|
|
115
|
+
</a:minorFont>
|
|
116
|
+
</a:fontScheme>
|
|
117
|
+
<a:fmtScheme name="Office">
|
|
118
|
+
<a:fillStyleLst>
|
|
119
|
+
<a:solidFill>
|
|
120
|
+
<a:schemeClr val="phClr"/>
|
|
121
|
+
</a:solidFill>
|
|
122
|
+
<a:gradFill rotWithShape="1">
|
|
123
|
+
<a:gsLst>
|
|
124
|
+
<a:gs pos="0">
|
|
125
|
+
<a:schemeClr val="phClr">
|
|
126
|
+
<a:tint val="50000"/>
|
|
127
|
+
<a:satMod val="300000"/>
|
|
128
|
+
</a:schemeClr>
|
|
129
|
+
</a:gs>
|
|
130
|
+
<a:gs pos="35000">
|
|
131
|
+
<a:schemeClr val="phClr">
|
|
132
|
+
<a:tint val="37000"/>
|
|
133
|
+
<a:satMod val="300000"/>
|
|
134
|
+
</a:schemeClr>
|
|
135
|
+
</a:gs>
|
|
136
|
+
<a:gs pos="100000">
|
|
137
|
+
<a:schemeClr val="phClr">
|
|
138
|
+
<a:tint val="15000"/>
|
|
139
|
+
<a:satMod val="350000"/>
|
|
140
|
+
</a:schemeClr>
|
|
141
|
+
</a:gs>
|
|
142
|
+
</a:gsLst>
|
|
143
|
+
<a:lin ang="16200000" scaled="1"/>
|
|
144
|
+
</a:gradFill>
|
|
145
|
+
<a:gradFill rotWithShape="1">
|
|
146
|
+
<a:gsLst>
|
|
147
|
+
<a:gs pos="0">
|
|
148
|
+
<a:schemeClr val="phClr">
|
|
149
|
+
<a:tint val="100000"/>
|
|
150
|
+
<a:shade val="100000"/>
|
|
151
|
+
<a:satMod val="130000"/>
|
|
152
|
+
</a:schemeClr>
|
|
153
|
+
</a:gs>
|
|
154
|
+
<a:gs pos="100000">
|
|
155
|
+
<a:schemeClr val="phClr">
|
|
156
|
+
<a:tint val="50000"/>
|
|
157
|
+
<a:shade val="100000"/>
|
|
158
|
+
<a:satMod val="350000"/>
|
|
159
|
+
</a:schemeClr>
|
|
160
|
+
</a:gs>
|
|
161
|
+
</a:gsLst>
|
|
162
|
+
<a:lin ang="16200000" scaled="0"/>
|
|
163
|
+
</a:gradFill>
|
|
164
|
+
</a:fillStyleLst>
|
|
165
|
+
<a:lnStyleLst>
|
|
166
|
+
<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
|
|
167
|
+
<a:solidFill>
|
|
168
|
+
<a:schemeClr val="phClr">
|
|
169
|
+
<a:shade val="95000"/>
|
|
170
|
+
<a:satMod val="105000"/>
|
|
171
|
+
</a:schemeClr>
|
|
172
|
+
</a:solidFill>
|
|
173
|
+
<a:prstDash val="solid"/>
|
|
174
|
+
</a:ln>
|
|
175
|
+
<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr">
|
|
176
|
+
<a:solidFill>
|
|
177
|
+
<a:schemeClr val="phClr"/>
|
|
178
|
+
</a:solidFill>
|
|
179
|
+
<a:prstDash val="solid"/>
|
|
180
|
+
</a:ln>
|
|
181
|
+
<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr">
|
|
182
|
+
<a:solidFill>
|
|
183
|
+
<a:schemeClr val="phClr"/>
|
|
184
|
+
</a:solidFill>
|
|
185
|
+
<a:prstDash val="solid"/>
|
|
186
|
+
</a:ln>
|
|
187
|
+
</a:lnStyleLst>
|
|
188
|
+
<a:effectStyleLst>
|
|
189
|
+
<a:effectStyle>
|
|
190
|
+
<a:effectLst>
|
|
191
|
+
<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0">
|
|
192
|
+
<a:srgbClr val="000000">
|
|
193
|
+
<a:alpha val="38000"/>
|
|
194
|
+
</a:srgbClr>
|
|
195
|
+
</a:outerShdw>
|
|
196
|
+
</a:effectLst>
|
|
197
|
+
</a:effectStyle>
|
|
198
|
+
<a:effectStyle>
|
|
199
|
+
<a:effectLst>
|
|
200
|
+
<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
|
|
201
|
+
<a:srgbClr val="000000">
|
|
202
|
+
<a:alpha val="35000"/>
|
|
203
|
+
</a:srgbClr>
|
|
204
|
+
</a:outerShdw>
|
|
205
|
+
</a:effectLst>
|
|
206
|
+
</a:effectStyle>
|
|
207
|
+
<a:effectStyle>
|
|
208
|
+
<a:effectLst>
|
|
209
|
+
<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
|
|
210
|
+
<a:srgbClr val="000000">
|
|
211
|
+
<a:alpha val="35000"/>
|
|
212
|
+
</a:srgbClr>
|
|
213
|
+
</a:outerShdw>
|
|
214
|
+
</a:effectLst>
|
|
215
|
+
<a:scene3d>
|
|
216
|
+
<a:camera prst="orthographicFront">
|
|
217
|
+
<a:rot lat="0" lon="0" rev="0"/>
|
|
218
|
+
</a:camera>
|
|
219
|
+
<a:lightRig rig="threePt" dir="t">
|
|
220
|
+
<a:rot lat="0" lon="0" rev="1200000"/>
|
|
221
|
+
</a:lightRig>
|
|
222
|
+
</a:scene3d>
|
|
223
|
+
<a:sp3d>
|
|
224
|
+
<a:bevelT w="63500" h="25400"/>
|
|
225
|
+
</a:sp3d>
|
|
226
|
+
</a:effectStyle>
|
|
227
|
+
</a:effectStyleLst>
|
|
228
|
+
<a:bgFillStyleLst>
|
|
229
|
+
<a:solidFill>
|
|
230
|
+
<a:schemeClr val="phClr"/>
|
|
231
|
+
</a:solidFill>
|
|
232
|
+
<a:gradFill rotWithShape="1">
|
|
233
|
+
<a:gsLst>
|
|
234
|
+
<a:gs pos="0">
|
|
235
|
+
<a:schemeClr val="phClr">
|
|
236
|
+
<a:tint val="40000"/>
|
|
237
|
+
<a:satMod val="350000"/>
|
|
238
|
+
</a:schemeClr>
|
|
239
|
+
</a:gs>
|
|
240
|
+
<a:gs pos="40000">
|
|
241
|
+
<a:schemeClr val="phClr">
|
|
242
|
+
<a:tint val="45000"/>
|
|
243
|
+
<a:shade val="99000"/>
|
|
244
|
+
<a:satMod val="350000"/>
|
|
245
|
+
</a:schemeClr>
|
|
246
|
+
</a:gs>
|
|
247
|
+
<a:gs pos="100000">
|
|
248
|
+
<a:schemeClr val="phClr">
|
|
249
|
+
<a:shade val="20000"/>
|
|
250
|
+
<a:satMod val="255000"/>
|
|
251
|
+
</a:schemeClr>
|
|
252
|
+
</a:gs>
|
|
253
|
+
</a:gsLst>
|
|
254
|
+
<a:path path="circle">
|
|
255
|
+
<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>
|
|
256
|
+
</a:path>
|
|
257
|
+
</a:gradFill>
|
|
258
|
+
<a:gradFill rotWithShape="1">
|
|
259
|
+
<a:gsLst>
|
|
260
|
+
<a:gs pos="0">
|
|
261
|
+
<a:schemeClr val="phClr">
|
|
262
|
+
<a:tint val="80000"/>
|
|
263
|
+
<a:satMod val="300000"/>
|
|
264
|
+
</a:schemeClr>
|
|
265
|
+
</a:gs>
|
|
266
|
+
<a:gs pos="100000">
|
|
267
|
+
<a:schemeClr val="phClr">
|
|
268
|
+
<a:shade val="30000"/>
|
|
269
|
+
<a:satMod val="200000"/>
|
|
270
|
+
</a:schemeClr>
|
|
271
|
+
</a:gs>
|
|
272
|
+
</a:gsLst>
|
|
273
|
+
<a:path path="circle">
|
|
274
|
+
<a:fillToRect l="50000" t="50000" r="50000" b="50000"/>
|
|
275
|
+
</a:path>
|
|
276
|
+
</a:gradFill>
|
|
277
|
+
</a:bgFillStyleLst>
|
|
278
|
+
</a:fmtScheme>
|
|
279
|
+
</a:themeElements>
|
|
280
|
+
<a:objectDefaults>
|
|
281
|
+
<a:spDef>
|
|
282
|
+
<a:spPr/>
|
|
283
|
+
<a:bodyPr/>
|
|
284
|
+
<a:lstStyle/>
|
|
285
|
+
<a:style>
|
|
286
|
+
<a:lnRef idx="1">
|
|
287
|
+
<a:schemeClr val="accent1"/>
|
|
288
|
+
</a:lnRef>
|
|
289
|
+
<a:fillRef idx="3">
|
|
290
|
+
<a:schemeClr val="accent1"/>
|
|
291
|
+
</a:fillRef>
|
|
292
|
+
<a:effectRef idx="2">
|
|
293
|
+
<a:schemeClr val="accent1"/>
|
|
294
|
+
</a:effectRef>
|
|
295
|
+
<a:fontRef idx="minor">
|
|
296
|
+
<a:schemeClr val="lt1"/>
|
|
297
|
+
</a:fontRef>
|
|
298
|
+
</a:style>
|
|
299
|
+
</a:spDef>
|
|
300
|
+
<a:lnDef>
|
|
301
|
+
<a:spPr/>
|
|
302
|
+
<a:bodyPr/>
|
|
303
|
+
<a:lstStyle/>
|
|
304
|
+
<a:style>
|
|
305
|
+
<a:lnRef idx="2">
|
|
306
|
+
<a:schemeClr val="accent1"/>
|
|
307
|
+
</a:lnRef>
|
|
308
|
+
<a:fillRef idx="0">
|
|
309
|
+
<a:schemeClr val="accent1"/>
|
|
310
|
+
</a:fillRef>
|
|
311
|
+
<a:effectRef idx="1">
|
|
312
|
+
<a:schemeClr val="accent1"/>
|
|
313
|
+
</a:effectRef>
|
|
314
|
+
<a:fontRef idx="minor">
|
|
315
|
+
<a:schemeClr val="tx1"/>
|
|
316
|
+
</a:fontRef>
|
|
317
|
+
</a:style>
|
|
318
|
+
</a:lnDef>
|
|
319
|
+
</a:objectDefaults>
|
|
320
|
+
<a:extraClrSchemeLst/>
|
|
321
|
+
</a:theme>
|
|
Binary file
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_pptx/text/font_metrics"
|
|
4
|
+
|
|
5
|
+
module Pptx
|
|
6
|
+
# Finds the largest whole point size at which a string still fits a box.
|
|
7
|
+
#
|
|
8
|
+
# The search is a port of python-pptx's: candidate sizes are tried largest
|
|
9
|
+
# first, and at each size the text is wrapped greedily onto as many lines as
|
|
10
|
+
# it needs and the total height compared against the box. Only the
|
|
11
|
+
# measurement underneath differs -- see {FontMetrics}.
|
|
12
|
+
class TextFitter
|
|
13
|
+
# @param text [String]
|
|
14
|
+
# @param extents [Array(Integer, Integer)] width and height in EMU
|
|
15
|
+
# @param max_size [Integer] largest point size to consider
|
|
16
|
+
# @param font_file [String] path to the font to measure with
|
|
17
|
+
# @return [Integer, nil] nil when the text does not fit at any size
|
|
18
|
+
def self.best_fit_font_size(text, extents:, max_size:, font_file:)
|
|
19
|
+
new(text, extents: extents, font_file: font_file).best_fit(max_size)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(text, extents:, font_file:)
|
|
23
|
+
@text = text
|
|
24
|
+
# Extents may arrive as Length; measurements are plain EMU integers.
|
|
25
|
+
@width, @height = extents.map(&:to_i)
|
|
26
|
+
@metrics = font_file.is_a?(FontMetrics) ? font_file : FontMetrics.open(font_file)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The largest whole size in 1..max_size that fits, or nil if none does.
|
|
30
|
+
def best_fit(max_size)
|
|
31
|
+
max_size.to_i.downto(1) { |size| return size if fits?(size) }
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Wrap +text+ at +point_size+ and report the lines it needs.
|
|
36
|
+
#
|
|
37
|
+
# Exposed because it is the interesting half of the algorithm and is worth
|
|
38
|
+
# being able to check on its own.
|
|
39
|
+
def wrap(point_size)
|
|
40
|
+
lines = []
|
|
41
|
+
remainder = @text.split
|
|
42
|
+
until remainder.empty?
|
|
43
|
+
line, remainder = break_line(remainder, point_size)
|
|
44
|
+
lines << line
|
|
45
|
+
end
|
|
46
|
+
lines
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# The height of a line is measured from "Ty" rather than from the text, so
|
|
52
|
+
# that every line is the same height whether or not it happens to contain
|
|
53
|
+
# an ascender or a descender.
|
|
54
|
+
def fits?(point_size)
|
|
55
|
+
line_height = @metrics.text_extents("Ty", point_size)[1]
|
|
56
|
+
wrap(point_size).size * line_height <= @height
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Take as many whole words as fit, but never fewer than one -- a single
|
|
60
|
+
# word wider than the box would otherwise loop forever. Such a word makes
|
|
61
|
+
# the line overflow, and the size search then rejects that size on height.
|
|
62
|
+
def break_line(words, point_size)
|
|
63
|
+
taken = 1
|
|
64
|
+
taken += 1 while taken < words.size && fits_width?(words[0, taken + 1], point_size)
|
|
65
|
+
[words[0, taken].join(" "), words[taken..]]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fits_width?(words, point_size)
|
|
69
|
+
@metrics.text_extents(words.join(" "), point_size)[0] <= @width
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pptx
|
|
4
|
+
# Text measurements read straight out of a TrueType or OpenType font file.
|
|
5
|
+
#
|
|
6
|
+
# This is what {TextFitter} measures with. python-pptx asks Pillow to
|
|
7
|
+
# rasterize the text and reports the ink bounding box of the result; there is
|
|
8
|
+
# no Ruby equivalent of that, so this reads the glyph outlines' own bounding
|
|
9
|
+
# boxes from the font instead. See PORTING.md for how far the two diverge --
|
|
10
|
+
# in short, well under a point size.
|
|
11
|
+
#
|
|
12
|
+
# Only the tables needed for measurement are parsed. Composite glyphs are not
|
|
13
|
+
# recursed into: their bounding box is stored on the glyph itself, which is
|
|
14
|
+
# all this needs.
|
|
15
|
+
class FontMetrics
|
|
16
|
+
EMU_PER_INCH = 914_400
|
|
17
|
+
POINTS_PER_INCH = 72.0
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Fonts are immutable and parsing one is not free, so they are cached.
|
|
21
|
+
def open(path)
|
|
22
|
+
real = File.realpath(path)
|
|
23
|
+
cache[real] ||= new(real)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear_cache = cache.clear
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def cache = @cache ||= {}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param path [String] a .ttf or .otf file
|
|
34
|
+
def initialize(path)
|
|
35
|
+
@path = path
|
|
36
|
+
@data = File.binread(path).b
|
|
37
|
+
@tables = read_table_directory
|
|
38
|
+
require_tables!
|
|
39
|
+
@units_per_em = u16(@tables["head"] + 18)
|
|
40
|
+
@index_to_loc_format = u16(@tables["head"] + 50)
|
|
41
|
+
@num_glyphs = u16(@tables["maxp"] + 4)
|
|
42
|
+
@num_h_metrics = u16(@tables["hhea"] + 34)
|
|
43
|
+
@cmap = read_cmap
|
|
44
|
+
@glyph_boxes = {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :path, :units_per_em
|
|
48
|
+
|
|
49
|
+
# The size of +text+ rendered at +point_size+, as [width, height] in EMU.
|
|
50
|
+
#
|
|
51
|
+
# The height is the ink height, so it depends on which glyphs are present:
|
|
52
|
+
# "Ty" is taller than "ace". That is deliberate -- it is how python-pptx
|
|
53
|
+
# measures, and {TextFitter} relies on it by always measuring "Ty".
|
|
54
|
+
def text_extents(text, point_size)
|
|
55
|
+
box = ink_box(text)
|
|
56
|
+
return [0, 0] if box.nil?
|
|
57
|
+
|
|
58
|
+
# Width is measured from the pen's starting position rather than from the
|
|
59
|
+
# first mark, which is what Pillow reports and so what python-pptx fits
|
|
60
|
+
# against: it counts a leading space, and the first glyph's left side
|
|
61
|
+
# bearing. Only ink that starts left of the origin pushes the edge out.
|
|
62
|
+
# Height is pure ink, top mark to bottom mark.
|
|
63
|
+
left = [box[0], 0].min
|
|
64
|
+
[to_emu(box[2] - left, point_size), to_emu(box[3] - box[1], point_size)]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inspect = "#<Pptx::FontMetrics #{File.basename(@path)} upem=#{@units_per_em}>"
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Laying the glyphs out along the baseline and unioning their boxes.
|
|
72
|
+
# Kerning is not applied; see PORTING.md.
|
|
73
|
+
def ink_box(text)
|
|
74
|
+
pen = 0
|
|
75
|
+
left = bottom = right = top = nil
|
|
76
|
+
text.each_char do |char|
|
|
77
|
+
gid = glyph_id(char.ord)
|
|
78
|
+
box = glyph_box(gid)
|
|
79
|
+
if box
|
|
80
|
+
left = min(left, pen + box[0])
|
|
81
|
+
bottom = min(bottom, box[1])
|
|
82
|
+
right = max(right, pen + box[2])
|
|
83
|
+
top = max(top, box[3])
|
|
84
|
+
end
|
|
85
|
+
pen += advance(gid)
|
|
86
|
+
end
|
|
87
|
+
left && [left, bottom, right, top]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def min(a, b) = a.nil? || b < a ? b : a
|
|
91
|
+
def max(a, b) = a.nil? || b > a ? b : a
|
|
92
|
+
|
|
93
|
+
# Font units are relative to the em square, so scaling by the point size
|
|
94
|
+
# gives points, which are 1/72 inch by definition.
|
|
95
|
+
def to_emu(units, point_size)
|
|
96
|
+
(units * point_size / @units_per_em.to_f / POINTS_PER_INCH * EMU_PER_INCH).to_i
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def glyph_id(codepoint) = @cmap[codepoint] || 0
|
|
100
|
+
|
|
101
|
+
# A font may store one advance for a run of trailing glyphs, in which case
|
|
102
|
+
# hmtx is truncated and the last entry stands for all of them.
|
|
103
|
+
def advance(gid)
|
|
104
|
+
u16(@tables["hmtx"] + ([gid, @num_h_metrics - 1].min * 4))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return [Array(Integer, Integer, Integer, Integer), nil] xMin, yMin,
|
|
108
|
+
# xMax, yMax in font units, or nil for a glyph with no outline
|
|
109
|
+
def glyph_box(gid)
|
|
110
|
+
return @glyph_boxes[gid] if @glyph_boxes.key?(gid)
|
|
111
|
+
|
|
112
|
+
@glyph_boxes[gid] = compute_glyph_box(gid)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def compute_glyph_box(gid)
|
|
116
|
+
return cff_glyph_box(gid) if @tables["glyf"].nil?
|
|
117
|
+
return nil if gid >= @num_glyphs
|
|
118
|
+
|
|
119
|
+
from = loca(gid)
|
|
120
|
+
# A glyph with no contours, such as the space, has a zero-length entry.
|
|
121
|
+
return nil if from == loca(gid + 1)
|
|
122
|
+
|
|
123
|
+
at = @tables["glyf"] + from
|
|
124
|
+
[s16(at + 2), s16(at + 4), s16(at + 6), s16(at + 8)]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# OpenType/CFF fonts have no glyf table, and reading their charstrings to
|
|
128
|
+
# find outlines is a disproportionate amount of machinery for a measurement
|
|
129
|
+
# that only has to be close. Fall back to the advance width and the font's
|
|
130
|
+
# overall vertical extents.
|
|
131
|
+
def cff_glyph_box(gid)
|
|
132
|
+
width = advance(gid)
|
|
133
|
+
return nil if width.zero?
|
|
134
|
+
|
|
135
|
+
ascender = s16(@tables["hhea"] + 4)
|
|
136
|
+
descender = s16(@tables["hhea"] + 6)
|
|
137
|
+
[0, descender, width, ascender]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def loca(index)
|
|
141
|
+
base = @tables["loca"]
|
|
142
|
+
@index_to_loc_format.zero? ? u16(base + (index * 2)) * 2 : u32(base + (index * 4))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def u16(at) = @data.byteslice(at, 2).unpack1("n")
|
|
146
|
+
def s16(at) = @data.byteslice(at, 2).unpack1("s>")
|
|
147
|
+
def u32(at) = @data.byteslice(at, 4).unpack1("N")
|
|
148
|
+
|
|
149
|
+
def read_table_directory
|
|
150
|
+
raise Error, "#{@path} is not a TrueType or OpenType font" unless sfnt_version?
|
|
151
|
+
|
|
152
|
+
u16(4).times.to_h do |i|
|
|
153
|
+
entry = @data.byteslice(12 + (i * 16), 16)
|
|
154
|
+
[entry.byteslice(0, 4), entry.byteslice(8, 4).unpack1("N")]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A TrueType outline font, an OpenType/CFF font, or an Apple-flavoured one.
|
|
159
|
+
def sfnt_version?
|
|
160
|
+
%W[\x00\x01\x00\x00 OTTO true].include?(@data.byteslice(0, 4))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def require_tables!
|
|
164
|
+
missing = %w[head maxp hhea hmtx cmap] - @tables.keys
|
|
165
|
+
return if missing.empty?
|
|
166
|
+
|
|
167
|
+
raise Error, "#{@path} is missing required font tables: #{missing.join(", ")}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# A Unicode BMP subtable is enough: presentation text outside the BMP falls
|
|
171
|
+
# back to glyph 0, which measures as .notdef rather than crashing.
|
|
172
|
+
def read_cmap
|
|
173
|
+
base = @tables["cmap"]
|
|
174
|
+
offset = nil
|
|
175
|
+
u16(base + 2).times do |i|
|
|
176
|
+
platform, encoding, sub = @data.byteslice(base + 4 + (i * 8), 8).unpack("nnN")
|
|
177
|
+
next unless unicode_bmp?(platform, encoding)
|
|
178
|
+
next unless u16(base + sub) == 4
|
|
179
|
+
|
|
180
|
+
offset = base + sub
|
|
181
|
+
break
|
|
182
|
+
end
|
|
183
|
+
offset.nil? ? {} : read_cmap_format4(offset)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def unicode_bmp?(platform, encoding)
|
|
187
|
+
(platform == 3 && encoding == 1) || (platform.zero? && encoding <= 4)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def read_cmap_format4(offset)
|
|
191
|
+
seg_x2 = u16(offset + 6)
|
|
192
|
+
ends = @data.byteslice(offset + 14, seg_x2).unpack("n*")
|
|
193
|
+
# A reserved padding word sits between the end and start arrays.
|
|
194
|
+
starts = @data.byteslice(offset + 16 + seg_x2, seg_x2).unpack("n*")
|
|
195
|
+
deltas = @data.byteslice(offset + 16 + (seg_x2 * 2), seg_x2).unpack("n*")
|
|
196
|
+
ranges_at = offset + 16 + (seg_x2 * 3)
|
|
197
|
+
ranges = @data.byteslice(ranges_at, seg_x2).unpack("n*")
|
|
198
|
+
|
|
199
|
+
map = {}
|
|
200
|
+
(seg_x2 / 2).times do |i|
|
|
201
|
+
(starts[i]..ends[i]).each do |code|
|
|
202
|
+
# The final segment is a sentinel ending at 0xFFFF.
|
|
203
|
+
next if code == 0xFFFF
|
|
204
|
+
|
|
205
|
+
gid = segment_glyph(i, code, starts, deltas, ranges, ranges_at)
|
|
206
|
+
map[code] = gid if gid
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
map
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# idRangeOffset is a byte offset from its own slot in the array, which is
|
|
213
|
+
# why the segment index has to be folded back in.
|
|
214
|
+
def segment_glyph(index, code, starts, deltas, ranges, ranges_at)
|
|
215
|
+
return (code + deltas[index]) % 65_536 if ranges[index].zero?
|
|
216
|
+
|
|
217
|
+
gid = u16(ranges_at + (index * 2) + ranges[index] + ((code - starts[index]) * 2))
|
|
218
|
+
gid.zero? ? nil : (gid + deltas[index]) % 65_536
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|