thinreports 0.8.2 → 0.9.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +2 -0
  3. data/.travis.yml +3 -3
  4. data/CHANGELOG.md +98 -1
  5. data/README.md +0 -9
  6. data/Rakefile +1 -2
  7. data/examples/character_spacing/character_spacing.tlf +293 -1
  8. data/examples/dynamic_image/dynamic_image.tlf +661 -1
  9. data/examples/dynamic_style/dynamic_style.rb +4 -4
  10. data/examples/dynamic_style/dynamic_style.tlf +1835 -1
  11. data/examples/dynamic_style/dynamic_style_in_list.tlf +344 -1
  12. data/examples/eudc/eudc.tlf +180 -1
  13. data/examples/hidden_shapes/hidden_shapes.tlf +449 -1
  14. data/examples/list_events/list_events.rb +1 -28
  15. data/examples/list_events/list_events.tlf +361 -0
  16. data/examples/list_manual_generation/list_manual_generation.tlf +132 -1
  17. data/examples/list_page_number/list_page_number.tlf +254 -1
  18. data/examples/page_number/page_number.tlf +215 -1
  19. data/examples/palleted_png/palleted_png.rb +2 -0
  20. data/examples/palleted_png/palleted_png.tlf +47 -1
  21. data/examples/password_setting/password_setting.tlf +45 -1
  22. data/examples/report_callbacks/report_callbacks.rb +1 -18
  23. data/examples/report_callbacks/report_callbacks.tlf +147 -1
  24. data/examples/single_line_tblock/single_line_tblock.tlf +170 -1
  25. data/examples/tblock_overflow/tblock_overflow.tlf +538 -1
  26. data/examples/tblock_styles/font_size.tlf +383 -1
  27. data/examples/tblock_styles/tblock_styles.tlf +889 -1
  28. data/examples/text_align/text_align.tlf +241 -1
  29. data/examples/typeB_page_size/B4_ISO.tlf +45 -1
  30. data/examples/typeB_page_size/B4_JIS.tlf +45 -1
  31. data/examples/word_wrap/word_wrap.tlf +297 -1
  32. data/lib/thinreports.rb +0 -1
  33. data/lib/thinreports/config.rb +3 -0
  34. data/lib/thinreports/core/errors.rb +0 -3
  35. data/lib/thinreports/core/format/base.rb +4 -4
  36. data/lib/thinreports/core/shape.rb +1 -9
  37. data/lib/thinreports/core/shape/basic.rb +3 -3
  38. data/lib/thinreports/core/shape/basic/block_format.rb +1 -1
  39. data/lib/thinreports/core/shape/basic/format.rb +2 -14
  40. data/lib/thinreports/core/shape/basic/internal.rb +2 -2
  41. data/lib/thinreports/core/shape/image_block.rb +3 -3
  42. data/lib/thinreports/core/shape/image_block/format.rb +0 -2
  43. data/lib/thinreports/core/shape/image_block/internal.rb +1 -1
  44. data/lib/thinreports/core/shape/list.rb +3 -7
  45. data/lib/thinreports/core/shape/list/format.rb +37 -24
  46. data/lib/thinreports/core/shape/list/manager.rb +1 -36
  47. data/lib/thinreports/core/shape/list/page.rb +0 -15
  48. data/lib/thinreports/core/shape/list/page_state.rb +1 -1
  49. data/lib/thinreports/core/shape/list/section_format.rb +14 -12
  50. data/lib/thinreports/core/shape/list/section_internal.rb +4 -7
  51. data/lib/thinreports/core/shape/manager/format.rb +0 -2
  52. data/lib/thinreports/core/shape/manager/internal.rb +1 -1
  53. data/lib/thinreports/core/shape/page_number.rb +1 -1
  54. data/lib/thinreports/core/shape/page_number/format.rb +9 -6
  55. data/lib/thinreports/core/shape/page_number/interface.rb +1 -1
  56. data/lib/thinreports/core/shape/page_number/internal.rb +2 -6
  57. data/lib/thinreports/core/shape/style/base.rb +11 -13
  58. data/lib/thinreports/core/shape/style/basic.rb +4 -4
  59. data/lib/thinreports/core/shape/style/graphic.rb +4 -10
  60. data/lib/thinreports/core/shape/style/text.rb +36 -69
  61. data/lib/thinreports/core/shape/text.rb +3 -3
  62. data/lib/thinreports/core/shape/text/format.rb +3 -13
  63. data/lib/thinreports/core/shape/text/internal.rb +2 -2
  64. data/lib/thinreports/core/shape/text_block.rb +3 -3
  65. data/lib/thinreports/core/shape/text_block/format.rb +9 -6
  66. data/lib/thinreports/core/shape/text_block/interface.rb +7 -7
  67. data/lib/thinreports/core/shape/text_block/internal.rb +1 -1
  68. data/lib/thinreports/generator/pdf.rb +0 -1
  69. data/lib/thinreports/generator/pdf/document.rb +2 -2
  70. data/lib/thinreports/generator/pdf/document/draw_shape.rb +25 -51
  71. data/lib/thinreports/generator/pdf/document/draw_template_items.rb +66 -0
  72. data/lib/thinreports/generator/pdf/document/font.rb +3 -8
  73. data/lib/thinreports/generator/pdf/document/graphics/attributes.rb +97 -77
  74. data/lib/thinreports/generator/pdf/document/graphics/basic.rb +18 -13
  75. data/lib/thinreports/generator/pdf/document/graphics/image.rb +17 -12
  76. data/lib/thinreports/generator/pdf/document/page.rb +1 -1
  77. data/lib/thinreports/generator/pdf/drawer/list.rb +5 -1
  78. data/lib/thinreports/generator/pdf/drawer/list_section.rb +1 -4
  79. data/lib/thinreports/generator/pdf/drawer/page.rb +9 -9
  80. data/lib/thinreports/layout.rb +3 -3
  81. data/lib/thinreports/layout/base.rb +0 -16
  82. data/lib/thinreports/layout/format.rb +38 -57
  83. data/lib/thinreports/layout/legacy_schema.rb +354 -0
  84. data/lib/thinreports/layout/version.rb +1 -1
  85. data/lib/thinreports/report.rb +0 -1
  86. data/lib/thinreports/report/base.rb +3 -17
  87. data/lib/thinreports/report/internal.rb +13 -55
  88. data/lib/thinreports/report/page.rb +1 -1
  89. data/lib/thinreports/version.rb +1 -1
  90. data/test/data/legacy_layout/all-items.tlf +1 -0
  91. data/test/schema_helper.rb +121 -0
  92. data/test/test_helper.rb +13 -50
  93. data/test/unit/core/format/test_base.rb +35 -31
  94. data/test/unit/core/shape/base/test_internal.rb +1 -1
  95. data/test/unit/core/shape/basic/test_block_format.rb +23 -0
  96. data/test/unit/core/shape/basic/{test_basic_interface.rb → test_block_interface.rb} +1 -1
  97. data/test/unit/core/shape/basic/{test_basic_internal.rb → test_block_internal.rb} +4 -7
  98. data/test/unit/core/shape/basic/test_format.rb +25 -47
  99. data/test/unit/core/shape/basic/test_interface.rb +5 -5
  100. data/test/unit/core/shape/basic/test_internal.rb +6 -11
  101. data/test/unit/core/shape/image_block/test_interface.rb +5 -5
  102. data/test/unit/core/shape/image_block/test_internal.rb +2 -2
  103. data/test/unit/core/shape/list/test_format.rb +86 -44
  104. data/test/unit/core/shape/list/test_manager.rb +10 -65
  105. data/test/unit/core/shape/list/test_page.rb +10 -61
  106. data/test/unit/core/shape/list/test_page_state.rb +1 -1
  107. data/test/unit/core/shape/list/test_section_format.rb +19 -28
  108. data/test/unit/core/shape/list/test_section_interface.rb +14 -6
  109. data/test/unit/core/shape/list/test_section_internal.rb +17 -23
  110. data/test/unit/core/shape/manager/test_format.rb +1 -1
  111. data/test/unit/core/shape/manager/test_internal.rb +59 -98
  112. data/test/unit/core/shape/manager/test_target.rb +21 -30
  113. data/test/unit/core/shape/page_number/test_format.rb +34 -52
  114. data/test/unit/core/shape/page_number/test_interface.rb +1 -1
  115. data/test/unit/core/shape/page_number/test_internal.rb +2 -2
  116. data/test/unit/core/shape/styles/test_base.rb +16 -41
  117. data/test/unit/core/shape/styles/test_basic.rb +7 -7
  118. data/test/unit/core/shape/styles/test_graphic.rb +27 -32
  119. data/test/unit/core/shape/styles/test_text.rb +68 -311
  120. data/test/unit/core/shape/text/test_format.rb +30 -59
  121. data/test/unit/core/shape/text/test_internal.rb +5 -5
  122. data/test/unit/core/shape/text_block/formatter/test_datetime.rb +13 -13
  123. data/test/unit/core/shape/text_block/test_format.rb +145 -146
  124. data/test/unit/core/shape/text_block/test_interface.rb +9 -9
  125. data/test/unit/core/shape/text_block/test_internal.rb +9 -9
  126. data/test/unit/core/test_shape.rb +19 -31
  127. data/test/unit/generator/pdf/document/graphics/test_attributes.rb +112 -122
  128. data/test/unit/generator/pdf/document/graphics/test_basic.rb +45 -0
  129. data/test/unit/generator/pdf/document/graphics/test_image.rb +5 -5
  130. data/test/unit/generator/pdf/document/test_font.rb +4 -10
  131. data/test/unit/generator/pdf/document/test_page.rb +13 -11
  132. data/test/unit/generator/pdf/document/test_parse_color.rb +5 -5
  133. data/test/unit/generator/test_base.rb +5 -5
  134. data/test/unit/generator/test_configuration.rb +4 -8
  135. data/test/unit/generator/test_pdf.rb +3 -2
  136. data/test/unit/layout/test_base.rb +16 -22
  137. data/test/unit/layout/test_format.rb +66 -86
  138. data/test/unit/layout/test_legacy_schema.rb +587 -0
  139. data/test/unit/layout/test_version.rb +20 -20
  140. data/test/unit/report/test_base.rb +32 -45
  141. data/test/unit/report/test_internal.rb +43 -99
  142. data/test/unit/test_config.rb +4 -2
  143. data/test/unit/test_layout.rb +1 -1
  144. metadata +20 -48
  145. data/examples/list_events/list_events_0_7_7.tlf +0 -1
  146. data/examples/list_events/list_events_0_8.tlf +0 -1
  147. data/lib/thinreports/core/events.rb +0 -69
  148. data/lib/thinreports/core/format/builder.rb +0 -62
  149. data/lib/thinreports/core/shape/list/configuration.rb +0 -54
  150. data/lib/thinreports/core/shape/list/events.rb +0 -47
  151. data/lib/thinreports/core/shape/list/store.rb +0 -33
  152. data/lib/thinreports/generator/pdf/configuration.rb +0 -33
  153. data/lib/thinreports/generator/pdf/document/parse_svg.rb +0 -104
  154. data/lib/thinreports/layout/configuration.rb +0 -29
  155. data/test/data/layout_block.tlf +0 -1
  156. data/test/data/layout_list.tlf +0 -1
  157. data/test/data/layout_list_noheader.tlf +0 -1
  158. data/test/data/layout_text1.tlf +0 -1
  159. data/test/data/layout_text2.tlf +0 -1
  160. data/test/unit/core/format/test_builder.rb +0 -109
  161. data/test/unit/core/shape/basic/test_basic_format.rb +0 -30
  162. data/test/unit/core/shape/image_block/test_format.rb +0 -58
  163. data/test/unit/core/shape/list/test_configuration.rb +0 -69
  164. data/test/unit/core/shape/list/test_events.rb +0 -36
  165. data/test/unit/core/shape/list/test_store.rb +0 -41
  166. data/test/unit/core/test_events.rb +0 -93
  167. data/test/unit/generator/pdf/document/test_draw_shape.rb +0 -44
  168. data/test/unit/generator/pdf/test_configuration.rb +0 -31
  169. data/test/unit/layout/test_configuration.rb +0 -21
  170. data/test/unit/report/test_events.rb +0 -22
