thinreports 0.8.2 → 0.9.0

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