pptx 0.0.1
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/README.md +57 -0
- data/lib/pptx.rb +24 -0
- data/lib/pptx/base.pptx/[Content_Types].xml +30 -0
- data/lib/pptx/base.pptx/docProps/app.xml +41 -0
- data/lib/pptx/base.pptx/docProps/core.xml +13 -0
- data/lib/pptx/base.pptx/docProps/thumbnail.jpeg +0 -0
- data/lib/pptx/base.pptx/ppt/_rels/presentation.xml.rels +9 -0
- data/lib/pptx/base.pptx/ppt/presProps.xml +11 -0
- data/lib/pptx/base.pptx/ppt/presentation.xml +105 -0
- data/lib/pptx/base.pptx/ppt/printerSettings/printerSettings1.bin +0 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels +4 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout1.xml +240 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout10.xml +168 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout11.xml +178 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout2.xml +168 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout3.xml +244 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout4.xml +286 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout5.xml +420 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout6.xml +116 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout7.xml +93 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout8.xml +275 -0
- data/lib/pptx/base.pptx/ppt/slideLayouts/slideLayout9.xml +251 -0
- data/lib/pptx/base.pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels +15 -0
- data/lib/pptx/base.pptx/ppt/slideMasters/slideMaster1.xml +508 -0
- data/lib/pptx/base.pptx/ppt/tableStyles.xml +2 -0
- data/lib/pptx/base.pptx/ppt/theme/theme1.xml +318 -0
- data/lib/pptx/base.pptx/ppt/viewProps.xml +32 -0
- data/lib/pptx/generate_sample.rb +57 -0
- data/lib/pptx/opc.rb +14 -0
- data/lib/pptx/opc/base_part.rb +36 -0
- data/lib/pptx/opc/binary_part.rb +20 -0
- data/lib/pptx/opc/content_types.rb +18 -0
- data/lib/pptx/opc/file_part.rb +28 -0
- data/lib/pptx/opc/package.rb +115 -0
- data/lib/pptx/opc/package_streamer.rb +48 -0
- data/lib/pptx/opc/part.rb +27 -0
- data/lib/pptx/opc/relationships.rb +32 -0
- data/lib/pptx/opc/s3_object_part.rb +25 -0
- data/lib/pptx/presentation.rb +33 -0
- data/lib/pptx/shapes.rb +10 -0
- data/lib/pptx/shapes/filled_rectangle.rb +32 -0
- data/lib/pptx/shapes/picture.rb +40 -0
- data/lib/pptx/shapes/shape.rb +46 -0
- data/lib/pptx/shapes/slide_number.rb +39 -0
- data/lib/pptx/shapes/textbox.rb +95 -0
- data/lib/pptx/slide.rb +79 -0
- data/lib/pptx/version.rb +3 -0
- metadata +156 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module OPC
|
|
3
|
+
class BinaryPart < BasePart
|
|
4
|
+
def initialize(package, original_name)
|
|
5
|
+
name = "ppt/media/binary-#{SecureRandom.hex(10)}#{File.extname(original_name)}"
|
|
6
|
+
super(package, name)
|
|
7
|
+
@original_name = original_name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def doc
|
|
11
|
+
throw "Binary parts can't be parsed as XML"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def marshal
|
|
15
|
+
throw 'Implement me!'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module OPC
|
|
3
|
+
class ContentTypes < BasePart
|
|
4
|
+
NS = 'http://schemas.openxmlformats.org/package/2006/content-types'
|
|
5
|
+
|
|
6
|
+
def set_override(part_name, value)
|
|
7
|
+
type_list = doc.xpath('c:Types', c: NS).first
|
|
8
|
+
override = type_list.xpath("./xmlns:Override[@PartName='#{part_name}']").first
|
|
9
|
+
unless override
|
|
10
|
+
override = Nokogiri::XML::Node.new('Override', doc)
|
|
11
|
+
override['PartName'] = '/' + part_name
|
|
12
|
+
type_list.add_child(override)
|
|
13
|
+
end
|
|
14
|
+
override['ContentType'] = value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module OPC
|
|
3
|
+
class FilePart < BinaryPart
|
|
4
|
+
def initialize(package, file)
|
|
5
|
+
super(package, File.basename(file))
|
|
6
|
+
@file = file
|
|
7
|
+
@chunk_size = 16 * 1024
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def marshal
|
|
11
|
+
IO.read(@file)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size
|
|
15
|
+
File.size(@file)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def stream(out)
|
|
19
|
+
File.open(@file, 'r') do |file|
|
|
20
|
+
while chunk = file.read(@chunk_size)
|
|
21
|
+
puts "Streaming file: #{chunk && chunk.bytesize} bytes"
|
|
22
|
+
out << chunk
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
require 'zip'
|
|
3
|
+
|
|
4
|
+
module PPTX
|
|
5
|
+
module OPC
|
|
6
|
+
# Package allows the following:
|
|
7
|
+
# * Create new package based on an existing PPTX file
|
|
8
|
+
# (extracted ZIP for better version control)
|
|
9
|
+
# * Replace certain files with generated parts
|
|
10
|
+
class Package
|
|
11
|
+
# Create a new package using the directory strucuture base
|
|
12
|
+
def initialize(base = nil)
|
|
13
|
+
base ||= base || File.join(File.dirname(__FILE__), '..', 'base.pptx')
|
|
14
|
+
@base = File.absolute_path(base)
|
|
15
|
+
@parts = Hash[default_part_names.map {|fn| [fn, nil]}]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def base
|
|
19
|
+
@base
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def content_types
|
|
23
|
+
@content_types ||= ContentTypes.new(self, '[Content_Types].xml')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def has_part?(name)
|
|
27
|
+
@parts.has_key?(name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def part(name)
|
|
31
|
+
@parts[name]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns a Hash of name -> Part
|
|
35
|
+
# Do not modify the hash directly, but use set_part.
|
|
36
|
+
def parts
|
|
37
|
+
@parts
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def template_part(name)
|
|
41
|
+
file = File.join(@base, name)
|
|
42
|
+
IO.read(file) if File.exist? file
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def marshal_part(name)
|
|
46
|
+
part = @parts[name]
|
|
47
|
+
if part
|
|
48
|
+
if part.is_a? BasePart
|
|
49
|
+
part.marshal
|
|
50
|
+
elsif part.instance_of? String
|
|
51
|
+
# TODO wrap binary parts, probably as references when implementing streaming
|
|
52
|
+
part
|
|
53
|
+
else
|
|
54
|
+
raise "marshal_part: Unknown part type: '#{p.class}' for part '#{name}'"
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
template_part name
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Used e.g. for creating slides with proper numbering (i.e. slide1.xml, slide2.xml)
|
|
62
|
+
def next_part_with_prefix(prefix, suffix)
|
|
63
|
+
num = @parts.select { |n, _| n.start_with?(prefix) && n.end_with?(suffix) }
|
|
64
|
+
.map { |n, _| n[prefix.length..-1][0..-(suffix.size + 1)].to_i }
|
|
65
|
+
.max
|
|
66
|
+
num = (num || 0) + 1
|
|
67
|
+
"#{prefix}#{num}#{suffix}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def set_part(name, obj, content_type = nil)
|
|
71
|
+
@parts[name] = obj
|
|
72
|
+
if content_type
|
|
73
|
+
content_types.set_override(name, content_type)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def presentation
|
|
78
|
+
@presentation ||= PPTX::Presentation.new(self, 'ppt/presentation.xml')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Stream a ZIP file while it's being generated
|
|
82
|
+
# The returned PackageStreamer instance has an each method that takes a block, which
|
|
83
|
+
# is called multiple times with chunks of data.
|
|
84
|
+
# When adding BinaryParts (e.g. S3ObjectPart or FilePart), these are also
|
|
85
|
+
# streamed into the ZIP file, so you can e.g. stream objects directly from S3
|
|
86
|
+
# to the client, encoded in the ZIP file.
|
|
87
|
+
# Use this e.g. in a Rails controller as `self.response_body`.
|
|
88
|
+
def stream_zip(ignore_part_exceptions=false)
|
|
89
|
+
PackageStreamer.new(self, ignore_part_exceptions)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_zip
|
|
93
|
+
buffer = ::StringIO.new('')
|
|
94
|
+
|
|
95
|
+
Zip::OutputStream.write_buffer(buffer) do |out|
|
|
96
|
+
@parts.each do |name, _|
|
|
97
|
+
out.put_next_entry name
|
|
98
|
+
out.write marshal_part(name)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
buffer.string
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
def default_part_names
|
|
107
|
+
@default_part_names ||= Dir.glob(File.join(@base, '**/*'), File::FNM_DOTMATCH)
|
|
108
|
+
.reject {|fn| File.directory?(fn) }
|
|
109
|
+
.reject {|fn| fn.end_with?('.DS_Store')}
|
|
110
|
+
.map {|fn| fn[(@base.length + 1)..-1]}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module OPC
|
|
3
|
+
class PackageStreamer
|
|
4
|
+
# Use ignore_part_exceptions to ignore exceptions while marshalling or streaming
|
|
5
|
+
# a part. Useful to ignore missing files on S3.
|
|
6
|
+
def initialize(package, ignore_part_exceptions=false)
|
|
7
|
+
@package = package
|
|
8
|
+
@ignore_part_exceptions = ignore_part_exceptions
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# This method uses parts of zipline to implement streaming.
|
|
12
|
+
#
|
|
13
|
+
# This is how Zipline usually works:
|
|
14
|
+
# * Rails calls response_body.each
|
|
15
|
+
# * response_body is Zipline::ZipGenerator, it creates a Zipline::FakeStream with the
|
|
16
|
+
# block given to each
|
|
17
|
+
# * The FakeStream is used as output for ZipLine::OutputStream (which inherits
|
|
18
|
+
# from Zip::OutputStream)
|
|
19
|
+
# * ZipGenerator goes through files and calls write_file on the OutputStream for each file
|
|
20
|
+
# * OutputStream writes the data as String to FakeStream via the << method
|
|
21
|
+
# * FakeStream yields the String to the block given by Rails
|
|
22
|
+
#
|
|
23
|
+
# This method uses Zipline's FakeStream and OutputStream, but writes the entries
|
|
24
|
+
# to the zip file on it's own, so we can write data for a file in multiple steps
|
|
25
|
+
# and don't have to rely on Zipline's crazy logic (e.g. on what can be streamed and
|
|
26
|
+
# what not - only IO and StringIO). Basically, this replaces ZipGenerator.
|
|
27
|
+
def each(&block)
|
|
28
|
+
fake_stream = Zipline::FakeStream.new(&block)
|
|
29
|
+
Zipline::OutputStream.open(fake_stream) do |zip|
|
|
30
|
+
@package.parts.each do |name, part|
|
|
31
|
+
begin
|
|
32
|
+
if part.respond_to?(:stream) && part.respond_to?(:size)
|
|
33
|
+
zip.put_next_entry name, part.size
|
|
34
|
+
part.stream zip
|
|
35
|
+
else
|
|
36
|
+
data = @package.marshal_part(name)
|
|
37
|
+
zip.put_next_entry name, data.size
|
|
38
|
+
zip << data
|
|
39
|
+
end
|
|
40
|
+
rescue => e
|
|
41
|
+
raise e unless @ignore_part_exceptions
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module PPTX
|
|
4
|
+
module OPC
|
|
5
|
+
class Part < BasePart
|
|
6
|
+
def initialize(package, part_name)
|
|
7
|
+
super(package, part_name)
|
|
8
|
+
@relationships = Relationships.new(package, relationship_part_name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def relative_part_name(name)
|
|
12
|
+
source = Pathname.new(File.dirname(part_name))
|
|
13
|
+
target = Pathname.new(name)
|
|
14
|
+
target.relative_path_from(source).to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def relationships
|
|
18
|
+
@relationships
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def relationship_part_name(part_name = nil)
|
|
22
|
+
part_name ||= @part_name
|
|
23
|
+
File.join(File.dirname(part_name), '_rels', File.basename(part_name) + '.rels')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module PPTX
|
|
4
|
+
module OPC
|
|
5
|
+
class Relationships < BasePart
|
|
6
|
+
NS = 'http://schemas.openxmlformats.org/package/2006/relationships'
|
|
7
|
+
|
|
8
|
+
def base_xml
|
|
9
|
+
'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
10
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
11
|
+
</Relationships>
|
|
12
|
+
'''
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add(relative_part_name, type)
|
|
16
|
+
ref_id = "rId#{SecureRandom.hex(10)}"
|
|
17
|
+
|
|
18
|
+
relationship = Nokogiri::XML::Node.new('Relationship', doc)
|
|
19
|
+
relationship['Id'] = ref_id
|
|
20
|
+
relationship['Target'] = relative_part_name
|
|
21
|
+
relationship['Type'] = type
|
|
22
|
+
list_xml.add_child(relationship)
|
|
23
|
+
|
|
24
|
+
ref_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def list_xml
|
|
28
|
+
@list_xml ||= doc.xpath('r:Relationships', r: NS).first
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module OPC
|
|
3
|
+
class S3ObjectPart < BinaryPart
|
|
4
|
+
def initialize(package, s3obj, size)
|
|
5
|
+
super(package, File.basename(s3obj.key))
|
|
6
|
+
@object = s3obj
|
|
7
|
+
@size = size
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def marshal
|
|
11
|
+
@object.read
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size
|
|
15
|
+
@size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def stream(out)
|
|
19
|
+
@object.read do |chunk|
|
|
20
|
+
out << chunk
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
class Presentation < OPC::Part
|
|
3
|
+
NS = 'http://schemas.openxmlformats.org/presentationml/2006/main'
|
|
4
|
+
MIN_SLIDE_ID = 256
|
|
5
|
+
|
|
6
|
+
def initialize(package, part_name)
|
|
7
|
+
super(package, part_name)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_slide(slide)
|
|
11
|
+
# TODO remove me - remove existing slide from template
|
|
12
|
+
# slide_list_xml.xpath('./p:sldId').each do |slide|
|
|
13
|
+
# slide_list.children.delete(slide)
|
|
14
|
+
# end
|
|
15
|
+
ref_id = relationships.add(relative_part_name(slide.part_name), RELTYPE_SLIDE)
|
|
16
|
+
|
|
17
|
+
# add slide to sldIdList in presentation
|
|
18
|
+
slide_id = Nokogiri::XML::Node.new('p:sldId', doc)
|
|
19
|
+
slide_id['id'] = next_slide_id
|
|
20
|
+
slide_id['r:id'] = ref_id
|
|
21
|
+
slide_list_xml.add_child(slide_id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def next_slide_id
|
|
25
|
+
ids = slide_list_xml.xpath('./p:sldId').map { |sid| sid['id'].to_i }
|
|
26
|
+
return (ids.max || MIN_SLIDE_ID - 1) + 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def slide_list_xml
|
|
30
|
+
@slide_list_xml ||= doc.xpath('/p:presentation/p:sldIdLst', p: NS).first
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/pptx/shapes.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module Shapes
|
|
3
|
+
class FilledRectangle < Shape
|
|
4
|
+
def initialize(transform, color)
|
|
5
|
+
super(transform)
|
|
6
|
+
@color = color
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def base_xml
|
|
10
|
+
# TODO replace cNvPr descr, id and name
|
|
11
|
+
"""
|
|
12
|
+
<p:sp xmlns:a='http://schemas.openxmlformats.org/drawingml/2006/main'
|
|
13
|
+
xmlns:p='http://schemas.openxmlformats.org/presentationml/2006/main'>
|
|
14
|
+
<p:nvSpPr>
|
|
15
|
+
<p:cNvPr id='2' name='Rectangle'/>
|
|
16
|
+
<p:cNvSpPr/>
|
|
17
|
+
<p:nvPr/>
|
|
18
|
+
</p:nvSpPr>
|
|
19
|
+
<p:spPr>
|
|
20
|
+
</p:spPr>
|
|
21
|
+
</p:sp>
|
|
22
|
+
"""
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build_node
|
|
26
|
+
base_node.tap do |node|
|
|
27
|
+
node.xpath('.//p:spPr', p: Presentation::NS).first.add_child build_solid_fill @color
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module Shapes
|
|
3
|
+
class Picture < Shape
|
|
4
|
+
def initialize(transform, relationship_id)
|
|
5
|
+
super(transform)
|
|
6
|
+
@relationship_id = relationship_id
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def base_xml
|
|
10
|
+
# TODO replace cNvPr descr, id and name
|
|
11
|
+
"""
|
|
12
|
+
<p:pic xmlns:a='http://schemas.openxmlformats.org/drawingml/2006/main'
|
|
13
|
+
xmlns:p='http://schemas.openxmlformats.org/presentationml/2006/main'>
|
|
14
|
+
<p:nvPicPr>
|
|
15
|
+
<p:cNvPr descr='test_photo.jpg' id='2' name='Picture 1'/>
|
|
16
|
+
<p:cNvPicPr>
|
|
17
|
+
<a:picLocks noChangeAspect='1'/>
|
|
18
|
+
</p:cNvPicPr>
|
|
19
|
+
<p:nvPr/>
|
|
20
|
+
</p:nvPicPr>
|
|
21
|
+
<p:blipFill>
|
|
22
|
+
<a:blip r:embed='REPLACEME'/>
|
|
23
|
+
<a:stretch>
|
|
24
|
+
<a:fillRect/>
|
|
25
|
+
</a:stretch>
|
|
26
|
+
</p:blipFill>
|
|
27
|
+
<p:spPr>
|
|
28
|
+
</p:spPr>
|
|
29
|
+
</p:pic>
|
|
30
|
+
"""
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_node
|
|
34
|
+
base_node.tap do |node|
|
|
35
|
+
node.xpath('.//a:blip', a: DRAWING_NS).first['r:embed'] = @relationship_id
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module PPTX
|
|
2
|
+
module Shapes
|
|
3
|
+
class Shape
|
|
4
|
+
def initialize(transform)
|
|
5
|
+
@transform = transform
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def base_node
|
|
9
|
+
@base_node ||= Nokogiri::XML::DocumentFragment.parse(base_xml).tap do |node|
|
|
10
|
+
set_shape_properties(node, *@transform)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set_shape_properties(node, x, y, width, height)
|
|
15
|
+
xml = """
|
|
16
|
+
<p:spPr xmlns:a='http://schemas.openxmlformats.org/drawingml/2006/main'
|
|
17
|
+
xmlns:p='http://schemas.openxmlformats.org/presentationml/2006/main'>
|
|
18
|
+
<a:xfrm>
|
|
19
|
+
<a:off x='#{x.to_i}' y='#{y.to_i}'/>
|
|
20
|
+
<a:ext cx='#{width.to_i}' cy='#{height.to_i}'/>
|
|
21
|
+
</a:xfrm>
|
|
22
|
+
<a:prstGeom prst='rect'>
|
|
23
|
+
<a:avLst/>
|
|
24
|
+
</a:prstGeom>
|
|
25
|
+
</p:spPr>
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
node.xpath('.//p:spPr', p: Presentation::NS)
|
|
29
|
+
.first
|
|
30
|
+
.replace(Nokogiri::XML::DocumentFragment.parse(xml))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_solid_fill(rgb_color)
|
|
34
|
+
fill_xml = """
|
|
35
|
+
<a:solidFill xmlns:a='http://schemas.openxmlformats.org/drawingml/2006/main'>
|
|
36
|
+
<a:srgbClr val='SETME'/>
|
|
37
|
+
</a:solidFill>
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
Nokogiri::XML::DocumentFragment.parse(fill_xml).tap do |node|
|
|
41
|
+
node.xpath('.//a:srgbClr', a: DRAWING_NS).first['val'] = rgb_color
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|