@@ -4,6 +4,11 @@ module Thinreports
4
4
  module Generator
5
5
 
6
6
  module PDF::Graphics
7
+ STROKE_DASH = {
8
+ dashed: [2, 2],
9
+ dotted: [1, 2]
10
+ }
11
+
7
12
  # @param [Numeric, String] x1
8
13
  # @param [Numeric, String] y1
9
14
  # @param [Numeric, String] x2
@@ -11,7 +16,7 @@ module Thinreports
11
16
  # @param [Hash] attrs ({})
12
17
  # @option attrs [String] :stroke
13
18
  # @option attrs [Numeric, String] :stroke_width
14
- # @option attrs [Array<Integer, String>] :stroke_dash
19
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
15
20
  def line(x1, y1, x2, y2, attrs = {})
16
21
  with_graphic_styles(attrs) do
17
22
  pdf.line(pos(x1, y1), pos(x2, y2))
@@ -26,7 +31,7 @@ module Thinreports
26
31
  # @option attrs [Integer, String] :radius
27
32
  # @option attrs [String] :stroke
28
33
  # @option attrs [Numeric, String] :stroke_width
29
- # @option attrs [Array<Integer, String>] :stroke_dash
34
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
30
35
  # @option attrs [String] :fill
31
36
  def rect(x, y, w, h, attrs = {})
