notare 0.0.2 → 0.0.4
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 +4 -4
- data/README.md +228 -3
- data/lib/notare/builder.rb +62 -15
- data/lib/notare/document.rb +44 -3
- data/lib/notare/nodes/break.rb +18 -0
- data/lib/notare/nodes/hyperlink.rb +20 -0
- data/lib/notare/nodes/list_item.rb +3 -2
- data/lib/notare/nodes/run.rb +6 -2
- data/lib/notare/nodes/table.rb +4 -3
- data/lib/notare/package.rb +9 -3
- data/lib/notare/style.rb +20 -5
- data/lib/notare/table_style.rb +83 -0
- data/lib/notare/version.rb +1 -1
- data/lib/notare/xml/document_xml.rb +42 -5
- data/lib/notare/xml/numbering.rb +24 -11
- data/lib/notare/xml/relationships.rb +13 -1
- data/lib/notare/xml/styles_xml.rb +67 -1
- data/lib/notare.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1dd4e9ab394b198d4a9ddd59eb40fac35c865159e527fe14a90113bba1071fd0
|
|
4
|
+
data.tar.gz: e10d211085e797c80850ab3433b8802270df96bc01cd3d2ac348da6f17c675ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba2c27ab7d6ad5a6f5b5239f54504e8352a5fee52374685f7e09d61284781ba93e9353a0c75f07a3e047571cc55d598c3764beb3c1d08663e54d11119e515fa5
|
|
7
|
+
data.tar.gz: b42bc57c20a639882a8912b1da7759b2c131443ce840f3d901a1a29a70a86e4f8238ce0b9d38ed688e0332fb99b77f7c5edcebffc611302273ede404c2a72197
|
data/README.md
CHANGED
|
@@ -58,6 +58,8 @@ Notare::Document.create("output.docx") do |doc|
|
|
|
58
58
|
doc.i { doc.text "italic" }
|
|
59
59
|
doc.text " and "
|
|
60
60
|
doc.u { doc.text "underlined" }
|
|
61
|
+
doc.text " and "
|
|
62
|
+
doc.s { doc.text "strikethrough" }
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
# Nested formatting (bold + italic)
|
|
@@ -66,6 +68,13 @@ Notare::Document.create("output.docx") do |doc|
|
|
|
66
68
|
doc.i { doc.text "bold and italic" }
|
|
67
69
|
end
|
|
68
70
|
end
|
|
71
|
+
|
|
72
|
+
# Show edits (strikethrough old, bold new)
|
|
73
|
+
doc.p do
|
|
74
|
+
doc.s { doc.text "old text" }
|
|
75
|
+
doc.text " "
|
|
76
|
+
doc.b { doc.text "new text" }
|
|
77
|
+
end
|
|
69
78
|
end
|
|
70
79
|
```
|
|
71
80
|
|
|
@@ -96,6 +105,21 @@ Notare includes built-in styles and supports custom style definitions.
|
|
|
96
105
|
|
|
97
106
|
#### Built-in Styles
|
|
98
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
|
+
|
|
99
123
|
```ruby
|
|
100
124
|
Notare::Document.create("output.docx") do |doc|
|
|
101
125
|
doc.p "This is a title", style: :title
|
|
@@ -146,10 +170,14 @@ end
|
|
|
146
170
|
- `bold: true/false`
|
|
147
171
|
- `italic: true/false`
|
|
148
172
|
- `underline: true/false`
|
|
173
|
+
- `strike: true/false` - strikethrough
|
|
174
|
+
- `highlight: "yellow"` - text highlight (see colors below)
|
|
149
175
|
- `color: "FF0000"` (hex RGB)
|
|
150
176
|
- `size: 14` (points)
|
|
151
177
|
- `font: "Arial"` (font family)
|
|
152
178
|
|
|
179
|
+
**Highlight colors:** `black`, `blue`, `cyan`, `darkBlue`, `darkCyan`, `darkGray`, `darkGreen`, `darkMagenta`, `darkRed`, `darkYellow`, `green`, `lightGray`, `magenta`, `red`, `white`, `yellow`
|
|
180
|
+
|
|
153
181
|
**Paragraph properties:**
|
|
154
182
|
- `align: :left / :center / :right / :justify`
|
|
155
183
|
- `indent: 720` (twips, 1 inch = 1440 twips)
|
|
@@ -182,6 +210,40 @@ Notare::Document.create("output.docx") do |doc|
|
|
|
182
210
|
end
|
|
183
211
|
```
|
|
184
212
|
|
|
213
|
+
#### Nested Lists
|
|
214
|
+
|
|
215
|
+
Lists can be nested inside list items. Mixed types (bullets inside numbered or vice versa) are supported.
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
Notare::Document.create("output.docx") do |doc|
|
|
219
|
+
doc.ol do
|
|
220
|
+
doc.li "First item"
|
|
221
|
+
doc.li "Second item" do
|
|
222
|
+
doc.ul do
|
|
223
|
+
doc.li "Nested bullet A"
|
|
224
|
+
doc.li "Nested bullet B"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
doc.li "Third item"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Deeply nested
|
|
231
|
+
doc.ul do
|
|
232
|
+
doc.li "Level 0"
|
|
233
|
+
doc.li "Has children" do
|
|
234
|
+
doc.ul do
|
|
235
|
+
doc.li "Level 1"
|
|
236
|
+
doc.li "Goes deeper" do
|
|
237
|
+
doc.ul do
|
|
238
|
+
doc.li "Level 2"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
185
247
|
### Tables
|
|
186
248
|
|
|
187
249
|
```ruby
|
|
@@ -199,6 +261,77 @@ Notare::Document.create("output.docx") do |doc|
|
|
|
199
261
|
end
|
|
200
262
|
```
|
|
201
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
|
+
|
|
202
335
|
### Images
|
|
203
336
|
|
|
204
337
|
Images can be added to paragraphs, table cells, and list items. Supports PNG and JPEG formats.
|
|
@@ -246,6 +379,91 @@ Notare::Document.create("output.docx") do |doc|
|
|
|
246
379
|
end
|
|
247
380
|
```
|
|
248
381
|
|
|
382
|
+
### Line Breaks
|
|
383
|
+
|
|
384
|
+
Use `br` for soft line breaks within a paragraph (text continues in the same paragraph but on a new line):
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
Notare::Document.create("output.docx") do |doc|
|
|
388
|
+
doc.p do
|
|
389
|
+
doc.text "Line one"
|
|
390
|
+
doc.br
|
|
391
|
+
doc.text "Line two (same paragraph)"
|
|
392
|
+
doc.br
|
|
393
|
+
doc.text "Line three"
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Useful for addresses
|
|
397
|
+
doc.p do
|
|
398
|
+
doc.b { doc.text "Address:" }
|
|
399
|
+
doc.br
|
|
400
|
+
doc.text "123 Main Street"
|
|
401
|
+
doc.br
|
|
402
|
+
doc.text "Anytown, ST 12345"
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Page Breaks
|
|
408
|
+
|
|
409
|
+
Use `page_break` to force content to start on a new page:
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
Notare::Document.create("output.docx") do |doc|
|
|
413
|
+
doc.h1 "Chapter 1"
|
|
414
|
+
doc.p "Content of chapter 1..."
|
|
415
|
+
|
|
416
|
+
doc.page_break
|
|
417
|
+
|
|
418
|
+
doc.h1 "Chapter 2"
|
|
419
|
+
doc.p "This starts on a new page."
|
|
420
|
+
end
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Hyperlinks
|
|
424
|
+
|
|
425
|
+
Add clickable links with `link`:
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
Notare::Document.create("output.docx") do |doc|
|
|
429
|
+
# Link with custom text
|
|
430
|
+
doc.p do
|
|
431
|
+
doc.text "Visit "
|
|
432
|
+
doc.link "https://example.com", "our website"
|
|
433
|
+
doc.text " for more info."
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Link showing the URL as text
|
|
437
|
+
doc.p do
|
|
438
|
+
doc.text "URL: "
|
|
439
|
+
doc.link "https://example.com"
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Link with formatted content
|
|
443
|
+
doc.p do
|
|
444
|
+
doc.link "https://github.com" do
|
|
445
|
+
doc.b { doc.text "GitHub" }
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Links in lists
|
|
450
|
+
doc.ul do
|
|
451
|
+
doc.li do
|
|
452
|
+
doc.link "https://ruby-lang.org", "Ruby"
|
|
453
|
+
end
|
|
454
|
+
doc.li do
|
|
455
|
+
doc.link "https://rubyonrails.org", "Rails"
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Email links
|
|
460
|
+
doc.p do
|
|
461
|
+
doc.text "Contact: "
|
|
462
|
+
doc.link "mailto:hello@example.com", "hello@example.com"
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
```
|
|
466
|
+
|
|
249
467
|
### Complete Example
|
|
250
468
|
|
|
251
469
|
```ruby
|
|
@@ -296,12 +514,19 @@ end
|
|
|
296
514
|
| `b { }` | Bold formatting |
|
|
297
515
|
| `i { }` | Italic formatting |
|
|
298
516
|
| `u { }` | Underline formatting |
|
|
517
|
+
| `s { }` | Strikethrough formatting |
|
|
518
|
+
| `br` | Line break (soft break within paragraph) |
|
|
519
|
+
| `page_break` | Page break (force new page) |
|
|
520
|
+
| `link(url, text)` | Hyperlink with custom text |
|
|
521
|
+
| `link(url) { }` | Hyperlink with block content |
|
|
299
522
|
| `define_style(name, **props)` | Define a custom style |
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
523
|
+
| `define_table_style(name, **props)` | Define a custom table style |
|
|
524
|
+
| `ul { }` | Bullet list (can be nested) |
|
|
525
|
+
| `ol { }` | Numbered list (can be nested) |
|
|
302
526
|
| `li(text)` | List item with text |
|
|
303
527
|
| `li { }` | List item with block content |
|
|
304
|
-
| `
|
|
528
|
+
| `li(text) { }` | List item with text and nested content |
|
|
529
|
+
| `table(style:) { }` | Table with optional style |
|
|
305
530
|
| `tr { }` | Table row |
|
|
306
531
|
| `td(text)` | Table cell with text |
|
|
307
532
|
| `td { }` | Table cell with block content |
|
data/lib/notare/builder.rb
CHANGED
|
@@ -60,6 +60,30 @@ module Notare
|
|
|
60
60
|
with_format(:underline, &block)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def s(&block)
|
|
64
|
+
with_format(:strike, &block)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def br
|
|
68
|
+
@current_target.add_run(Nodes::Break.new(type: :line))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def page_break
|
|
72
|
+
@nodes << Nodes::Break.new(type: :page)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def link(url, text = nil, &block)
|
|
76
|
+
hyperlink = register_hyperlink(url)
|
|
77
|
+
if block
|
|
78
|
+
with_target(hyperlink, &block)
|
|
79
|
+
elsif text
|
|
80
|
+
hyperlink.add_run(Nodes::Run.new(text, underline: true, color: "0000FF"))
|
|
81
|
+
else
|
|
82
|
+
hyperlink.add_run(Nodes::Run.new(url, underline: true, color: "0000FF"))
|
|
83
|
+
end
|
|
84
|
+
@current_target.add_run(hyperlink)
|
|
85
|
+
end
|
|
86
|
+
|
|
63
87
|
def ul(&block)
|
|
64
88
|
list(:bullet, &block)
|
|
65
89
|
end
|
|
@@ -69,17 +93,15 @@ module Notare
|
|
|
69
93
|
end
|
|
70
94
|
|
|
71
95
|
def li(text = nil, &block)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
item.add_run(Nodes::Run.new(text, **current_formatting))
|
|
77
|
-
end
|
|
96
|
+
current_type = @list_type_stack.last
|
|
97
|
+
item = Nodes::ListItem.new([], list_type: current_type, num_id: @current_list.num_id, level: @list_level)
|
|
98
|
+
item.add_run(Nodes::Run.new(text, **current_formatting)) if text
|
|
99
|
+
with_target(item, &block) if block
|
|
78
100
|
@current_list.add_item(item)
|
|
79
101
|
end
|
|
80
102
|
|
|
81
|
-
def table(&block)
|
|
82
|
-
tbl = Nodes::Table.new
|
|
103
|
+
def table(style: nil, &block)
|
|
104
|
+
tbl = Nodes::Table.new(style: resolve_table_style(style))
|
|
83
105
|
previous_table = @current_table
|
|
84
106
|
@current_table = tbl
|
|
85
107
|
block.call
|
|
@@ -110,14 +132,31 @@ module Notare
|
|
|
110
132
|
|
|
111
133
|
def list(type, &block)
|
|
112
134
|
@num_id_counter ||= 0
|
|
113
|
-
@
|
|
135
|
+
@list_level ||= 0
|
|
136
|
+
@list_type_stack ||= []
|
|
114
137
|
|
|
115
|
-
list_node = Nodes::List.new(type: type, num_id: @num_id_counter)
|
|
116
138
|
previous_list = @current_list
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
nested = !previous_list.nil?
|
|
140
|
+
|
|
141
|
+
if nested
|
|
142
|
+
# Nested list: reuse parent list, push new type, increment level
|
|
143
|
+
@list_level += 1
|
|
144
|
+
@list_type_stack.push(type)
|
|
145
|
+
block.call
|
|
146
|
+
@list_type_stack.pop
|
|
147
|
+
@list_level -= 1
|
|
148
|
+
else
|
|
149
|
+
# Top-level list: new List node
|
|
150
|
+
@num_id_counter += 1
|
|
151
|
+
mark_has_lists!
|
|
152
|
+
list_node = Nodes::List.new(type: type, num_id: @num_id_counter)
|
|
153
|
+
@list_type_stack.push(type)
|
|
154
|
+
@current_list = list_node
|
|
155
|
+
block.call
|
|
156
|
+
@current_list = previous_list
|
|
157
|
+
@list_type_stack.pop
|
|
158
|
+
@nodes << list_node
|
|
159
|
+
end
|
|
121
160
|
end
|
|
122
161
|
|
|
123
162
|
def with_format(format, &block)
|
|
@@ -139,7 +178,8 @@ module Notare
|
|
|
139
178
|
{
|
|
140
179
|
bold: @format_stack.include?(:bold),
|
|
141
180
|
italic: @format_stack.include?(:italic),
|
|
142
|
-
underline: @format_stack.include?(:underline)
|
|
181
|
+
underline: @format_stack.include?(:underline),
|
|
182
|
+
strike: @format_stack.include?(:strike)
|
|
143
183
|
}
|
|
144
184
|
end
|
|
145
185
|
|
|
@@ -158,5 +198,12 @@ module Notare
|
|
|
158
198
|
|
|
159
199
|
style(style_or_name) || raise(ArgumentError, "Unknown style: #{style_or_name}")
|
|
160
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
|
|
161
208
|
end
|
|
162
209
|
end
|
data/lib/notare/document.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Notare
|
|
|
4
4
|
class Document
|
|
5
5
|
include Builder
|
|
6
6
|
|
|
7
|
-
attr_reader :nodes, :styles
|
|
7
|
+
attr_reader :nodes, :styles, :table_styles, :hyperlinks
|
|
8
8
|
|
|
9
9
|
def self.create(path, &block)
|
|
10
10
|
doc = new
|
|
@@ -21,9 +21,13 @@ module Notare
|
|
|
21
21
|
@current_table = nil
|
|
22
22
|
@current_row = nil
|
|
23
23
|
@num_id_counter = 0
|
|
24
|
+
@has_lists = false
|
|
24
25
|
@images = {}
|
|
26
|
+
@hyperlinks = []
|
|
25
27
|
@styles = {}
|
|
28
|
+
@table_styles = {}
|
|
26
29
|
register_built_in_styles
|
|
30
|
+
register_built_in_table_styles
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def define_style(name, **properties)
|
|
@@ -34,6 +38,14 @@ module Notare
|
|
|
34
38
|
@styles[name]
|
|
35
39
|
end
|
|
36
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
|
+
|
|
37
49
|
def save(path)
|
|
38
50
|
Package.new(self).save(path)
|
|
39
51
|
end
|
|
@@ -42,10 +54,25 @@ module Notare
|
|
|
42
54
|
@nodes.select { |n| n.is_a?(Nodes::List) }
|
|
43
55
|
end
|
|
44
56
|
|
|
57
|
+
def uses_lists?
|
|
58
|
+
@has_lists
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def mark_has_lists!
|
|
62
|
+
@has_lists = true
|
|
63
|
+
end
|
|
64
|
+
|
|
45
65
|
def images
|
|
46
66
|
@images.values
|
|
47
67
|
end
|
|
48
68
|
|
|
69
|
+
def register_hyperlink(url)
|
|
70
|
+
rid = next_hyperlink_rid
|
|
71
|
+
hyperlink = Nodes::Hyperlink.new(url: url, rid: rid)
|
|
72
|
+
@hyperlinks << hyperlink
|
|
73
|
+
hyperlink
|
|
74
|
+
end
|
|
75
|
+
|
|
49
76
|
def register_image(path, width: nil, height: nil)
|
|
50
77
|
return @images[path] if @images[path]
|
|
51
78
|
|
|
@@ -61,11 +88,17 @@ module Notare
|
|
|
61
88
|
def next_image_rid
|
|
62
89
|
# rId1 = styles.xml (always present)
|
|
63
90
|
# rId2 = numbering.xml (if lists present)
|
|
64
|
-
# rId3+ = images
|
|
65
|
-
base =
|
|
91
|
+
# rId3+ = images, then hyperlinks
|
|
92
|
+
base = @has_lists ? 3 : 2
|
|
66
93
|
"rId#{base + @images.size}"
|
|
67
94
|
end
|
|
68
95
|
|
|
96
|
+
def next_hyperlink_rid
|
|
97
|
+
# Hyperlinks come after images
|
|
98
|
+
base = @has_lists ? 3 : 2
|
|
99
|
+
"rId#{base + @images.size + @hyperlinks.size}"
|
|
100
|
+
end
|
|
101
|
+
|
|
69
102
|
def register_built_in_styles
|
|
70
103
|
# Headings (spacing_before ensures they're rendered as paragraph styles)
|
|
71
104
|
define_style :heading1, size: 24, bold: true, spacing_before: 240, spacing_after: 120
|
|
@@ -81,5 +114,13 @@ module Notare
|
|
|
81
114
|
define_style :quote, italic: true, color: "666666", indent: 720
|
|
82
115
|
define_style :code, font: "Courier New", size: 10
|
|
83
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
|
|
84
125
|
end
|
|
85
126
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Nodes
|
|
5
|
+
class Hyperlink < Base
|
|
6
|
+
attr_reader :url, :rid, :runs
|
|
7
|
+
|
|
8
|
+
def initialize(url:, rid:)
|
|
9
|
+
super()
|
|
10
|
+
@url = url
|
|
11
|
+
@rid = rid
|
|
12
|
+
@runs = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_run(run)
|
|
16
|
+
@runs << run
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module Notare
|
|
4
4
|
module Nodes
|
|
5
5
|
class ListItem < Base
|
|
6
|
-
attr_reader :runs, :list_type, :num_id
|
|
6
|
+
attr_reader :runs, :list_type, :num_id, :level
|
|
7
7
|
|
|
8
|
-
def initialize(runs = [], list_type:, num_id:)
|
|
8
|
+
def initialize(runs = [], list_type:, num_id:, level: 0)
|
|
9
9
|
super()
|
|
10
10
|
@runs = runs
|
|
11
11
|
@list_type = list_type
|
|
12
12
|
@num_id = num_id
|
|
13
|
+
@level = level
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def add_run(run)
|
data/lib/notare/nodes/run.rb
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
module Notare
|
|
4
4
|
module Nodes
|
|
5
5
|
class Run < Base
|
|
6
|
-
attr_reader :text, :bold, :italic, :underline, :style
|
|
6
|
+
attr_reader :text, :bold, :italic, :underline, :strike, :highlight, :color, :style
|
|
7
7
|
|
|
8
|
-
def initialize(text, bold: false, italic: false, underline: false,
|
|
8
|
+
def initialize(text, bold: false, italic: false, underline: false,
|
|
9
|
+
strike: false, highlight: nil, color: nil, style: nil)
|
|
9
10
|
super()
|
|
10
11
|
@text = text
|
|
11
12
|
@bold = bold
|
|
12
13
|
@italic = italic
|
|
13
14
|
@underline = underline
|
|
15
|
+
@strike = strike
|
|
16
|
+
@highlight = highlight
|
|
17
|
+
@color = color
|
|
14
18
|
@style = style
|
|
15
19
|
end
|
|
16
20
|
end
|
data/lib/notare/nodes/table.rb
CHANGED
data/lib/notare/package.rb
CHANGED
|
@@ -29,13 +29,17 @@ module Notare
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
31
|
def lists?
|
|
32
|
-
@document.
|
|
32
|
+
@document.uses_lists?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def images
|
|
36
36
|
@document.images
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def hyperlinks
|
|
40
|
+
@document.hyperlinks
|
|
41
|
+
end
|
|
42
|
+
|
|
39
43
|
def content_types_xml
|
|
40
44
|
Xml::ContentTypes.new(has_numbering: lists?, images: images, has_styles: true).to_xml
|
|
41
45
|
end
|
|
@@ -45,7 +49,9 @@ module Notare
|
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
def document_relationships_xml
|
|
48
|
-
Xml::DocumentRelationships.new(
|
|
52
|
+
Xml::DocumentRelationships.new(
|
|
53
|
+
has_numbering: lists?, images: images, hyperlinks: hyperlinks, has_styles: true
|
|
54
|
+
).to_xml
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def document_xml
|
|
@@ -53,7 +59,7 @@ module Notare
|
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
def styles_xml
|
|
56
|
-
Xml::StylesXml.new(@document.styles).to_xml
|
|
62
|
+
Xml::StylesXml.new(@document.styles, @document.table_styles).to_xml
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def numbering_xml
|
data/lib/notare/style.rb
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Notare
|
|
4
4
|
class Style
|
|
5
|
-
attr_reader :name, :bold, :italic, :underline, :color, :size, :font,
|
|
5
|
+
attr_reader :name, :bold, :italic, :underline, :strike, :highlight, :color, :size, :font,
|
|
6
6
|
:align, :indent, :spacing_before, :spacing_after
|
|
7
7
|
|
|
8
8
|
ALIGNMENTS = %i[left center right justify].freeze
|
|
9
|
+
HIGHLIGHT_COLORS = %w[
|
|
10
|
+
black blue cyan darkBlue darkCyan darkGray darkGreen darkMagenta
|
|
11
|
+
darkRed darkYellow green lightGray magenta red white yellow
|
|
12
|
+
].freeze
|
|
9
13
|
|
|
10
|
-
def initialize(name, bold: nil, italic: nil, underline: nil,
|
|
11
|
-
|
|
12
|
-
spacing_before: nil, spacing_after: nil)
|
|
14
|
+
def initialize(name, bold: nil, italic: nil, underline: nil, strike: nil,
|
|
15
|
+
highlight: nil, color: nil, size: nil, font: nil, align: nil,
|
|
16
|
+
indent: nil, spacing_before: nil, spacing_after: nil)
|
|
13
17
|
@name = name
|
|
14
18
|
@bold = bold
|
|
15
19
|
@italic = italic
|
|
16
20
|
@underline = underline
|
|
21
|
+
@strike = strike
|
|
22
|
+
@highlight = validate_highlight(highlight)
|
|
17
23
|
@color = normalize_color(color)
|
|
18
24
|
@size = size
|
|
19
25
|
@font = font
|
|
@@ -36,7 +42,7 @@ module Notare
|
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def text_properties?
|
|
39
|
-
!!(bold || italic || underline || color || size || font)
|
|
45
|
+
!!(bold || italic || underline || strike || highlight || color || size || font)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# Size in half-points for OOXML (14pt = 28 half-points)
|
|
@@ -61,5 +67,14 @@ module Notare
|
|
|
61
67
|
|
|
62
68
|
raise ArgumentError, "Invalid alignment: #{align}. Use #{ALIGNMENTS.join(", ")}"
|
|
63
69
|
end
|
|
70
|
+
|
|
71
|
+
def validate_highlight(highlight)
|
|
72
|
+
return nil if highlight.nil?
|
|
73
|
+
|
|
74
|
+
color = highlight.to_s
|
|
75
|
+
return color if HIGHLIGHT_COLORS.include?(color)
|
|
76
|
+
|
|
77
|
+
raise ArgumentError, "Invalid highlight color: #{highlight}. Use one of: #{HIGHLIGHT_COLORS.join(", ")}"
|
|
78
|
+
end
|
|
64
79
|
end
|
|
65
80
|
end
|
|
@@ -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
|
data/lib/notare/version.rb
CHANGED
|
@@ -37,6 +37,16 @@ module Notare
|
|
|
37
37
|
render_list(xml, node)
|
|
38
38
|
when Nodes::Table
|
|
39
39
|
render_table(xml, node)
|
|
40
|
+
when Nodes::Break
|
|
41
|
+
render_page_break(xml, node)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_page_break(xml, _node)
|
|
46
|
+
xml["w"].p do
|
|
47
|
+
xml["w"].r do
|
|
48
|
+
xml["w"].br("w:type" => "page")
|
|
49
|
+
end
|
|
40
50
|
end
|
|
41
51
|
end
|
|
42
52
|
|
|
@@ -59,7 +69,7 @@ module Notare
|
|
|
59
69
|
xml["w"].p do
|
|
60
70
|
xml["w"].pPr do
|
|
61
71
|
xml["w"].numPr do
|
|
62
|
-
xml["w"].ilvl("w:val" =>
|
|
72
|
+
xml["w"].ilvl("w:val" => item.level.to_s)
|
|
63
73
|
xml["w"].numId("w:val" => item.num_id.to_s)
|
|
64
74
|
end
|
|
65
75
|
end
|
|
@@ -71,19 +81,42 @@ module Notare
|
|
|
71
81
|
case run
|
|
72
82
|
when Nodes::Image
|
|
73
83
|
render_image(xml, run)
|
|
84
|
+
when Nodes::Break
|
|
85
|
+
render_break(xml, run)
|
|
86
|
+
when Nodes::Hyperlink
|
|
87
|
+
render_hyperlink(xml, run)
|
|
74
88
|
when Nodes::Run
|
|
75
89
|
render_text_run(xml, run)
|
|
76
90
|
end
|
|
77
91
|
end
|
|
78
92
|
|
|
93
|
+
def render_hyperlink(xml, hyperlink)
|
|
94
|
+
xml["w"].hyperlink("r:id" => hyperlink.rid) do
|
|
95
|
+
hyperlink.runs.each { |run| render_run(xml, run) }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def render_break(xml, break_node)
|
|
100
|
+
xml["w"].r do
|
|
101
|
+
if break_node.page?
|
|
102
|
+
xml["w"].br("w:type" => "page")
|
|
103
|
+
else
|
|
104
|
+
xml["w"].br
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
79
109
|
def render_text_run(xml, run)
|
|
80
110
|
xml["w"].r do
|
|
81
|
-
if run.bold || run.italic || run.underline || run.style
|
|
111
|
+
if run.bold || run.italic || run.underline || run.strike || run.highlight || run.color || run.style
|
|
82
112
|
xml["w"].rPr do
|
|
83
113
|
xml["w"].rStyle("w:val" => run.style.style_id) if run.style
|
|
84
114
|
xml["w"].b if run.bold
|
|
85
115
|
xml["w"].i if run.italic
|
|
86
116
|
xml["w"].u("w:val" => "single") if run.underline
|
|
117
|
+
xml["w"].strike if run.strike
|
|
118
|
+
xml["w"].highlight("w:val" => run.highlight) if run.highlight
|
|
119
|
+
xml["w"].color("w:val" => run.color) if run.color
|
|
87
120
|
end
|
|
88
121
|
end
|
|
89
122
|
xml["w"].t(run.text, "xml:space" => "preserve")
|
|
@@ -136,9 +169,13 @@ module Notare
|
|
|
136
169
|
xml["w"].tbl do
|
|
137
170
|
xml["w"].tblPr do
|
|
138
171
|
xml["w"].tblW("w:w" => "5000", "w:type" => "pct")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
179
|
end
|
|
143
180
|
end
|
|
144
181
|
end
|
data/lib/notare/xml/numbering.rb
CHANGED
|
@@ -4,6 +4,8 @@ module Notare
|
|
|
4
4
|
module Xml
|
|
5
5
|
class Numbering
|
|
6
6
|
NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
7
|
+
BULLET_CHARS = ["•", "○", "■"].freeze
|
|
8
|
+
NUMBER_FORMATS = %w[decimal lowerLetter lowerRoman].freeze
|
|
7
9
|
|
|
8
10
|
def initialize(lists)
|
|
9
11
|
@lists = lists
|
|
@@ -28,13 +30,16 @@ module Notare
|
|
|
28
30
|
|
|
29
31
|
def render_abstract_num(xml, list)
|
|
30
32
|
xml["w"].abstractNum("w:abstractNumId" => list.num_id.to_s) do
|
|
31
|
-
|
|
32
|
-
xml["w"].
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
xml["w"].
|
|
33
|
+
9.times do |level|
|
|
34
|
+
xml["w"].lvl("w:ilvl" => level.to_s) do
|
|
35
|
+
xml["w"].start("w:val" => "1")
|
|
36
|
+
xml["w"].numFmt("w:val" => num_format_for_level(list.type, level))
|
|
37
|
+
xml["w"].lvlText("w:val" => lvl_text_for_level(list.type, level))
|
|
38
|
+
xml["w"].lvlJc("w:val" => "left")
|
|
39
|
+
xml["w"].pPr do
|
|
40
|
+
left = 720 * (level + 1)
|
|
41
|
+
xml["w"].ind("w:left" => left.to_s, "w:hanging" => "360")
|
|
42
|
+
end
|
|
38
43
|
end
|
|
39
44
|
end
|
|
40
45
|
end
|
|
@@ -46,12 +51,20 @@ module Notare
|
|
|
46
51
|
end
|
|
47
52
|
end
|
|
48
53
|
|
|
49
|
-
def
|
|
50
|
-
type == :bullet
|
|
54
|
+
def num_format_for_level(type, level)
|
|
55
|
+
if type == :bullet
|
|
56
|
+
"bullet"
|
|
57
|
+
else
|
|
58
|
+
NUMBER_FORMATS[level % NUMBER_FORMATS.length]
|
|
59
|
+
end
|
|
51
60
|
end
|
|
52
61
|
|
|
53
|
-
def
|
|
54
|
-
type == :bullet
|
|
62
|
+
def lvl_text_for_level(type, level)
|
|
63
|
+
if type == :bullet
|
|
64
|
+
BULLET_CHARS[level % BULLET_CHARS.length]
|
|
65
|
+
else
|
|
66
|
+
"%#{level + 1}."
|
|
67
|
+
end
|
|
55
68
|
end
|
|
56
69
|
end
|
|
57
70
|
end
|
|
@@ -24,10 +24,12 @@ module Notare
|
|
|
24
24
|
STYLES_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
|
|
25
25
|
NUMBERING_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
|
|
26
26
|
IMAGE_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
|
|
27
|
+
HYPERLINK_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
|
27
28
|
|
|
28
|
-
def initialize(has_numbering: false, images: [], has_styles: false)
|
|
29
|
+
def initialize(has_numbering: false, images: [], hyperlinks: [], has_styles: false)
|
|
29
30
|
@has_numbering = has_numbering
|
|
30
31
|
@images = images
|
|
32
|
+
@hyperlinks = hyperlinks
|
|
31
33
|
@has_styles = has_styles
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -60,6 +62,16 @@ module Notare
|
|
|
60
62
|
Target: "media/#{image.filename}"
|
|
61
63
|
)
|
|
62
64
|
end
|
|
65
|
+
|
|
66
|
+
# Hyperlinks come after images
|
|
67
|
+
@hyperlinks.each do |hyperlink|
|
|
68
|
+
xml.Relationship(
|
|
69
|
+
Id: hyperlink.rid,
|
|
70
|
+
Type: HYPERLINK_TYPE,
|
|
71
|
+
Target: hyperlink.url,
|
|
72
|
+
TargetMode: "External"
|
|
73
|
+
)
|
|
74
|
+
end
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
builder.to_xml
|
|
@@ -12,8 +12,15 @@ module Notare
|
|
|
12
12
|
justify: "both"
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
|
-
|
|
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,10 @@ module Notare
|
|
|
24
31
|
@styles.each_value do |style|
|
|
25
32
|
render_style(xml, style)
|
|
26
33
|
end
|
|
34
|
+
|
|
35
|
+
@table_styles.each_value do |style|
|
|
36
|
+
render_table_style(xml, style)
|
|
37
|
+
end
|
|
27
38
|
end
|
|
28
39
|
end
|
|
29
40
|
builder.to_xml
|
|
@@ -59,6 +70,61 @@ module Notare
|
|
|
59
70
|
xml["w"].b if style.bold
|
|
60
71
|
xml["w"].i if style.italic
|
|
61
72
|
xml["w"].u("w:val" => "single") if style.underline
|
|
73
|
+
xml["w"].strike if style.strike
|
|
74
|
+
xml["w"].highlight("w:val" => style.highlight) if style.highlight
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_table_style(xml, style)
|
|
79
|
+
xml["w"].style("w:type" => "table", "w:styleId" => style.style_id) do
|
|
80
|
+
xml["w"].name("w:val" => style.display_name)
|
|
81
|
+
|
|
82
|
+
xml["w"].tblPr do
|
|
83
|
+
render_table_borders(xml, style.borders) if style.borders
|
|
84
|
+
render_table_shading(xml, style.shading) if style.shading
|
|
85
|
+
render_table_cell_margins(xml, style.cell_margins) if style.cell_margins
|
|
86
|
+
xml["w"].jc("w:val" => TABLE_ALIGNMENT_MAP[style.align]) if style.align
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_table_borders(xml, borders)
|
|
92
|
+
xml["w"].tblBorders do
|
|
93
|
+
%i[top left bottom right insideH insideV].each do |pos|
|
|
94
|
+
border = borders == :none ? :none : (borders[pos] || borders)
|
|
95
|
+
render_single_border(xml, pos, border)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def render_single_border(xml, position, border)
|
|
101
|
+
if border == :none
|
|
102
|
+
xml["w"].send(position, "w:val" => "nil")
|
|
103
|
+
else
|
|
104
|
+
xml["w"].send(position,
|
|
105
|
+
"w:val" => border[:style],
|
|
106
|
+
"w:sz" => border[:size].to_s,
|
|
107
|
+
"w:space" => "0",
|
|
108
|
+
"w:color" => border[:color])
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def render_table_shading(xml, color)
|
|
113
|
+
xml["w"].shd("w:val" => "clear", "w:color" => "auto", "w:fill" => color)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def render_table_cell_margins(xml, margins)
|
|
117
|
+
xml["w"].tblCellMar do
|
|
118
|
+
if margins.is_a?(Hash)
|
|
119
|
+
xml["w"].top("w:w" => margins[:top].to_s, "w:type" => "dxa") if margins[:top]
|
|
120
|
+
xml["w"].left("w:w" => margins[:left].to_s, "w:type" => "dxa") if margins[:left]
|
|
121
|
+
xml["w"].bottom("w:w" => margins[:bottom].to_s, "w:type" => "dxa") if margins[:bottom]
|
|
122
|
+
xml["w"].right("w:w" => margins[:right].to_s, "w:type" => "dxa") if margins[:right]
|
|
123
|
+
else
|
|
124
|
+
%i[top left bottom right].each do |side|
|
|
125
|
+
xml["w"].send(side, "w:w" => margins.to_s, "w:type" => "dxa")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
62
128
|
end
|
|
63
129
|
end
|
|
64
130
|
end
|
data/lib/notare.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "nokogiri"
|
|
|
4
4
|
|
|
5
5
|
require_relative "notare/version"
|
|
6
6
|
require_relative "notare/nodes/base"
|
|
7
|
+
require_relative "notare/nodes/break"
|
|
8
|
+
require_relative "notare/nodes/hyperlink"
|
|
7
9
|
require_relative "notare/nodes/run"
|
|
8
10
|
require_relative "notare/nodes/image"
|
|
9
11
|
require_relative "notare/nodes/paragraph"
|
|
@@ -14,6 +16,7 @@ require_relative "notare/nodes/table_row"
|
|
|
14
16
|
require_relative "notare/nodes/table_cell"
|
|
15
17
|
require_relative "notare/image_dimensions"
|
|
16
18
|
require_relative "notare/style"
|
|
19
|
+
require_relative "notare/table_style"
|
|
17
20
|
require_relative "notare/xml/content_types"
|
|
18
21
|
require_relative "notare/xml/relationships"
|
|
19
22
|
require_relative "notare/xml/document_xml"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: notare
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mathias
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: fastimage
|
|
@@ -121,6 +121,8 @@ files:
|
|
|
121
121
|
- lib/notare/document.rb
|
|
122
122
|
- lib/notare/image_dimensions.rb
|
|
123
123
|
- lib/notare/nodes/base.rb
|
|
124
|
+
- lib/notare/nodes/break.rb
|
|
125
|
+
- lib/notare/nodes/hyperlink.rb
|
|
124
126
|
- lib/notare/nodes/image.rb
|
|
125
127
|
- lib/notare/nodes/list.rb
|
|
126
128
|
- lib/notare/nodes/list_item.rb
|
|
@@ -131,6 +133,7 @@ files:
|
|
|
131
133
|
- lib/notare/nodes/table_row.rb
|
|
132
134
|
- lib/notare/package.rb
|
|
133
135
|
- lib/notare/style.rb
|
|
136
|
+
- lib/notare/table_style.rb
|
|
134
137
|
- lib/notare/version.rb
|
|
135
138
|
- lib/notare/xml/content_types.rb
|
|
136
139
|
- lib/notare/xml/document_xml.rb
|