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 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