32
37
  w, h = s2f(w, h)
@@ -49,6 +54,7 @@ module Thinreports
49
54
  # @option attrs [String] :stroke
50
55
  # @option attrs [Numeric, String] :stroke_width
51
56
  # @option attrs [Array<Integer, String>] :stroke_dash
57
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
52
58
  # @option attrs [String] :fill
53
59
  def ellipse(x, y, rx, ry, attrs = {})
54
60
  rx, ry = s2f(rx, ry)
@@ -58,8 +64,6 @@ module Thinreports
58
64
  end
59
65
  end
60
66
 
61
- private
62
-
63
67
  # @param [Hash] attrs
64
68
  def with_graphic_styles(attrs, &block)
65
69
  stroke = build_stroke_styles(attrs)
@@ -108,22 +112,23 @@ module Thinreports
108
112
  def build_stroke_styles(styles)
109
113
  color = styles[:stroke]
110
114
  width = styles[:stroke_width]
111
-
112
- if color && color != 'none' && width && width != 0
113
- {color: parse_color(color),
114
- width: s2f(width),
115
- dash: s2f(*styles[:stroke_dash])}
116
- end
115
+ return nil unless color && color != 'none'
116
+ return nil unless width && width != 0
117
+
118
+ {
119
+ color: parse_color(color),
120
+ width: s2f(width),
121
+ dash: STROKE_DASH[styles[:stroke_type].to_sym]
122
+ }
117
123
  end
118
124
 
119
125
  # @param [Hash] styles
120
126
  # @return [Hash, nil]
121
127
  def build_fill_styles(styles)
122
128
  color = styles[:fill]
129
+ return nil unless color && color != 'none'
123
130
 
124
- if color && color != 'none'
125
- {color: parse_color(color)}
126
- end
131
+ { color: parse_color(color) }
127
132
  end
128
133
 
129
134
  end
@@ -30,7 +30,7 @@ module Thinreports
30
30
  image(image_path, x, y, w, h)
31
31
  end
32
32
 
33
- # @param file (see Prawn#image)
33
+ # @param [String, IO] file
34
34
  # @param [Numeric, Strng] x
35
35
  # @param [Numeric, Strng] y
