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,430 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_pptx/pattern_matching"
|
|
4
|
+
|
|
5
|
+
require "ruby_pptx/element_proxy"
|
|
6
|
+
require "ruby_pptx/shapes/shape_tree"
|
|
7
|
+
require "ruby_pptx/dml/fill"
|
|
8
|
+
|
|
9
|
+
module Pptx
|
|
10
|
+
# Behaviour common to slides, layouts, masters and notes slides.
|
|
11
|
+
class BaseSlide < PartElementProxy
|
|
12
|
+
# The internal name of this slide; an empty string when unnamed.
|
|
13
|
+
def name = @element.cSld.name
|
|
14
|
+
|
|
15
|
+
def name=(value)
|
|
16
|
+
@element.cSld.name = value.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def shape_tree = @element.spTree
|
|
20
|
+
|
|
21
|
+
# The slide background.
|
|
22
|
+
#
|
|
23
|
+
# Note that merely reading this is destructive: a background given by a
|
|
24
|
+
# style reference, or inherited, is replaced with an explicit no-fill so
|
|
25
|
+
# there is something to interrogate. python-pptx behaves the same way.
|
|
26
|
+
def background = @background ||= Background.new(@element.cSld)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The background of a slide, layout or master.
|
|
30
|
+
class Background < ElementProxy
|
|
31
|
+
def fill = @fill ||= FillFormat.from_fill_parent(@element.get_or_add_bgPr)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Common to slide masters and the notes master.
|
|
35
|
+
class BaseMaster < BaseSlide
|
|
36
|
+
# The shapes on this master.
|
|
37
|
+
def shapes = @shapes ||= MasterShapes.new(@element.spTree, self)
|
|
38
|
+
|
|
39
|
+
# The placeholders on this master, in `idx` order.
|
|
40
|
+
def placeholders = @placeholders ||= MasterPlaceholders.new(@element.spTree, self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The master every notes page in a presentation inherits from.
|
|
44
|
+
#
|
|
45
|
+
# There is at most one per presentation, and it is created on first use --
|
|
46
|
+
# the first time a slide is given speaker notes, or {Presentation#notes_master}
|
|
47
|
+
# is asked for.
|
|
48
|
+
class NotesMaster < BaseMaster
|
|
49
|
+
def inspect = "#<Pptx::NotesMaster #{part.partname}>"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The speaker notes page belonging to one slide.
|
|
53
|
+
#
|
|
54
|
+
# slide.notes_slide.notes_text_frame.text = "Mention the Q3 numbers"
|
|
55
|
+
# slide.notes = "Mention the Q3 numbers" # the same, shorter
|
|
56
|
+
class NotesSlide < BaseSlide
|
|
57
|
+
# Placeholder types copied from the notes master onto a new notes slide.
|
|
58
|
+
# The header, date and footer stay on the master, as they do in PowerPoint.
|
|
59
|
+
CLONEABLE = %i[SLIDE_IMAGE BODY SLIDE_NUMBER].freeze
|
|
60
|
+
|
|
61
|
+
def shapes = @shapes ||= NotesSlideShapes.new(@element.spTree, self)
|
|
62
|
+
|
|
63
|
+
def placeholders = @placeholders ||= NotesSlidePlaceholders.new(@element.spTree, self)
|
|
64
|
+
|
|
65
|
+
# The body placeholder that holds the notes text, or nil if it was removed.
|
|
66
|
+
def notes_placeholder = placeholders.find { |ph| ph.placeholder_format.type.name == :BODY }
|
|
67
|
+
|
|
68
|
+
# The text frame of the notes placeholder, or nil when there is none.
|
|
69
|
+
#
|
|
70
|
+
# @return [TextFrame, nil]
|
|
71
|
+
def notes_text_frame = notes_placeholder&.text_frame
|
|
72
|
+
|
|
73
|
+
# Copy the cloneable placeholders from +notes_master+, keeping their order.
|
|
74
|
+
# Called once, when the notes slide is created.
|
|
75
|
+
def clone_master_placeholders(notes_master)
|
|
76
|
+
notes_master.shapes.each do |shape|
|
|
77
|
+
next unless shape.placeholder?
|
|
78
|
+
next unless CLONEABLE.include?(shape.element.ph_type.name)
|
|
79
|
+
|
|
80
|
+
shapes.clone_placeholder(shape)
|
|
81
|
+
end
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def inspect = "#<Pptx::NotesSlide #{part.partname}>"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# One slide in a presentation.
|
|
89
|
+
class Slide < BaseSlide
|
|
90
|
+
include PatternMatching
|
|
91
|
+
|
|
92
|
+
pattern_keys :slide_id, :name, :layout, :shapes, :placeholders
|
|
93
|
+
|
|
94
|
+
# The id that identifies this slide within the presentation, stable across
|
|
95
|
+
# reordering.
|
|
96
|
+
def slide_id = part.slide_id
|
|
97
|
+
|
|
98
|
+
# The layout this slide takes its appearance from.
|
|
99
|
+
def layout = part.slide_layout
|
|
100
|
+
|
|
101
|
+
# True when this slide inherits the master's background.
|
|
102
|
+
def follows_master_background? = @element.bg.nil?
|
|
103
|
+
|
|
104
|
+
def notes_slide? = part.notes_slide?
|
|
105
|
+
|
|
106
|
+
# This slide's speaker notes page, created on first use.
|
|
107
|
+
#
|
|
108
|
+
# Use {#notes_slide?} to ask whether there is one without creating it, or
|
|
109
|
+
# {#notes} and {#notes=} for the common case of just the text.
|
|
110
|
+
#
|
|
111
|
+
# @return [NotesSlide]
|
|
112
|
+
def notes_slide = part.notes_slide
|
|
113
|
+
|
|
114
|
+
# The speaker notes as plain text, or nil when the slide has none.
|
|
115
|
+
#
|
|
116
|
+
# Reading never creates a notes slide; that would add parts to a file
|
|
117
|
+
# merely by looking at it.
|
|
118
|
+
def notes = notes_slide? ? notes_slide.notes_text_frame&.text : nil
|
|
119
|
+
|
|
120
|
+
# Replace the speaker notes, creating the notes slide if need be.
|
|
121
|
+
def notes=(text)
|
|
122
|
+
frame = notes_slide.notes_text_frame
|
|
123
|
+
raise Error, "this slide's notes page has no notes placeholder" if frame.nil?
|
|
124
|
+
|
|
125
|
+
frame.text = text
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The shapes on this slide, in z-order.
|
|
129
|
+
def shapes = @shapes ||= SlideShapes.new(@element.spTree, self)
|
|
130
|
+
|
|
131
|
+
# The placeholders on this slide, keyed by `idx`.
|
|
132
|
+
def placeholders = @placeholders ||= SlidePlaceholders.new(@element.spTree, self)
|
|
133
|
+
|
|
134
|
+
def inspect = "#<Pptx::Slide id=#{slide_id} #{part.partname}>"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The slides of a presentation, in order.
|
|
138
|
+
#
|
|
139
|
+
# Enumerable, and indexable by position.
|
|
140
|
+
class Slides < ParentedElementProxy
|
|
141
|
+
include DeconstructToArray
|
|
142
|
+
|
|
143
|
+
include Enumerable
|
|
144
|
+
|
|
145
|
+
def initialize(sld_id_list, presentation)
|
|
146
|
+
super
|
|
147
|
+
@sld_id_list = sld_id_list
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def each
|
|
151
|
+
return enum_for(:each) { size } unless block_given?
|
|
152
|
+
|
|
153
|
+
@sld_id_list.sldId_list.each { |sld_id| yield part.related_slide(sld_id.rId) }
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def size = @sld_id_list.size
|
|
158
|
+
alias length size
|
|
159
|
+
|
|
160
|
+
def empty? = size.zero?
|
|
161
|
+
|
|
162
|
+
# Indexed access, supporting a negative index as Ruby arrays do.
|
|
163
|
+
#
|
|
164
|
+
# @return [Slide, nil] nil when +index+ is out of range
|
|
165
|
+
def [](index, length = nil)
|
|
166
|
+
slice_members(@sld_id_list.sldId_list, index, length) { |entry| part.related_slide(entry.rId) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# As {#[]}, but raises rather than returning nil.
|
|
170
|
+
def fetch(index)
|
|
171
|
+
self[index] || raise(IndexError, "slide index #{index} out of range")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Enumerable gives us #first but not #last.
|
|
175
|
+
def last = self[-1]
|
|
176
|
+
|
|
177
|
+
# The slide with the given slide id.
|
|
178
|
+
#
|
|
179
|
+
# @return [Slide, nil]
|
|
180
|
+
def by_id(slide_id) = part.slide_by_id(slide_id)
|
|
181
|
+
|
|
182
|
+
# Add a slide inheriting from +slide_layout+, and return it.
|
|
183
|
+
#
|
|
184
|
+
# The layout's placeholders are copied onto the new slide, preserving
|
|
185
|
+
# z-order; latent ones are left to the layout.
|
|
186
|
+
def add(slide_layout)
|
|
187
|
+
r_id, slide = part.add_slide(slide_layout)
|
|
188
|
+
slide.shapes.clone_layout_placeholders(slide_layout)
|
|
189
|
+
@sld_id_list.add_slide_id(r_id)
|
|
190
|
+
slide
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# The zero-based position of +slide+.
|
|
194
|
+
#
|
|
195
|
+
# @return [Integer, nil] nil when the slide is not in this collection
|
|
196
|
+
def index(slide) = each_with_index.find { |s, _| s == slide }&.last
|
|
197
|
+
|
|
198
|
+
def inspect = "#<Pptx::Slides size=#{size}>"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# A slide layout: the arrangement a slide inherits from.
|
|
202
|
+
class SlideLayout < BaseSlide
|
|
203
|
+
include PatternMatching
|
|
204
|
+
|
|
205
|
+
pattern_keys :name, :type, :slide_master, :shapes, :placeholders
|
|
206
|
+
|
|
207
|
+
# Placeholders PowerPoint renders from the layout rather than copying onto
|
|
208
|
+
# each slide, so they are not cloned when a slide is created.
|
|
209
|
+
LATENT_PLACEHOLDER_TYPES = [
|
|
210
|
+
Enum::PP_PLACEHOLDER::DATE,
|
|
211
|
+
Enum::PP_PLACEHOLDER::FOOTER,
|
|
212
|
+
Enum::PP_PLACEHOLDER::SLIDE_NUMBER
|
|
213
|
+
].freeze
|
|
214
|
+
|
|
215
|
+
# The shapes on this layout.
|
|
216
|
+
def shapes = @shapes ||= LayoutShapes.new(@element.spTree, self)
|
|
217
|
+
|
|
218
|
+
# The placeholders on this layout, in `idx` order.
|
|
219
|
+
def placeholders = @placeholders ||= LayoutPlaceholders.new(@element.spTree, self)
|
|
220
|
+
|
|
221
|
+
# The placeholders a new slide based on this layout should receive.
|
|
222
|
+
def cloneable_placeholders
|
|
223
|
+
placeholders.reject { |ph| LATENT_PLACEHOLDER_TYPES.include?(ph.element.ph_type) }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# The master this layout inherits from.
|
|
227
|
+
def slide_master = part.slide_master
|
|
228
|
+
|
|
229
|
+
# The kind of layout this is, e.g. "title" or "obj". PowerPoint uses it to
|
|
230
|
+
# decide which layout to offer for a given command.
|
|
231
|
+
def type = @element.type
|
|
232
|
+
|
|
233
|
+
def type=(value)
|
|
234
|
+
@element.type = value&.to_s
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The slides based on this layout.
|
|
238
|
+
def used_by_slides
|
|
239
|
+
part.package.presentation_part.presentation.slides.select { |s| s.layout == self }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def inspect = "#<Pptx::SlideLayout #{name.inspect}>"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# The layouts belonging to one slide master.
|
|
246
|
+
class SlideLayouts < ParentedElementProxy
|
|
247
|
+
include DeconstructToArray
|
|
248
|
+
|
|
249
|
+
include Enumerable
|
|
250
|
+
|
|
251
|
+
def initialize(sld_layout_id_list, slide_master)
|
|
252
|
+
super
|
|
253
|
+
@sld_layout_id_list = sld_layout_id_list
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def each
|
|
257
|
+
return enum_for(:each) { size } unless block_given?
|
|
258
|
+
|
|
259
|
+
@sld_layout_id_list.sldLayoutId_list.each do |entry|
|
|
260
|
+
yield part.related_slide_layout(entry.rId)
|
|
261
|
+
end
|
|
262
|
+
self
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def size = @sld_layout_id_list.size
|
|
266
|
+
alias length size
|
|
267
|
+
|
|
268
|
+
# Indexed by position, or looked up by layout name.
|
|
269
|
+
#
|
|
270
|
+
# layouts[1]
|
|
271
|
+
# layouts["Title and Content"]
|
|
272
|
+
def [](key, length = nil)
|
|
273
|
+
return by_name(key) if key.is_a?(String)
|
|
274
|
+
|
|
275
|
+
slice_members(@sld_layout_id_list.sldLayoutId_list, key, length) do |entry|
|
|
276
|
+
part.related_slide_layout(entry.rId)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def fetch(key)
|
|
281
|
+
self[key] || raise(IndexError, "no slide layout #{key.inspect}")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# @return [SlideLayout, nil]
|
|
285
|
+
def by_name(name) = find { |layout| layout.name == name }
|
|
286
|
+
|
|
287
|
+
# @return [Integer, nil]
|
|
288
|
+
def index(slide_layout) = each_with_index.find { |l, _| l == slide_layout }&.last
|
|
289
|
+
|
|
290
|
+
# Remove +slide_layout+ from this collection and from the package.
|
|
291
|
+
#
|
|
292
|
+
# @raise [Error] when one or more slides still use the layout
|
|
293
|
+
def delete(slide_layout)
|
|
294
|
+
unless slide_layout.used_by_slides.empty?
|
|
295
|
+
raise Error, "cannot remove a slide layout in use by one or more slides"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
target_index = index(slide_layout) or
|
|
299
|
+
raise NotFoundError, "layout is not in this collection"
|
|
300
|
+
|
|
301
|
+
entry = @sld_layout_id_list.sldLayoutId_list[target_index]
|
|
302
|
+
r_id = entry.rId
|
|
303
|
+
# Removing the id stops the layout appearing; dropping the relationship
|
|
304
|
+
# is what actually removes it from the package, along with anything only
|
|
305
|
+
# it referred to.
|
|
306
|
+
@sld_layout_id_list.remove(entry)
|
|
307
|
+
slide_layout.slide_master.part.drop_rel(r_id)
|
|
308
|
+
slide_layout
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Add a layout to this master.
|
|
312
|
+
#
|
|
313
|
+
# layout = master.slide_layouts.add("Section Header", type: "secHead")
|
|
314
|
+
# layout.placeholders.add(:title, at: [x, y], size: [cx, cy])
|
|
315
|
+
#
|
|
316
|
+
# The layout starts empty; placeholders are added to it explicitly, which
|
|
317
|
+
# is what decides what a slide using it inherits.
|
|
318
|
+
#
|
|
319
|
+
# @param name [String] the name PowerPoint shows in the layout gallery
|
|
320
|
+
# @param type [String, Symbol, nil] one of the 36 ST_SlideLayoutType values
|
|
321
|
+
# @return [SlideLayout]
|
|
322
|
+
def add(name, type: "obj")
|
|
323
|
+
master_part = parent.part
|
|
324
|
+
layout_part = Parts::SlideLayoutPart.new_layout(master_part.package, master_part, name,
|
|
325
|
+
type&.to_s)
|
|
326
|
+
master_part.add_slide_layout(layout_part)
|
|
327
|
+
layout = layout_part.slide_layout
|
|
328
|
+
yield layout if block_given?
|
|
329
|
+
layout
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def inspect = "#<Pptx::SlideLayouts size=#{size}>"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# A slide master.
|
|
336
|
+
class SlideMaster < BaseMaster
|
|
337
|
+
# The layouts that inherit from this master.
|
|
338
|
+
def slide_layouts
|
|
339
|
+
@slide_layouts ||= SlideLayouts.new(@element.get_or_add_sldLayoutIdLst, self)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# The colours and fonts this master draws from.
|
|
343
|
+
#
|
|
344
|
+
# @return [Pptx::Theme]
|
|
345
|
+
def theme = part.theme_part.theme
|
|
346
|
+
|
|
347
|
+
# The placeholders every layout and slide under this master inherits from.
|
|
348
|
+
#
|
|
349
|
+
# @return [MasterPlaceholders]
|
|
350
|
+
def placeholders = @placeholders ||= MasterPlaceholders.new(@element.spTree, self)
|
|
351
|
+
|
|
352
|
+
def inspect = "#<Pptx::SlideMaster #{part.partname}>"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# The slide masters of a presentation.
|
|
356
|
+
class SlideMasters < ParentedElementProxy
|
|
357
|
+
include DeconstructToArray
|
|
358
|
+
|
|
359
|
+
include Enumerable
|
|
360
|
+
|
|
361
|
+
def initialize(sld_master_id_list, presentation)
|
|
362
|
+
super
|
|
363
|
+
@sld_master_id_list = sld_master_id_list
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def each
|
|
367
|
+
return enum_for(:each) { size } unless block_given?
|
|
368
|
+
|
|
369
|
+
@sld_master_id_list.sldMasterId_list.each do |entry|
|
|
370
|
+
yield part.related_slide_master(entry.rId)
|
|
371
|
+
end
|
|
372
|
+
self
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def size = @sld_master_id_list.size
|
|
376
|
+
alias length size
|
|
377
|
+
|
|
378
|
+
def [](index, length = nil)
|
|
379
|
+
slice_members(@sld_master_id_list.sldMasterId_list, index, length) do |entry|
|
|
380
|
+
part.related_slide_master(entry.rId)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Add a slide master to the presentation, with its own theme.
|
|
385
|
+
#
|
|
386
|
+
# master = deck.slide_masters.add(name: "Corporate")
|
|
387
|
+
# master.theme.colors.update(accent1: "1F497D")
|
|
388
|
+
# master.slide_layouts.add("Title Slide", type: "title")
|
|
389
|
+
#
|
|
390
|
+
# The master starts with the five placeholders PowerPoint expects of one --
|
|
391
|
+
# title, body, date, footer and slide number -- positioned proportionally
|
|
392
|
+
# to this presentation's slide size. Pass <tt>placeholders: :none</tt> to
|
|
393
|
+
# get a bare master and place them yourself.
|
|
394
|
+
#
|
|
395
|
+
# @param name [String] names both the master's theme and its schemes
|
|
396
|
+
# @param placeholders [Symbol] :standard or :none
|
|
397
|
+
# @return [SlideMaster]
|
|
398
|
+
def add(name: "Office Theme", placeholders: :standard)
|
|
399
|
+
unless %i[standard none].include?(placeholders)
|
|
400
|
+
raise ArgumentError, "placeholders must be :standard or :none, got #{placeholders.inspect}"
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
master_part = Parts::SlideMasterPart.new_master(part.package, name: name)
|
|
404
|
+
register(master_part)
|
|
405
|
+
master = master_part.slide_master
|
|
406
|
+
master.placeholders.add_standard_set(slide_size) if placeholders == :standard
|
|
407
|
+
yield master if block_given?
|
|
408
|
+
master
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def inspect = "#<Pptx::SlideMasters size=#{size}>"
|
|
412
|
+
|
|
413
|
+
private
|
|
414
|
+
|
|
415
|
+
# Master ids are numbered from 2147483648 upwards, above the slide-id range.
|
|
416
|
+
def register(master_part)
|
|
417
|
+
r_id = part.relate_to(master_part, Opc::RELATIONSHIP_TYPE::SLIDE_MASTER)
|
|
418
|
+
entry = @sld_master_id_list.add_sldMasterId
|
|
419
|
+
entry.rId = r_id
|
|
420
|
+
used = @sld_master_id_list.sldMasterId_list.filter_map(&:id)
|
|
421
|
+
entry.id = used.empty? ? 2_147_483_648 : used.max + 1
|
|
422
|
+
r_id
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def slide_size
|
|
426
|
+
size_element = part.element.sldSz
|
|
427
|
+
[size_element.cx, size_element.cy]
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|