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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4696bbf7c9772c7cf36a9fa33e10d2c5ddb0b60e
4
- data.tar.gz: cb8b674323d9ad87c4d75386994c8554c956ef5b
3
+ metadata.gz: cd6059a64d9d4e1b4bbc58706ce46a0ea0168c6a
4
+ data.tar.gz: 912010021274d86f8cb2496c0bafbbe6bbc95c8e
5
5
  SHA512:
6
- metadata.gz: '0827d3f9ce0bfb0c9e92efa91e0ef6dbfff0bc0008801d377ca261472baa3b5ab2854d2366ec8a82fd77d422cccae58db2b8533e8d2f94ae528ea73987172f8a'
7
- data.tar.gz: 05eb44acdc466d1e1fd3a27669fcde7a2a65a09e07ebc7e678394551a3f10a8003d46a47c6dca281b449405fd0e0d777ee096ae1e4277efab11ddce5131f9901
6
+ metadata.gz: e43626e294c217f9ffc664e0f4238543651f9c51709f016587777acf2a67549a12dcd85d66d964cc279c756e93f06aeb4ca5fd5cf96cbece90780709ad5babe5
7
+ data.tar.gz: 7b36e441b798ca7ddcfbb99d452f3eb2690d150e3f47728043b0e83eeb62077ccd5fff5410db683a4e94d90345a73f911917e3d400f836089d4fc4037be3bb33
data/CHANGELOG.md CHANGED
@@ -27,3 +27,7 @@ _Version 1.0.1:_
27
27
 
28
28
  - Allow remote source for images with open-uri.
29
29
  - Fix issue with setting "text_margin" for text boxes.
30
+
31
+ _Version 1.1.0:_
32
+
33
+ - Add support for sections.
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
+ }
@@ -0,0 +1,11 @@
1
+ require 'rrtf'
2
+
3
+ DIR = File.dirname(__FILE__)
4
+
5
+ rtf = RRTF::Document.new
6
+
7
+ rtf.paragraph << "Redshirt Pocket Guide"
8
+ rtf.section("columns" => 2)
9
+ rtf.paragraph << "Section Text"
10
+
11
+ File.open(DIR+'/11.rtf', 'w') { |file| file.write(rtf.to_rtf) }
@@ -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{ |value| "\\landscape" if value == :landscape }
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{ |value| "\\paperw#{value.width}\\paperh#{value.height}" }
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{ |value| "\\margl#{value.left}\\margr#{value.right}\\margt#{value.top}\\margb#{value.bottom}" }
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{ |value| "\\gutter#{value}" unless value.nil? }
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
- node = ParagraphNode.new(self, style)
105
- yield node if block_given?
106
- self.store(node)
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
@@ -5,3 +5,4 @@ require 'rrtf/style/shading_style'
5
5
  require 'rrtf/style/style'
6
6
  require 'rrtf/style/paragraph_style'
7
7
  require 'rrtf/style/character_style'
8
+ require 'rrtf/style/section_style'
data/lib/rrtf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RRTF
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
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.1
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-01 00:00:00.000000000 Z
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