thinreports 0.10.2 → 0.12.1

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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +2 -2
  3. data/.github/dependabot.yml +11 -0
  4. data/.github/workflows/test.yml +50 -0
  5. data/.gitignore +1 -0
  6. data/CHANGELOG.md +43 -0
  7. data/Gemfile +2 -1
  8. data/{MIT-LICENSE → LICENSE.txt} +2 -0
  9. data/README.md +70 -56
  10. data/Rakefile +12 -25
  11. data/gemfiles/prawn-2.2.gemfile +5 -0
  12. data/gemfiles/prawn-2.3.gemfile +5 -0
  13. data/gemfiles/prawn-2.4.gemfile +5 -0
  14. data/lib/thinreports/core/shape/basic/format.rb +5 -0
  15. data/lib/thinreports/core/shape/image_block.rb +1 -1
  16. data/lib/thinreports/core/shape/list.rb +1 -1
  17. data/lib/thinreports/core/shape/page_number.rb +1 -1
  18. data/lib/thinreports/core/shape/stack_view/format.rb +27 -0
  19. data/lib/thinreports/core/shape/stack_view/interface.rb +17 -0
  20. data/lib/thinreports/core/shape/stack_view/internal.rb +22 -0
  21. data/lib/thinreports/core/shape/stack_view/row_format.rb +39 -0
  22. data/lib/thinreports/core/shape/stack_view.rb +17 -0
  23. data/lib/thinreports/core/shape/style/basic.rb +4 -1
  24. data/lib/thinreports/core/shape/text.rb +1 -1
  25. data/lib/thinreports/core/shape/text_block/formatter/number.rb +6 -1
  26. data/lib/thinreports/core/shape/text_block/internal.rb +0 -2
  27. data/lib/thinreports/core/shape/text_block.rb +1 -1
  28. data/lib/thinreports/core/shape.rb +2 -0
  29. data/lib/thinreports/generate.rb +11 -0
  30. data/lib/thinreports/generator/pdf/document/draw_shape.rb +31 -10
  31. data/lib/thinreports/generator/pdf/document/draw_template_items.rb +1 -1
  32. data/lib/thinreports/generator/pdf/document/graphics/image.rb +19 -1
  33. data/lib/thinreports/generator/pdf/document/graphics/text.rb +10 -5
  34. data/lib/thinreports/generator/pdf/document/page.rb +12 -0
  35. data/lib/thinreports/generator/pdf/prawn_ext/width_of.rb +7 -7
  36. data/lib/thinreports/layout/base.rb +1 -1
  37. data/lib/thinreports/layout/legacy_schema.rb +1 -0
  38. data/lib/thinreports/layout/version.rb +1 -1
  39. data/lib/thinreports/report/internal.rb +2 -0
  40. data/lib/thinreports/report.rb +2 -2
  41. data/lib/thinreports/section_report/build.rb +32 -0
  42. data/lib/thinreports/section_report/builder/item_builder.rb +49 -0
  43. data/lib/thinreports/section_report/builder/report_builder.rb +82 -0
  44. data/lib/thinreports/section_report/builder/report_data.rb +13 -0
  45. data/lib/thinreports/section_report/builder/stack_view_builder.rb +53 -0
  46. data/lib/thinreports/section_report/builder/stack_view_data.rb +11 -0
  47. data/lib/thinreports/section_report/generate.rb +26 -0
  48. data/lib/thinreports/section_report/pdf/render.rb +23 -0
  49. data/lib/thinreports/section_report/pdf/renderer/draw_item.rb +68 -0
  50. data/lib/thinreports/section_report/pdf/renderer/group_renderer.rb +57 -0
  51. data/lib/thinreports/section_report/pdf/renderer/section_height.rb +100 -0
  52. data/lib/thinreports/section_report/pdf/renderer/section_renderer.rb +39 -0
  53. data/lib/thinreports/section_report/pdf/renderer/stack_view_renderer.rb +55 -0
  54. data/lib/thinreports/section_report/pdf/renderer/stack_view_row_renderer.rb +38 -0
  55. data/lib/thinreports/section_report/schema/loader.rb +28 -0
  56. data/lib/thinreports/section_report/schema/parser.rb +52 -0
  57. data/lib/thinreports/section_report/schema/report.rb +30 -0
  58. data/lib/thinreports/section_report/schema/section.rb +47 -0
  59. data/lib/thinreports/version.rb +1 -1
  60. data/lib/thinreports.rb +5 -0
  61. data/thinreports.gemspec +11 -10
  62. metadata +48 -150
  63. data/.travis.yml +0 -16
  64. data/examples/character_spacing/character_spacing.rb +0 -8
  65. data/examples/character_spacing/character_spacing.tlf +0 -293
  66. data/examples/dynamic_image/dynamic_image.rb +0 -31
  67. data/examples/dynamic_image/dynamic_image.tlf +0 -661
  68. data/examples/dynamic_image/img200x100.png +0 -0
  69. data/examples/dynamic_image/img50x50.png +0 -0
  70. data/examples/dynamic_style/dynamic_style.rb +0 -150
  71. data/examples/dynamic_style/dynamic_style.tlf +0 -1835
  72. data/examples/dynamic_style/dynamic_style_in_list.tlf +0 -344
  73. data/examples/dynamic_style/image.png +0 -0
  74. data/examples/eudc/eudc.rb +0 -20
  75. data/examples/eudc/eudc.tlf +0 -180
  76. data/examples/eudc/eudc.ttf +0 -0
  77. data/examples/helper.rb +0 -50
  78. data/examples/hidden_shapes/hidden_shapes.rb +0 -9
  79. data/examples/hidden_shapes/hidden_shapes.tlf +0 -449
  80. data/examples/list_events/list_events.rb +0 -32
  81. data/examples/list_events/list_events.tlf +0 -361
  82. data/examples/list_manual_generation/list_manual_generation.rb +0 -22
  83. data/examples/list_manual_generation/list_manual_generation.tlf +0 -132
  84. data/examples/list_page_number/list_page_number.rb +0 -17
  85. data/examples/list_page_number/list_page_number.tlf +0 -254
  86. data/examples/page_number/page_number.rb +0 -33
  87. data/examples/page_number/page_number.tlf +0 -215
  88. data/examples/palleted_png/palleted_png.png +0 -0
  89. data/examples/palleted_png/palleted_png.rb +0 -9
  90. data/examples/palleted_png/palleted_png.tlf +0 -47
  91. data/examples/password_setting/password_setting.rb +0 -10
  92. data/examples/password_setting/password_setting.tlf +0 -45
  93. data/examples/report_callbacks/report_callbacks.rb +0 -14
  94. data/examples/report_callbacks/report_callbacks.tlf +0 -147
  95. data/examples/single_line_tblock/single_line_tblock.rb +0 -13
  96. data/examples/single_line_tblock/single_line_tblock.tlf +0 -170
  97. data/examples/tblock_overflow/tblock_overflow.rb +0 -20
  98. data/examples/tblock_overflow/tblock_overflow.tlf +0 -538
  99. data/examples/tblock_styles/font_size.tlf +0 -383
  100. data/examples/tblock_styles/tblock_styles.rb +0 -43
  101. data/examples/tblock_styles/tblock_styles.tlf +0 -889
  102. data/examples/text_align/text_align.rb +0 -8
  103. data/examples/text_align/text_align.tlf +0 -241
  104. data/examples/typeB_page_size/B4_ISO.tlf +0 -45
  105. data/examples/typeB_page_size/B4_JIS.tlf +0 -45
  106. data/examples/typeB_page_size/typeB_page_size.rb +0 -17
  107. data/examples/word_wrap/word_wrap.rb +0 -26
  108. data/examples/word_wrap/word_wrap.tlf +0 -297
  109. data/test/data/font.ttf +0 -0
  110. data/test/data/image_normal.jpg +0 -0
  111. data/test/data/image_normal.png +0 -0
  112. data/test/data/image_normal_jpg_noext +0 -0
  113. data/test/data/image_normal_png_noext +0 -0
  114. data/test/data/image_pallete_based.png +0 -0
  115. data/test/data/legacy_layout/all-items.tlf +0 -1
  116. data/test/schema_helper.rb +0 -122
  117. data/test/test_helper.rb +0 -48
  118. data/test/tmp/.gitkeep +0 -0
  119. data/test/unit/core/format/test_base.rb +0 -152
  120. data/test/unit/core/shape/base/test_internal.rb +0 -87
  121. data/test/unit/core/shape/basic/test_block_format.rb +0 -23
  122. data/test/unit/core/shape/basic/test_block_interface.rb +0 -29
  123. data/test/unit/core/shape/basic/test_block_internal.rb +0 -55
  124. data/test/unit/core/shape/basic/test_format.rb +0 -37
  125. data/test/unit/core/shape/basic/test_interface.rb +0 -108
  126. data/test/unit/core/shape/basic/test_internal.rb +0 -55
  127. data/test/unit/core/shape/image_block/test_interface.rb +0 -24
  128. data/test/unit/core/shape/image_block/test_internal.rb +0 -31
  129. data/test/unit/core/shape/list/test_format.rb +0 -111
  130. data/test/unit/core/shape/list/test_manager.rb +0 -67
  131. data/test/unit/core/shape/list/test_page.rb +0 -156
  132. data/test/unit/core/shape/list/test_page_state.rb +0 -31
  133. data/test/unit/core/shape/list/test_section_format.rb +0 -36
  134. data/test/unit/core/shape/list/test_section_interface.rb +0 -75
  135. data/test/unit/core/shape/list/test_section_internal.rb +0 -50
  136. data/test/unit/core/shape/manager/test_format.rb +0 -38
  137. data/test/unit/core/shape/manager/test_internal.rb +0 -132
  138. data/test/unit/core/shape/manager/test_target.rb +0 -140
  139. data/test/unit/core/shape/page_number/test_format.rb +0 -55
  140. data/test/unit/core/shape/page_number/test_interface.rb +0 -33
  141. data/test/unit/core/shape/page_number/test_internal.rb +0 -81
  142. data/test/unit/core/shape/styles/test_base.rb +0 -191
  143. data/test/unit/core/shape/styles/test_basic.rb +0 -24
  144. data/test/unit/core/shape/styles/test_graphic.rb +0 -50
  145. data/test/unit/core/shape/styles/test_text.rb +0 -97
  146. data/test/unit/core/shape/text/test_format.rb +0 -44
  147. data/test/unit/core/shape/text/test_internal.rb +0 -20
  148. data/test/unit/core/shape/text_block/formatter/test_basic.rb +0 -24
  149. data/test/unit/core/shape/text_block/formatter/test_datetime.rb +0 -49
  150. data/test/unit/core/shape/text_block/formatter/test_number.rb +0 -76
  151. data/test/unit/core/shape/text_block/formatter/test_padding.rb +0 -77
  152. data/test/unit/core/shape/text_block/test_format.rb +0 -169
  153. data/test/unit/core/shape/text_block/test_formatter.rb +0 -28
  154. data/test/unit/core/shape/text_block/test_interface.rb +0 -63
  155. data/test/unit/core/shape/text_block/test_internal.rb +0 -128
  156. data/test/unit/core/test_shape.rb +0 -52
  157. data/test/unit/core/test_utils.rb +0 -68
  158. data/test/unit/generator/pdf/document/graphics/test_attributes.rb +0 -135
  159. data/test/unit/generator/pdf/document/graphics/test_basic.rb +0 -46
  160. data/test/unit/generator/pdf/document/graphics/test_image.rb +0 -46
  161. data/test/unit/generator/pdf/document/graphics/test_text.rb +0 -171
  162. data/test/unit/generator/pdf/document/test_font.rb +0 -110
  163. data/test/unit/generator/pdf/document/test_graphics.rb +0 -42
  164. data/test/unit/generator/pdf/document/test_page.rb +0 -122
  165. data/test/unit/generator/pdf/document/test_parse_color.rb +0 -30
  166. data/test/unit/generator/pdf/prawn_ext/test_calc_image_dimensions.rb +0 -41
  167. data/test/unit/generator/pdf/prawn_ext/test_width_of.rb +0 -22
  168. data/test/unit/generator/pdf/test_document.rb +0 -22
  169. data/test/unit/generator/test_pdf.rb +0 -26
  170. data/test/unit/layout/test_base.rb +0 -41
  171. data/test/unit/layout/test_format.rb +0 -100
  172. data/test/unit/layout/test_legacy_schema.rb +0 -574
  173. data/test/unit/layout/test_version.rb +0 -26
  174. data/test/unit/report/test_base.rb +0 -248
  175. data/test/unit/report/test_internal.rb +0 -206
  176. data/test/unit/test_config.rb +0 -36
  177. data/test/unit/test_layout.rb +0 -12
  178. data/test/unit/test_report.rb +0 -18