36
36
  # @param [Numeric, Strng] w
@@ -39,13 +39,16 @@ module Thinreports
39
39
  # @option options [:left, :center, :right] :position_x (:left)
40
40
  # @option options [:top, :center, :bottom] :position_y (:top)
41
41
  def image_box(file, x, y, w, h, options = {})
42
- image_path = normalize_image_from_file(file)
42
+ image_data_or_path = normalize_image_from_file(file)
43
43
 
44
44
  w, h = s2f(w, h)
45
45
  pdf.bounding_box(pos(x, y), width: w, height: h) do
46
- pdf.image(image_path, position: options[:position_x] || :left,
47
- vposition: options[:position_y] || :top,
48
- auto_fit: [w, h])
46
+ pdf.image(
47
+ image_data_or_path,
48
+ position: options[:position_x] || :left,
49
+ vposition: options[:position_y] || :top,
50
+ auto_fit: [w, h]
51
+ )
49
52
  end
50
53
  end
51
54
 
@@ -69,13 +72,16 @@ module Thinreports
69
72
  create_temp_imagefile(image_id, image_data)
70
73
  end
71
74
 
72
- def normalize_image_from_file(filename)
75
+ def normalize_image_from_file(file)
76
+ return file unless file.is_a?(String)
77
+
78
+ filename = file
73
79
  extname = File.extname(filename)
74
80
 
75
81
  return filename unless png_conversion_enabled?
76
82
  return filename unless extname.downcase == '.png' || extname.empty?
77
83
 
78
- return temp_image_registry[filename] if temp_image_registry.key?(filename)
84
+ return temp_image_registry[filename].path if temp_image_registry.key?(filename)
79
85
 
80
86
  png_normalizer = PNGNormalizer.load_file(filename)
81
87
 
@@ -101,17 +107,16 @@ module Thinreports
101
107
 
102
108
  # @param [String] image_id
103
109
  # @param [String] image_data
104
- # @return [String] imagefile path
110
+ # @return [String] Path to imagefile
105
111
  def create_temp_imagefile(image_id, image_data)
106
112
  temp_image_registry[image_id] ||= begin
107
113
  file = Tempfile.new('temp-image')
108
114
  file.binmode
109
115
  file.write(image_data)
110
- file.path
111
- ensure
112
- file.close
116
+ file.open
117
+ file
113
118
  end
114
- temp_image_registry[image_id]
119
+ temp_image_registry[image_id].path
115
120
  end
116
121
 
117
122
  def png_conversion_enabled?
@@ -48,7 +48,7 @@ module Thinreports
48
48
  # @param [Thinreports::Layout::Format] format
49
49
  def create_format_stamp(format)
50
50
  create_stamp(format.identifier.to_s) do
51
- parse_svg(format.layout, '/svg/g')
51
+ draw_template_items(format.attributes['items'])
52
52
  end
53
53
  format_stamp_registry << format.identifier
54
54
  end
@@ -43,7 +43,11 @@ module Thinreports
43
43
  # @param [Thinreports::Core::Shape::List::SectionInterface] section
44
44
  def draw_section(section)
45
45
  internal = section.internal
46
- drawer(internal).draw(section, internal.relative_position)
46
+
47
+ base_top = @format.section_base_position_top(internal.section_name)
48
+ position = [internal.relative_left, base_top + internal.relative_top]
49
+
50
+ drawer(internal).draw(section, position)
47
51
  end
48
52
 
49
53
  # @param [Thinreports::Core::Shape::List::SectionInternal] section
@@ -26,10 +26,7 @@ module Thinreports
26
26
  id = @format.identifier.to_s
27
27
 
28
28
  unless @stamp_created
29
- @pdf.create_stamp(id) do
30
- @pdf.parse_svg('<svg xmlns:xlink="http://www.w3.org/1999/xlink">' +
31
- "#{@format.layout}</svg>", '/svg')
32
- end
29
+ @pdf.create_stamp(id) { @pdf.draw_template_items(@format.attributes['items']) }
33
30
  @stamp_created = true
34
31
  end
35
32
  pdf_stamp(id)
@@ -20,7 +20,7 @@ module Thinreports
20
20
 
21
21
  shape = shape.internal
22
22
 
23
- if shape.type_of?(:pageno)
23
+ if shape.type_of?(Core::Shape::PageNumber::TYPE_NAME)
24
24
  # Do not draw pageno if is not for Report
25
25
  draw_pageno_shape(shape, page) if page.count? && shape.for_report?
26
26
  else
@@ -33,11 +33,11 @@ module Thinreports
33
33
 
34
34
  def draw_shape(shape)
35
35
  case
36
- when shape.type_of?(:tblock)
36
+ when shape.type_of?(Core::Shape::TextBlock::TYPE_NAME)
37
37
  draw_tblock_shape(shape)
38
- when shape.type_of?(:list)
38
+ when shape.type_of?(Core::Shape::List::TYPE_NAME)
39
39
  draw_list_shape(shape)
40
- when shape.type_of?(:iblock)
40
+ when shape.type_of?(Core::Shape::ImageBlock::TYPE_NAME)
41
41
  draw_iblock_shape(shape)
42
42
  else
43
43
  id = shape.identifier
@@ -72,11 +72,11 @@ module Thinreports
72
72
  # @param [Thinreports::Core::Shape::Base::Internal] shape
