docxi 0.0.2 → 0.1.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6c090f48de3b5f08211cfcf6772bede9af088528
4
- data.tar.gz: 0dda1273cb0c9cdd092a36db7a9448538828dbe6
2
+ SHA256:
3
+ metadata.gz: 92d0e49fe9e1a189dc7d1cb7dccb3c7e7a3f4877f4b4c56f5cfb2cc779a944de
4
+ data.tar.gz: 8a0192d020e0471216c6224de46478a7b2ac4a71a04c4a05569f6774ae3c0b76
5
5
  SHA512:
6
- metadata.gz: 2ada27ba61335c8421b4d8e2dd65757afb79a960b8ffdb20d121d48d3310c042bc9264dd882f3568918c9ca689f9edec5c52beee4a68c70fa3480ac07f843a48
7
- data.tar.gz: ba26ae7dfe72ad1e40ce4bfb64602965d31e67e3240a9d9501817d70230442740be4c2e80ab434a4ac549bd03cad1082f0445968a1cd6c5e2ce50f3be2c82845
6
+ metadata.gz: 1060898ce0f6fe2a1122a98cce52b2220460c96969ea78ebeb576e32f20fadefed3b8b6bd741c623cdc9ae0d4721189fa649ef7e0d04fbf5767852001ac0392f
7
+ data.tar.gz: 266f7794a16fbad300b520f5d49bf70e6fd15407143e8a3bc54f6b8fff91a32ed7358a498a76ddaca98be92df6a87310c55612da45c5ece198130ad64e9fe63f
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ docxi installs and runs again in current apps, with or without Rails. No code changes are needed, and the generated documents are the same as 0.0.6's (the tests compare them against 0.0.6's output).
6
+
7
+ ### Compatibility
8
+
9
+ - Requires Ruby 2.5 or newer. Apps on older Rubies stay on 0.0.6.
10
+ - Works with or without Rails, and is tested alongside Rails 5.2 through 8.1.
11
+ - nokogiri `>= 1.8, < 2` instead of `>= 1.6.3, < 1.7`, which conflicted with every Rails version since 6.0.
12
+ - rubyzip `>= 1.3, < 4` instead of `~> 0.9.9`.
13
+ - No longer calls ActiveSupport's `blank?` and `present?`, which only worked when Rails had loaded them.
14
+
15
+ ### Fixes
16
+
17
+ - Documents written with rubyzip 3 no longer contain Zip64 records, which Google Docs and older LibreOffice versions reject.
18
+ - `add_media` with an image URL works on Ruby 3 again.
19
+ - `add_media` no longer runs a string that starts with `|` as a shell command.
20
+ - `tab` and `frame` no longer raise `NameError`.
21
+ - `document.frame("text")` no longer raises `TypeError`.
22
+ - `page_numbers` in a header no longer raises `NameError`.
23
+
24
+ ### Gem
25
+
26
+ - New maintainer, homepage and changelog links.
27
+ - Releases require MFA.
28
+
29
+ ## 0.0.6 (2016-10-18)
30
+
31
+ Last release before the 0.1.0 update.
data/README.md CHANGED
@@ -11,11 +11,36 @@ Docxi gem provides ability to create docx documents using Ruby.
11
11
  7. Page Numbering
12
12
 
13
13
  At the moment, the gem supports a limited number of functions, but the development of the gem is still in progress, and perhaps functionality will be expanded in the future. First of all, try to add documentation for the code and tests.
14
+ ## NOTE
15
+ Use Latest version of Gem. Old Version has bugs.
16
+
17
+ ## Installation
18
+
19
+ ```ruby
20
+ gem "docxi", "~> 0.1"
21
+ ```
22
+
23
+ ## Supported versions
24
+
25
+ | Dependency | Supported versions |
26
+ |---|---|
27
+ | Ruby | 2.5 or newer |
28
+ | Rails | Not required. Tested alongside Rails 5.2, 6.0, 6.1, 7.0, 7.1, 7.2, 8.0 and 8.1 |
29
+ | nokogiri | 1.8 or newer |
30
+ | rubyzip | 1.3 or newer, including 3.x |
31
+
32
+ ### Upgrading from 0.0.x
33
+
34
+ Run `bundle update docxi`. The API is unchanged and 0.1.0 generates the same documents as 0.0.6. Apps on a Ruby older than 2.5 stay on 0.0.6 automatically.
14
35
 