@@ -18,6 +18,7 @@ module Thinreports
18
18
  when TextBlock::TYPE_NAME then TextBlock
19
19
  when ImageBlock::TYPE_NAME then ImageBlock
20
20
  when List::TYPE_NAME then List
21
+ when StackView::TYPE_NAME then StackView
21
22
  when Text::TYPE_NAME then Text
22
23
  when PageNumber::TYPE_NAME then PageNumber
23
24
  when *Basic::TYPE_NAMES then Basic
@@ -37,4 +38,5 @@ require_relative 'shape/text'
37
38
  require_relative 'shape/text_block'
38
39
  require_relative 'shape/image_block'
39
40
  require_relative 'shape/list'
41
+ require_relative 'shape/stack_view'
40
42
  require_relative 'shape/page_number'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'section_report/generate'
4
+
5
+ module Thinreports
6
+ class Generate
7
+ def call(report_params, filename: nil)
8
+ SectionReport::Generate.new.call(report_params, filename: filename)
9
+ end
10
+ end
11
+ end
@@ -5,20 +5,26 @@ module Thinreports
5
5
  class PDF
6
6
  module DrawShape
7
7
  # @param [Thinreports::Core::Shape::TextBlock::Internal] shape
8
- def draw_shape_tblock(shape)
9
- x, y, w, h = shape.format.attributes.values_at('x', 'y', 'width', 'height')
8
+ # @param [Numeric] height (nil) It will be used as rendering height if present.
9
+ # Otherwise, the rendering height is the height of schema.
10
+ # @param [:truncate, :shrink_to_fit, :expand] overflow (nil) It will be set the overflow attribute if present.
11
+ def draw_shape_tblock(shape, height: nil, overflow: nil, &block)
12
+ x, y, w = shape.format.attributes.values_at('x', 'y', 'width')
13
+
14
+ h = height || shape.format.attributes['height']
10
15
 
