notare 0.0.3 → 0.0.5

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
2
  SHA256:
3
- metadata.gz: 97f9b8f11f3cbda0479fa7a05f428505475c572d46c8d0dfbad65af11740c3d5
4
- data.tar.gz: 81a6c3ce45d3f3e8dece12d1c48f8bb3345760fdb6b8488ef5b9481860190367
3
+ metadata.gz: 959b59c2f2dc30ff265115057c1fd304f3b7747adfe16c9818e6de9e5c564fc1
4
+ data.tar.gz: 6c3da88bce319ed81fdb519308d6854c00f6a846343e37178e6bafab2b1fefbb
5
5
  SHA512:
6
- metadata.gz: 470684cd1e7a5ed4ee7169f3e01aa0547feee8ba13fe89c68bc7b556c50235d2c43b27e32dd0f66d97f16460a53a2b088e31d56e1c083a309ead256d14fd6d0d
7
- data.tar.gz: bb58e3d474b6cea285fd548c6c87e9e3bfad55bce889ac4b2edb8cbdcdb9fb5557ff6d280992f29045338350522800b846ba6c99ede4143b14fb7ba45b4bfffd
6
+ metadata.gz: '00558f142269e3927cdb5aab15271a92533a48ff5ff2cf19c349e8757ccd9da77af9d27a249bc1a2551c910b8578a8bc9a354622cea7c20424183b7230bc06b9'
7
+ data.tar.gz: 2f179ca2e773afe314151b61fb9de4e48a6b0037bba3ce50397636b9f2a26790f5e0399c11f5d807c4f3a3090f562e094d0e02da40b4a77719f1bd4c3ea7ee8a
data/README.md CHANGED
@@ -105,6 +105,21 @@ Notare includes built-in styles and supports custom style definitions.
105
105
 
106
106
  #### Built-in Styles
107
107
 