15
36
  ## Usage
16
37
 
17
38
  ```ruby
18
- word = Docxi::Document.new
39
+ # default attributes (top: 720,right: 1440,left: 1440,bottom: 720,footer: 200, header: 190)
40
+ word = Docxi::Document.new
41
+ # or change margins
42
+ # word = Docxi::Document.new(top: 0,right: 0,left: 0,bottom: 0)
43
+
19
44
  document = word.document
20
45
 
21
46
  # Creates Header
@@ -39,8 +64,8 @@ end
39
64
  document.add_footer(footer)
40
65
 
41
66
  # Creates paragraph
42
- document.p(size: 16, align: "center") do |p|
43
- p.text "Title"
67
+ document.p(size: 12, bold: true, align: "center", color: "000000", fill: "AAAAAA") do |p|
68
+ p.text "Title", size: 12, bold: true, align: "center", color: "000000"
44
69
  # Adds 3 break lines
45
70
  p.br times: 3
46
71
  end
@@ -103,17 +128,67 @@ document.table( columns_width: [ 316, 160, 135 ] ) do |table|
103
128
  end
104
129
  end
105
130
 
131
+ # Adds table with row height and column indentation
132
+ document.table( columns_width: [ 316, 160, 135 ], height: 268, iwidth: 108) do |table|
133
+ # Adds row with specific styles
134
+ table.tr(fill: "FBD4B4", bold: true, align: 'center' ) do |tr|
135
+ # Adds cells
136
+ tr.tc(borders: { top: 1, right: nil, bottom: 1, left: 1 }) do |tc|
137
+ tc.text "Header 1"
138
+ end
139
+ tr.tc(borders: { top: 1, right: nil, bottom: 1, left: nil }) do |tc|
140
+ tc.text "Header 2"
141
+ end
142
+ tr.tc(borders: { top: 1, right: 1, bottom: 1, left: nil }) do |tc|
143
+ tc.text "Header 3"
144
+ end
145
+ end
146
+ end
147
+
148
+
106
149
  # Adds image
107
150
  document.p do |p|
108
151
  p.text "Image Example", size: 16, bold: true, italic: true, underline: true, br: true
109
152
 
110
153
  # Adds image to container
111
154
  media = document.add_media File.open('image.png')
155
+ # Or add image URL
156
+ # media = document.add_media image_url
112
157
  # Insert media into paragraph
113
158
  p.image media, style: { width: '200pt', height: '200pt' }
159
+
160
+ # Add Hyperlink
161
+ hyper = document.add_hyperlink("http://google.com")
162
+ p.hyperlink "Go to Google", "http://google.com", hyper
163
+
164
+ end
165
+
166
+ # Add Underline
167
+ document.p(bottom: true) do |p|
168
+ p.text "Example text"
169
+ end
170
+
171
+ # Set position of Paragraph
172
+ document.p(fill: "d7d7d7",align: "center", right: -1000, left: -1000) do |p|
173
+ p.text "Example Text 2"
174
+ p.br
114
175
  end
115
176
 
116
177
  # Creates Word Document
117
178
  io = word.render
118
179
  File.open('doc_1.docx', 'wb'){|f| f.write(io.string) }
119
- ```
180
+ ```
181
+
182
+ ## Development
183
+
184
+ ```sh
185
+ bundle install
186
+ bundle exec rake test # run the tests
187
+ bundle exec rake samples # write tmp/samples/*.docx to open in Word, LibreOffice or Google Docs
188
+
189
+ # run the tests with a specific Rails version loaded
190
+ BUNDLE_GEMFILE=gemfiles/rails_8.1.gemfile bundle install
191
+ BUNDLE_GEMFILE=gemfiles/rails_8.1.gemfile DOCXI_WITH_RAILS=1 bundle exec rake test
192
+ ```
193
+
194
+ `test/golden` holds the XML that 0.0.6 generated for the sample documents in `test/support/sample_documents.rb`, and the tests check every release against it. After an intentional change to the generated XML, update it with `bundle exec rake golden` and review the diff.
data/Rakefile CHANGED
@@ -4,6 +4,34 @@ require 'rake/testtask'
4
4
 
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs << 'test'
7
+ t.pattern = 'test/**/test_*.rb'
8
+ t.warning = false
9
+ end
10
+
11
+ desc 'Rewrite test/golden from the current code (only after an intentional output change)'
12
+ task :golden do
13
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
14
+ require 'docxi'
15
+ require_relative 'test/support/package'
16
+ require_relative 'test/support/sample_documents'
17
+
18
+ SampleDocuments.all.each do |name, word|
19
+ Package.write_golden(File.join(__dir__, 'test', 'golden', name), word.render.string)
20
+ end
21
+ end
22
+
23
+ desc 'Write the sample documents to tmp/samples for opening in Word, LibreOffice or Google Docs'
24
+ task :samples do
25
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
26
+ require 'docxi'
27
+ require 'fileutils'
28
+ require_relative 'test/support/sample_documents'
29
+
30
+ FileUtils.mkdir_p('tmp/samples')
31
+ SampleDocuments.all.each do |name, word|
32
+ File.binwrite("tmp/samples/#{name}.docx", word.render.string)
33
+ puts "tmp/samples/#{name}.docx"
34
+ end
7
35
  end
8
36
 
9
37
  desc "Run tests"
@@ -11,6 +11,7 @@ module Docxi
11
11
  add(:default, "rels", "application/vnd.openxmlformats-package.relationships+xml")
12
12
  add(:default, "xml", "application/xml")
13
13
  add(:default, "png", "image/png")
14
+ add(:override, "/_rels/.rels","application/vnd.openxmlformats-package.relationships+xml")
14
15
  add(:override, "/word/document.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")
15
16
  add(:override, "/word/styles.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml")
16
17
  add(:override, "/word/stylesWithEffects.xml", "application/vnd.ms-word.stylesWithEffects+xml")
@@ -24,7 +25,11 @@ module Docxi
24
25
  add(:override, "/word/footnotes.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml")
25
26
  add(:override, "/word/endnotes.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml")
26
27
  add(:override, "/word/footer1.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml")
28
+ add(:override, "/word/footer2.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml")
27
29
  add(:override, "/word/header1.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml")
30
+ add(:override, "/word/header2.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml")
31
+ add(:override, "/word/_rels/header2.xml.rels", "application/vnd.openxmlformats-package.relationships+xml")
32
+ add(:override, "/word/_rels/document.xml.rels", "application/vnd.openxmlformats-package.relationships+xml")
28
33
  end
29
34
 
30
35
  def add(type, attr, content)
@@ -34,8 +39,7 @@ module Docxi
34
39
  end
35
40
 
36
41
  def render(zip)
37
- zip.put_next_entry('[Content_Types].xml')
38
- zip.write(Docxi.to_xml(document))
42
+ Docxi.write_entry(zip, '[Content_Types].xml', Docxi.to_xml(document))
39
43
  end
40
44
 
41
45
  private
@@ -1,10 +1,10 @@
1
1
  module Docxi
2
2
  class Document
3
3
 
4
- attr_accessor :document, :properties, :relationships, :content_types
4
+ attr_accessor :document, :properties, :relationships, :content_types, :options
5
5
 
6
- def initialize
7
-
6
+ def initialize(options = {top: 720,right: 1440,left: 1440,bottom: 720,footer: 200, header: 190})
7
+ @options = options
8
8
  end
9
9
 
10
10
  def relationships
@@ -20,11 +20,11 @@ module Docxi
20
20
  end
21
21
 
22
22
  def document
23
- @document ||= Word::Document.new({})
23
+ @document ||= Word::Document.new(options)
24
24
  end
25
25
 
26
26
  def render
27
- Zip::ZipOutputStream.write_buffer do |zip|
27
+ Zip::OutputStream.write_buffer do |zip|
28
28
  document.render(zip)
29
29
  properties.render(zip)
30
30
  relationships.render(zip)
@@ -18,11 +18,9 @@ module Docxi
18
18
  end
19
19
 
20
20
  def render(zip)
21
- zip.put_next_entry('docProps/app.xml')
22
- zip.write(Docxi.to_xml(app.document))
21
+ Docxi.write_entry(zip, 'docProps/app.xml', Docxi.to_xml(app.document))
23
22
 
24
- zip.put_next_entry('docProps/core.xml')
25
- zip.write(Docxi.to_xml(core.document))
23
+ Docxi.write_entry(zip, 'docProps/core.xml', Docxi.to_xml(core.document))
26
24
  end
27
25
 
28
26
  end
@@ -21,8 +21,7 @@ module Docxi
21
21
  end
22
22
 
23
23
  def render(zip)
24
- zip.put_next_entry('_rels/.rels')
25
- zip.write(Docxi.to_xml(document))
24
+ Docxi.write_entry(zip, '_rels/.rels', Docxi.to_xml(document))
26
25
  end
27
26
 
28
27
  private
data/lib/docxi/util.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Docxi
2
+ module Util
3
+
4
+ # Same rules as ActiveSupport's Object#blank?, which older versions of this
5
+ # gem called without depending on ActiveSupport.
6
+ def self.blank?(value)
7
+ case value
8
+ when nil, false then true
9
+ when String then /\A[[:space:]]*\z/ === value
10
+ else value.respond_to?(:empty?) ? !!value.empty? : false
11
+ end
12
+ end
13
+
14
+ def self.present?(value)
15
+ !blank?(value)
16
+ end
17
+
18
+ end
19
+ end
data/lib/docxi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Docxi
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0.rc1"
3
3
  end
@@ -0,0 +1,84 @@
1
+ module Docxi
2
+ module Word
3
+ module Contents
4
+
5
+ class Frame
6
+
7
+ attr_accessor :content, :options
8
+ def initialize(options={})
9
+ @content = []
10
+ @options = options
11
+ if block_given?
12
+ yield self
13
+ else
14
+
15
+ end
16
+ end
17
+
18
+ def render(xml)
19
+ xml['w'].r do
20
+ xml['w'].pict do
21
+ xml['v'].rect("fillcolor"=>"#bfbfbf[2412]", "strokecolor"=>"000000","strokeweight"=>"0pt", "style"=> @options[:style]) do
22
+ xml['v'].fill("opacity" =>"52429f")
23
+ xml['v'].textbox("inset" => "0in,0in,0in,0in") do
24
+ xml['w'].txbxContent do
25
+ xml['w'].p do
26
+ xml['w'].pPr do
27
+ xml['w'].pStyle('w:val'=>"Normal")
28
+ xml['w'].pBdr do
29
+ xml['w'].top('w:val'=>"nil")
30
+ xml['w'].left('w:val'=>"nil")
31
+ xml['w'].bottom('w:val'=>"nil")
32
+ xml['w'].right('w:val'=>"nil")
33
+ end
34
+ if @options[:spacing]
35
+ xml['w'].spacing('w:before'=>"240")
36
+ end
37
+ end
38
+ @content.each do |element|
39
+ element.render(xml)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ xml['w10'].wrap('type'=>"topAndBottom")
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def br(options={})
51
+ br = Docxi::Word::Contents::Break.new(options)
52
+ @content << br
53
+ br
54
+ end
55
+
56
+ def tab(options={})
57
+ tab = Docxi::Word::Contents::Tab.new(options)
58
+ @content << tab
59
+ tab
60
+ end
61
+
62
+ def frame(options={}, &block)
63
+ element = Docxi::Word::Contents::Frame.new(options, &block)
64
+ @content << element
65
+ element
66
+ end
67
+
68
+ def text(text, options={})
69
+ options = @options.merge(options)
70
+ text = Docxi::Word::Contents::Text.new(text, options)
71
+ @content << text
72
+ text
73
+ end
74
+
75
+ def image(image, options={})
76
+ img = Docxi::Word::Contents::Image.new(image, options)
77
+ @content << img
78
+ img
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -7,7 +7,7 @@ module Docxi
7
7
  attr_accessor :media, :options
8
8
  def initialize(media, options={})
9
9
  @media = media
10
- @media.file.rewind
10
+ # @media.file.rewind
11
11
  @options = options
12
12
  end
13
13
 
@@ -19,16 +19,80 @@ module Docxi
19
19
 
20
20
  def render(xml)
21
21
  xml['w'].r do
22
- xml['w'].rPr do
23
- xml['w'].noProof
24
- end
25
- xml['w'].pict do
26
- xml['v'].shape( 'id' => @media.uniq_id, 'type' => @media.type, 'style' => styles ) do
27
- xml['v'].imagedata( 'r:id' => @media.sequence, 'o:title' => @options[:title] )
22
+ if options[:pH] and options[:pV] and options[:wrap]
23
+ xml['w'].drawing do
24
+ xml['wp'].anchor( "behindDoc" => 1 ,"distT" => 0,"distB" => 0, "distL" => 0, "distR" => 0 ,"simplePos" => 0 ,"locked" => 0, "layoutInCell" => 1, "allowOverlap" => 1, "relativeHeight" => 3) do
25
+ xml['wp'].simplePos('x'=> 0 , 'y' => 0 )
26
+ xml['wp'].positionH("relativeFrom" => "column") do
27
+ xml['wp'].posOffset options[:pH]
28
+ end
29
+ xml['wp'].positionV("relativeFrom" => "paragraph") do
30
+ xml['wp'].posOffset options[:pV]
31
+ end
32
+ xml['wp'].extent( 'cx' => ( options[:width] * options[:height] * 14.92 ).to_i, 'cy' => ( options[:width] * options[:height] * 14.92 ).to_i ) if options[:width] && options[:height]
33
+ xml['wp'].effectExtent( 'l' => 0, 't' => 0, 'r' => 0, 'b' => 0 )
34
+ xml['wp'].wrapNone
35
+ xml['wp'].docPr( 'id' => 1, 'name'=> "Image", 'descr' => "image")
36
+ xml['wp'].cNvGraphicFramePr do
37
+ xml.graphicFrameLocks( 'xmlns:a' => "http://schemas.openxmlformats.org/drawingml/2006/main", 'noChangeAspect' => "1" ) do
38
+ xml.parent.namespace = xml.parent.namespace_definitions.find{|ns| ns.prefix == "a" }
39
+ end
40
+ end
41
+ xml.graphic( 'xmlns:a' => "http://schemas.openxmlformats.org/drawingml/2006/main" ) do
42
+ xml.parent.namespace = xml.parent.namespace_definitions.find{|ns| ns.prefix == "a" }
43
+ xml['a'].graphicData( 'uri' => "http://schemas.openxmlformats.org/drawingml/2006/picture") do
44
+ xml.pic( 'xmlns:pic' => "http://schemas.openxmlformats.org/drawingml/2006/picture") do
45
+ xml.parent.namespace = xml.parent.namespace_definitions.find{|ns| ns.prefix == "pic" }
46
+ xml['pic'].nvPicPr do
47
+ xml['pic'].cNvPr( 'id' => 0, 'name' => "Image" )
48
+ xml['pic'].cNvPicPr do
49
+ xml['a'].picLocks( 'noChangeAspect' => "1", 'noChangeArrowheads' => "1" )
50
+ end
51
+ end
52
+ xml['pic'].blipFill do
53
+ xml['a'].blip( 'r:embed' => @media.sequence )
54
+ xml['a'].stretch do
55
+ xml['a'].fillRect
56
+ end
57
+ end
58
+ xml['pic'].spPr( 'bwMode' => "auto" ) do
59
+ xml['a'].xfrm do
60
+ xml['a'].off( 'x' => 0, 'y' => 0 )
61
+ xml['a'].ext( 'cx' => ( options[:width] * options[:height] * 14.92 ).to_i, 'cy' => ( options[:width] * options[:height] * 14.92).to_i ) if options[:width] && options[:height]
62
+ end
63
+ xml['a'].prstGeom( 'prst' => "rect" ) do
64
+ xml['a'].avLst
65
+ end
66
+ xml['a'].noFill
67
+ xml['a'].ln('w' => "9525") do
68
+ xml['a'].noFill
69
+ xml['a'].miter('lim'=>"800000")
70
+ xml['a'].headEnd
71
+ xml['a'].tailEnd
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ else
80
+ xml['w'].pict do
81
+ unless options[:fill_color]
82
+ xml['v'].rect( 'id' => @media.uniq_id, 'type' => @media.type, 'style' => styles, :stroked => "f" ) do
83
+ xml['v'].imagedata( 'r:id' => @media.sequence, 'o:title' => @options[:title] )
84
+ xml['v'].wrap('v:type' => 'none') if options[:wrap]
85
+ end
86
+ else
87
+ xml['v'].rect("fillcolor"=> options[:fill_color], 'id' => @media.uniq_id, 'type' => @media.type, 'style' => styles, :stroked => "f" ) do
88
+ if Docxi::Util.present?(options[:opacity])
89
+ xml['v'].fill("opacity" => options[:opacity])
90
+ end
91
+ xml['v'].imagedata( 'r:id' => @media.sequence, 'o:title' => @options[:title] )
92
+ xml['v'].wrap('v:type' => 'none') if options[:wrap]
93
+ end
94
+ end
28
95
  end
29
- end
30
- xml['w'].rPr do
31
- xml['w'].noProof
32
96
  end
33
97
  end
34
98
  end
@@ -4,10 +4,11 @@ module Docxi
4
4
 
5
5
  class Paragraph
6
6
 
7
- attr_accessor :content, :options
7
+ attr_accessor :content, :options, :relationships, :id
8
8
  def initialize(options={})
9
9
  @content = []
10
10
  @options = options
11
+ @relationships = []
11
12
 
12
13
  if block_given?
13
14
  yield self
@@ -16,16 +17,70 @@ module Docxi
16
17
  end
17
18
  end
18
19
 
20
+ def content_type
21
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
22
+ end
23
+
24
+ def target
25
+ "document.xml"
26
+ end
27
+
28
+
29
+ def hyperlink(text,link,id)
30
+ text = Hyperlink.new(text, link, id)
31
+ @content << text
32
+ end
33
+
34
+ class Hyperlink
35
+ attr_accessor :text, :link, :id
36
+ def initialize(text, link, id )
37
+ @text = text
38
+ @link = link
39
+ @id = id
40
+ end
41
+
42
+ def content_type
43
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
44
+ end
45
+
46
+ def render(xml)
47
+ xml['w'].pPr do
48
+ xml['w'].pStyle('w:val' => "Normal")
49
+ xml['w'].rPr do
50
+ xml['w'].rStyle('w:val' => "InternetLink")
51
+ end
52
+ end
53
+ xml['w'].hyperlink('r:id' => @id) do
54
+ xml['w'].r do
55
+ xml['w'].rPr do
56
+ xml['w'].rStyle( 'w:val' => "InternetLink")
57
+ end
58
+ xml['w'].t @text
59
+ end
60
+ end
61
+ end
62
+ end
63
+
19
64
  def render(xml)
20
65
  xml['w'].p do
21
66
  xml['w'].pPr do
22
67
  xml['w'].jc( 'w:val' => @options[:align]) if @options[:align]
68
+ xml['w'].shd( 'w:val' => 'clear','w:fill' => @options[:fill] ) if @options[:fill]
69
+ xml['w'].ind( 'w:left' => @options[:left], 'w:right' => @options[:right] ) if @options[:left] and @options[:right]
23
70
  if options[:ul]
24
71
  xml['w'].numPr do
25
72
  xml['w'].ilvl( 'w:val' => 0 )
26
73
  xml['w'].numId( 'w:val' => 1 )
27
74
  end
28
75
  end
76
+ if options[:bottom]
77
+ xml['w'].pBdr do
78
+ xml['w'].top( 'w:val' => "nil")
79
+ xml['w'].left('w:val' => "nil")
80
+ xml['w'].bottom('w:val'=>"single", 'w:sz'=>"4", 'w:space'=>"1", 'w:color'=>"000000")
81
+ xml['w'].right('w:val'=>"nil")
82
+ end
83
+ end
29
84
  end
30
85
  @content.each do |element|
31
86
  element.render(xml)
@@ -40,12 +95,24 @@ module Docxi
40
95
  text
41
96
  end
42
97
 
98
+ def frame(options={}, &block)
99
+ element = Docxi::Word::Contents::Frame.new(options, &block)
100
+ @content << element
101
+ element
102
+ end
103
+
43
104
  def br(options={})
44
105
  br = Docxi::Word::Contents::Break.new(options)
45
106
  @content << br
46
107
  br
47
108
  end
48
109
 
110
+ def tab(options={})
111
+ tab = Docxi::Word::Contents::Tab.new(options)
112
+ @content << tab
113
+ tab
114
+ end
115
+
49
116
  def image(image, options={})
50
117
  img = Docxi::Word::Contents::Image.new(image, options)
51
118
  @content << img
@@ -0,0 +1,33 @@
1
+ module Docxi
2
+ module Word
3
+ module Contents
4
+
5
+ class Tab
6
+
7
+ attr_accessor :options
8
+ def initialize(options={})
9
+ @options = options
10
+ @options[:times] ||= 1
11
+ end
12
+
13
+ def render(xml)
14
+ if @options[:page]
15
+ xml['w'].p do
16
+ xml['w'].r do
17
+ xml['w'].tab
18
+ end
19
+ end
20
+ else
21
+ @options[:times].times do
22
+ xml['w'].r do
23
+ xml['w'].tab
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end