11
16
  content = shape.real_value.to_s
12
17
  return if content.empty?
13
18
 
14
19
  attrs = build_text_attributes(shape.style.finalized_styles)
20
+ attrs[:overflow] = overflow if overflow
15
21
 
16
22
  unless shape.multiple?
17
23
  content = content.tr("\n", ' ')
18
24
  attrs[:single] = true
19
25
  end
20
26
 
21
- text_box(content, x, y, w, h, attrs)
27
+ text_box(content, x, y, w, h, attrs, &block)
22
28
  end
23
29
 
24
30
  def draw_shape_pageno(shape, page_no, page_count)
@@ -44,6 +50,21 @@ module Thinreports
44
50
  style = shape.style.finalized_styles
45
51
 
46
52
  image_box(
53
+ shape.src, x, y, w, h,
54
+ position_x: image_position_x(style['position-x']),
55
+ position_y: image_position_y(style['position-y']),
56
+ offset_x: style['offset-x'],
57
+ offset_y: style['offset-y']
58
+ )
59
+ end
60
+
61
+ def shape_iblock_dimenions(shape)
62
+ return nil if blank_value?(shape.src)
63
+
64
+ x, y, w, h = shape.format.attributes.values_at('x', 'y', 'width', 'height')
65
+ style = shape.style.finalized_styles
66
+
67
+ image_dimensions(
47
68
  shape.src, x, y, w, h,
48
69
  position_x: image_position_x(style['position-x']),
49
70
  position_y: image_position_y(style['position-y'])
@@ -51,10 +72,10 @@ module Thinreports
51
72
  end
52
73
 
53
74
  # @param [Thinreports::Core::Shape::Text::Internal] shape
54
- def draw_shape_text(shape)
75
+ def draw_shape_text(shape, dheight = 0)
55
76
  x, y, w, h = shape.format.attributes.values_at('x', 'y', 'width', 'height')
56
77
  text(
57
- shape.texts.join("\n"), x, y, w, h,
78
+ shape.texts.join("\n"), x, y, w, h + dheight,
58
79
  build_text_attributes(shape.style.finalized_styles)
59
80
  )
60
81
  end
@@ -66,18 +87,18 @@ module Thinreports
66
87
  end
67
88
 
68
89
  # @param [Thinreports::Core::Shape::Basic::Internal] shape
69
- def draw_shape_line(shape)
90
+ def draw_shape_line(shape, dy1 = 0, dy2 = 0)
70
91
  x1, y1, x2, y2 = shape.format.attributes.values_at('x1', 'y1', 'x2', 'y2')
71
- line(x1, y1, x2, y2, build_graphic_attributes(shape.style.finalized_styles))
92
+ line(x1, y1 + dy1, x2, y2 + dy2, build_graphic_attributes(shape.style.finalized_styles))
72
93
  end
73
94
 
74
95
  # @param [Thinreports::Core::Shape::Basic::Internal] shape
75
- def draw_shape_rect(shape)
96
+ def draw_shape_rect(shape, dheight = 0)
76
97
  x, y, w, h = shape.format.attributes.values_at('x', 'y', 'width', 'height')
77
98
  rect_attributes = build_graphic_attributes(shape.style.finalized_styles) do |attrs|
78
- attrs[:radius] = shape.format.attributes['rx']
99
+ attrs[:radius] = shape.format.attributes['border-radius']
79
100
  end
80
- rect(x, y, w, h, rect_attributes)
101
+ rect(x, y, w, h + dheight, rect_attributes)
81
102
  end
82
103
  end
83
104
  end
@@ -25,7 +25,7 @@ module Thinreports
25
25
  def draw_rect(item_attributes)
26
26
  x, y, w, h = item_attributes.values_at('x', 'y', 'width', 'height')
27
27
  graphic_attributes = build_graphic_attributes(item_attributes['style']) do |attrs|
28
- attrs[:radius] = item_attributes['rx']
28
+ attrs[:radius] = item_attributes['border-radius']
29
29
  end
30
30
 
31
31
  rect(x, y, w, h, graphic_attributes)
@@ -39,9 +39,16 @@ module Thinreports
39
39
  # @param [Hash] options
40
40
  # @option options [:left, :center, :right] :position_x (:left)
41
41
  # @option options [:top, :center, :bottom] :position_y (:top)
42
+ # @option options [Numeric] :offset_x
43
+ # @option options [Numeric] :offset_y
42
44
  def image_box(filename_or_io, x, y, w, h, options = {})
43
45
  w, h = s2f(w, h)
44
- pdf.bounding_box(pos(x, y), width: w, height: h) do
46
+
47
+ computed_position = pos(
48
+ x + (options[:offset_x] || 0),
49
+ y + (options[:offset_y] || 0)
50
+ )
51
+ pdf.bounding_box(computed_position, width: w, height: h) do
45
52
  pdf.image(
46
53
  filename_or_io,
47
54
  position: options[:position_x] || :left,
@@ -51,6 +58,17 @@ module Thinreports
51
58
  end
52
59
  end
53
60
 
61
+ def image_dimensions(filename_or_io, x, y, w, h, options = {})
62
+ w, h = s2f(w, h)
63
+ # XXX: Calling @private method
64
+ _pdf_obj, info = pdf.build_image_object(filename_or_io)
65
+ info.calc_image_dimensions(
66
+ position: options[:position_x] || :left,
67
+ vposition: options[:position_y] || :top,
68
+ auto_fit: [w, h]
69
+ )
70
+ end
71
+
54
72
  def clean_temp_images
55
73
  temp_image_registry.each_value do |image_path|
56
74
  File.delete(image_path) if File.exist?(image_path)
@@ -22,7 +22,7 @@ module Thinreports
22
22
  # @option attrs [Boolean] :single (false)
23
23
  # @option attrs [:trancate, :shrink_to_fit, :expand] :overflow (:trancate)
24
24
  # @option attrs [:none, :break_word] :word_wrap (:none)
25
- def text_box(content, x, y, w, h, attrs = {})
25
+ def text_box(content, x, y, w, h, attrs = {}, &block)
26
26
  w, h = s2f(w, h)
27
27
 
28
28
  box_attrs = text_box_attrs(
@@ -35,10 +35,15 @@ module Thinreports
35
35
  content = text_without_line_wrap(content) if attrs[:word_wrap] == :none
36
36
 
37
37
  with_text_styles(attrs) do |built_attrs, font_styles|
38
- pdf.formatted_text_box(
39
- [{ text: content, styles: font_styles }],
40
- built_attrs.merge(box_attrs)
41
- )
38
+ if block
39
+ block.call [{ text: content, styles: font_styles }],
40
+ built_attrs.merge(box_attrs)
41
+ else
42
+ pdf.formatted_text_box(
43
+ [{ text: content, styles: font_styles }],
44
+ built_attrs.merge(box_attrs)
45
+ )
46
+ end
42
47
  end
43
48
  rescue Prawn::Errors::CannotFit
44
49
  # Nothing to do.
@@ -26,6 +26,18 @@ module Thinreports
26
26
  stamp(format_id.to_s)
27
27
  end
28
28
 
29
+ def start_new_page_for_section_report(format)
30
+ @current_page_format = format
31
+ pdf.start_new_page(new_basic_page_options(current_page_format).merge(
32
+ top_margin: current_page_format.page_margin[0],
33
+ bottom_margin: current_page_format.page_margin[2]
34
+ ))
35
+ end
36
+
37
+ def max_content_height
38
+ pdf.margin_box.height
39
+ end
40
+
29
41
  def add_blank_page
30
42
  pdf.start_new_page(pdf.page_count.zero? ? { size: 'A4' } : {})
31
43
  end
@@ -4,16 +4,13 @@ module Thinreports
4
4
  module Generator
5
5
  module PrawnExt
6
6
  module WidthOf
7
- # Subtract the width of one character space from the string width calculation result.
7
+ # This patch fixes the character_spacing effect on text width calculation.
8
8
  #
9
- # The original Prawn::Document#width_of returns the following result:
10
- #
11
- # Width of Character is 1
12
- # Width of Character Space is 1
9
+ # The original #width_of:
13
10
  #
14
11
  # width_of('abcd') #=> 4 + 4 = 8
15
12
  #
16
- # In this width_of, returns the following result:
13
+ # The #width_of in this patch:
17
14
  #
18
15
  # width_of('abcd') #=> 4 + 3 = 7
19
16
  #
@@ -26,4 +23,7 @@ module Thinreports
26
23
  end
27
24
  end
28
25
 
29
- Prawn::Document.prepend Thinreports::Generator::PrawnExt::WidthOf
26
+ # Prawn v2.3 and later includes this patch by https://github.com/prawnpdf/prawn/pull/1117.
27
+ if Prawn::VERSION < '2.3.0'
28
+ Prawn::Document.prepend Thinreports::Generator::PrawnExt::WidthOf
29
+ end
@@ -3,7 +3,7 @@
3
3
  module Thinreports
4
4
  module Layout
5
5
  class Base
6
- EXT_NAME = 'tlf'.freeze
6
+ EXT_NAME = 'tlf'
7
7
  include Utils
8
8
 
9
9
  class << self
@@ -104,6 +104,7 @@ module Thinreports
104
104
  'width' => attributes['width'].to_f,
105
105
  'height' => attributes['height'].to_f,
106
106
  'display' => display(attributes['x-display']),
107
+ 'border-radius' => attributes['rx'].to_i,
107
108
  'style' => {
108
109
  'border-width' => attributes['stroke-width'].to_f,
109
110
  'border-color' => attributes['stroke'],
@@ -4,7 +4,7 @@ module Thinreports
4
4
  module Layout
5
5
  class Version
6
6
  COMPATIBLE_RULES = ['>= 0.8.0', '< 1.0.0'].freeze
7
- NEW_SCHEMA_FROM = '0.9.0'.freeze
7
+ NEW_SCHEMA_FROM = '0.9.0'
8
8
 
9
9
  class << self
10
10
  def compatible_rules
@@ -96,6 +96,8 @@ module Thinreports
96
96
  end
97
97
 
98
98
  def init_layout(filename, id = nil)
99
+ filename = filename.to_path if filename.is_a?(Pathname)
100
+
99
101
  Thinreports::Layout.new(filename, id: id)
100
102
  end
101
103
  end
@@ -13,8 +13,8 @@ module Thinreports
13
13
  end
14
14
 
15
15
  # @see Thinreports::Report::Base#generate
16
- def self.generate(*args, &block)
17
- Base.generate(*args, &block)
16
+ def self.generate(**args, &block)
17
+ Base.generate(**args, &block)
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'schema/loader'
4
+ require_relative 'builder/report_builder'
5
+
6
+ module Thinreports
7
+ module SectionReport
8
+ class Build
9
+ def call(report_params)
10
+ schema = load_schema(report_params)
11
+ params = report_params[:params] || {}
12
+
13
+ Builder::ReportBuilder.new(schema).build(params)
14
+ end
15
+
16
+ private
17
+
18
+ def load_schema(report_params)
19
+ loader = Schema::Loader.new
20
+
21
+ case
22
+ when report_params[:layout_file]
23
+ loader.load_from_file(report_params[:layout_file])
24
+ when report_params[:layout_data]
25
+ loader.load_from_data(report_params[:layout_data])
26
+ else
27
+ raise Errors::LayoutFileNotFound
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'stack_view_builder'
4
+
5
+ module Thinreports
6
+ module SectionReport
7
+ module Builder
8
+ class ItemBuilder
9
+ Context = Struct.new(:parent_schema)
10
+
11
+ def initialize(item_schema, parent_schema)
12
+ @item = Core::Shape::Interface(nil, item_schema)
13
+ @parent_schema = parent_schema
14
+ end
15
+
16
+ def build(item_params)
17
+ params = build_params(item_params)
18
+
19
+ item.visible(params[:display]) if params.key?(:display)
20
+ item.value(params[:value]) if params.key?(:value)
21
+ item.styles(params[:styles]) if params.key?(:styles)
22
+
23
+ if item.internal.format.attributes['type'] == Core::Shape::StackView::TYPE_NAME
24
+ StackViewBuilder.new(item).update(params)
25
+ end
26
+
27
+ item
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :item, :parent_schema
33
+
34
+ def build_params(params)
35
+ return {} unless params
36
+
37
+ case params
38
+ when Hash
39
+ params
40
+ when Proc
41
+ params.call(Context.new(parent_schema))
42
+ else
43
+ { value: params }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'report_data'
4
+ require_relative 'item_builder'
5
+
6
+ module Thinreports
7
+ module SectionReport
8
+ module Builder
9
+ class ReportBuilder
10
+ def initialize(schema)
11
+ @schema = schema
12
+ end
13
+
14
+ def build(params)
15
+ ReportData::Main.new(
16
+ schema,
17
+ build_groups(params[:groups])
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :schema
24
+
25
+ def build_groups(groups_params)
26
+ return [] unless groups_params
27
+
28
+ groups_params.map do |group_params|
29
+ ReportData::Group.new(
30
+ build_sections(:header, group_params[:headers] || {}),
31
+ build_detail_sections(group_params[:details] || []),
32
+ build_sections(:footer, group_params[:footers] || {})
33
+ )
34
+ end
35
+ end
36
+
37
+ def build_sections(section_type, sections_params)
38
+ sections_schemas =
39
+ case section_type
40
+ when :header then schema.headers
41
+ when :footer then schema.footers
42
+ end
43
+
44
+ sections_schemas.each_with_object([]) do |(section_id, section_schema), sections|
45
+ section_params = sections_params[section_id.to_sym] || {}
46
+ next unless section_enabled?(section_schema, section_params)
47
+
48
+ items = build_items(section_schema, section_params[:items] || {})
49
+ sections << ReportData::Section.new(section_schema, items, section_params[:min_height])
50
+ end
51
+ end
52
+
53
+ def build_detail_sections(details_params)
54
+ details_params.each_with_object([]) do |detail_params, details|
55
+ detail_id = detail_params[:id].to_sym
56
+ detail_schema = schema.details[detail_id]
57
+
58
+ next unless detail_schema
59
+
60
+ items = build_items(detail_schema, detail_params[:items] || {})
61
+ details << ReportData::Section.new(detail_schema, items, detail_params[:min_height])
62
+ end
63
+ end
64
+
65
+ def build_items(section_schema, items_params)
66
+ section_schema.items.each_with_object([]) do |item_schema, items|
67
+ item = ItemBuilder.new(item_schema, section_schema).build(items_params[item_schema.id&.to_sym])
68
+ items << item if item.visible?
69
+ end
70
+ end
71
+
72
+ def section_enabled?(section_schema, section_params)
73
+ if section_params.key?(:display)
74
+ section_params[:display]
75
+ else
76
+ section_schema.display?
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module SectionReport
5
+ module Builder
6
+ module ReportData
7
+ Main = Struct.new :schema, :groups
8
+ Group = Struct.new :headers, :details, :footers
9
+ Section = Struct.new :schema, :items, :min_height
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'stack_view_data'
4
+
5
+ module Thinreports
6
+ module SectionReport
7
+ module Builder
8
+ class StackViewBuilder
9
+ def initialize(item)
10
+ @item = item
11
+ end
12
+
13
+ def update(params)
14
+ rows_params = params[:rows] || {}
15
+ rows_schema = item.internal.format.rows
16
+
17
+ rows = []
18
+ rows_schema.each do |row_schema|
19
+ row_params = rows_params[row_schema.id.to_sym] || {}
20
+ next unless row_enabled?(row_schema, row_params)
21
+
22
+ items = build_row_items(
23
+ row_schema,
24
+ row_params[:items] || {}
25
+ )
26
+
27
+ rows << StackViewData::Row.new(row_schema, items, row_params[:min_height])
28
+ end
29
+ item.internal.rows = rows
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :item
35
+
36
+ def build_row_items(row_schema, items_params)
37
+ row_schema.items.each_with_object([]) do |item_schema, items|
38
+ item = ItemBuilder.new(item_schema, row_schema).build(items_params[item_schema.id&.to_sym])
39
+ items << item if item.visible?
40
+ end
41
+ end
42
+
43
+ def row_enabled?(row_schema, row_params)
44
+ if row_params.key?(:display)
45
+ row_params[:display]
46
+ else
47
+ row_schema.display?
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module SectionReport
5
+ module Builder
6
+ module StackViewData
7
+ Row = Struct.new :schema, :items, :min_height
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'build'
4
+ require_relative 'pdf/render'
5
+
6
+ module Thinreports
7
+ module SectionReport
8
+ class Generate
9
+ def initialize
10
+ @pdf = Thinreports::Generator::PDF::Document.new
11
+ end
12
+
13
+ def call(report_params, filename: nil)
14
+ report = Build.new.call(report_params)
15
+
16
+ PDF::Render.new(pdf).call!(report)
17
+
18
+ filename ? pdf.render_file(filename) : pdf.render
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :pdf
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'renderer/group_renderer'
4
+
5
+ module Thinreports
6
+ module SectionReport
7
+ module PDF
8
+ class Render
9
+ def initialize(pdf)
10
+ @group_renderer = Renderer::GroupRenderer.new(pdf)
11
+ end
12
+
13
+ def call!(report)
14
+ report.groups.each { |group| group_renderer.render(report, group) }
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :group_renderer
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module SectionReport
5
+ module Renderer
6
+ module DrawItem
7
+ def draw_item(item, expanded_height = 0)
8
+ shape = item.internal
9
+
10
+ if shape.type_of?(Core::Shape::TextBlock::TYPE_NAME)
11
+ computed_height = shape.format.attributes['height']
12
+ computed_height += expanded_height if shape.format.follow_stretch == 'height'
13
+
14
+ if shape.style.finalized_styles['overflow'] == 'expand'
15
+ # When overflow is "expand", the value of the height argument is ignored and the shape is expanded to
16
+ # the bottom of the outer bounding box.
17
+ # That causes a position shift problem if vertical-align is "middle" or "bottom".
18
+ # To solve it, we overwrite the overflow to "truncate" when drawing.
19
+ # To emulate the "expand" behavior in the "truncate" mode,
20
+ # here we pass the greater value of the computed_height and the text height as text block height.
21
+ pdf.draw_shape_tblock(shape, height: [computed_height, calc_text_block_height(shape)].max, overflow: :truncate)
22
+ else
23
+ pdf.draw_shape_tblock(shape, height: computed_height)
24
+ end
25
+ elsif shape.type_of?(Core::Shape::ImageBlock::TYPE_NAME)
26
+ pdf.draw_shape_iblock(shape)
27
+ elsif shape.type_of?('text')
28
+ case shape.format.follow_stretch
29
+ when 'height'
30
+ pdf.draw_shape_text(shape, expanded_height)
31
+ else
32
+ pdf.draw_shape_text(shape)
33
+ end
34
+ elsif shape.type_of?('image')
35
+ pdf.draw_shape_image(shape)
36
+ elsif shape.type_of?('ellipse')
37
+ pdf.draw_shape_ellipse(shape)
38
+ elsif shape.type_of?('rect')
39
+ case shape.format.follow_stretch
40
+ when 'height'
41
+ pdf.draw_shape_rect(shape, expanded_height)
42
+ else
43
+ pdf.draw_shape_rect(shape)
44
+ end
45
+ elsif shape.type_of?('line')
46
+ case shape.format.follow_stretch
47
+ when 'height'
48
+ y1, y2 = shape.format.attributes.values_at('y1', 'y2')
49
+ if y1 < y2
50
+ pdf.draw_shape_line(shape, 0, expanded_height)
51
+ else
52
+ pdf.draw_shape_line(shape, expanded_height, 0)
53
+ end
54
+ when 'y'
55
+ pdf.draw_shape_line(shape, expanded_height, expanded_height)
56
+ else
57
+ pdf.draw_shape_line(shape)
58
+ end
59
+ elsif shape.type_of?(Core::Shape::StackView::TYPE_NAME)
60
+ stack_view_renderer.render(shape)
61
+ else
62
+ raise Thinreports::Errors::UnknownShapeType
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end