rrtf 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|