73
73
  def create_basic_shape_stamp(shape)
74
74
  case
75
- when shape.type_of?(:text) then create_text_stamp(shape)
76
- when shape.type_of?(:image) then create_image_stamp(shape)
77
- when shape.type_of?(:ellipse) then create_ellipse_stamp(shape)
78
- when shape.type_of?(:rect) then create_rect_stamp(shape)
79
- when shape.type_of?(:line) then create_line_stamp(shape)
75
+ when shape.type_of?('text') then create_text_stamp(shape)
76
+ when shape.type_of?('image') then create_image_stamp(shape)
77
+ when shape.type_of?('ellipse') then create_ellipse_stamp(shape)
78
+ when shape.type_of?('rect') then create_rect_stamp(shape)
79
+ when shape.type_of?('line') then create_line_stamp(shape)
80
80
  end
81
81
  end
82
82
 
@@ -1,17 +1,17 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Thinreports
4
-
4
+
5
5
  module Layout
6
6
  # @see Thinreports::Layout::Base#initialize
7
7
  def self.new(filename, options = {})
8
8
  Base.new(filename, options)
9
9
  end
10
10
  end
11
-
11
+
12
12
  end
13
13
 
14
14
  require 'thinreports/layout/version'
15
15
  require 'thinreports/layout/base'
16
16
  require 'thinreports/layout/format'
17
- require 'thinreports/layout/configuration'
17
+ require 'thinreports/layout/legacy_schema'
@@ -44,22 +44,6 @@ module Thinreports
44
44
  def default?
45
45
  @id.nil?
46
46
  end
47
-
48
- # @yield [config]
49
- # @yieldparam [List::Configuration] config
50
- # @return [List::Configuration]
51
- def config(&block)
52
- @config ||= Layout::Configuration.new(self)
53
- call_block_in(@config, &block)
54
- end
55
-
56
- # @param [Thinreports::Report::Base] parent
57
- # @param [Hash] options ({})
58
- # @option option [Boolean] :count (true)
59
- # @return [Page]
60
- def new_page(parent, options = {})
61
- Report::Page.new(parent, self, options)
62
- end
63
47
  end
64
48
 
65
49
  end
@@ -1,80 +1,61 @@
1
1
  # coding: utf-8
2
-
3
- require 'digest/sha1'
2
+ require 'json'
4
3
 
5
4
  module Thinreports
6
5
  module Layout
7
-
8
6
  class Format < Core::Shape::Manager::Format
9
7
  config_reader last_version: %w( version )
10
- config_reader report_title: %w( config title )
11
- config_reader page_paper_type: %w( config page paper-type ),
12
- page_width: %w( config page width ),
13
- page_height: %w( config page height ),
14
- page_orientation: %w( config page orientation )
15
-
16
- config_checker 'user', user_paper_type: %w( config page paper-type )
8
+ config_reader report_title: %w( title )
9
+ config_reader page_paper_type: %w( report paper-type ),
10
+ page_width: %w( report width ),
11
+ page_height: %w( report height ),
12
+ page_orientation: %w( report orientation )
17
13
 
18
14
  class << self
15
+ def build(filename)
16
+ schema = JSON.parse(read_file(filename))
19
17
 
20
- private
21
-
22
- # @param [String] filename
23
- # @param [Hash] options
24
- # @option options [Boolean] :force (false)
25
- def build_internal(filename, options = {})
26
- build_once(filename, options[:force]) do |content, id|
27
- raw_format = parse_json(content)
28
-
29
- # Check the compatibility of specified layout file.
30
- unless Thinreports::Layout::Version.compatible?(raw_format['version'])
31
- info = [filename, raw_format['version'],
32
- Thinreports::Layout::Version.inspect_required_rules]
33
- raise Thinreports::Errors::IncompatibleLayoutFormat.new(*info)
34
- end
35
-
36
- compact_format!(raw_format)
18
+ unless Layout::Version.compatible?(schema['version'])
19
+ raise Errors::IncompatibleLayoutFormat.new(
20
+ filename, schema['version'], Thinreports::Layout::Version.inspect_required_rules
21
+ )
22
+ end
37
23
 
38
- # Build and initialize format.
39
- new(raw_format, id) do |f|
40
- build_layout(f) do |type, shape_format|
41
- Core::Shape::Format(type).build(shape_format)
42
- end
43
- clean(f.layout)
44
- end
24
+ if schema['version'] < '0.9.0'
25
+ warn '[DEPRECATION] Support for the layout file with old format that generated with Editor 0.8 or lower will be dropped in Thinreports 1.1.' \
26
+ ' Please convert to new layout format using Thinreports Editor 0.9 or 1.0.'
27
+ schema = Layout::LegacySchema.new(schema).upgrade
45
28
  end
29
+
30
+ new schema
46
31
  end
47
32
 
48
- # @param [Hash] raw_format A parsed json.
49
- def compact_format!(raw_format)
50
- %w( finger-print state version ).each {|attr| raw_format.delete(attr) }
33
+ def read_file(filename)
34
+ File.open(filename, 'r:UTF-8') {|f| f.read }
51
35
  end
36
+ end
52
37
 
53
- # @param [String] filename
54
- # @param [Boolean] force (false)
55
- def build_once(filename, force = false, &block)
56
- content = read_format_file(filename)
38
+ def initialize(*)
39
+ super
40
+ initialize_items(attributes['items'])
41
+ end
57
42
 
