rrtf 0.1.2 → 1.0.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/.byebug_history +6 -3
  3. data/CHANGELOG.md +24 -0
  4. data/README.md +194 -84
  5. data/documentation/RRTF.html +5 -5
  6. data/documentation/RRTF/AnonymousStyle.html +792 -0
  7. data/documentation/RRTF/BorderFormatting.html +821 -0
  8. data/documentation/RRTF/BorderStyle.html +493 -0
  9. data/documentation/RRTF/CharacterFormatting.html +293 -162
  10. data/documentation/RRTF/CharacterStyle.html +53 -109
  11. data/documentation/RRTF/Colour.html +61 -1
  12. data/documentation/RRTF/ColourTable.html +52 -52
  13. data/documentation/RRTF/CommandNode.html +367 -971
  14. data/documentation/RRTF/ContainerNode.html +44 -44
  15. data/documentation/RRTF/Converters.html +1 -1
  16. data/documentation/RRTF/Converters/HTML.html +1 -1
  17. data/documentation/RRTF/Converters/HTML/Helpers.html +1 -1
  18. data/documentation/RRTF/Converters/HTML/Node.html +1 -1
  19. data/documentation/RRTF/Converters/HTML/NodeSet.html +1 -1
  20. data/documentation/RRTF/Document.html +267 -255
  21. data/documentation/RRTF/DocumentFormatting.html +833 -0
  22. data/documentation/RRTF/DocumentProperties.html +444 -0
  23. data/documentation/RRTF/Font.html +1 -1
  24. data/documentation/RRTF/FontTable.html +1 -1
  25. data/documentation/RRTF/FooterNode.html +16 -16
  26. data/documentation/RRTF/GeometryNode.html +774 -0
  27. data/documentation/RRTF/GeometryProperties.html +1014 -0
  28. data/documentation/RRTF/HeaderNode.html +16 -16
  29. data/documentation/RRTF/ImageNode.html +705 -492
  30. data/documentation/RRTF/Information.html +1 -1
  31. data/documentation/RRTF/LinkNode.html +10 -10
  32. data/documentation/RRTF/ListLevel.html +1 -1
  33. data/documentation/RRTF/ListLevelNode.html +37 -37
  34. data/documentation/RRTF/ListMarker.html +1 -1
  35. data/documentation/RRTF/ListNode.html +19 -19
  36. data/documentation/RRTF/ListTable.html +1 -1
  37. data/documentation/RRTF/ListTemplate.html +1 -1
  38. data/documentation/RRTF/ListTextNode.html +14 -14
  39. data/documentation/RRTF/Node.html +26 -26
  40. data/documentation/RRTF/Page.html +129 -0
  41. data/documentation/RRTF/Page/Margin.html +1158 -0
  42. data/documentation/RRTF/Page/Size.html +946 -0
  43. data/documentation/RRTF/PageFormatting.html +954 -0
  44. data/documentation/RRTF/ParagraphFormatting.html +338 -56
  45. data/documentation/RRTF/ParagraphNode.html +10 -10
  46. data/documentation/RRTF/ParagraphStyle.html +72 -111
  47. data/documentation/RRTF/PositionFormatting.html +780 -0
  48. data/documentation/RRTF/PositionStyle.html +424 -0
  49. data/documentation/RRTF/Properties.html +243 -0
  50. data/documentation/RRTF/RTFError.html +21 -10
  51. data/documentation/RRTF/ShadingFormatting.html +712 -0
  52. data/documentation/RRTF/ShadingStyle.html +424 -0
  53. data/documentation/RRTF/Style.html +284 -697
  54. data/documentation/RRTF/Stylesheet.html +36 -3
  55. data/documentation/RRTF/TableCellNode.html +131 -131
  56. data/documentation/RRTF/TableNode.html +82 -82
  57. data/documentation/RRTF/TableRowNode.html +53 -53
  58. data/documentation/RRTF/TextNode.html +46 -46
  59. data/documentation/RRTF/Utilities.html +837 -17
  60. data/documentation/_index.html +139 -6
  61. data/documentation/class_list.html +1 -1
  62. data/documentation/file.README.html +218 -87
  63. data/documentation/index.html +218 -87
  64. data/documentation/method_list.html +631 -391
  65. data/documentation/top-level-namespace.html +1 -1
  66. data/examples/01.rtf +947 -20
  67. data/examples/01_everything.rb +176 -0
  68. data/examples/02.rtf +13 -0
  69. data/examples/02_basic_paragraph.rb +10 -0
  70. data/examples/03.rtf +20 -0
  71. data/examples/03_paragraph_inline_style.rb +14 -0
  72. data/examples/04.rtf +21 -0
  73. data/examples/04_paragraph_with_character_style.rb +18 -0
  74. data/examples/05.rtf +21 -0
  75. data/examples/05_hyperlinks.rb +21 -0
  76. data/examples/06.rtf +21 -0
  77. data/examples/06_basic_list.rb +21 -0
  78. data/examples/07.rtf +28 -0
  79. data/examples/07_nested_list.rb +27 -0
  80. data/examples/08.rtf +807 -0
  81. data/examples/08_images.rb +17 -0
  82. data/examples/09.rtf +84 -0
  83. data/examples/09_shapes.rb +56 -0
  84. data/examples/10.rtf +34 -0
  85. data/examples/10_stylesheet.rb +18 -0
  86. data/examples/resources/images/redshirt.png +0 -0
  87. data/examples/resources/images/redshirts.jpg +0 -0
  88. data/examples/resources/json/redshirt_styles.json +72 -8
  89. data/examples/~$01.rtf +0 -0
  90. data/lib/rrtf.rb +4 -16
  91. data/lib/rrtf/colour.rb +8 -0
  92. data/lib/rrtf/formatting.rb +988 -0
  93. data/lib/rrtf/node.rb +17 -1851
  94. data/lib/rrtf/node/command_node.rb +242 -0
  95. data/lib/rrtf/node/container_node.rb +75 -0
  96. data/lib/rrtf/node/document.rb +339 -0
  97. data/lib/rrtf/node/footer_node.rb +47 -0
  98. data/lib/rrtf/node/geometry_node.rb +65 -0
  99. data/lib/rrtf/node/header_node.rb +47 -0
  100. data/lib/rrtf/node/image_node.rb +175 -0
  101. data/lib/rrtf/node/link_node.rb +10 -0
  102. data/lib/rrtf/node/list_level_node.rb +44 -0
  103. data/lib/rrtf/node/list_node.rb +30 -0
  104. data/lib/rrtf/node/list_text_node.rb +22 -0
  105. data/lib/rrtf/node/node.rb +53 -0
  106. data/lib/rrtf/node/paragraph_node.rb +11 -0
  107. data/lib/rrtf/node/table_cell_node.rb +233 -0
  108. data/lib/rrtf/node/table_node.rb +136 -0
  109. data/lib/rrtf/node/table_row_node.rb +92 -0
  110. data/lib/rrtf/node/text_node.rb +76 -0
  111. data/lib/rrtf/page.rb +7 -0
  112. data/lib/rrtf/page/margin.rb +98 -0
  113. data/lib/rrtf/page/size.rb +98 -0
  114. data/lib/rrtf/properties.rb +3 -0
  115. data/lib/rrtf/properties/document_properties.rb +34 -0
  116. data/lib/rrtf/properties/geometry_properties.rb +380 -0
  117. data/lib/rrtf/properties/properties.rb +13 -0
  118. data/lib/rrtf/style.rb +4 -5
  119. data/lib/rrtf/style/anonymous_style.rb +73 -0
  120. data/lib/rrtf/style/border_style.rb +27 -0
  121. data/lib/rrtf/style/character_style.rb +1 -7
  122. data/lib/rrtf/style/paragraph_style.rb +0 -6
  123. data/lib/rrtf/style/position_style.rb +26 -0
  124. data/lib/rrtf/style/shading_style.rb +26 -0
  125. data/lib/rrtf/style/style.rb +60 -101
  126. data/lib/rrtf/utilities.rb +138 -0
  127. data/lib/rrtf/version.rb +1 -1
  128. data/rrtf.gemspec +1 -0
  129. metadata +85 -10
  130. data/examples/01_mac_libreoffice5_2_3_3.png +0 -0
  131. data/examples/01_mac_pages6_2.png +0 -0
  132. data/examples/01_mac_textedit1_12.png +0 -0
  133. data/examples/01_mac_word15_36.png +0 -0
  134. data/examples/01_styles_and_paragraphs.rb +0 -32
  135. data/lib/rrtf/paper.rb +0 -53
  136. data/lib/rrtf/style/document_style.rb +0 -116
  137. data/lib/rrtf/style/formatting.rb +0 -320