108
+ | Style | Properties |
109
+ |-------|------------|
110
+ | `:title` | 26pt, bold, centered |
111
+ | `:subtitle` | 15pt, italic, gray (#666666) |
112
+ | `:quote` | italic, gray (#666666), indented |
113
+ | `:code` | Courier New, 10pt |
114
+ | `:heading1` | 24pt, bold |
115
+ | `:heading2` | 18pt, bold |
116
+ | `:heading3` | 14pt, bold |
117
+ | `:heading4` | 12pt, bold |
118
+ | `:heading5` | 11pt, bold, italic |
119
+ | `:heading6` | 10pt, bold, italic |
120
+
121
+ Note: `h1` through `h6` methods use the corresponding heading styles automatically.
122
+
108
123
  ```ruby
109
124
  Notare::Document.create("output.docx") do |doc|
110
125
  doc.p "This is a title", style: :title
@@ -246,6 +261,146 @@ Notare::Document.create("output.docx") do |doc|
246
261
  end
247
262
  ```
248
263
 
264
+ #### Table Styles
265
+
266
+ Define reusable table styles with borders, shading, cell margins, and alignment:
267
+
268
+ ```ruby
269
+ Notare::Document.create("output.docx") do |doc|
270
+ # Define a custom table style
271
+ doc.define_table_style :fancy,
272
+ borders: { style: "double", color: "0066CC", size: 6 },
273
+ shading: "E6F2FF",
274
+ cell_margins: 100,
275
+ align: :center
276
+
277
+ # Apply the style to a table
278
+ doc.table(style: :fancy) do
279
+ doc.tr do
280
+ doc.td "Product"
281
+ doc.td "Price"
282
+ end
283
+ doc.tr do
284
+ doc.td "Widget"
285
+ doc.td "$10.00"
286
+ end
287
+ end
288
+ end
289
+ ```
290
+
291
+ #### Table Style Properties
292
+
293
+ | Property | Description | Example |
294
+ |----------|-------------|---------|
295
+ | `borders` | Border configuration | `{ style: "single", color: "000000", size: 4 }` |
296
+ | `shading` | Background color (hex) | `"EEEEEE"` |
297
+ | `cell_margins` | Cell padding (twips) | `100` or `{ top: 50, bottom: 50, left: 100, right: 100 }` |
298
+ | `align` | Table alignment | `:left`, `:center`, `:right` |
299
+
300
+ **Border styles:** `single`, `double`, `dotted`, `dashed`, `triple`, `none`
301
+
302
+ **Border configuration options:**
303
+
304
+ ```ruby
305
+ # All borders the same
306
+ borders: { style: "single", color: "000000", size: 4 }
307
+
308
+ # Per-edge borders
309
+ borders: {
310
+ top: { style: "double", color: "FF0000", size: 8 },
311
+ bottom: { style: "single", color: "000000", size: 4 },
312
+ left: { style: "none" },
313
+ right: { style: "none" },
314
+ insideH: { style: "dotted", color: "CCCCCC", size: 2 },
315
+ insideV: { style: "dotted", color: "CCCCCC", size: 2 }
316
+ }
317
+
318
+ # No borders
319
+ borders: :none
320
+ ```
321
+
322
+ #### Built-in Table Styles
323
+
324
+ | Style | Description |
325
+ |-------|-------------|
326
+ | `:grid` | Standard black single-line borders |
327
+ | `:borderless` | No borders |
328
+
329
+ ```ruby
330
+ doc.table(style: :borderless) do
331
+ doc.tr { doc.td "No borders here" }
332
+ end
333
+ ```
334
+
335
+ #### Column Sizing
336
+
337
+ Control table column widths with layout modes and explicit sizing.
338
+
339
+ **Auto-layout** - columns adjust to fit content:
340
+
341
+ ```ruby
342
+ doc.table(layout: :auto) do
343
+ doc.tr do
344
+ doc.td "Short"
345
+ doc.td "This column expands to fit longer content"
346
+ end
347
+ end
348
+ ```
349
+
350
+ **Fixed column widths** - specify widths for all columns:
351
+
352
+ ```ruby
353
+ # Inches
354
+ doc.table(columns: %w[2in 3in 1.5in]) do
355
+ doc.tr do
356
+ doc.td "2 inches"
357
+ doc.td "3 inches"
358
+ doc.td "1.5 inches"
359
+ end
360
+ end
361
+
362
+ # Centimeters
363
+ doc.table(columns: %w[5cm 10cm]) do
364
+ doc.tr { doc.td "5cm"; doc.td "10cm" }
365
+ end
366
+
367
+ # Percentages
368
+ doc.table(columns: %w[25% 50% 25%]) do
369
+ doc.tr { doc.td "Quarter"; doc.td "Half"; doc.td "Quarter" }
370
+ end
371
+ ```
372
+
373
+ **Per-cell widths** - set width on individual cells:
374
+
375
+ ```ruby
376
+ doc.table do
377
+ doc.tr do
378
+ doc.td("Narrow", width: "1in")
379
+ doc.td("Wide", width: "4in")
380
+ end
381
+ end
382
+ ```
383
+
384
+ **Combined layout and columns:**
385
+
386
+ ```ruby
387
+ doc.table(layout: :fixed, columns: %w[2in 2in 2in]) do
388
+ doc.tr do
389
+ doc.td "A"
390
+ doc.td "B"
391
+ doc.td "C"
392
+ end
393
+ end
394
+ ```
395
+
396
+ **Width formats:**
397
+
398
+ | Format | Example | Description |
399
+ |--------|---------|-------------|
400
+ | Inches | `"2in"` | Fixed width in inches |
401
+ | Centimeters | `"5cm"` | Fixed width in centimeters |
402
+ | Percentage | `"50%"` | Percentage of table width |
403
+
249
404
  ### Images
250
405
 
251
406
  Images can be added to paragraphs, table cells, and list items. Supports PNG and JPEG formats.
@@ -434,15 +589,16 @@ end
434
589
  | `link(url, text)` | Hyperlink with custom text |
435
590
  | `link(url) { }` | Hyperlink with block content |
436
591
  | `define_style(name, **props)` | Define a custom style |
592
+ | `define_table_style(name, **props)` | Define a custom table style |
437
593
  | `ul { }` | Bullet list (can be nested) |
438
594
  | `ol { }` | Numbered list (can be nested) |
439
595
  | `li(text)` | List item with text |
440
596
  | `li { }` | List item with block content |
441
597
  | `li(text) { }` | List item with text and nested content |
442
- | `table { }` | Table |
598
+ | `table(style:, layout:, columns:) { }` | Table with optional style, layout (`:auto`/`:fixed`), and column widths |
443
599
  | `tr { }` | Table row |
444
- | `td(text)` | Table cell with text |
445
- | `td { }` | Table cell with block content |
600
+ | `td(text, width:)` | Table cell with text and optional width |
601
+ | `td(width:) { }` | Table cell with block content and optional width |
446
602
  | `image(path, width:, height:)` | Insert image (PNG/JPEG). Dimensions: `"2in"`, `"5cm"`, `"100px"`, or integer pixels |
447
603
 
448
604
  ## Development
@@ -100,8 +100,8 @@ module Notare
100
100
  @current_list.add_item(item)
101
101
  end
102
102
 
103
- def table(&block)
104
- tbl = Nodes::Table.new
103
+ def table(style: nil, layout: nil, columns: nil, &block)
104
+ tbl = Nodes::Table.new(style: resolve_table_style(style), layout: layout, columns: columns)
105
105
  previous_table = @current_table
106
106
  @current_table = tbl
107
107
  block.call
@@ -118,8 +118,8 @@ module Notare
118
118
  @current_table.add_row(row)
119
119
  end
120
120
 
121
- def td(text = nil, &block)
122
- cell = Nodes::TableCell.new
121
+ def td(text = nil, width: nil, &block)
122
+ cell = Nodes::TableCell.new(width: width)
123
123
  if block
124
124
  with_target(cell, &block)
125
125
  elsif text
@@ -198,5 +198,12 @@ module Notare
198
198
 
199
199
  style(style_or_name) || raise(ArgumentError, "Unknown style: #{style_or_name}")
200
200
  end
201
+
202
+ def resolve_table_style(style_or_name)
203
+ return nil if style_or_name.nil?
204
+ return style_or_name if style_or_name.is_a?(TableStyle)
205
+
206
+ table_style(style_or_name) || raise(ArgumentError, "Unknown table style: #{style_or_name}")
207
+ end
201
208
  end
202
209
  end
@@ -4,7 +4,7 @@ module Notare
4
4
  class Document
5
5
  include Builder
6
6
 
7
- attr_reader :nodes, :styles, :hyperlinks
7
+ attr_reader :nodes, :styles, :table_styles, :hyperlinks
8
8
 
9
9
  def self.create(path, &block)
10
10
  doc = new
@@ -25,7 +25,9 @@ module Notare
25
25
  @images = {}
26
26
  @hyperlinks = []
27
27
  @styles = {}
28
+ @table_styles = {}
28
29
  register_built_in_styles
30
+ register_built_in_table_styles
29
31
  end
30
32
 
31
33
  def define_style(name, **properties)
@@ -36,6 +38,14 @@ module Notare
36
38
  @styles[name]
37
39
  end
38
40
 
41
+ def define_table_style(name, **properties)
42
+ @table_styles[name] = TableStyle.new(name, **properties)
43
+ end
44
+
45
+ def table_style(name)
46
+ @table_styles[name]
47
+ end
48
+
39
49
  def save(path)
40
50
  Package.new(self).save(path)
41
51
  end
@@ -78,13 +88,13 @@ module Notare
78
88
  def next_image_rid
79
89
  # rId1 = styles.xml (always present)
80
90
  # rId2 = numbering.xml (if lists present)
81
- # rId3+ = images, then hyperlinks
91
+ # rId3+ = images and hyperlinks share the same ID space
82
92
  base = @has_lists ? 3 : 2
83
- "rId#{base + @images.size}"
93
+ "rId#{base + @images.size + @hyperlinks.size}"
84
94
  end
85
95
 
86
96
  def next_hyperlink_rid
87
- # Hyperlinks come after images
97
+ # Images and hyperlinks share the same ID space
88
98
  base = @has_lists ? 3 : 2
89
99
  "rId#{base + @images.size + @hyperlinks.size}"
90
100
  end
@@ -104,5 +114,13 @@ module Notare
104
114
  define_style :quote, italic: true, color: "666666", indent: 720
105
115
  define_style :code, font: "Courier New", size: 10
106
116
  end
117
+
118
+ def register_built_in_table_styles
119
+ define_table_style :grid,
120
+ borders: { style: "single", color: "000000", size: 4 }
121
+
122
+ define_table_style :borderless,
123
+ borders: :none
124
+ end
107
125
  end
108
126
  end
@@ -3,11 +3,14 @@
3
3
  module Notare
4
4
  module Nodes
5
5
  class Table < Base
6
- attr_reader :rows
6
+ attr_reader :rows, :style, :layout, :columns
7
7
 
8
- def initialize
9
- super
8
+ def initialize(style: nil, layout: nil, columns: nil)
9
+ super()
10
10
  @rows = []
11
+ @style = style
12
+ @layout = layout
13
+ @columns = columns
11
14
  end
12
15
 
13
16
  def add_row(row)
@@ -3,11 +3,12 @@
3
3
  module Notare
4
4
  module Nodes
5
5
  class TableCell < Base
6
- attr_reader :runs
6
+ attr_reader :runs, :width
7
7
 
8
- def initialize
9
- super
8
+ def initialize(width: nil)
9
+ super()
10
10
  @runs = []
11
+ @width = width
11
12
  end
12
13
 
13
14
  def add_run(run)
@@ -59,7 +59,7 @@ module Notare
59
59
  end
60
60
 
61
61
  def styles_xml
62
- Xml::StylesXml.new(@document.styles).to_xml
62
+ Xml::StylesXml.new(@document.styles, @document.table_styles).to_xml
63
63
  end
64
64
 
65
65
  def numbering_xml
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notare
4
+ class TableStyle
5
+ attr_reader :name, :borders, :shading, :cell_margins, :align
6
+
7
+ BORDER_STYLES = %w[single double dotted dashed triple none nil].freeze
8
+ BORDER_POSITIONS = %i[top bottom left right insideH insideV].freeze
9
+ ALIGNMENTS = %i[left center right].freeze
10
+
11
+ def initialize(name, borders: nil, shading: nil, cell_margins: nil, align: nil)
12
+ @name = name
13
+ @borders = normalize_borders(borders)
14
+ @shading = normalize_color(shading)
15
+ @cell_margins = normalize_cell_margins(cell_margins)
16
+ @align = validate_align(align)
17
+ end
18
+
19
+ def style_id
20
+ name.to_s.split("_").map(&:capitalize).join
21
+ end
22
+
23
+ def display_name
24
+ name.to_s.split("_").map(&:capitalize).join(" ")
25
+ end
26
+
27
+ private
28
+
29
+ def normalize_borders(borders)
30
+ return nil if borders.nil?
31
+ return :none if borders == :none
32
+
33
+ # Check if it's a per-edge configuration
34
+ if borders.keys.any? { |k| BORDER_POSITIONS.include?(k) }
35
+ borders.transform_values { |v| normalize_single_border(v) }
36
+ else
37
+ # Single border config applied to all edges
38
+ normalize_single_border(borders)
39
+ end
40
+ end
41
+
42
+ def normalize_single_border(border)
43
+ return :none if border == :none || border[:style] == "none"
44
+
45
+ style = border[:style] || "single"
46
+ unless BORDER_STYLES.include?(style)
47
+ raise ArgumentError, "Invalid border style: #{style}. Use #{BORDER_STYLES.join(", ")}"
48
+ end
49
+
50
+ {
51
+ style: style,
52
+ color: normalize_color(border[:color]) || "000000",
53
+ size: border[:size] || 4
54
+ }
55
+ end
56
+
57
+ def normalize_color(color)
58
+ return nil if color.nil?
59
+
60
+ hex = color.to_s.sub(/^#/, "").upcase
61
+ return hex if hex.match?(/\A[0-9A-F]{6}\z/)
62
+
63
+ raise ArgumentError, "Invalid color: #{color}. Use 6-digit hex (e.g., 'FF0000')"
64
+ end
65
+
66
+ def normalize_cell_margins(margins)
67
+ return nil if margins.nil?
68
+
69
+ if margins.is_a?(Hash)
70
+ margins.slice(:top, :bottom, :left, :right)
71
+ else
72
+ margins.to_i
73
+ end
74
+ end
75
+
76
+ def validate_align(align)
77
+ return nil if align.nil?
78
+ return align if ALIGNMENTS.include?(align)
79
+
80
+ raise ArgumentError, "Invalid alignment: #{align}. Use #{ALIGNMENTS.join(", ")}"
81
+ end
82
+ end
83
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Notare
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.5"
5
5
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notare
4
+ module WidthParser
5
+ TWIPS_PER_INCH = 1440
6
+ TWIPS_PER_CM = 567
7
+ PCT_MULTIPLIER = 50
8
+
9
+ ParsedWidth = Struct.new(:value, :type, keyword_init: true)
10
+
11
+ def self.parse(value)
12
+ case value
13
+ when :auto, nil
14
+ ParsedWidth.new(value: 0, type: "auto")
15
+ when Integer
16
+ ParsedWidth.new(value: value, type: "dxa")
17
+ when /\A(\d+(?:\.\d+)?)\s*in\z/i
18
+ twips = (::Regexp.last_match(1).to_f * TWIPS_PER_INCH).to_i
19
+ ParsedWidth.new(value: twips, type: "dxa")
20
+ when /\A(\d+(?:\.\d+)?)\s*cm\z/i
21
+ twips = (::Regexp.last_match(1).to_f * TWIPS_PER_CM).to_i
22
+ ParsedWidth.new(value: twips, type: "dxa")
23
+ when /\A(\d+(?:\.\d+)?)\s*%\z/
24
+ pct = (::Regexp.last_match(1).to_f * PCT_MULTIPLIER).to_i
25
+ ParsedWidth.new(value: pct, type: "pct")
26
+ else
27
+ raise ArgumentError, "Invalid width: #{value}. Use '2in', '5cm', '50%', :auto, or integer twips."
28
+ end
29
+ end
30
+ end
31
+ end
@@ -163,37 +163,105 @@ module Notare
163
163
  end
164
164
 
165
165
  def render_table(xml, table)
166
- column_count = table.rows.first&.cells&.size || 1
167
- col_width = 5000 / column_count
166
+ column_widths = compute_column_widths(table)
168
167
 
169
168
  xml["w"].tbl do
170
169
  xml["w"].tblPr do
171
- xml["w"].tblW("w:w" => "5000", "w:type" => "pct")
172
- xml["w"].tblBorders do
173
- %w[top left bottom right insideH insideV].each do |border|
174
- xml["w"].send(border, "w:val" => "single", "w:sz" => "4", "w:space" => "0", "w:color" => "000000")
170
+ render_table_width(xml, column_widths)
171
+ render_table_layout(xml, table.layout)
172
+ if table.style
173
+ xml["w"].tblStyle("w:val" => table.style.style_id)
174
+ else
175
+ xml["w"].tblBorders do
176
+ %w[top left bottom right insideH insideV].each do |border|
177
+ xml["w"].send(border, "w:val" => "single", "w:sz" => "4", "w:space" => "0", "w:color" => "000000")
178
+ end
175
179
  end
176
180
  end
177
181
  end
178
- xml["w"].tblGrid do
179
- column_count.times do
180
- xml["w"].gridCol("w:w" => col_width.to_s)
181
- end
182
+ render_table_grid(xml, column_widths)
183
+ table.rows.each { |row| render_table_row(xml, row, column_widths) }
184
+ end
185
+ end
186
+
187
+ def compute_column_widths(table)
188
+ if table.columns
189
+ table.columns.map { |c| WidthParser.parse(c) }
190
+ elsif table.layout == :auto
191
+ first_row = table.rows.first
192
+ cell_count = first_row&.cells&.size || 1
193
+ Array.new(cell_count) { WidthParser::ParsedWidth.new(value: 0, type: "auto") }
194
+ else
195
+ infer_widths_from_first_row(table)
196
+ end
197
+ end
198
+
199
+ def infer_widths_from_first_row(table)
200
+ first_row = table.rows.first
201
+ return [WidthParser::ParsedWidth.new(value: 5000, type: "pct")] unless first_row
202
+
203
+ cells = first_row.cells
204
+ has_explicit_widths = cells.any?(&:width)
205
+
206
+ if has_explicit_widths
207
+ cells.map do |cell|
208
+ cell.width ? WidthParser.parse(cell.width) : WidthParser::ParsedWidth.new(value: 0, type: "auto")
209
+ end
210
+ else
211
+ col_width = 5000 / cells.size
212
+ cells.map { WidthParser::ParsedWidth.new(value: col_width, type: "pct") }
213
+ end
214
+ end
215
+
216
+ def render_table_width(xml, column_widths)
217
+ if column_widths.all? { |w| w.type == "pct" }
218
+ total = column_widths.sum(&:value)
219
+ xml["w"].tblW("w:w" => total.to_s, "w:type" => "pct")
220
+ elsif column_widths.all? { |w| w.type == "dxa" }
221
+ total = column_widths.sum(&:value)
222
+ xml["w"].tblW("w:w" => total.to_s, "w:type" => "dxa")
223
+ else
224
+ xml["w"].tblW("w:w" => "0", "w:type" => "auto")
225
+ end
226
+ end
227
+
228
+ def render_table_layout(xml, layout)
229
+ return unless layout
230
+
231
+ layout_type = layout == :auto ? "autofit" : "fixed"
232
+ xml["w"].tblLayout("w:type" => layout_type)
233
+ end
234
+
235
+ def render_table_grid(xml, column_widths)
236
+ xml["w"].tblGrid do
237
+ column_widths.each do |width|
238
+ grid_width = case width.type
239
+ when "pct" then pct_to_approximate_dxa(width.value)
240
+ when "auto" then 1440 # Default 1 inch for auto columns
241
+ else width.value
242
+ end
243
+ xml["w"].gridCol("w:w" => grid_width.to_s)
182
244
  end
183
- table.rows.each { |row| render_table_row(xml, row, col_width) }
184
245
  end
185
246
  end
186
247
 
187
- def render_table_row(xml, row, col_width)
248
+ # Convert percentage (in fiftieths) to approximate twips
249
+ # Assumes 6.5 inch content width = 9360 twips
250
+ def pct_to_approximate_dxa(pct_value)
251
+ (pct_value * 9360 / 5000.0).to_i
252
+ end
253
+
254
+ def render_table_row(xml, row, column_widths)
188
255
  xml["w"].tr do
189
- row.cells.each { |cell| render_table_cell(xml, cell, col_width) }
256
+ row.cells.each_with_index { |cell, idx| render_table_cell(xml, cell, column_widths[idx]) }
190
257
  end
191
258
  end
192
259
 
193
- def render_table_cell(xml, cell, col_width)
260
+ def render_table_cell(xml, cell, column_width)
261
+ width = column_width || WidthParser::ParsedWidth.new(value: 0, type: "auto")
194
262
  xml["w"].tc do
195
263
  xml["w"].tcPr do
196
- xml["w"].tcW("w:w" => col_width.to_s, "w:type" => "pct")
264
+ xml["w"].tcW("w:w" => width.value.to_s, "w:type" => width.type)
197
265
  end
198
266
  xml["w"].p do
199
267
  cell.runs.each { |run| render_run(xml, run) }
@@ -12,8 +12,15 @@ module Notare
12
12
  justify: "both"
13
13
  }.freeze
14
14
 
15
- def initialize(styles)
15
+ TABLE_ALIGNMENT_MAP = {
16
+ left: "left",
17
+ center: "center",
18
+ right: "right"
19
+ }.freeze
20
+
21
+ def initialize(styles, table_styles = {})
16
22
  @styles = styles
23
+ @table_styles = table_styles
17
24
  end
18
25
 
19
26
  def to_xml
@@ -24,6 +31,12 @@ module Notare
24
31
  @styles.each_value do |style|
25
32
  render_style(xml, style)
26
33
  end
34
+
35
+ render_table_normal_style(xml) if @table_styles.any?
36
+
37
+ @table_styles.each_value do |style|
38
+ render_table_style(xml, style)
39
+ end
27
40
  end
28
41
  end
29
42
  builder.to_xml
@@ -46,8 +59,12 @@ module Notare
46
59
  xml["w"].pPr do
47
60
  xml["w"].jc("w:val" => ALIGNMENT_MAP[style.align]) if style.align
48
61
  xml["w"].ind("w:left" => style.indent.to_s) if style.indent
49
- xml["w"].spacing("w:before" => style.spacing_before.to_s) if style.spacing_before
50
- xml["w"].spacing("w:after" => style.spacing_after.to_s) if style.spacing_after
62
+ if style.spacing_before || style.spacing_after
63
+ spacing_attrs = {}
64
+ spacing_attrs["w:before"] = style.spacing_before.to_s if style.spacing_before
65
+ spacing_attrs["w:after"] = style.spacing_after.to_s if style.spacing_after
66
+ xml["w"].spacing(spacing_attrs)
67
+ end
51
68
  end
52
69
  end
53
70
 
@@ -63,6 +80,74 @@ module Notare
63
80
  xml["w"].highlight("w:val" => style.highlight) if style.highlight
64
81
  end
65
82
  end
83
+
84
+ def render_table_normal_style(xml)
85
+ xml["w"].style("w:type" => "table", "w:default" => "1", "w:styleId" => "TableNormal") do
86
+ xml["w"].name("w:val" => "Normal Table")
87
+ xml["w"].tblPr do
88
+ xml["w"].tblCellMar do
89
+ xml["w"].top("w:w" => "0", "w:type" => "dxa")
90
+ xml["w"].left("w:w" => "108", "w:type" => "dxa")
91
+ xml["w"].bottom("w:w" => "0", "w:type" => "dxa")
92
+ xml["w"].right("w:w" => "108", "w:type" => "dxa")
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def render_table_style(xml, style)
99
+ xml["w"].style("w:type" => "table", "w:styleId" => style.style_id) do
100
+ xml["w"].name("w:val" => style.display_name)
101
+ xml["w"].basedOn("w:val" => "TableNormal")
102
+
103
+ xml["w"].tblPr do
104
+ render_table_borders(xml, style.borders) if style.borders
105
+ render_table_shading(xml, style.shading) if style.shading
106
+ render_table_cell_margins(xml, style.cell_margins) if style.cell_margins
107
+ xml["w"].jc("w:val" => TABLE_ALIGNMENT_MAP[style.align]) if style.align
108
+ end
109
+ end
110
+ end
111
+
112
+ def render_table_borders(xml, borders)
113
+ xml["w"].tblBorders do
114
+ %i[top left bottom right insideH insideV].each do |pos|
115
+ border = borders == :none ? :none : (borders[pos] || borders)
116
+ render_single_border(xml, pos, border)
117
+ end
118
+ end
119
+ end
120
+
121
+ def render_single_border(xml, position, border)
122
+ if border == :none
123
+ xml["w"].send(position, "w:val" => "nil")
124
+ else
125
+ xml["w"].send(position,
126
+ "w:val" => border[:style],
127
+ "w:sz" => border[:size].to_s,
128
+ "w:space" => "0",
129
+ "w:color" => border[:color])
130
+ end
131
+ end
132
+
133
+ def render_table_shading(xml, color)
134
+ xml["w"].shd("w:val" => "clear", "w:color" => "auto", "w:fill" => color)
135
+ end
136
+
137
+ def render_table_cell_margins(xml, margins)
138
+ xml["w"].tblCellMar do
139
+ if margins.is_a?(Hash)
140
+ xml["w"].top("w:w" => margins[:top].to_s, "w:type" => "dxa") if margins[:top]
141
+ xml["w"].left("w:w" => margins[:left].to_s, "w:type" => "dxa") if margins[:left]
142
+ xml["w"].bottom("w:w" => margins[:bottom].to_s, "w:type" => "dxa") if margins[:bottom]
143
+ xml["w"].right("w:w" => margins[:right].to_s, "w:type" => "dxa") if margins[:right]
144
+ else
145
+ %i[top left bottom right].each do |side|
146
+ xml["w"].send(side, "w:w" => margins.to_s, "w:type" => "dxa")
147
+ end
148
+ end
149
+ end
150
+ end
66
151
  end
67
152
  end
68
153
  end
data/lib/notare.rb CHANGED
@@ -16,6 +16,8 @@ require_relative "notare/nodes/table_row"
16
16
  require_relative "notare/nodes/table_cell"
17
17
  require_relative "notare/image_dimensions"
18
18
  require_relative "notare/style"
19
+ require_relative "notare/table_style"
20
+ require_relative "notare/width_parser"
19
21
  require_relative "notare/xml/content_types"
20
22
  require_relative "notare/xml/relationships"
21
23
  require_relative "notare/xml/document_xml"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notare
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathias
@@ -133,7 +133,9 @@ files:
133
133
  - lib/notare/nodes/table_row.rb
134
134
  - lib/notare/package.rb
135
135
  - lib/notare/style.rb
136
+ - lib/notare/table_style.rb
136
137
  - lib/notare/version.rb
138
+ - lib/notare/width_parser.rb
137
139
  - lib/notare/xml/content_types.rb
138
140
  - lib/notare/xml/document_xml.rb
139
141
  - lib/notare/xml/numbering.rb