58
- if force
59
- block.call(content, nil)
60
- else
61
- id = Digest::SHA1.hexdigest(content).to_sym
62
- built_format_registry[id] ||= block.call(content, id)
63
- end
64
- end
43
+ def user_paper_type?
44
+ page_paper_type == 'user'
45
+ end
65
46
 
66
- # @param [String] filename
67
- # @return [String]
68
- def read_format_file(filename)
69
- File.open(filename, 'r:UTF-8') {|f| f.read }
70
- end
47
+ private
48
+
49
+ def initialize_items(item_schemas)
50
+ item_schemas.each do |item_schema|
51
+ id, type = item_schema.values_at 'id', 'type'
71
52
 
72
- # @return [Hash]
73
- def built_format_registry
74
- @built_format_registry ||= {}
53
+ next if id.empty? && type != 'page-number'
54
+
55
+ item = Core::Shape::Format(type).new(item_schema)
56
+ self.shapes[item.id.to_sym] = item
75
57
  end
76
58
  end
77
59
  end
78
-
79
60
  end
80
61
  end
@@ -0,0 +1,354 @@
1
+ # coding: utf-8
2
+
3
+ require 'json'
4
+ require 'rexml/document'
5
+
6
+ module Thinreports
7
+ module Layout
8
+ class LegacySchema
9
+ def initialize(legacy_schema)
10
+ @legacy_schema = legacy_schema
11
+ end
12
+
13
+ def upgrade
14
+ config = legacy_schema['config']
15
+ page_config = config['page']
16
+
17
+ {
18
+ 'version' => legacy_schema['version'],
19
+ 'title' => legacy_schema['config']['title'],
20
+ 'report' => {
21
+ 'paper-type' => page_config['paper-type'],
22
+ 'width' => page_config['width'].to_f,
23
+ 'height' => page_config['height'].to_f,
24
+ 'orientation' => page_config['orientation'],
25
+ 'margin' => page_config.values_at('margin-top', 'margin-right', 'margin-bottom', 'margin-left').map(&:to_f)
26
+ },
27
+ 'items' => build_item_schemas_from_svg(legacy_schema['svg'].dup, '/svg/g', 1)
28
+ }
29
+ end
30
+
31
+ attr_reader :legacy_schema
32
+
33
+ def build_item_schemas_from_svg(svg, root_xpath, level)
34
+ item_schemas = extract_item_schemas(svg, level)
35
+
36
+ svg_doc = REXML::Document.new(normalize_svg(svg, level))
37
+ items = []
38
+
39
+ svg_doc.elements[root_xpath].each do |item_element|
40
+ item_schema = item_schemas[item_element.attributes['x-id']] || {}
41
+ item_attributes = item_element.attributes
42
+
43
+ items <<
44
+ case item_element.attributes['class']
45
+ when 's-text' then text_item_schema(item_attributes, extract_texts_from(item_element))
46
+ when 's-image' then image_item_schema(item_attributes)
47
+ when 's-rect' then rect_item_schema(item_attributes)
48
+ when 's-ellipse' then ellipse_item_schema(item_attributes)
49
+ when 's-line' then line_item_schema(item_attributes)
50
+ when 's-tblock' then text_block_item_schema(item_attributes)
51
+ when 's-iblock' then image_block_item_schema(item_attributes)
52
+ when 's-pageno' then page_number_item_schema(item_attributes)
53
+ when 's-list' then list_item_schema(item_schema)
54
+ else raise 'Unknown item type'
55
+ end
56
+ end
57
+
58
+ items
59
+ end
60
+
61
+ def text_item_schema(attributes, texts)
62
+ {
63
+ 'id' => attributes['x-id'],
64
+ 'type' => Core::Shape::Text::TYPE_NAME,
65
+ 'x' => attributes['x-left'].to_f,
66
+ 'y' => attributes['x-top'].to_f,
67
+ 'width' => attributes['x-width'].to_f,
68
+ 'height' => attributes['x-height'].to_f,
69
+ 'display' => display(attributes['x-display']),
70
+ 'texts' => texts,
71
+ 'style' => {
72
+ 'font-family' => [ attributes['font-family'] ],
73
+ 'font-size' => attributes['font-size'].to_f,
74
+ 'color' => attributes['fill'],
75
+ 'font-style' => font_style(attributes),
76
+ 'text-align' => text_align(attributes['text-anchor']),
77
+ 'vertical-align' => vertical_align(attributes['x-valign']),
78
+ 'line-height' => line_height(attributes['x-line-height']),
79
+ 'letter-spacing' => letter_spacing(attributes['kerning'])
80
+ }
81
+ }
82
+ end
83
+
84
+ def rect_item_schema(attributes)
85
+ {
86
+ 'id' => attributes['x-id'],
87
+ 'type' => 'rect',
88
+ 'x' => attributes['x'].to_f,
89
+ 'y' => attributes['y'].to_f,
90
+ 'width' => attributes['width'].to_f,
91
+ 'height' => attributes['height'].to_f,
92
+ 'display' => display(attributes['x-display']),
93
+ 'style' => {
94
+ 'border-width' => attributes['stroke-width'].to_f,
95
+ 'border-color' => attributes['stroke'],
96
+ 'border-style' => attributes['x-stroke-type'],
97
+ 'fill-color' => attributes['fill']
98
+ }
99
+ }
100
+ end
101
+
102
+ def line_item_schema(attributes)
103
+ {
104
+ 'id' => attributes['x-id'],
105
+ 'type' => 'line',
106
+ 'x1' => attributes['x1'].to_f,
107
+ 'y1' => attributes['y1'].to_f,
108
+ 'x2' => attributes['x2'].to_f,
109
+ 'y2' => attributes['y2'].to_f,
110
+ 'display' => display(attributes['x-display']),
111
+ 'style' => {
112
+ 'border-width' => attributes['stroke-width'].to_f,
113
+ 'border-color' => attributes['stroke'],
114
+ 'border-style' => attributes['x-stroke-type']
115
+ }
116
+ }
117
+ end
118
+
119
+ def ellipse_item_schema(attributes)
120
+ {
121
+ 'id' => attributes['x-id'],
122
+ 'type' => 'ellipse',
123
+ 'cx' => attributes['cx'].to_f,
124
+ 'cy' => attributes['cy'].to_f,
125
+ 'rx' => attributes['rx'].to_f,
126
+ 'ry' => attributes['ry'].to_f,
127
+ 'display' => display(attributes['x-display']),
128
+ 'style' => {
129
+ 'border-width' => attributes['stroke-width'].to_f,
130
+ 'border-color' => attributes['stroke'],
131
+ 'border-style' => attributes['x-stroke-type'],
132
+ 'fill-color' => attributes['fill']
133
+ }
134
+ }
135
+ end
136
+
137
+ def image_item_schema(attributes)
138
+ _, image_type, image_data = attributes['xlink:href'].match(%r|^data:(image/[a-z]+?);base64,(.+)|).to_a
139
+
140
+ {
141
+ 'id' => attributes['x-id'],
142
+ 'type' => 'image',
143
+ 'x' => attributes['x'].to_f,
144
+ 'y' => attributes['y'].to_f,
145
+ 'width' => attributes['width'].to_f,
146
+ 'height' => attributes['height'].to_f,
147
+ 'display' => display(attributes['x-display']),
148
+ 'data' => {
149
+ 'mime-type' => image_type,
150
+ 'base64' => image_data
151
+ }
152
+ }
153
+ end
154
+
155
+ def page_number_item_schema(attributes)
156
+ {
157
+ 'id' => attributes['x-id'],
158
+ 'type' => Core::Shape::PageNumber::TYPE_NAME,
159
+ 'x' => attributes['x-left'].to_f,
160
+ 'y' => attributes['x-top'].to_f,
161
+ 'width' => attributes['x-width'].to_f,
162
+ 'height' => attributes['x-height'].to_f,
163
+ 'format' => attributes['x-format'],
164
+ 'target' => attributes['x-target'],
165
+ 'display' => display(attributes['x-display']),
166
+ 'style' => {
167
+ 'font-family' => [ attributes['font-family'] ],
168
+ 'font-size' => attributes['font-size'].to_f,
169
+ 'color' => attributes['fill'],
170
+ 'font-style' => font_style(attributes),
171
+ 'text-align' => text_align(attributes['text-anchor']),
172
+ 'overflow' => attributes['x-overflow']
173
+ }
174
+ }
175
+ end
176
+
177
+ def image_block_item_schema(attributes)
178
+ {
179
+ 'id' => attributes['x-id'],
180
+ 'type' => Core::Shape::ImageBlock::TYPE_NAME,
181
+ 'x' => attributes['x-left'].to_f,
182
+ 'y' => attributes['x-top'].to_f,
183
+ 'width' => attributes['x-width'].to_f,
184
+ 'height' => attributes['x-height'].to_f,
185
+ 'display' => display(attributes['x-display']),
186
+ 'style' => {
187
+ 'position-x' => attributes['x-position-x'],
188
+ 'position-y' => image_position_y(attributes['x-position-y'])
189
+ }
190
+ }
191
+ end
192
+
193
+ def text_block_item_schema(attributes)
194
+ text_format = {
195
+ 'base' => attributes['x-format-base'],
196
+ 'type' => attributes['x-format-type']
197
+ }
198
+ case text_format['type']
199
+ when 'datetime'
200
+ text_format['datetime'] = {
201
+ 'format' => attributes['x-format-datetime-format']
202
+ }
203
+ when 'number'
204
+ text_format['number'] = {
205
+ 'delimiter' => attributes['x-format-number-delimiter'],
206
+ 'precision' => attributes['x-format-number-precision'].to_i
207
+ }
208
+ when 'padding'
209
+ text_format['padding'] = {
210
+ 'length' => attributes['x-format-padding-length'].to_i,
211
+ 'char' => attributes['x-format-padding-char'],
212
+ 'direction' => attributes['x-format-padding-direction']
213
+ }
214
+ end
215
+
216
+ {
217
+ 'id' => attributes['x-id'],
218
+ 'type' => Core::Shape::TextBlock::TYPE_NAME,
219
+ 'x' => attributes['x-left'].to_f,
220
+ 'y' => attributes['x-top'].to_f,
221
+ 'width' => attributes['x-width'].to_f,
222
+ 'height' => attributes['x-height'].to_f,
223
+ 'display' => display(attributes['x-display']),
224
+ 'value' => attributes['x-value'],
225
+ 'multiple-line' => attributes['x-multiple'] == 'true',
226
+ 'format' => text_format,
227
+ 'style' => {
228
+ 'font-family' => [ attributes['font-family'] ],
229
+ 'font-size' => attributes['font-size'].to_f,
230
+ 'color' => attributes['fill'],
231
+ 'font-style' => font_style(attributes),
232
+ 'text-align' => text_align(attributes['text-anchor']),
233
+ 'vertical-align' => vertical_align(attributes['x-valign']),
234
+ 'line-height' => line_height(attributes['x-line-height']),
235
+ 'letter-spacing' => letter_spacing(attributes['kerning']),
236
+ 'overflow' => attributes['x-overflow'],
237
+ 'word-wrap' => attributes['x-word-wrap']
238
+ }
239
+ }
240
+ end
241
+
242
+ def list_item_schema(legacy_schema)
243
+ {
244
+ 'id' => legacy_schema['id'],
245
+ 'type' => Core::Shape::List::TYPE_NAME,
246
+ 'content-height' => legacy_schema['content-height'].to_f,
247
+ 'auto-page-break' => legacy_schema['page-break'] == 'true',
248
+ 'display' => display(legacy_schema['display']),
249
+ 'header' => list_section_schema('header', legacy_schema),
250
+ 'detail' => list_section_schema('detail', legacy_schema),
251
+ 'page-footer' => list_section_schema('page-footer', legacy_schema),
252
+ 'footer' => list_section_schema('footer', legacy_schema)
253
+ }
254
+ end
255
+
256
+ def list_section_schema(section_name, legacy_list_schema)
257
+ legacy_section_schema = legacy_list_schema[section_name]
258
+
259
+ section_svg = %(<svg xmlns:xlink="http://www.w3.org/1999/xlink">#{legacy_section_schema['svg']['content']}</svg>)
260
+
261
+ section_schema = {
262
+ 'height' => legacy_section_schema['height'].to_f,
263
+ 'translate' => {
264
+ 'x' => legacy_section_schema['translate']['x'].to_f,
265
+ 'y' => legacy_section_schema['translate']['y'].to_f
266
+ },
267
+ 'items' => build_item_schemas_from_svg(section_svg, '/svg', 2)
268
+ }
269
+
270
+ unless section_name == 'detail'
271
+ section_schema['enabled'] = legacy_list_schema["#{section_name}-enabled"] == 'true'
272
+ end
273
+ section_schema
274
+ end
275
+
276
+ def extract_texts_from(text_item_element)
277
+ [].tap do |texts|
278
+ text_item_element.each_element('text') { |e| texts << e.text }
279
+ end
280
+ end
281
+
282
+ def image_position_y(legacy_position_y)
283
+ case legacy_position_y
284
+ when 'top' then 'top'
285
+ when 'center' then 'middle'
286
+ when 'bottom' then 'bottom'
287
+ end
288
+ end
289
+
290
+ def display(legacy_display)
291
+ legacy_display == 'true'
292
+ end
293
+
294
+ def font_style(attributes)
295
+ style = []
296
+ style << 'bold' if attributes['font-weight'] == 'bold'
297
+ style << 'italic' if attributes['font-style'] == 'italic'
298
+ style << 'underline' if attributes['text-decoration'].include?('underline')
299
+ style << 'linethrough' if attributes['text-decoration'].include?('line-through')
300
+ style
301
+ end
302
+
303
+ def text_align(legacy_text_align)
304
+ case legacy_text_align
305
+ when 'start' then 'left'
306
+ when 'middle' then 'center'
307
+ when 'end' then 'right'
308
+ else 'left'
309
+ end
310
+ end
311
+
312
+ def vertical_align(legacy_vertical_align)
313
+ return nil unless legacy_vertical_align
314
+
315
+ case legacy_vertical_align
316
+ when 'top' then 'top'
317
+ when 'center' then 'middle'
318
+ when 'bottom' then 'bottom'
319
+ else 'top'
320
+ end
321
+ end
322
+
323
+ def line_height(legacy_line_height)
324
+ legacy_line_height == '' ? '' : legacy_line_height.to_f
325
+ end
326
+
327
+ def letter_spacing(legacy_letter_spacing)
328
+ case legacy_letter_spacing
329
+ when 'auto', '' then ''
330
+ else legacy_letter_spacing.to_f
331
+ end
332
+ end
333
+
334
+ def extract_item_schemas(svg, level)
335
+ items = {}
336
+ svg.scan(/<!--#{level_symbol(level)}SHAPE(.*?)SHAPE#{level_symbol(level)}-->/) do |(item_schema_json)|
337
+ item_schema = JSON.parse(item_schema_json)
338
+ items[item_schema['id']] = item_schema
339
+ end
340
+ items
341
+ end
342
+
343
+ def normalize_svg(svg, level)
344
+ svg.gsub!(/<!--#{level_symbol(level)}SHAPE.*?SHAPE#{level_symbol(level)}-->/, '')
345
+ svg.gsub!(/<!--#{level_symbol(level)}LAYOUT(.*?)LAYOUT#{level_symbol(level)}-->/) { $1 }
346
+ svg
347
+ end
348
+
349
+ def level_symbol(level)
350
+ '-' * (level - 1)
351
+ end
352
+ end
353
+ end
354
+ end