@@ -0,0 +1,17 @@
1
+ require 'rrtf'
2
+
3
+ DIR = File.dirname(__FILE__)
4
+
5
+ rtf = RRTF::Document.new
6
+ rtf.image(DIR+'/resources/images/redshirt.png',
7
+ "width" => "2in", # can also set "height"
8
+ "sizing_mode" => "FIX_ASPECT_RATIO", # can also be "ABSOLUTE"
9
+ "border" => {
10
+ "sides" => "ALL",
11
+ "color" => '#ff0000',
12
+ "line_type" => "DOT",
13
+ "width" => "5pt",
14
+ "spacing" => "12pt"
15
+ }
16
+ )
17
+ File.open(DIR+'/08.rtf', 'w') { |file| file.write(rtf.to_rtf) }
@@ -0,0 +1,84 @@
1
+ {\rtf1\ansi\deff0\deflang1033\plain\fs24\fet1
2
+ {\fonttbl
3
+ {\f0\fswiss Helvetica;}
4
+ }
5
+ {\colortbl
6
+ ;
7
+ \red255\green255\blue255;
8
+ }
9
+ {\info
10
+ {\createim\yr2017\mo7\dy31\hr20\min2}
11
+ }
12
+
13
+ \hyphauto1\paperw12247\paperh15819\margl1440\margr1440\margt1440\margb1440
14
+ {\shp{\*\shpinst\shpleft0\shpright2880\shptop0\shpbottom2880\shpbxpage\shpbxignore\shpbypage\shpbyignore
15
+ {\sp{\sn shapeType}{\sv 1}}
16
+ {\sp{\sn posh}{\sv 0}}
17
+ {\sp{\sn posrelh}{\sv 1}}
18
+ {\sp{\sn posv}{\sv 0}}
19
+ {\sp{\sn posrelv}{\sv 1}}
20
+ {\sp{\sn sizerelh}{\sv 0}}
21
+ {\sp{\sn sizerelv}{\sv 0}}
22
+ {\sp{\sn fFilled}{\sv 1}}
23
+ {\sp{\sn fillColor}{\sv 13421772}}
24
+ {\sp{\sn fLine}{\sv 0}}
25
+ {\sp{\sn geoLeft}{\sv 0}}
26
+ {\sp{\sn geoTop}{\sv 0}}
27
+ {\sp{\sn geoRight}{\sv 30000}}
28
+ {\sp{\sn geoBottom}{\sv 30000}}
29
+ {\sp{\sn fLineOK}{\sv 1}}
30
+ {\sp{\sn fFillOK}{\sv 1}}
31
+ {\sp{\sn f3DOK}{\sv 1}}
32
+ }}
33
+ {\shp{\*\shpinst\shpleft3600\shpright7920\shptop7200\shpbottom11520\shpbxpage\shpbxignore\shpbypage\shpbyignore
34
+ {\sp{\sn shapeType}{\sv 202}}
35
+ {\sp{\sn posh}{\sv 0}}
36
+ {\sp{\sn posrelh}{\sv 1}}
37
+ {\sp{\sn posv}{\sv 0}}
38
+ {\sp{\sn posrelv}{\sv 1}}
39
+ {\sp{\sn sizerelh}{\sv 0}}
40
+ {\sp{\sn sizerelv}{\sv 0}}
41
+ {\sp{\sn fFilled}{\sv 1}}
42
+ {\sp{\sn fillColor}{\sv 255}}
43
+ {\sp{\sn fLine}{\sv 1}}
44
+ {\sp{\sn lineColor}{\sv 0}}
45
+ {\sp{\sn lineWidth}{\sv 38100}}
46
+ {\sp{\sn geoLeft}{\sv 0}}
47
+ {\sp{\sn geoTop}{\sv 0}}
48
+ {\sp{\sn geoRight}{\sv 30000}}
49
+ {\sp{\sn geoBottom}{\sv 30000}}
50
+ {\sp{\sn fLineOK}{\sv 1}}
51
+ {\sp{\sn fFillOK}{\sv 1}}
52
+ {\sp{\sn f3DOK}{\sv 1}}
53
+ {\shptxt
54
+ {\pard\ql\ltrpar\cf1
55
+ Should you ever find yourself on a spacefaring vessel wearing a
56
+ {\b\i\ul
57
+ red
58
+ }
59
+ shirt, take heed and be on guard, for danger is immanent and you are likely expendable among the crew.
60
+ \par}}}}
61
+ {\shp{\*\shpinst\shpleft5760\shpright10080\shptop0\shpbottom4320\shpbxmargin\shpbxignore\shpbymargin\shpbyignore
62
+ {\sp{\sn shapeType}{\sv 0}}
63
+ {\sp{\sn posh}{\sv 0}}
64
+ {\sp{\sn posrelh}{\sv 0}}
65
+ {\sp{\sn posv}{\sv 0}}
66
+ {\sp{\sn posrelv}{\sv 0}}
67
+ {\sp{\sn sizerelh}{\sv 0}}
68
+ {\sp{\sn sizerelv}{\sv 0}}
69
+ {\sp{\sn fFilled}{\sv 1}}
70
+ {\sp{\sn fillColor}{\sv 52224}}
71
+ {\sp{\sn fLine}{\sv 1}}
72
+ {\sp{\sn lineColor}{\sv 10027008}}
73
+ {\sp{\sn geoLeft}{\sv 0}}
74
+ {\sp{\sn geoTop}{\sv 0}}
75
+ {\sp{\sn geoRight}{\sv 30000}}
76
+ {\sp{\sn geoBottom}{\sv 30000}}
77
+ {\sp{\sn pVerticies}{\sv 8;6;(0,0);(20000,0);(30000,0);(30000,15000);(30000,30000);(0,0)}}
78
+ {\sp{\sn pSegmentInfo}{\sv 2;6;16384;1;8193;1;24577;32768}}
79
+ {\sp{\sn pConnectionSites}{\sv 8;4;(0,0);(20000,0);(30000,30000);(0,0)}}
80
+ {\sp{\sn fLineOK}{\sv 1}}
81
+ {\sp{\sn fFillOK}{\sv 1}}
82
+ {\sp{\sn f3DOK}{\sv 1}}
83
+ }}
84
+ }
@@ -0,0 +1,56 @@
1
+ require 'rrtf'
2
+
3
+ DIR = File.dirname(__FILE__)
4
+
5
+ rtf = RRTF::Document.new
6
+ rtf.geometry(
7
+ "type" => "RECTANGLE",
8
+ "fill_color" => '#cccccc',
9
+ "top" => 0,
10
+ "left" => 0,
11
+ "width" => "2in",
12
+ "height" => "2in",
13
+ "horizontal_reference" => "PAGE",
14
+ "vertical_reference" => "PAGE"
15
+ )
16
+ rtf.geometry(
17
+ "type" => "TEXT_BOX",
18
+ "fill_color" => '#ff0000',
19
+ "line_color" => '#000000',
20
+ "line_width" => '3pt',
21
+ "top" => "5in",
22
+ "left" => "2.5in",
23
+ "width" => "3in",
24
+ "height" => "3in",
25
+ "horizontal_reference" => "PAGE",
26
+ "vertical_reference" => "PAGE"
27
+ ) do |box|
28
+ box.paragraph("foreground_color" => '#ffffff') do |p|
29
+ p << "Should you ever find yourself on a spacefaring vessel wearing a "
30
+ p.apply(
31
+ "italic" => true,
32
+ "bold" => true,
33
+ "underline" => "SINGLE"
34
+ ) << "red"
35
+ p << " shirt, take heed and be on guard, for danger "
36
+ p << "is immanent and you are likely expendable among the crew."
37
+ end
38
+ end
39
+ rtf.geometry(
40
+ "type" => "CUSTOM",
41
+ "path" => [
42
+ ["START_AT", [0,0] ],
43
+ ["LINE_TO", ['2in', 0] ],
44
+ ["CUBIC_BEZIER_TO", ['3in', 0], ['3in', '1.5in'], ['3in', '3in'] ],
45
+ ["LINE_TO", [0, 0] ],
46
+ ["CLOSE_PATH" ],
47
+ ["END" ]
48
+ ],
49
+ "fill_color" => '#00cc00',
50
+ "line_color" => '#000099',
51
+ "top" => 0,
52
+ "left" => "4in",
53
+ "width" => "3in",
54
+ "height" => "3in"
55
+ )
56
+ File.open(DIR+'/09.rtf', 'w') { |file| file.write(rtf.to_rtf) }
@@ -0,0 +1,34 @@
1
+ {\rtf1\ansi\deff0\deflang1033\plain\fs24\fet1
2
+ {\fonttbl
3
+ {\f0\fswiss Helvetica;}
4
+ }
5
+ {\colortbl
6
+ ;
7
+ \red255\green255\blue255;
8
+ \red255\green0\blue0;
9
+ }
10
+ {\stylesheet
11
+ {\s1 \ql\ltrpar\absw5040\absh7200\phpg\pvpg\posx0\posy0\shading100\cbpat2\cf1 \spriority1 Emphasis Background;}
12
+ {\s2 \qc\ltrpar\brdrb\brdrth\brdrw60\brsp0\brdrcf1 \brdrt\brdrth\brdrw60\brsp0\brdrcf1\b\caps\cf1\fs30 \sbasedon0 \sautoupd \snext0 \sqformat \spriority2 Title;}
13
+ {\s3 \qc\ltrpar\caps\expnd20\cf1\fs24 \sqformat \spriority3 Subtitle;}
14
+ {\s4 \ql\sb200\sa40\keepn\ltrpar\b\ul\ulc2\fs24 \snext0 \sqformat \spriority4 Heading 1;}
15
+ {\s0 \ql\sa200\hyphpar\ltrpar\fs24 \sqformat \spriority5 Normal;}
16
+ {\s5 \ql\sl-540\ltrpar\box\brdrdot\brdrw20\brdrcf2\absw0\absh-576\pvpara\wraparound\dropcapli2\dropcapt1\fs56 \sqformat \spriority6 Drop Caps;}
17
+ {\*\cs6 \b\i\cf2 \additive \spriority7 Emphasis;}
18
+ }
19
+ {\info
20
+ {\createim\yr2017\mo7\dy31\hr22\min4}
21
+ }
22
+
23
+ \hyphauto1\paperw12247\paperh15819\margl1440\margr1440\margt1440\margb1440
24
+ {\pard\s4 \ql\sb200\sa40\keepn\ltrpar\b\ul\ulc2\fs24
25
+ Redshirt Pocket Guide
26
+ \par}
27
+ {\pard\s0 \ql\sa200\hyphpar\ltrpar\fs24
28
+ 3
29
+ {\super
30
+ rd
31
+ }
32
+ Edition
33
+ \par}
34
+ }
@@ -0,0 +1,18 @@
1
+ require 'rrtf'
2
+ require 'JSON'
3
+
4
+ DIR = File.dirname(__FILE__)
5
+
6
+ raw_styles = JSON.parse File.read(DIR+'/resources/json/redshirt_styles.json')
7
+ rtf = RRTF::Document.new("stylesheet" => raw_styles)
8
+ styles = rtf.stylesheet.styles
9
+
10
+ rtf.paragraph(styles['H1']) << "Redshirt Pocket Guide"
11
+ rtf.paragraph(styles['BODY']) do |p|
12
+ p << "3"
13
+ # apply an anonymous character style
14
+ p.apply("superscript" => true) << "rd"
15
+ p << " Edition"
16
+ end
17
+
18
+ File.open(DIR+'/10.rtf', 'w') { |file| file.write(rtf.to_rtf) }
@@ -1,4 +1,20 @@
1
1
  [
2
+ {
3
+ "type": "paragraph",
4
+ "id": "PRIMARY_BACKGROUND",
5
+ "name": "Emphasis Background",
6
+ "foreground_color": "#ffffff",
7
+ "position": {
8
+ "size": "3.5in,5in",
9
+ "horizontal_position": 0,
10
+ "vertical_position": 0,
11
+ "horizontal_reference": "PAGE",
12
+ "vertical_reference": "PAGE"
13
+ },
14
+ "shading": {
15
+ "background_color": "#ff0000"
16
+ }
17
+ },
2
18
  {
3
19
  "type": "paragraph",
4
20
  "id": "TITLE",
@@ -8,12 +24,37 @@
8
24
  "next_style": "BODY",
9
25
  "base_style": "BODY",
10
26
  "justification": "CENTER",
11
- "space_after": 100,
12
27
  "bold": true,
13
- "underline": "DOUBLE",
14
- "underline_color": "#ff0000",
15
28
  "uppercase": true,
16
- "font_size": 36
29
+ "font_size": "15pt",
30
+ "foreground_color": "#ffffff",
31
+ "border": [
32
+ {
33
+ "sides": "BOTTOM",
34
+ "line_type": "THICK",
35
+ "width": "3pt",
36
+ "spacing": 0,
37
+ "color": "#ffffff"
38
+ },
39
+ {
40
+ "sides": "TOP",
41
+ "line_type": "THICK",
42
+ "width": "3pt",
43
+ "spacing": 0,
44
+ "color": "#ffffff"
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "type": "paragraph",
50
+ "id": "SUBTITLE",
51
+ "name": "Subtitle",
52
+ "primary": true,
53
+ "justification": "CENTER",
54
+ "font_size": "12pt",
55
+ "foreground_color": "#ffffff",
56
+ "character_spacing_offset": "5pt",
57
+ "uppercase": true
17
58
  },
18
59
  {
19
60
  "type": "paragraph",
@@ -21,12 +62,13 @@
21
62
  "name": "Heading 1",
22
63
  "primary": true,
23
64
  "next_style": "BODY",
24
- "space_after": 40,
25
- "space_before": 200,
65
+ "space_after": "2pt",
66
+ "space_before": "10pt",
67
+ "no_break_with_next": true,
26
68
  "underline": "SINGLE",
27
69
  "underline_color": "#ff0000",
28
70
  "bold": true,
29
- "font_size": 24
71
+ "font_size": "12pt"
30
72
  },
31
73
  {
32
74
  "type": "paragraph",
@@ -35,9 +77,31 @@
35
77
  "primary": true,
36
78
  "default": true,
37
79
  "justification": "LEFT",
38
- "font_size": 24,
80
+ "font_size": "12pt",
81
+ "space_after": "10pt",
39
82
  "hyphenate": true
40
83
  },
84
+ {
85
+ "type": "paragraph",
86
+ "id": "DROP_CAPS",
87
+ "name": "Drop Caps",
88
+ "primary": true,
89
+ "font_size": "28pt",
90
+ "line_spacing": "-27pt",
91
+ "position": {
92
+ "drop_cap_lines": 2,
93
+ "drop_cap_type": "IN_TEXT",
94
+ "text_wrap": "AROUND",
95
+ "vertical_reference": "PARAGRAPH",
96
+ "size": "0pt,-28.8pt"
97
+ },
98
+ "border": {
99
+ "sides": "ALL",
100
+ "line_type": "DOT",
101
+ "width": "1pt",
102
+ "color": "#ff0000"
103
+ }
104
+ },
41
105
  {
42
106
  "type": "character",
43
107
  "id": "EMPH",
Binary file
@@ -1,27 +1,15 @@
1
1
  require 'rrtf/version'
2
+ require 'rrtf/utilities'
2
3
  require 'rrtf/font'
3
4
  require 'rrtf/colour'
5
+ require 'rrtf/page'
6
+ require 'rrtf/formatting'
7
+ require 'rrtf/properties'
4
8
  require 'rrtf/style'
5
9
  require 'rrtf/stylesheet'
6
10
  require 'rrtf/information'
7
- require 'rrtf/paper'
8
11
  require 'rrtf/node'
9
12
  require 'rrtf/list'
10
13
 
11
14
  module RRTF
12
- class RTFError < StandardError
13
- def initialize(message=nil)
14
- super(message == nil ? 'No error message available.' : message)
15
- end
16
-
17
- def RTFError.fire(message=nil)
18
- raise RTFError.new(message)
19
- end
20
- end # class RTFError
21
-
22
- class Utilities
23
- def self.constantize(string)
24
- string.split('::').inject(Object) {|o,c| o.const_get c}
25
- end
26
- end # class Utilities
27
15
  end # module RRTF
@@ -80,6 +80,14 @@ module RRTF
80
80
  prefix = indent > 0 ? ' ' * indent : ''
81
81
  "#{prefix}\\red#{@red}\\green#{@green}\\blue#{@blue};"
82
82
  end
83
+
84
+ def to_decimal(options = {})
85
+ if options["reverse_bytes"]
86
+ (@blue * 65536) + (@green * 256) + @red
87
+ else
88
+ (@red * 65536) + (@green * 256) + @blue
89
+ end # if
90
+ end
83
91
  end # End of the Colour class.
84
92
 
85
93
 
@@ -0,0 +1,988 @@
1
+ require 'stringio'
2
+
3
+ # Character formatting attributes & methods shared between style types.
4
+ # @author Wesley Hileman
5
+ # @since 0.0.2
6
+ module RRTF::CharacterFormatting
7
+ # Formatting attributes that can be applied to any text in an RTF document.
8
+ # @return [Hash<String, Hash>] a hash mapping each attribute to a hash that
9
+ # describes (1) the attribute's default value, (2) how to parse the attribute
10
+ # from the user, and (3) how to convert the attribute to an RTF sequence.
11
+ CHARACTER_ATTRIBUTES = {
12
+ # toggable attributes
13
+ "bold" => {
14
+ "default" => nil,
15
+ "to_rtf" => lambda{ |value, document| (value ? '\b' : '\b0') unless value.nil? }
16
+ },
17
+ "italic" => {
18
+ "default" => nil,
19
+ "to_rtf" => lambda{ |value, document| (value ? '\i' : '\i0') unless value.nil? }
20
+ },
21
+ "underline" => {
22
+ "default" => nil,
23
+ "dictionary" => {
24
+ "SINGLE" => "",
25
+ "DOUBLE" => "db",
26
+ "THICK" => "th",
27
+ "DASH" => "dash",
28
+ "LONG_DASH" => "ldash",
29
+ "DOT" => "d",
30
+ "DASH_DOT" => "dashd",
31
+ "DASH_DOT_DOT" => "dashdd",
32
+ "WAVE" => 'wave',
33
+ "THICK_DASH" => "thdash",
34
+ "THICK_LONG_DASH" => "thldash",
35
+ "THICK_DOT" => "thd",
36
+ "THICK_DASH_DOT" => "thdashd",
37
+ "THICK_DASH_DOT_DOT" => "thdashdd",
38
+ "THICK_WAVE" => 'hwave',
39
+ "DOUBLE_WAVE" => 'uldbwave'
40
+ },
41
+ "to_rtf" => lambda do |value, document|
42
+ return if value.nil?
43
+ case value
44
+ when TrueClass
45
+ '\ul'
46
+ when FalseClass
47
+ '\ulnone'
48
+ when String
49
+ "\\ul#{value}"
50
+ end # case
51
+ end
52
+ },
53
+ "uppercase" => {
54
+ "default" => nil,
55
+ "to_rtf" => lambda{ |value, document| (value ? '\caps' : '\caps0') unless value.nil? }
56
+ },
57
+ "superscript" => {
58
+ "default" => nil,
59
+ "to_rtf" => lambda{ |value, document| (value ? '\super' : '\super0') unless value.nil? }
60
+ },
61
+ "subscript" => {
62
+ "default" => nil,
63
+ "to_rtf" => lambda{ |value, document| (value ? '\sub' : '\sub0') unless value.nil? }
64
+ },
65
+ "strike" => {
66
+ "default" => nil,
67
+ "to_rtf" => lambda{ |value, document| (value ? '\strike' : '\strike0') unless value.nil? }
68
+ },
69
+ "emboss" => {
70
+ "default" => nil,
71
+ "to_rtf" => lambda{ |value, document| (value ? '\embo' : '\embo0') unless value.nil? }
72
+ },
73
+ "imprint" => {
74
+ "default" => nil,
75
+ "to_rtf" => lambda{ |value, document| (value ? '\impr' : '\impr0') unless value.nil? }
76
+ },
77
+ "outline" => {
78
+ "default" => nil,
79
+ "to_rtf" => lambda{ |value, document| (value ? '\outl' : '\outl0') unless value.nil? }
80
+ },
81
+ "text_hidden" => {
82
+ "default" => nil,
83
+ "to_rtf" => lambda{ |value, document| (value ? '\v' : '\v0') unless value.nil? }
84
+ },
85
+ "kerning" => {
86
+ "default" => nil,
87
+ "to_rtf" => lambda{ |value, document| (value.is_a?(Integer) ? "\\kerning#{value}" : '\kerning0') unless value.nil? }
88
+ },
89
+ # non-toggable attributes
90
+ "character_spacing_offset" => {
91
+ "default" => nil,
92
+ "from_user" => lambda{ |value| RRTF::Utilities.value2quarterpt(value) },
93
+ "to_rtf" => lambda{ |value, document| "\\expnd#{value}" unless value.nil? }
94
+ },
95
+ "foreground_color" => {
96
+ "default" => nil,
97
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
98
+ "to_rtf" => lambda{ |value, document| "\\cf#{document.colours.index(value)}" unless value.nil? }
99
+ },
100
+ "background_color" => {
101
+ "default" => nil,
102
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
103
+ "to_rtf" => lambda{ |value, document| "\\cb#{document.colours.index(value)}" unless value.nil? }
104
+ },
105
+ "underline_color" => {
106
+ "default" => nil,
107
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
108
+ "to_rtf" => lambda{ |value, document| "\\ulc#{document.colours.index(value)}" unless value.nil? }
109
+ },
110
+ "highlight_color" => {
111
+ "default" => nil,
112
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
113
+ "to_rtf" => lambda{ |value, document| "\\highlight#{document.colours.index(value)}" unless value.nil? }
114
+ },
115
+ "font" => {
116
+ "default" => nil,
117
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Font) ? value : RRTF::Font.from_string(value) },
118
+ "to_rtf" => lambda{ |value, document| "\\f#{document.fonts.index(value)}" unless value.nil? }
119
+ },
120
+ "font_size" => {
121
+ "default" => nil,
122
+ "from_user" => lambda{ |value| RRTF::Utilities.value2halfpt(value) },
123
+ "to_rtf" => lambda{ |value, document| "\\fs#{value}" unless value.nil? }
124
+ }
125
+ }.freeze
126
+
127
+ # Generates attribute accessors for all character attributes when the module
128
+ # is included in another module or class.
129
+ def self.included(base)
130
+ # define accessors in base for paragraph attributes
131
+ base.class_eval do
132
+ CHARACTER_ATTRIBUTES.each do |key, options|
133
+ attr_accessor :"#{key}"
134
+ end # each
135
+ end # class_eval
136
+ end
137
+
138
+ # Initializes character formatting attributes.
139
+ # @note The RTF specification states the "highlight_color" attribute can not
140
+ # be applied to a style definition in a stylesheet.
141
+ #
142
+ # @param [Hash] options the character formatting options.
143
+ # @option options [Boolean] "bold" (nil) enable or disable bold (nil to remain same).
144
+ # @option options [Boolean] "italic" (nil) enable or disable italic (nil to remain same).
145
+ # @option options [Boolean, String] "underline" (nil) enable or disable underline (nil to remain same); can also be a string (see {CharacterFormatting::CHARACTER_ATTRIBUTES}).
146
+ # @option options [Boolean] "uppercase" (nil) enable or disable all caps (nil to remain same).
147
+ # @option options [Boolean] "superscript" (nil) enable or disable superscript (nil to remain same).
148
+ # @option options [Boolean] "subscript" (nil) enable or disable subscript (nil to remain same).
149
+ # @option options [Boolean] "strike" (nil) enable or disable single line-through (nil to remain same).
150
+ # @option options [Boolean] "emboss" (nil) enable or disable emboss (nil to remain same).
151
+ # @option options [Boolean] "imprint" (nil) enable or disable imprint (nil to remain same).
152
+ # @option options [Boolean] "outline" (nil) enable or disable outline (nil to remain same).
153
+ # @option options [Boolean] "text_hidden" (nil) enable or disable hidden (nil to remain same).
154
+ # @option options [Boolean, Integer] "kerning" (nil) enable or disable kerning (nil to remain same); to enable specify the font size in half-points above which kerining will be applied.
155
+ # @option options [Integer] "character_spacing_offset" (nil) quarter points by which to expand or compress character spacing (negative for compress).
156
+ # @option options [String, Colour] "foreground_color" (nil) colour to apply to the foreground (text); see {Colour.from_string} for string format.
157
+ # @option options [String, Colour] "background_color" (nil) colour to apply to the background; see {Colour.from_string} for string format.
158
+ # @option options [String, Colour] "underline_color" (nil) colour to apply to the underline; see {Colour.from_string} for string format.
159
+ # @option options [String, Colour] "highlight_color" (nil) colour with which to highlight text.
160
+ # @option options [String, Font] "font" (nil) font to apply to text; see {Font.from_string} for string format.
161
+ # @option options [Integer] "font_size" (nil) font size in half-points.
162
+ def initialize_character_formatting(options = {})
163
+ # load default attribute values
164
+ CHARACTER_ATTRIBUTES.each do |key, options|
165
+ send("#{key}=", options["default"])
166
+ end # each
167
+ # overwrite default attribute values with given values
168
+ set_character_formatting_from_hashmap(options)
169
+ end
170
+
171
+ # Sets character formatting attributes according to the supplied hashmap.
172
+ # @see #initialize_character_formatting
173
+ def set_character_formatting_from_hashmap(hash)
174
+ hash.each do |attribute, value|
175
+ # skip unreconized attributes
176
+ next unless(CHARACTER_ATTRIBUTES.keys.include?(attribute))
177
+ # preprocess value if nessesary
178
+ if CHARACTER_ATTRIBUTES[attribute].has_key?("from_user")
179
+ value = CHARACTER_ATTRIBUTES[attribute]["from_user"].call(value)
180
+ elsif CHARACTER_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
181
+ value = CHARACTER_ATTRIBUTES[attribute]["dictionary"][value]
182
+ end # if
183
+ # set attribute value
184
+ send("#{attribute}=", value)
185
+ end # each
186
+ end
187
+
188
+ # Generates an RTF string representing all applied character formatting.
189
+ # @note To generate correct RTF control words for colours and fonts, a
190
+ # document object must be provided to this method so that colour and font
191
+ # indicies may be found in the document's colour and font tables, respectively.
192
+ #
193
+ # @param [Document] document the document for which the RTF is to be generated.
194
+ def character_formatting_to_rtf(document = nil)
195
+ text = StringIO.new
196
+
197
+ # accumulate RTF representations of attributes
198
+ CHARACTER_ATTRIBUTES.each do |key, options|
199
+ if options.has_key?("to_rtf")
200
+ rtf = options["to_rtf"].call(send(key), document)
201
+ text << rtf unless rtf.nil?
202
+ end # if
203
+ end # each
204
+
205
+ text.string
206
+ end
207
+ end # module CharacterFormatting
208
+
209
+ # Paragraph formatting attributes and methods shared between style types.
210
+ # @author Wesley Hileman
211
+ # @since 0.0.2
212
+ module RRTF::ParagraphFormatting
213
+ # Formatting attributes that can be applied to any paragraph in an RTF document.
214
+ # @return [Hash<String, Hash>] a hash mapping each attribute to a hash that
215
+ # describes (1) the attribute's default value, (2) how to parse the attribute
216
+ # from the user, and (3) how to convert the attribute to an RTF sequence.
217
+ PARAGRAPH_ATTRIBUTES = {
218
+ "justification" => {
219
+ "default" => "l",
220
+ "dictionary" => {
221
+ "LEFT" => "l",
222
+ "RIGHT" => "r",
223
+ "CENTER" => "c",
224
+ "CENTRE" => "c",
225
+ "FULL" => "j"
226
+ },
227
+ "to_rtf" => lambda{ |value, document| "\\q#{value}" }
228
+ },
229
+ "left_indent" => {
230
+ "default" => nil,
231
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
232
+ "to_rtf" => lambda{ |value, document| "\\li#{value}" unless value.nil? }
233
+ },
234
+ "right_indent" => {
235
+ "default" => nil,
236
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
237
+ "to_rtf" => lambda{ |value, document| "\\ri#{value}" unless value.nil? }
238
+ },
239
+ "first_line_indent" => {
240
+ "default" => nil,
241
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
242
+ "to_rtf" => lambda{ |value, document| "\\fi#{value}" unless value.nil? }
243
+ },
244
+ "space_before" => {
245
+ "default" => nil,
246
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
247
+ "to_rtf" => lambda{ |value, document| "\\sb#{value}" unless value.nil? }
248
+ },
249
+ "space_after" => {
250
+ "default" => nil,
251
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
252
+ "to_rtf" => lambda{ |value, document| "\\sa#{value}" unless value.nil? }
253
+ },
254
+ "line_spacing" => {
255
+ "default" => nil,
256
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
257
+ "to_rtf" => lambda{ |value, document| "\\sl#{value}" unless value.nil? }
258
+ },
259
+ "widow_orphan_ctl" => {
260
+ "default" => nil,
261
+ "to_rtf" => lambda{ |value, document| (value ? "\\widctlpar" : "\\nowidctlpar") unless value.nil? }
262
+ },
263
+ "no_break" => {
264
+ "default" => false,
265
+ "to_rtf" => lambda{ |value, document| "\\keep" if value }
266
+ },
267
+ "no_break_with_next" => {
268
+ "default" => false,
269
+ "to_rtf" => lambda{ |value, document| "\\keepn" if value }
270
+ },
271
+ "hyphenate" => {
272
+ "default" => nil,
273
+ "to_rtf" => lambda{ |value, document| (value ? "\\hyphpar" : "\\hyphpar0") unless value.nil? }
274
+ },
275
+ "paragraph_flow" => {
276
+ "default" => 'ltr',
277
+ "dictionary" => {
278
+ "LEFT_TO_RIGHT" => 'ltr',
279
+ "RIGHT_TO_LEFT" => 'rtl'
280
+ },
281
+ "to_rtf" => lambda{ |value, document| "\\#{value}par" unless value.nil? }
282
+ },
283
+ "border" => {
284
+ "default" => nil,
285
+ "from_user" => lambda do |value|
286
+ case value
287
+ when Array
288
+ value.collect do |b|
289
+ case b
290
+ when Hash
291
+ RRTF::BorderStyle.new(b)
292
+ when RRTF::BorderStyle
293
+ b
294
+ else
295
+ RRTF::RTFError.fire("Invalid border '#{b}'.")
296
+ end # case
297
+ end # collect
298
+ when Hash
299
+ [RRTF::BorderStyle.new(value)]
300
+ when RRTF::BorderStyle
301
+ [value]
302
+ else
303
+ RRTF::RTFError.fire("Invalid border '#{value}'.")
304
+ end # case
305
+ end,
306
+ "to_rtf" => lambda{ |value, document| value.collect{ |border| border.rtf_formatting(document) }.join(' ') unless value.nil? }
307
+ },
308
+ "position" => {
309
+ "default" => nil,
310
+ "from_user" => lambda do |value|
311
+ case value
312
+ when Hash
313
+ RRTF::PositionStyle.new(value)
314
+ when PositionStyle
315
+ value
316
+ else
317
+ RRTF::RTFError.fire("Invalid position '#{value}'.")
318
+ end # case
319
+ end,
320
+ "to_rtf" => lambda{ |value, document| value.rtf_formatting(document) unless value.nil? }
321
+ },
322
+ "shading" => {
323
+ "default" => nil,
324
+ "from_user" => lambda do |value|
325
+ case value
326
+ when Hash
327
+ RRTF::ShadingStyle.new(value)
328
+ when ShadingStyle
329
+ value
330
+ else
331
+ RRTF::RTFError.fire("Invalid shading '#{value}'.")
332
+ end # case
333
+ end,
334
+ "to_rtf" => lambda{ |value, document| value.rtf_formatting(document) unless value.nil? }
335
+ }
336
+ }.freeze
337
+
338
+ # Generates attribute accessors for all paragraph attributes when the module
339
+ # is included in another module or class.
340
+ def self.included(base)
341
+ # define accessors in base for paragraph attributes
342
+ base.class_eval do
343
+ PARAGRAPH_ATTRIBUTES.each do |key, options|
344
+ attr_accessor :"#{key}"
345
+ end # each
346
+ end # class_eval
347
+ end
348
+
349
+ # Initializes paragraph formatting attributes.
350
+ #
351
+ # @param [Hash] options the paragraph formatting options.
352
+ # @option options [String] "justification" ('LEFT') the paragraph justification ('LEFT', 'CENTER'/'CENTRE', 'RIGHT', or 'FULL').
353
+ # @option options [Integer] "left_indent" (nil) the left indent of the paragraph (twentieth points).
354
+ # @option options [Integer] "right_indent" (nil) the right indent of the paragraph (twentieth points).
355
+ # @option options [Integer] "first_line_indent" (nil) the first line indent of the paragraph (twentieth points).
356
+ # @option options [Integer] "space_before" (nil) the space before the paragraph (twentieth points).
357
+ # @option options [Integer] "space_after" (nil) the space after the paragraph (twentieth points).
358
+ # @option options [Integer] "line_spacing" (nil) the line spacing in the paragraph (twentieth points).
359
+ # @option options [Boolean] "widow_orphan_ctl" (nil) enable or disable widow-and-orphan control.
360
+ # @option options [Boolean] "no_break" (nil) when true, tries to keep the paragraph on the same page (i.e. without breaking).
361
+ # @option options [Boolean] "no_break_with_next" (nil) when true, tries to keep the paragraph with the next paragraph on the same page (i.e. without breaking).
362
+ # @option options [Boolean] "hyphenate" (nil) enable or disable hyphenation for the paragraph.
363
+ # @option options [String] "paragraph_flow" ('LEFT_TO_RIGHT') the text flow direction in the paragraph ('LEFT_TO_RIGHT' or 'RIGHT_TO_LEFT').
364
+ # @option options [Array<Hash, BorderStyle>, Hash, BorderStyle] "border" (nil) the border style(s) to be applied to the paragraph (see {BorderFormatting#initialize_border_formatting}).
365
+ def initialize_paragraph_formatting(options = {})
366
+ # load default attribute values
367
+ PARAGRAPH_ATTRIBUTES.each do |key, options|
368
+ send("#{key}=", options["default"])
369
+ end # each
370
+ # overwrite default attribute values with given values
371
+ set_paragraph_formatting_from_hashmap(options)
372
+ end
373
+
374
+ # Sets paragraph formatting attributes according to the supplied hashmap.
375
+ # @see #initialize_document_formatting
376
+ def set_paragraph_formatting_from_hashmap(hash)
377
+ hash.each do |attribute, value|
378
+ # skip unreconized attributes
379
+ next unless(PARAGRAPH_ATTRIBUTES.keys.include?(attribute))
380
+ # preprocess value if nessesary
381
+ if PARAGRAPH_ATTRIBUTES[attribute].has_key?("from_user")
382
+ value = PARAGRAPH_ATTRIBUTES[attribute]["from_user"].call(value)
383
+ elsif PARAGRAPH_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
384
+ value = PARAGRAPH_ATTRIBUTES[attribute]["dictionary"][value]
385
+ end # if
386
+ # set attribute value
387
+ send("#{attribute}=", value)
388
+ end # each
389
+ end
390
+
391
+ # Generates an RTF string representing all applied paragraph formatting.
392
+ # @note To generate correct RTF control words for colours and fonts, a
393
+ # document object must be provided to this method so that colour and font
394
+ # indicies may be found in the document's colour and font tables, respectively.
395
+ #
396
+ # @param [Document] document the document for which the RTF is to be generated.
397
+ def paragraph_formatting_to_rtf(document)
398
+ text = StringIO.new
399
+
400
+ # accumulate RTF representations of paragraph attributes
401
+ PARAGRAPH_ATTRIBUTES.each do |key, options|
402
+ if options.has_key?("to_rtf")
403
+ rtf = options["to_rtf"].call(send(key), document)
404
+ text << rtf unless rtf.nil?
405
+ end # if
406
+ end # each
407
+
408
+ text.string
409
+ end
410
+ end # module ParagraphFormatting
411
+
412
+ # Paragraph, table, and image border formatting attributes and methods.
413
+ # @author Wesley Hileman
414
+ # @since 1.0.0
415
+ module RRTF::BorderFormatting
416
+ # Formatting attributes that can be applied to borders of paragraphs, tables, & images.
417
+ # @note The "sides" attribute must appear at the top of this hash so that
418
+ # {#border_formatting_to_rtf} generates correct RTF (borders are initiated by the "sides"
419
+ # attribute in the RTF spec).
420
+ # @note The "line_type" attribute must appear second in this hash so that
421
+ # {#border_formatting_to_rtf} generates correct RTF.
422
+ BORDER_ATTRIBUTES = {
423
+ "sides" => {
424
+ "default" => "box",
425
+ "dictionary" => {
426
+ "ALL" => "box",
427
+ "LEFT" => "brdrl",
428
+ "RIGHT" => "brdrr",
429
+ "TOP" => "brdrt",
430
+ "BOTTOM" => "brdrb"
431
+ },
432
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
433
+ },
434
+ "line_type" => {
435
+ "default" => "brdrs",
436
+ "dictionary" => {
437
+ "SINGLE" => "brdrs",
438
+ "THICK" => "brdrth",
439
+ "DOUBLE" => "brdrdb",
440
+ "DOT" => "brdrdot",
441
+ "DASH" => "brdrdash",
442
+ "HAIRLINE" => "brdrhair"
443
+ },
444
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
445
+ },
446
+ "width" => {
447
+ "default" => nil,
448
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
449
+ "to_rtf" => lambda{ |value, document| "\\brdrw#{value}" unless value.nil? }
450
+ },
451
+ "spacing" => {
452
+ "default" => nil,
453
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
454
+ "to_rtf" => lambda{ |value, document| "\\brsp#{value}" unless value.nil? }
455
+ },
456
+ "color" => {
457
+ "default" => nil,
458
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
459
+ "to_rtf" => lambda{ |value, document| "\\brdrcf#{document.colours.index(value)}" unless value.nil? }
460
+ }
461
+ }.freeze
462
+
463
+ # Generates attribute accessors for all paragraph attributes when the module
464
+ # is included in another module or class.
465
+ def self.included(base)
466
+ # define accessors in base for paragraph attributes
467
+ base.class_eval do
468
+ BORDER_ATTRIBUTES.each do |key, options|
469
+ attr_accessor :"#{key}"
470
+ end # each
471
+ end # class_eval
472
+ end
473
+
474
+ # Initializes border formatting attributes.
475
+ #
476
+ # @param [Hash] options the border formatting options.
477
+ # @option options [String] "sides" ('ALL') the sides to which the border applied ("ALL", "LEFT", "RIGHT", "TOP", or "BOTTOM").
478
+ # @option options [String] "line_type" ('SINGLE') the border line type ("SINGLE", "THICK", "DOUBLE", "DOT", "DASH", or "HAIRLINE").
479
+ # @option options [String] "width" (nil) the width of the the border line in twips (can be a string, see {Utilities.value2twips}).
480
+ # @option options [String] "spacing" (nil) the spacing between the border and paragraph content in twips (can be a string, see {Utilities.value2twips}).
481
+ # @option options [String, Colour] "color" (nil) the color of the the border line (can be a string, see {Colour.from_string}).
482
+ def initialize_border_formatting(options = {})
483
+ # load default attribute values
484
+ BORDER_ATTRIBUTES.each do |key, options|
485
+ send("#{key}=", options["default"])
486
+ end # each
487
+ # overwrite default attribute values with given values
488
+ set_border_formatting_from_hashmap(options)
489
+ end
490
+
491
+ # Sets paragraph formatting attributes according to the supplied hashmap.
492
+ # @see #initialize_border_formatting
493
+ def set_border_formatting_from_hashmap(hash)
494
+ hash.each do |attribute, value|
495
+ # skip unreconized attributes
496
+ next unless(BORDER_ATTRIBUTES.keys.include?(attribute))
497
+ # preprocess value if nessesary
498
+ if BORDER_ATTRIBUTES[attribute].has_key?("from_user")
499
+ value = BORDER_ATTRIBUTES[attribute]["from_user"].call(value)
500
+ elsif BORDER_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
501
+ value = BORDER_ATTRIBUTES[attribute]["dictionary"][value]
502
+ end # if
503
+ # set attribute value
504
+ send("#{attribute}=", value)
505
+ end # each
506
+ end
507
+
508
+ # Generates an RTF string representing all applied border formatting.
509
+ # @note To generate correct RTF control words for colours and fonts, a
510
+ # document object must be provided to this method so that colour and font
511
+ # indicies may be found in the document's colour and font tables, respectively.
512
+ #
513
+ # @param [Document] document the document for which the RTF is to be generated.
514
+ def border_formatting_to_rtf(document)
515
+ text = StringIO.new
516
+
517
+ # accumulate RTF representations of paragraph attributes
518
+ BORDER_ATTRIBUTES.each do |key, options|
519
+ if options.has_key?("to_rtf")
520
+ rtf = options["to_rtf"].call(send(key), document)
521
+ text << rtf unless rtf.nil?
522
+ end # if
523
+ end # each
524
+
525
+ text.string
526
+ end
527
+ end
528
+
529
+ # Paragraph absolute positioning formatting attributes and methods.
530
+ # @author Wesley Hileman
531
+ # @since 1.0.0
532
+ module RRTF::PositionFormatting
533
+ # Formatting attributes that can be applied to position paragraphs as frames.
534
+ POSITION_ATTRIBUTES = {
535
+ "size" => {
536
+ "default" => nil,
537
+ "from_user" => lambda{ |value| RRTF::Page::Size.new(value) },
538
+ "to_rtf" => lambda{ |value, document| "\\absw#{value.width}\\absh#{value.height}" unless value.nil? }
539
+ },
540
+ "horizontal_reference" => {
541
+ "default" => nil,
542
+ "dictionary" => {
543
+ "PAGE" => "phpg",
544
+ "MARGIN" => "phmrg",
545
+ "COLUMN" => "phcol"
546
+ },
547
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
548
+ },
549
+ "vertical_reference" => {
550
+ "default" => nil,
551
+ "dictionary" => {
552
+ "PAGE" => "pvpg",
553
+ "MARGIN" => "pvmrg",
554
+ "PARAGRAPH" => "pvpara"
555
+ },
556
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
557
+ },
558
+ "horizontal_position" => {
559
+ "default" => nil,
560
+ "dictionary" => {
561
+ "CENTER" => "posxc",
562
+ "LEFT" => "posxl",
563
+ "RIGHT" => "posxr"
564
+ },
565
+ "from_user" => lambda do |value|
566
+ return nil if value.nil?
567
+ if value.is_a?(String) && value =~ /^([A-Z]+)$/
568
+ POSITION_ATTRIBUTES["horizontal_position"]["dictionary"][value]
569
+ else
570
+ "posx#{RRTF::Utilities.value2twips(value)}"
571
+ end
572
+ end,
573
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
574
+ },
575
+ "vertical_position" => {
576
+ "default" => nil,
577
+ "dictionary" => {
578
+ "CENTER" => "posyc",
579
+ "TOP" => "posyt",
580
+ "BOTTOM" => "posyb"
581
+ },
582
+ "from_user" => lambda do |value|
583
+ return nil if value.nil?
584
+ if value.is_a?(String) && value =~ /^([A-Z]+)$/
585
+ POSITION_ATTRIBUTES["vertical_position"]["dictionary"][value]
586
+ else
587
+ "posy#{RRTF::Utilities.value2twips(value)}"
588
+ end
589
+ end,
590
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
591
+ },
592
+ "text_wrap" => {
593
+ "default" => nil,
594
+ "dictionary" => {
595
+ "NONE" => "nowrap",
596
+ "DEFAULT" => "wrapdefault",
597
+ "AROUND" => "wraparound",
598
+ "TIGHT" => "wraptight",
599
+ "THROUGH" => "wrapthrough"
600
+ },
601
+ "to_rtf" => lambda{ |value, document| "\\#{value}" unless value.nil? }
602
+ },
603
+ "drop_cap_lines" => {
604
+ "default" => nil,
605
+ "to_rtf" => lambda{ |value, document| "\\dropcapli#{value}" unless value.nil? }
606
+ },
607
+ "drop_cap_type" => {
608
+ "default" => nil,
609
+ "dictionary" => {
610
+ "IN_TEXT" => 1,
611
+ "IN_MARGIN" => 2
612
+ },
613
+ "to_rtf" => lambda{ |value, document| "\\dropcapt#{value}" unless value.nil? }
614
+ },
615
+ "lock_anchor" => {
616
+ "default" => nil,
617
+ "to_rtf" => lambda{ |value, document| (value ? "\\abslock0" : "\\abslock1") unless value.nil? }
618
+ }
619
+ }.freeze
620
+
621
+ # Generates attribute accessors for all position attributes when the module
622
+ # is included in another module or class.
623
+ def self.included(base)
624
+ # define accessors in base for position attributes
625
+ base.class_eval do
626
+ POSITION_ATTRIBUTES.each do |key, options|
627
+ attr_accessor :"#{key}"
628
+ end # each
629
+ end # class_eval
630
+ end
631
+
632
+ # Initializes position formatting attributes.
633
+ #
634
+ # @param [Hash] options the border formatting options.
635
+ # @option options [String] "sides" ('ALL') the sides to which the border applied ("ALL", "LEFT", "RIGHT", "TOP", or "BOTTOM").
636
+ def initialize_position_formatting(options = {})
637
+ # load default attribute values
638
+ POSITION_ATTRIBUTES.each do |key, options|
639
+ send("#{key}=", options["default"])
640
+ end # each
641
+ # overwrite default attribute values with given values
642
+ set_position_formatting_from_hashmap(options)
643
+ end
644
+
645
+ # Sets formatting attributes according to the supplied hashmap.
646
+ # @see #initialize_position_formatting
647
+ def set_position_formatting_from_hashmap(hash)
648
+ hash.each do |attribute, value|
649
+ # skip unreconized attributes
650
+ next unless(POSITION_ATTRIBUTES.keys.include?(attribute))
651
+ # preprocess value if nessesary
652
+ if POSITION_ATTRIBUTES[attribute].has_key?("from_user")
653
+ value = POSITION_ATTRIBUTES[attribute]["from_user"].call(value)
654
+ elsif POSITION_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
655
+ value = POSITION_ATTRIBUTES[attribute]["dictionary"][value]
656
+ end # if
657
+ # set attribute value
658
+ send("#{attribute}=", value)
659
+ end # each
660
+ end
661
+
662
+ # Generates an RTF string representing all applied position formatting.
663
+ # @note To generate correct RTF control words for colours and fonts, a
664
+ # document object must be provided to this method so that colour and font
665
+ # indicies may be found in the document's colour and font tables, respectively.
666
+ #
667
+ # @param [Document] document the document for which the RTF is to be generated.
668
+ def position_formatting_to_rtf(document)
669
+ text = StringIO.new
670
+
671
+ # accumulate RTF representations of paragraph attributes
672
+ POSITION_ATTRIBUTES.each do |key, options|
673
+ if options.has_key?("to_rtf")
674
+ rtf = options["to_rtf"].call(send(key), document)
675
+ text << rtf unless rtf.nil?
676
+ end # if
677
+ end # each
678
+
679
+ text.string
680
+ end
681
+ end
682
+
683
+ # Paragraph shading formatting attributes and methods.
684
+ # @author Wesley Hileman
685
+ # @since 1.0.0
686
+ module RRTF::ShadingFormatting
687
+ # Formatting attributes that can be applied to shade the background
688
+ # of paragraphs.
689
+ SHADING_ATTRIBUTES = {
690
+ "opacity" => {
691
+ "default" => 100,
692
+ "from_user" => lambda{ |value| RRTF::Utilities.value2hunpercent(value) },
693
+ "to_rtf" => lambda{ |value, document| "\\shading#{value}" unless value.nil? }
694
+ },
695
+ "foreground_color" => {
696
+ "default" => nil,
697
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
698
+ "to_rtf" => lambda{ |value, document| "\\cfpat#{document.colours.index(value)}" unless value.nil? }
699
+ },
700
+ "background_color" => {
701
+ "default" => nil,
702
+ "from_user" => lambda{ |value| value.is_a?(RRTF::Colour) ? value : RRTF::Colour.from_string(value) },
703
+ "to_rtf" => lambda{ |value, document| "\\cbpat#{document.colours.index(value)}" unless value.nil? }
704
+ }
705
+ }.freeze
706
+
707
+ # Generates attribute accessors for all position attributes when the module
708
+ # is included in another module or class.
709
+ def self.included(base)
710
+ # define accessors in base for position attributes
711
+ base.class_eval do
712
+ SHADING_ATTRIBUTES.each do |key, options|
713
+ attr_accessor :"#{key}"
714
+ end # each
715
+ end # class_eval
716
+ end
717
+
718
+ # Initializes position formatting attributes.
719
+ #
720
+ # @param [Hash] options the border formatting options.
721
+ # @option options [String] "sides" ('ALL') the sides to which the border applied ("ALL", "LEFT", "RIGHT", "TOP", or "BOTTOM").
722
+ def initialize_shading_formatting(options = {})
723
+ # load default attribute values
724
+ SHADING_ATTRIBUTES.each do |key, options|
725
+ send("#{key}=", options["default"])
726
+ end # each
727
+ # overwrite default attribute values with given values
728
+ set_shading_formatting_from_hashmap(options)
729
+ end
730
+
731
+ # Sets formatting attributes according to the supplied hashmap.
732
+ # @see #initialize_shading_formatting
733
+ def set_shading_formatting_from_hashmap(hash)
734
+ hash.each do |attribute, value|
735
+ # skip unreconized attributes
736
+ next unless(SHADING_ATTRIBUTES.keys.include?(attribute))
737
+ # preprocess value if nessesary
738
+ if SHADING_ATTRIBUTES[attribute].has_key?("from_user")
739
+ value = SHADING_ATTRIBUTES[attribute]["from_user"].call(value)
740
+ elsif SHADING_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
741
+ value = SHADING_ATTRIBUTES[attribute]["dictionary"][value]
742
+ end # if
743
+ # set attribute value
744
+ send("#{attribute}=", value)
745
+ end # each
746
+ end
747
+
748
+ # Generates an RTF string representing all applied position formatting.
749
+ # @note To generate correct RTF control words for colours and fonts, a
750
+ # document object must be provided to this method so that colour and font
751
+ # indicies may be found in the document's colour and font tables, respectively.
752
+ #
753
+ # @param [Document] document the document for which the RTF is to be generated.
754
+ def shading_formatting_to_rtf(document)
755
+ text = StringIO.new
756
+
757
+ # accumulate RTF representations of paragraph attributes
758
+ SHADING_ATTRIBUTES.each do |key, options|
759
+ if options.has_key?("to_rtf")
760
+ rtf = options["to_rtf"].call(send(key), document)
761
+ text << rtf unless rtf.nil?
762
+ end # if
763
+ end # each
764
+
765
+ text.string
766
+ end
767
+ end
768
+
769
+ # Document formatting attributes and methods.
770
+ # @author Wesley Hileman
771
+ # @since 1.0.0
772
+ module RRTF::DocumentFormatting
773
+ # Formatting attributes that can be applied to an RTF document.
774
+ # @return [Hash<String, Hash>] a hash mapping each attribute to a hash that
775
+ # describes (1) the attribute's default value, (2) how to parse the attribute
776
+ # from the user, and (3) how to convert the attribute to an RTF sequence.
777
+ DOCUMENT_ATTRIBUTES = {
778
+ "facing_pages" => {
779
+ "default" => nil,
780
+ "to_rtf" => lambda{ |value| "\\facingp" if value }
781
+ },
782
+ "mirror_margins" => {
783
+ "default" => nil,
784
+ "to_rtf" => lambda{ |value| "\\margmirror" if value }
785
+ },
786
+ "widow_orphan_ctl" => {
787
+ "default" => nil,
788
+ "to_rtf" => lambda{ |value| "\\widowctl" if value }
789
+ },
790
+ "tab_width" => {
791
+ "default" => nil,
792
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
793
+ "to_rtf" => lambda{ |value| "\\deftab#{value}" unless value.nil? }
794
+ },
795
+ "hyphenation_width" => {
796
+ "default" => nil,
797
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
798
+ "to_rtf" => lambda{ |value| "\\hyphhotz#{value}" unless value.nil? }
799
+ },
800
+ "max_consecutive_hyphenation" => {
801
+ "default" => nil,
802
+ "to_rtf" => lambda{ |value| "\\hyphconsec#{value}" unless value.nil? }
803
+ },
804
+ "hyphenate" => {
805
+ "default" => true,
806
+ "to_rtf" => lambda{ |value| (value ? "\\hyphauto1" : "\\hyphauto0") unless value.nil? }
807
+ }
808
+ }.freeze
809
+
810
+ # Generates attribute accessors for all document attributes when the module
811
+ # is included in another module or class.
812
+ def self.included(base)
813
+ # define accessors in base for document attributes
814
+ base.class_eval do
815
+ DOCUMENT_ATTRIBUTES.each do |key, options|
816
+ attr_accessor :"#{key}"
817
+ end # each
818
+ end # class_eval
819
+ end
820
+
821
+ # Initializes document formatting attributes.
822
+ #
823
+ # @param [Hash] options the document formatting options.
824
+ # @option options [Boolean] "facing_pages" (nil) whether or not to enable facing pages in the document.
825
+ # @option options [Boolean] "mirror_margins" (nil) whether or not to enable mirrored margins (when facing pages is enabled) in the document.
826
+ # @option options [Boolean] "widow_orphan_ctl" (nil) whether or not to enable widow and orphan control for the document.
827
+ # @option options [String] "tab_width" (nil) the default tab width for the document (specify a string, see {Utilities.value2twips}).
828
+ # @option options [String] "hyphenation_width" (nil) the space from the right margin in which hyphenation occurs in the document (specify a string, see {Utilities.value2twips}).
829
+ # @option options [Integer] "max_consecutive_hyphenation" (nil) the maximum number of consecutive hyphentated lines allowed in the document.
830
+ # @option options [Boolean] "hyphenate" (nil) enable or disable hyphenation in the document.
831
+ def initialize_document_formatting(options = {})
832
+ # load default attribute values
833
+ DOCUMENT_ATTRIBUTES.each do |key, options|
834
+ send("#{key}=", options["default"])
835
+ end # each
836
+ # overwrite default attribute values with given values
837
+ set_document_formatting_from_hashmap(options)
838
+ end
839
+
840
+ # Sets document formatting attributes according to the supplied hashmap.
841
+ # @see #initialize_document_formatting
842
+ def set_document_formatting_from_hashmap(hash)
843
+ hash.each do |attribute, value|
844
+ # skip unreconized attributes
845
+ next unless(DOCUMENT_ATTRIBUTES.keys.include?(attribute))
846
+ # preprocess value if nessesary
847
+ if DOCUMENT_ATTRIBUTES[attribute].has_key?("from_user")
848
+ value = DOCUMENT_ATTRIBUTES[attribute]["from_user"].call(value)
849
+ elsif DOCUMENT_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
850
+ value = DOCUMENT_ATTRIBUTES[attribute]["dictionary"][value]
851
+ end # if
852
+ # set attribute value
853
+ send("#{attribute}=", value)
854
+ end # each
855
+ end
856
+
857
+ # Generates an RTF string representing all applied document formatting.
858
+ #
859
+ # @return [String] RTF string.
860
+ def document_formatting_to_rtf
861
+ text = StringIO.new
862
+
863
+ # accumulate RTF representations of document attributes
864
+ DOCUMENT_ATTRIBUTES.each do |key, options|
865
+ if options.has_key?("to_rtf")
866
+ rtf = options["to_rtf"].call(send(key))
867
+ text << rtf unless rtf.nil?
868
+ end # if
869
+ end # each
870
+
871
+ text.string
872
+ end
873
+ end # module DocumentFormatting
874
+
875
+ # Page formatting attributes and methods.
876
+ # @author Wesley Hileman
877
+ # @since 1.0.0
878
+ module RRTF::PageFormatting
879
+ # Formatting attributes that can be applied to an RTF document or a section
880
+ # in a document.
881
+ # @return [Hash<String, Hash>] a hash mapping each attribute to a hash that
882
+ # describes (1) the attribute's default value, (2) how to parse the attribute
883
+ # from the user, and (3) how to convert the attribute to an RTF sequence.
884
+ PAGE_ATTRIBUTES = {
885
+ "orientation" => {
886
+ "default" => :portrait,
887
+ "dictionary" => {
888
+ "PORTRAIT" => :portrait,
889
+ "LANDSCAPE" => :landscape
890
+ },
891
+ "to_rtf" => lambda{ |value| "\\landscape" if value == :landscape }
892
+ },
893
+ "size" => {
894
+ "default" => RRTF::Page::Size.new,
895
+ "from_user" => lambda{ |value| RRTF::Page::Size.new(value) },
896
+ "to_rtf" => lambda{ |value| "\\paperw#{value.width}\\paperh#{value.height}" }
897
+ },
898
+ "margin" => {
899
+ "default" => RRTF::Page::Margin.new,
900
+ "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}" }
902
+ },
903
+ "gutter" => {
904
+ "default" => nil,
905
+ "from_user" => lambda{ |value| RRTF::Utilities.value2twips(value) },
906
+ "to_rtf" => lambda{ |value| "\\gutter#{value}" unless value.nil? }
907
+ }
908
+ }.freeze
909
+
910
+ # Generates attribute accessors for all page attributes when the module
911
+ # is included in another module or class.
912
+ def self.included(base)
913
+ # define accessors in base for document attributes
914
+ base.class_eval do
915
+ PAGE_ATTRIBUTES.each do |key, options|
916
+ attr_accessor :"#{key}"
917
+ end # each
918
+ end # class_eval
919
+ end
920
+
921
+ # Initializes page formatting attributes.
922
+ # @note The behavior of the "gutter" option changes with the document
923
+ # "facing_pages" setting.
924
+ #
925
+ # @param [Hash] options the document formatting options.
926
+ # @option options [String] "orientation" ("PORTRAIT") the orientation of the paper ("PORTRAIT" or "LANDSCAPE").
927
+ # @option options [String, Page::Size] "size" (Page::Size.new) the size of the paper (object or string; see {Page::Size#initialize}).
928
+ # @option options [String, Page::Margin] "margin" (Page::Margin.new) the paper margin (object or string; see {Page::Margin#initialize}).
929
+ # @option options [String] "gutter" (nil) the page gutter width (specify a string, see {Utilities.value2twips}).
930
+ def initialize_page_formatting(options = {})
931
+ # load default attribute values
932
+ PAGE_ATTRIBUTES.each do |key, options|
933
+ send("#{key}=", options["default"])
934
+ end # each
935
+ # overwrite default attribute values with given values
936
+ set_page_formatting_from_hashmap(options)
937
+ end
938
+
939
+ # Sets document formatting attributes according to the supplied hashmap.
940
+ # @see #initialize_page_formatting
941
+ def set_page_formatting_from_hashmap(hash)
942
+ hash.each do |attribute, value|
943
+ # skip unreconized attributes
944
+ next unless(PAGE_ATTRIBUTES.keys.include?(attribute))
945
+ # preprocess value if nessesary
946
+ if PAGE_ATTRIBUTES[attribute].has_key?("from_user")
947
+ value = PAGE_ATTRIBUTES[attribute]["from_user"].call(value)
948
+ elsif PAGE_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
949
+ value = PAGE_ATTRIBUTES[attribute]["dictionary"][value]
950
+ end # if
951
+ # set attribute value
952
+ send("#{attribute}=", value)
953
+ end # each
954
+ end
955
+
956
+ # Generates an RTF string representing all applied page formatting.
957
+ #
958
+ # @return [String] RTF string.
959
+ def page_formatting_to_rtf
960
+ text = StringIO.new
961
+
962
+ # accumulate RTF representations of page attributes
963
+ PAGE_ATTRIBUTES.each do |key, options|
964
+ if options.has_key?("to_rtf")
965
+ rtf = options["to_rtf"].call(send(key))
966
+ text << rtf unless rtf.nil?
967
+ end # if
968
+ end # each
969
+
970
+ text.string
971
+ 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
+ end