rrtf 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +10 -0
- data/examples/11.rtf +18 -0
- data/examples/11_sections.rb +11 -0
- data/lib/rrtf/formatting.rb +128 -22
- data/lib/rrtf/node/command_node.rb +22 -4
- data/lib/rrtf/node/section_node.rb +21 -0
- data/lib/rrtf/node.rb +1 -0
- data/lib/rrtf/style/section_style.rb +66 -0
- data/lib/rrtf/style.rb +1 -0
- data/lib/rrtf/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd6059a64d9d4e1b4bbc58706ce46a0ea0168c6a
|
4
|
+
data.tar.gz: 912010021274d86f8cb2496c0bafbbe6bbc95c8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e43626e294c217f9ffc664e0f4238543651f9c51709f016587777acf2a67549a12dcd85d66d964cc279c756e93f06aeb4ca5fd5cf96cbece90780709ad5babe5
|
7
|
+
data.tar.gz: 7b36e441b798ca7ddcfbb99d452f3eb2690d150e3f47728043b0e83eeb62077ccd5fff5410db683a4e94d90345a73f911917e3d400f836089d4fc4037be3bb33
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,7 @@ RRTF enables programatic creation of Rich Text Format (RTF) documents in Ruby, f
|
|
10
10
|
- __Images__: Embed, size, and define borders for PNG, JPEG, and BMP images.
|
11
11
|
- __Shapes__: Draw basic shapes, custom shapes, and text boxes.
|
12
12
|
- __Stylesheets__: Define paragraph and character styles, enabling the end user to easily modify the look of RTF documents.
|
13
|
+
- __Sections__: Add sections to a document with custom page and column formatting.
|
13
14
|
|
14
15
|
The gem was created with reference to the [Microsoft Office RTF Specification (v1.9.1)](https://www.microsoft.com/en-us/download/details.aspx?id=10725). The syntax for custom shapes was determined by reverse engineering the RTF output from Word 2016 and reference to [Microsoft's Binary Format Specification (pp. 32-33)](https://www.loc.gov/preservation/digital/formats/digformatspecs/OfficeDrawing97-2007BinaryFormatSpecification.pdf).
|
15
16
|
|
@@ -221,6 +222,15 @@ rtf.paragraph(styles['SUBTITLE']) do |p|
|
|
221
222
|
end
|
222
223
|
```
|
223
224
|
|
225
|
+
#### Sections
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
rtf.paragraph << "Redshirt Pocket Guide"
|
229
|
+
# start a new section with the prescribed styling
|
230
|
+
rtf.section("columns" => 2)
|
231
|
+
rtf.paragraph << "Section Text"
|
232
|
+
```
|
233
|
+
|
224
234
|
## TODO
|
225
235
|
|
226
236
|
- Develop rspec examples to replace the unit tests for the classes in the original ifad-rtf gem.
|
data/examples/11.rtf
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
{\rtf1\ansi\deff0\deflang1033\plain\fs24\fet1
|
2
|
+
{\fonttbl
|
3
|
+
{\f0\fswiss Helvetica;}
|
4
|
+
}
|
5
|
+
{\info
|
6
|
+
{\createim\yr2017\mo8\dy2\hr9\min41}
|
7
|
+
}
|
8
|
+
|
9
|
+
\hyphauto1\paperw12247\paperh15819\margl1440\margr1440\margt1440\margb1440
|
10
|
+
{\pard
|
11
|
+
Redshirt Pocket Guide
|
12
|
+
\par}
|
13
|
+
\sect\sectd\cols2 \paperw12247\paperh15819\margl1440\margr1440\margt1440\margb1440
|
14
|
+
|
15
|
+
{\pard
|
16
|
+
Section Text
|
17
|
+
\par}
|
18
|
+
}
|
data/lib/rrtf/formatting.rb
CHANGED
@@ -872,6 +872,91 @@ module RRTF::DocumentFormatting
|
|
872
872
|
end
|
873
873
|
end # module DocumentFormatting
|
874
874
|
|
875
|
+
# Section formatting attributes and methods.
|
876
|
+
# @author Wesley Hileman
|
877
|
+
# @since 1.0.0
|
878
|
+
module RRTF::SectionFormatting
|
879
|
+
# Formatting attributes that can be applied to an RTF document section.
|
880
|
+
# @return [Hash<String, Hash>] a hash mapping each attribute to a hash that
|
881
|
+
# describes (1) the attribute's default value, (2) how to parse the attribute
|
882
|
+
# from the user, and (3) how to convert the attribute to an RTF sequence.
|
883
|
+
SECTION_ATTRIBUTES = {
|
884
|
+
"columns" => {
|
885
|
+
"default" => nil,
|
886
|
+
"to_rtf" => lambda{ |value| "\\cols#{value}" unless value.nil? }
|
887
|
+
},
|
888
|
+
"column_spacing" => {
|
889
|
+
"default" => nil,
|
890
|
+
"from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
|
891
|
+
"to_rtf" => lambda{ |value| "\\colsx#{value}" unless value.nil? }
|
892
|
+
},
|
893
|
+
"mirror_margins" => {
|
894
|
+
"default" => nil,
|
895
|
+
"to_rtf" => lambda{ |value| "\\margmirrorsxn" if value }
|
896
|
+
}
|
897
|
+
}.freeze
|
898
|
+
|
899
|
+
# Generates attribute accessors for all section attributes when the module
|
900
|
+
# is included in another module or class.
|
901
|
+
def self.included(base)
|
902
|
+
# define accessors in base for document attributes
|
903
|
+
base.class_eval do
|
904
|
+
SECTION_ATTRIBUTES.each do |key, options|
|
905
|
+
attr_accessor :"#{key}"
|
906
|
+
end # each
|
907
|
+
end # class_eval
|
908
|
+
end
|
909
|
+
|
910
|
+
# Initializes section formatting attributes.
|
911
|
+
#
|
912
|
+
# @param [Hash] options the section formatting options.
|
913
|
+
# @option options [Integer] "columns" (nil) the number of columns in the section.
|
914
|
+
# @option options [String, Integer] "column_spacing" (nil) the column spacing in twips (can also be a string, see {Utilities.value2twips}).
|
915
|
+
# @option options [Boolean] "mirror_margins" (nil) whether or not to enable mirrored margins (when facing pages is enabled) in the document.
|
916
|
+
def initialize_section_formatting(options = {})
|
917
|
+
# load default attribute values
|
918
|
+
SECTION_ATTRIBUTES.each do |key, options|
|
919
|
+
send("#{key}=", options["default"])
|
920
|
+
end # each
|
921
|
+
# overwrite default attribute values with given values
|
922
|
+
set_section_formatting_from_hashmap(options)
|
923
|
+
end
|
924
|
+
|
925
|
+
# Sets section formatting attributes according to the supplied hashmap.
|
926
|
+
# @see #initialize_section_formatting
|
927
|
+
def set_section_formatting_from_hashmap(hash)
|
928
|
+
hash.each do |attribute, value|
|
929
|
+
# skip unreconized attributes
|
930
|
+
next unless(SECTION_ATTRIBUTES.keys.include?(attribute))
|
931
|
+
# preprocess value if nessesary
|
932
|
+
if SECTION_ATTRIBUTES[attribute].has_key?("from_user")
|
933
|
+
value = SECTION_ATTRIBUTES[attribute]["from_user"].call(value)
|
934
|
+
elsif SECTION_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
|
935
|
+
value = SECTION_ATTRIBUTES[attribute]["dictionary"][value]
|
936
|
+
end # if
|
937
|
+
# set attribute value
|
938
|
+
send("#{attribute}=", value)
|
939
|
+
end # each
|
940
|
+
end
|
941
|
+
|
942
|
+
# Generates an RTF string representing all applied section formatting.
|
943
|
+
#
|
944
|
+
# @return [String] RTF string.
|
945
|
+
def section_formatting_to_rtf
|
946
|
+
text = StringIO.new
|
947
|
+
|
948
|
+
# accumulate RTF representations of section attributes
|
949
|
+
SECTION_ATTRIBUTES.each do |key, options|
|
950
|
+
if options.has_key?("to_rtf")
|
951
|
+
rtf = options["to_rtf"].call(send(key))
|
952
|
+
text << rtf unless rtf.nil?
|
953
|
+
end # if
|
954
|
+
end # each
|
955
|
+
|
956
|
+
text.string
|
957
|
+
end
|
958
|
+
end # module SectionFormatting
|
959
|
+
|
875
960
|
# Page formatting attributes and methods.
|
876
961
|
# @author Wesley Hileman
|
877
962
|
# @since 1.0.0
|
@@ -888,25 +973,60 @@ module RRTF::PageFormatting
|
|
888
973
|
"PORTRAIT" => :portrait,
|
889
974
|
"LANDSCAPE" => :landscape
|
890
975
|
},
|
891
|
-
"to_rtf" => lambda
|
976
|
+
"to_rtf" => lambda do |value, targ|
|
977
|
+
case targ
|
978
|
+
when :document
|
979
|
+
"\\landscape" if value == :landscape
|
980
|
+
when :section
|
981
|
+
"\\lndscpsxn" if value == :landscape
|
982
|
+
end # case
|
983
|
+
end
|
892
984
|
},
|
893
985
|
"size" => {
|
894
986
|
"default" => RRTF::Page::Size.new,
|
895
987
|
"from_user" => lambda{ |value| RRTF::Page::Size.new(value) },
|
896
|
-
"to_rtf" => lambda
|
988
|
+
"to_rtf" => lambda do |value, targ|
|
989
|
+
case targ
|
990
|
+
when :document
|
991
|
+
"\\paperw#{value.width}\\paperh#{value.height}"
|
992
|
+
when :section
|
993
|
+
"\\pgwsxn#{value.width}\\pghsxn#{value.height}"
|
994
|
+
end # case
|
995
|
+
end
|
897
996
|
},
|
898
997
|
"margin" => {
|
899
998
|
"default" => RRTF::Page::Margin.new,
|
900
999
|
"from_user" => lambda{ |value| RRTF::Page::Margin.new(value) },
|
901
|
-
"to_rtf" => lambda
|
1000
|
+
"to_rtf" => lambda do |value, targ|
|
1001
|
+
case targ
|
1002
|
+
when :document
|
1003
|
+
"\\margl#{value.left}\\margr#{value.right}\\margt#{value.top}\\margb#{value.bottom}"
|
1004
|
+
when :section
|
1005
|
+
"\\marglsxn#{value.left}\\margrsnx#{value.right}\\margtsxn#{value.top}\\margbsnx#{value.bottom}"
|
1006
|
+
end # case
|
1007
|
+
end
|
902
1008
|
},
|
903
1009
|
"gutter" => {
|
904
1010
|
"default" => nil,
|
905
1011
|
"from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
|
906
|
-
"to_rtf" => lambda
|
1012
|
+
"to_rtf" => lambda do |value, targ|
|
1013
|
+
case targ
|
1014
|
+
when :document
|
1015
|
+
"\\gutter#{value}" unless value.nil?
|
1016
|
+
when :section
|
1017
|
+
"\\guttersxn#{value}" unless value.nil?
|
1018
|
+
end # case
|
1019
|
+
end
|
907
1020
|
}
|
908
1021
|
}.freeze
|
909
1022
|
|
1023
|
+
PAGE_FORMATTING_TARGET_DICTIONARY = {
|
1024
|
+
"DOCUMENT" => :document,
|
1025
|
+
"SECTION" => :section
|
1026
|
+
}.freeze
|
1027
|
+
|
1028
|
+
attr_accessor :target
|
1029
|
+
|
910
1030
|
# Generates attribute accessors for all page attributes when the module
|
911
1031
|
# is included in another module or class.
|
912
1032
|
def self.included(base)
|
@@ -927,7 +1047,9 @@ module RRTF::PageFormatting
|
|
927
1047
|
# @option options [String, Page::Size] "size" (Page::Size.new) the size of the paper (object or string; see {Page::Size#initialize}).
|
928
1048
|
# @option options [String, Page::Margin] "margin" (Page::Margin.new) the paper margin (object or string; see {Page::Margin#initialize}).
|
929
1049
|
# @option options [String] "gutter" (nil) the page gutter width (specify a string, see {Utilities.value2twips}).
|
930
|
-
def initialize_page_formatting(options = {})
|
1050
|
+
def initialize_page_formatting(options = {}, target = "DOCUMENT")
|
1051
|
+
@target = PAGE_FORMATTING_TARGET_DICTIONARY[target]
|
1052
|
+
|
931
1053
|
# load default attribute values
|
932
1054
|
PAGE_ATTRIBUTES.each do |key, options|
|
933
1055
|
send("#{key}=", options["default"])
|
@@ -962,27 +1084,11 @@ module RRTF::PageFormatting
|
|
962
1084
|
# accumulate RTF representations of page attributes
|
963
1085
|
PAGE_ATTRIBUTES.each do |key, options|
|
964
1086
|
if options.has_key?("to_rtf")
|
965
|
-
rtf = options["to_rtf"].call(send(key))
|
1087
|
+
rtf = options["to_rtf"].call(send(key), @target)
|
966
1088
|
text << rtf unless rtf.nil?
|
967
1089
|
end # if
|
968
1090
|
end # each
|
969
1091
|
|
970
1092
|
text.string
|
971
1093
|
end
|
972
|
-
|
973
|
-
def body_width
|
974
|
-
if orientation == :portrait
|
975
|
-
size.width - (margin.left + margin.right)
|
976
|
-
else
|
977
|
-
size.height - (margin.top + margin.bottom)
|
978
|
-
end
|
979
|
-
end
|
980
|
-
|
981
|
-
def body_height
|
982
|
-
if orientation == :portrait
|
983
|
-
size.height - (margin.top + margin.bottom)
|
984
|
-
else
|
985
|
-
size.width - (margin.left + margin.right)
|
986
|
-
end
|
987
|
-
end
|
988
1094
|
end
|
@@ -68,6 +68,24 @@ module RRTF
|
|
68
68
|
text.string
|
69
69
|
end
|
70
70
|
|
71
|
+
def section(style = nil)
|
72
|
+
# parse style
|
73
|
+
case style
|
74
|
+
when Hash
|
75
|
+
style = SectionStyle.new(style)
|
76
|
+
when SectionStyle
|
77
|
+
# use without modification
|
78
|
+
when nil
|
79
|
+
# allow nil style
|
80
|
+
else
|
81
|
+
RTFError.fire("Invalid section style '#{style}'.")
|
82
|
+
end # case
|
83
|
+
|
84
|
+
node = SectionNode.new(self, style)
|
85
|
+
yield node if block_given?
|
86
|
+
self.store(node)
|
87
|
+
end
|
88
|
+
|
71
89
|
# This method provides a short cut means of creating a paragraph command
|
72
90
|
# node. The method accepts a block that will be passed a single parameter
|
73
91
|
# which will be a reference to the paragraph node created. After the
|
@@ -84,7 +102,7 @@ module RRTF
|
|
84
102
|
# rtf.paragraph("bold" => true, "font" => "SWISS:Arial") do |p|
|
85
103
|
# p << "Paragraph formatted with an anonymous style."
|
86
104
|
# end
|
87
|
-
def paragraph(style=nil)
|
105
|
+
def paragraph(style = nil)
|
88
106
|
# parse style
|
89
107
|
case style
|
90
108
|
when Hash
|
@@ -101,9 +119,9 @@ module RRTF
|
|
101
119
|
style.push_colours(root.colours) unless style.nil?
|
102
120
|
style.push_fonts(root.fonts) unless style.nil?
|
103
121
|
|
104
|
-
|
105
|
-
|
106
|
-
|
122
|
+
node = ParagraphNode.new(self, style)
|
123
|
+
yield node if block_given?
|
124
|
+
self.store(node)
|
107
125
|
end
|
108
126
|
|
109
127
|
# This method provides a short cut means of creating a new ordered or
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RRTF
|
2
|
+
# This class represents a section within an RTF document. Section nodes
|
3
|
+
# do not contain other nodes; instead, they mark the start of a new section.
|
4
|
+
# @author Wesley Hileman
|
5
|
+
class SectionNode < CommandNode
|
6
|
+
def initialize(parent, style=nil)
|
7
|
+
prefix = '\sect\sectd'
|
8
|
+
prefix << style.prefix(parent.root) unless style.nil?
|
9
|
+
|
10
|
+
super(parent, prefix, '', true, false)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Overrides {ContainerNode#store} to prevent child nodes from being
|
14
|
+
# added to sections.
|
15
|
+
#
|
16
|
+
# @raise [RTFError] whenever called.
|
17
|
+
def store(node)
|
18
|
+
RTFError.fire("Cannot add child nodes to section nodes: tried to add #{node}.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/rrtf/node.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rrtf/node/node'
|
|
2
2
|
require 'rrtf/node/text_node'
|
3
3
|
require 'rrtf/node/container_node'
|
4
4
|
require 'rrtf/node/command_node'
|
5
|
+
require 'rrtf/node/section_node'
|
5
6
|
require 'rrtf/node/paragraph_node'
|
6
7
|
require 'rrtf/node/list_node'
|
7
8
|
require 'rrtf/node/list_level_node'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module RRTF
|
4
|
+
# This class represents a section style for an RTF document.
|
5
|
+
class SectionStyle < Style
|
6
|
+
include SectionFormatting
|
7
|
+
include PageFormatting
|
8
|
+
|
9
|
+
# This is the constructor for the SectionStyle class.
|
10
|
+
#
|
11
|
+
# @param [Hash] options the section style options.
|
12
|
+
# @option options (see Style#initialize)
|
13
|
+
# @option options (see SectionFormatting#initialize_section_formatting)
|
14
|
+
# @option options (see PageFormatting#initialize_page_formatting)
|
15
|
+
def initialize(options = {})
|
16
|
+
super(options)
|
17
|
+
initialize_section_formatting(options)
|
18
|
+
initialize_page_formatting(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Converts the stylesheet character style into its RTF representation
|
22
|
+
# (for stylesheet)
|
23
|
+
def to_rtf(document, options = {})
|
24
|
+
# load default options
|
25
|
+
options = {
|
26
|
+
"uglify" => false,
|
27
|
+
"base_indent" => 0
|
28
|
+
}.merge(options)
|
29
|
+
# build formatting helpers
|
30
|
+
base_prefix = options["uglify"] ? '' : ' '*options["base_indent"]
|
31
|
+
name_prefix = options["uglify"] ? ' ' : ''
|
32
|
+
suffix = options["uglify"] ? '' : ' '
|
33
|
+
|
34
|
+
rtf = StringIO.new
|
35
|
+
|
36
|
+
rtf << base_prefix
|
37
|
+
rtf << "{\\*\\ds#{handle}#{suffix}"
|
38
|
+
rtf << "#{rtf_formatting(document)}#{suffix}"
|
39
|
+
rtf << "\\additive#{suffix}" if @additive
|
40
|
+
rtf << "\\sbasedon#{@based_on_style_handle}#{suffix}" unless @based_on_style_handle.nil?
|
41
|
+
rtf << "\\sautoupd#{suffix}" if @auto_update
|
42
|
+
rtf << "\\snext#{@next_style_handle}#{suffix}" unless @next_style_handle.nil?
|
43
|
+
rtf << "\\sqformat#{suffix}" if @primary
|
44
|
+
rtf << "\\spriority#{@priority}#{suffix}" unless @priority.nil?
|
45
|
+
rtf << "\\shidden#{suffix}" if @hidden
|
46
|
+
rtf << "#{name_prefix}#{name};}"
|
47
|
+
|
48
|
+
rtf.string
|
49
|
+
end
|
50
|
+
|
51
|
+
# This method generates a string containing the prefix associated with the
|
52
|
+
# style object.
|
53
|
+
def prefix(document)
|
54
|
+
text = StringIO.new
|
55
|
+
|
56
|
+
text << "\\ds#{handle} " unless handle.nil?
|
57
|
+
text << rtf_formatting
|
58
|
+
|
59
|
+
text.string
|
60
|
+
end
|
61
|
+
|
62
|
+
def rtf_formatting
|
63
|
+
"#{section_formatting_to_rtf} #{page_formatting_to_rtf}"
|
64
|
+
end
|
65
|
+
end # End of the CharacterStyle class.
|
66
|
+
end # module RRTF
|
data/lib/rrtf/style.rb
CHANGED
data/lib/rrtf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rrtf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wesley Hileman
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -231,6 +231,8 @@ files:
|
|
231
231
|
- examples/09_shapes.rb
|
232
232
|
- examples/10.rtf
|
233
233
|
- examples/10_stylesheet.rb
|
234
|
+
- examples/11.rtf
|
235
|
+
- examples/11_sections.rb
|
234
236
|
- examples/resources/images/redshirt.png
|
235
237
|
- examples/resources/images/redshirts.jpg
|
236
238
|
- examples/resources/json/redshirt_styles.json
|
@@ -257,6 +259,7 @@ files:
|
|
257
259
|
- lib/rrtf/node/list_text_node.rb
|
258
260
|
- lib/rrtf/node/node.rb
|
259
261
|
- lib/rrtf/node/paragraph_node.rb
|
262
|
+
- lib/rrtf/node/section_node.rb
|
260
263
|
- lib/rrtf/node/table_cell_node.rb
|
261
264
|
- lib/rrtf/node/table_node.rb
|
262
265
|
- lib/rrtf/node/table_row_node.rb
|
@@ -274,6 +277,7 @@ files:
|
|
274
277
|
- lib/rrtf/style/character_style.rb
|
275
278
|
- lib/rrtf/style/paragraph_style.rb
|
276
279
|
- lib/rrtf/style/position_style.rb
|
280
|
+
- lib/rrtf/style/section_style.rb
|
277
281
|
- lib/rrtf/style/shading_style.rb
|
278
282
|
- lib/rrtf/style/style.rb
|
279
283
|
- lib/rrtf/stylesheet.rb
|