thinreports 0.12.0 → 0.13.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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/test.yml +31 -40
  4. data/.gitignore +1 -1
  5. data/CHANGELOG.md +25 -1
  6. data/Gemfile +11 -0
  7. data/{MIT-LICENSE → LICENSE.txt} +2 -0
  8. data/README.md +50 -66
  9. data/Rakefile +17 -11
  10. data/lib/thinreports/basic_report/core/errors.rb +62 -0
  11. data/lib/thinreports/basic_report/core/format/base.rb +91 -0
  12. data/lib/thinreports/basic_report/core/shape/base/interface.rb +42 -0
  13. data/lib/thinreports/basic_report/core/shape/base/internal.rb +53 -0
  14. data/lib/thinreports/basic_report/core/shape/basic/block_format.rb +15 -0
  15. data/lib/thinreports/basic_report/core/shape/basic/block_interface.rb +34 -0
  16. data/lib/thinreports/basic_report/core/shape/basic/block_internal.rb +36 -0
  17. data/lib/thinreports/basic_report/core/shape/basic/format.rb +24 -0
  18. data/lib/thinreports/basic_report/core/shape/basic/interface.rb +87 -0
  19. data/lib/thinreports/basic_report/core/shape/basic/internal.rb +28 -0
  20. data/lib/thinreports/{core → basic_report/core}/shape/basic.rb +6 -4
  21. data/lib/thinreports/basic_report/core/shape/image_block/format.rb +14 -0
  22. data/lib/thinreports/basic_report/core/shape/image_block/interface.rb +25 -0
  23. data/lib/thinreports/basic_report/core/shape/image_block/internal.rb +19 -0
  24. data/lib/thinreports/{core → basic_report/core}/shape/image_block.rb +6 -4
  25. data/lib/thinreports/basic_report/core/shape/list/format.rb +92 -0
  26. data/lib/thinreports/basic_report/core/shape/list/manager.rb +231 -0
  27. data/lib/thinreports/basic_report/core/shape/list/page.rb +112 -0
  28. data/lib/thinreports/basic_report/core/shape/list/page_state.rb +50 -0
  29. data/lib/thinreports/basic_report/core/shape/list/section_format.rb +37 -0
  30. data/lib/thinreports/basic_report/core/shape/list/section_interface.rb +51 -0
  31. data/lib/thinreports/basic_report/core/shape/list/section_internal.rb +33 -0
  32. data/lib/thinreports/{core → basic_report/core}/shape/list.rb +6 -4
  33. data/lib/thinreports/basic_report/core/shape/manager/format.rb +33 -0
  34. data/lib/thinreports/basic_report/core/shape/manager/internal.rb +109 -0
  35. data/lib/thinreports/basic_report/core/shape/manager/target.rb +114 -0
  36. data/lib/thinreports/basic_report/core/shape/page_number/format.rb +32 -0
  37. data/lib/thinreports/basic_report/core/shape/page_number/interface.rb +31 -0
  38. data/lib/thinreports/basic_report/core/shape/page_number/internal.rb +57 -0
  39. data/lib/thinreports/{core → basic_report/core}/shape/page_number.rb +6 -4
  40. data/lib/thinreports/basic_report/core/shape/stack_view/format.rb +29 -0
  41. data/lib/thinreports/basic_report/core/shape/stack_view/interface.rb +19 -0
  42. data/lib/thinreports/basic_report/core/shape/stack_view/internal.rb +24 -0
  43. data/lib/thinreports/basic_report/core/shape/stack_view/row_format.rb +41 -0
  44. data/lib/thinreports/{core → basic_report/core}/shape/stack_view.rb +6 -4
  45. data/lib/thinreports/basic_report/core/shape/style/base.rb +149 -0
  46. data/lib/thinreports/basic_report/core/shape/style/basic.rb +24 -0
  47. data/lib/thinreports/basic_report/core/shape/style/graphic.rb +43 -0
  48. data/lib/thinreports/basic_report/core/shape/style/text.rb +122 -0
  49. data/lib/thinreports/basic_report/core/shape/text/format.rb +17 -0
  50. data/lib/thinreports/basic_report/core/shape/text/interface.rb +20 -0
  51. data/lib/thinreports/basic_report/core/shape/text/internal.rb +24 -0
  52. data/lib/thinreports/{core → basic_report/core}/shape/text.rb +6 -4
  53. data/lib/thinreports/basic_report/core/shape/text_block/format.rb +43 -0
  54. data/lib/thinreports/basic_report/core/shape/text_block/formatter/basic.rb +41 -0
  55. data/lib/thinreports/basic_report/core/shape/text_block/formatter/datetime.rb +25 -0
  56. data/lib/thinreports/basic_report/core/shape/text_block/formatter/number.rb +69 -0
  57. data/lib/thinreports/basic_report/core/shape/text_block/formatter/padding.rb +27 -0
  58. data/lib/thinreports/basic_report/core/shape/text_block/formatter.rb +38 -0
  59. data/lib/thinreports/basic_report/core/shape/text_block/interface.rb +37 -0
  60. data/lib/thinreports/basic_report/core/shape/text_block/internal.rb +75 -0
  61. data/lib/thinreports/{core → basic_report/core}/shape/text_block.rb +6 -4
  62. data/lib/thinreports/basic_report/core/shape.rb +44 -0
  63. data/lib/thinreports/basic_report/core/utils.rb +47 -0
  64. data/lib/thinreports/basic_report/generator/pdf/document/draw_shape.rb +108 -0
  65. data/lib/thinreports/basic_report/generator/pdf/document/draw_template_items.rb +71 -0
  66. data/lib/thinreports/basic_report/generator/pdf/document/font.rb +90 -0
  67. data/lib/thinreports/basic_report/generator/pdf/document/graphics/attributes.rb +149 -0
  68. data/lib/thinreports/basic_report/generator/pdf/document/graphics/basic.rb +137 -0
  69. data/lib/thinreports/basic_report/generator/pdf/document/graphics/image.rb +99 -0
  70. data/lib/thinreports/basic_report/generator/pdf/document/graphics/text.rb +180 -0
  71. data/lib/thinreports/basic_report/generator/pdf/document/graphics.rb +42 -0
  72. data/lib/thinreports/basic_report/generator/pdf/document/page.rb +96 -0
  73. data/lib/thinreports/basic_report/generator/pdf/document/parse_color.rb +51 -0
  74. data/lib/thinreports/basic_report/generator/pdf/document.rb +132 -0
  75. data/lib/thinreports/basic_report/generator/pdf/drawer/base.rb +50 -0
  76. data/lib/thinreports/basic_report/generator/pdf/drawer/list.rb +65 -0
  77. data/lib/thinreports/basic_report/generator/pdf/drawer/list_section.rb +51 -0
  78. data/lib/thinreports/basic_report/generator/pdf/drawer/page.rb +127 -0
  79. data/lib/thinreports/basic_report/generator/pdf/prawn_ext/calc_image_dimensions.rb +34 -0
  80. data/lib/thinreports/basic_report/generator/pdf/prawn_ext/width_of.rb +31 -0
  81. data/lib/thinreports/basic_report/generator/pdf.rb +66 -0
  82. data/lib/thinreports/basic_report/layout/base.rb +48 -0
  83. data/lib/thinreports/basic_report/layout/format.rb +67 -0
  84. data/lib/thinreports/basic_report/layout/legacy_schema.rb +383 -0
  85. data/lib/thinreports/basic_report/layout/version.rb +41 -0
  86. data/lib/thinreports/{layout.rb → basic_report/layout.rb} +6 -4
  87. data/lib/thinreports/basic_report/report/base.rb +173 -0
  88. data/lib/thinreports/basic_report/report/internal.rb +107 -0
  89. data/lib/thinreports/basic_report/report/page.rb +97 -0
  90. data/lib/thinreports/basic_report/report.rb +26 -0
  91. data/lib/thinreports/basic_report.rb +15 -0
  92. data/lib/thinreports/config.rb +1 -1
  93. data/lib/thinreports/section_report/builder/stack_view_builder.rb +0 -2
  94. data/lib/thinreports/section_report.rb +12 -0
  95. data/lib/thinreports/version.rb +1 -1
  96. data/lib/thinreports.rb +2 -13
  97. data/thinreports.gemspec +3 -7
  98. metadata +99 -157
  99. data/Dockerfile +0 -12
  100. data/gemfiles/prawn-2.2.gemfile +0 -5
  101. data/gemfiles/prawn-2.3.gemfile +0 -5
  102. data/gemfiles/prawn-2.4.gemfile +0 -5
  103. data/lib/thinreports/core/errors.rb +0 -60
  104. data/lib/thinreports/core/format/base.rb +0 -89
  105. data/lib/thinreports/core/shape/base/interface.rb +0 -40
  106. data/lib/thinreports/core/shape/base/internal.rb +0 -51
  107. data/lib/thinreports/core/shape/basic/block_format.rb +0 -13
  108. data/lib/thinreports/core/shape/basic/block_interface.rb +0 -32
  109. data/lib/thinreports/core/shape/basic/block_internal.rb +0 -34
  110. data/lib/thinreports/core/shape/basic/format.rb +0 -22
  111. data/lib/thinreports/core/shape/basic/interface.rb +0 -85
  112. data/lib/thinreports/core/shape/basic/internal.rb +0 -26
  113. data/lib/thinreports/core/shape/image_block/format.rb +0 -12
  114. data/lib/thinreports/core/shape/image_block/interface.rb +0 -23
  115. data/lib/thinreports/core/shape/image_block/internal.rb +0 -17
  116. data/lib/thinreports/core/shape/list/format.rb +0 -90
  117. data/lib/thinreports/core/shape/list/manager.rb +0 -229
  118. data/lib/thinreports/core/shape/list/page.rb +0 -110
  119. data/lib/thinreports/core/shape/list/page_state.rb +0 -48
  120. data/lib/thinreports/core/shape/list/section_format.rb +0 -35
  121. data/lib/thinreports/core/shape/list/section_interface.rb +0 -49
  122. data/lib/thinreports/core/shape/list/section_internal.rb +0 -31
  123. data/lib/thinreports/core/shape/manager/format.rb +0 -31
  124. data/lib/thinreports/core/shape/manager/internal.rb +0 -107
  125. data/lib/thinreports/core/shape/manager/target.rb +0 -112
  126. data/lib/thinreports/core/shape/page_number/format.rb +0 -30
  127. data/lib/thinreports/core/shape/page_number/interface.rb +0 -29
  128. data/lib/thinreports/core/shape/page_number/internal.rb +0 -55
  129. data/lib/thinreports/core/shape/stack_view/format.rb +0 -27
  130. data/lib/thinreports/core/shape/stack_view/interface.rb +0 -17
  131. data/lib/thinreports/core/shape/stack_view/internal.rb +0 -22
  132. data/lib/thinreports/core/shape/stack_view/row_format.rb +0 -39
  133. data/lib/thinreports/core/shape/style/base.rb +0 -146
  134. data/lib/thinreports/core/shape/style/basic.rb +0 -22
  135. data/lib/thinreports/core/shape/style/graphic.rb +0 -41
  136. data/lib/thinreports/core/shape/style/text.rb +0 -120
  137. data/lib/thinreports/core/shape/text/format.rb +0 -15
  138. data/lib/thinreports/core/shape/text/interface.rb +0 -18
  139. data/lib/thinreports/core/shape/text/internal.rb +0 -22
  140. data/lib/thinreports/core/shape/text_block/format.rb +0 -41
  141. data/lib/thinreports/core/shape/text_block/formatter/basic.rb +0 -39
  142. data/lib/thinreports/core/shape/text_block/formatter/datetime.rb +0 -23
  143. data/lib/thinreports/core/shape/text_block/formatter/number.rb +0 -67
  144. data/lib/thinreports/core/shape/text_block/formatter/padding.rb +0 -25
  145. data/lib/thinreports/core/shape/text_block/formatter.rb +0 -34
  146. data/lib/thinreports/core/shape/text_block/interface.rb +0 -35
  147. data/lib/thinreports/core/shape/text_block/internal.rb +0 -73
  148. data/lib/thinreports/core/shape.rb +0 -42
  149. data/lib/thinreports/core/utils.rb +0 -45
  150. data/lib/thinreports/generate.rb +0 -11
  151. data/lib/thinreports/generator/pdf/document/draw_shape.rb +0 -106
  152. data/lib/thinreports/generator/pdf/document/draw_template_items.rb +0 -69
  153. data/lib/thinreports/generator/pdf/document/font.rb +0 -88
  154. data/lib/thinreports/generator/pdf/document/graphics/attributes.rb +0 -147
  155. data/lib/thinreports/generator/pdf/document/graphics/basic.rb +0 -135
  156. data/lib/thinreports/generator/pdf/document/graphics/image.rb +0 -98
  157. data/lib/thinreports/generator/pdf/document/graphics/text.rb +0 -178
  158. data/lib/thinreports/generator/pdf/document/graphics.rb +0 -40
  159. data/lib/thinreports/generator/pdf/document/page.rb +0 -94
  160. data/lib/thinreports/generator/pdf/document/parse_color.rb +0 -49
  161. data/lib/thinreports/generator/pdf/document.rb +0 -130
  162. data/lib/thinreports/generator/pdf/drawer/base.rb +0 -48
  163. data/lib/thinreports/generator/pdf/drawer/list.rb +0 -63
  164. data/lib/thinreports/generator/pdf/drawer/list_section.rb +0 -49
  165. data/lib/thinreports/generator/pdf/drawer/page.rb +0 -125
  166. data/lib/thinreports/generator/pdf/prawn_ext/calc_image_dimensions.rb +0 -32
  167. data/lib/thinreports/generator/pdf/prawn_ext/width_of.rb +0 -29
  168. data/lib/thinreports/generator/pdf.rb +0 -63
  169. data/lib/thinreports/layout/base.rb +0 -46
  170. data/lib/thinreports/layout/format.rb +0 -65
  171. data/lib/thinreports/layout/legacy_schema.rb +0 -381
  172. data/lib/thinreports/layout/version.rb +0 -39
  173. data/lib/thinreports/report/base.rb +0 -169
  174. data/lib/thinreports/report/internal.rb +0 -105
  175. data/lib/thinreports/report/page.rb +0 -95
  176. data/lib/thinreports/report.rb +0 -24
  177. data/lib/thinreports/section_report/pdf/renderer/headers_renderer.rb +0 -24
  178. /data/lib/thinreports/{core → basic_report/core}/shape/base.rb +0 -0
  179. /data/lib/thinreports/{core → basic_report/core}/shape/manager.rb +0 -0
  180. /data/lib/thinreports/{core → basic_report/core}/shape/style.rb +0 -0
  181. /data/lib/thinreports/{generator → basic_report/generator}/pdf/prawn_ext.rb +0 -0
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module BasicReport
5
+ module Generator
6
+ class PDF
7
+ module Graphics
8
+ # @param [Hash] style
9
+ # @yield [attrs]
10
+ # @yieldparam [Hash] attrs
11
+ # @return [Hash]
12
+ def build_graphic_attributes(style, &block)
13
+ graphic_attributes = {
14
+ stroke: style['border-color'],
15
+ stroke_width: style['border-width'],
16
+ stroke_type: style['border-style'],
17
+ fill: style['fill-color']
18
+ }
19
+ block.call(graphic_attributes) if block_given?
20
+ graphic_attributes
21
+ end
22
+
23
+ # @param [Hash] style
24
+ # @yield [attrs]
25
+ # @yieldparam [Hash] attrs
26
+ # @return [Hash]
27
+ def build_text_attributes(style, &block)
28
+ text_attributes = {
29
+ font: font_family(style['font-family']),
30
+ size: style['font-size'],
31
+ color: style['color'],
32
+ align: text_align(style['text-align']),
33
+ valign: text_valign(style['vertical-align']),
34
+ styles: font_styles(style['font-style']),
35
+ letter_spacing: letter_spacing(style['letter-spacing']),
36
+ line_height: line_height(style['line-height']),
37
+ overflow: text_overflow(style['overflow']),
38
+ word_wrap: word_wrap(style['word-wrap'])
39
+ }
40
+ block.call(text_attributes) if block_given?
41
+ text_attributes
42
+ end
43
+
44
+ # @param [Array<String>] font_names
45
+ # @return [String]
46
+ def font_family(font_names)
47
+ font_name = font_names.first
48
+ default_family_if_missing(font_name)
49
+ end
50
+
51
+ # @param [Array<String>] styles
52
+ # @return [Array<Symbol>]
53
+ def font_styles(styles)
54
+ styles.map do |font_style|
55
+ case font_style
56
+ when 'bold' then :bold
57
+ when 'italic' then :italic
58
+ when 'underline' then :underline
59
+ when 'linethrough' then :strikethrough
60
+ end
61
+ end
62
+ end
63
+
64
+ # @param [Float, "", nil] spacing
65
+ # @return [Float, nil]
66
+ def letter_spacing(spacing)
67
+ blank_value?(spacing) ? nil : spacing
68
+ end
69
+
70
+ # @param ["left", "center", "right", ""] align
71
+ # @return [:left, :center, :right]
72
+ def text_align(align)
73
+ case align
74
+ when 'left' then :left
75
+ when 'center' then :center
76
+ when 'right' then :right
77
+ when '' then :left
78
+ else :left
79
+ end
80
+ end
81
+
82
+ # @param ["top", "middle", "bottom", "", nil] valign
83
+ # @return [:top, :center, :bottom]
84
+ def text_valign(valign)
85
+ case valign
86
+ when 'top' then :top
87
+ when 'middle' then :center
88
+ when 'bottom' then :bottom
89
+ when '' then :top
90
+ else :top
91
+ end
92
+ end
93
+
94
+ # @param ["truncate", "fit", "expand", "", nil] overflow
95
+ # @return [:truncate, :shrink_to_fit, :expand]
96
+ def text_overflow(overflow)
97
+ case overflow
98
+ when 'truncate' then :truncate
99
+ when 'fit' then :shrink_to_fit
100
+ when 'expand' then :expand
101
+ when '' then :truncate
102
+ else :truncate
103
+ end
104
+ end
105
+
106
+ # @param ["break-word", "none", "", nil] word_wrap
107
+ # @return [:break_word, :none]
108
+ def word_wrap(word_wrap)
109
+ case word_wrap
110
+ when 'break-word' then :break_word
111
+ when 'none' then :none
112
+ else :none
113
+ end
114
+ end
115
+
116
+ # @param [Float, "", nil] height
117
+ # @return [Float, nil]
118
+ def line_height(height)
119
+ blank_value?(height) ? nil : height
120
+ end
121
+
122
+ # @param ["left", "center", "right", ""] position
123
+ # @return [:left, :center, :right]
124
+ def image_position_x(position)
125
+ case position
126
+ when 'left' then :left
127
+ when 'center' then :center
128
+ when 'right' then :right
129
+ when '' then :left
130
+ else :left
131
+ end
132
+ end
133
+
134
+ # @param ["top", "middle", "bottom", ""] position
135
+ # @return [:left, :center, :right]
136
+ def image_position_y(position)
137
+ case position
138
+ when 'top' then :top
139
+ when 'middle' then :center
140
+ when 'bottom' then :bottom
141
+ when '' then :top
142
+ else :top
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module BasicReport
5
+ module Generator
6
+ class PDF
7
+ module Graphics
8
+ STROKE_DASH = {
9
+ dashed: [2, 2],
10
+ dotted: [1, 2]
11
+ }.freeze
12
+
13
+ # @param [Numeric, String] x1
14
+ # @param [Numeric, String] y1
15
+ # @param [Numeric, String] x2
16
+ # @param [Numeric, String] y2
17
+ # @param [Hash] attrs ({})
18
+ # @option attrs [String] :stroke
19
+ # @option attrs [Numeric, String] :stroke_width
20
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
21
+ def line(x1, y1, x2, y2, attrs = {})
22
+ with_graphic_styles(attrs) do
23
+ pdf.line(pos(x1, y1), pos(x2, y2))
24
+ end
25
+ end
26
+
27
+ # @param [Numeric, String] x
28
+ # @param [Numeric, String] y
29
+ # @param [Numeric, String] w width
30
+ # @param [Numeric, String] h height
31
+ # @param [Hash] attrs ({})
32
+ # @option attrs [Integer, String] :radius
33
+ # @option attrs [String] :stroke
34
+ # @option attrs [Numeric, String] :stroke_width
35
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
36
+ # @option attrs [String] :fill
37
+ def rect(x, y, w, h, attrs = {})
38
+ w, h = s2f(w, h)
39
+ radius = s2f(attrs[:radius])
40
+
41
+ with_graphic_styles(attrs) do
42
+ if radius && !radius.zero?
43
+ pdf.rounded_rectangle(pos(x, y), w, h, radius)
44
+ else
45
+ pdf.rectangle(pos(x, y), w, h)
46
+ end
47
+ end
48
+ end
49
+
50
+ # @param [Numeric, String] x center-x
51
+ # @param [Numeric, String] y center-y
52
+ # @param [Numeric, String] rx
53
+ # @param [Numeric, String] ry
54
+ # @param [Hash] attrs ({})
55
+ # @option attrs [String] :stroke
56
+ # @option attrs [Numeric, String] :stroke_width
57
+ # @option attrs [Array<Integer, String>] :stroke_dash
58
+ # @option attrs ["solid", "dashed", "dotted"] :stroke_type
59
+ # @option attrs [String] :fill
60
+ def ellipse(x, y, rx, ry, attrs = {})
61
+ rx, ry = s2f(rx, ry)
62
+
63
+ with_graphic_styles(attrs) do
64
+ pdf.ellipse(pos(x, y), rx, ry)
65
+ end
66
+ end
67
+
68
+ # @param [Hash] attrs
69
+ def with_graphic_styles(attrs, &block)
70
+ stroke = build_stroke_styles(attrs)
71
+ fill = build_fill_styles(attrs)
72
+
73
+ # Do not draw if no colors given.
74
+ return unless fill || stroke
75
+
76
+ save_graphics_state
77
+
78
+ # Apply stroke-dashed.
79
+ if stroke && stroke[:dash]
80
+ length, space = stroke[:dash]
81
+ pdf.dash(length, space: space)
82
+ end
83
+
84
+ # Draw with fill and stroke.
85
+ if fill && stroke
86
+ pdf.fill_and_stroke do
87
+ line_width(stroke[:width])
88
+ pdf.fill_color(fill[:color])
89
+ pdf.stroke_color(stroke[:color])
90
+ block.call
91
+ end
92
+ # Draw only with fill.
93
+ elsif fill
94
+ pdf.fill do
95
+ pdf.fill_color(fill[:color])
96
+ block.call
97
+ end
98
+ # Draw only with stroke.
99
+ elsif stroke
100
+ pdf.stroke do
101
+ line_width(stroke[:width])
102
+ pdf.stroke_color(stroke[:color])
103
+ block.call
104
+ end
105
+ end
106
+
107
+ restore_graphics_state
108
+ end
109
+
110
+ # @param [Hash] styles
111
+ # @return [Hash, nil]
112
+ def build_stroke_styles(styles)
113
+ color = styles[:stroke]
114
+ width = styles[:stroke_width]
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
+ }
123
+ end
124
+
125
+ # @param [Hash] styles
126
+ # @return [Hash, nil]
127
+ def build_fill_styles(styles)
128
+ color = styles[:fill]
129
+ return nil unless color && color != 'none'
130
+
131
+ { color: parse_color(color) }
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require 'base64'
5
+ require 'digest/md5'
6
+
7
+ module Thinreports
8
+ module BasicReport
9
+ module Generator
10
+ class PDF
11
+ module Graphics
12
+ # @param [String, IO] filename_or_io
13
+ # @param [Numeric, Strng] x
14
+ # @param [Numeric, Strng] y
15
+ # @param [Numeric, Strng] w
16
+ # @param [Numeric, Strng] h
17
+ def image(filename_or_io, x, y, w, h)
18
+ w, h = s2f(w, h)
19
+ pdf.image(filename_or_io, at: pos(x, y), width: w, height: h)
20
+ end
21
+
22
+ # @param [String] base64_data
23
+ # @param [Numeric, Strng] x
24
+ # @param [Numeric, Strng] y
25
+ # @param [Numeric, Strng] w
26
+ # @param [Numeric, Strng] h
27
+ def base64image(base64_data, x, y, w, h)
28
+ image_data = Base64.decode64(base64_data)
29
+ image_id = Digest::MD5.hexdigest(base64_data)
30
+ image_path = create_temp_imagefile(image_id, image_data)
31
+
32
+ image(image_path, x, y, w, h)
33
+ end
34
+
35
+ # @param [String, IO] filename_or_io
36
+ # @param [Numeric, Strng] x
37
+ # @param [Numeric, Strng] y
38
+ # @param [Numeric, Strng] w
39
+ # @param [Numeric, Strng] h
40
+ # @param [Hash] options
41
+ # @option options [:left, :center, :right] :position_x (:left)
42
+ # @option options [:top, :center, :bottom] :position_y (:top)
43
+ # @option options [Numeric] :offset_x
44
+ # @option options [Numeric] :offset_y
45
+ def image_box(filename_or_io, x, y, w, h, options = {})
46
+ w, h = s2f(w, h)
47
+
48
+ computed_position = pos(
49
+ x + (options[:offset_x] || 0),
50
+ y + (options[:offset_y] || 0)
51
+ )
52
+ pdf.bounding_box(computed_position, width: w, height: h) do
53
+ pdf.image(
54
+ filename_or_io,
55
+ position: options[:position_x] || :left,
56
+ vposition: options[:position_y] || :top,
57
+ auto_fit: [w, h]
58
+ )
59
+ end
60
+ end
61
+
62
+ def image_dimensions(filename_or_io, x, y, w, h, options = {})
63
+ w, h = s2f(w, h)
64
+ # XXX: Calling @private method
65
+ _pdf_obj, info = pdf.build_image_object(filename_or_io)
66
+ info.calc_image_dimensions(
67
+ position: options[:position_x] || :left,
68
+ vposition: options[:position_y] || :top,
69
+ auto_fit: [w, h]
70
+ )
71
+ end
72
+
73
+ def clean_temp_images
74
+ temp_image_registry.each_value(&:close!)
75
+ temp_image_registry.clear
76
+ end
77
+
78
+ def temp_image_registry
79
+ @temp_image_registry ||= {}
80
+ end
81
+
82
+ # @param [String] image_id
83
+ # @param [String] image_data
84
+ # @return [String] Path to imagefile
85
+ def create_temp_imagefile(image_id, image_data)
86
+ temp_image_registry[image_id] ||= begin
87
+ file = Tempfile.new('temp-image')
88
+ file.binmode
89
+ file.write(image_data)
90
+ file.open
91
+ file
92
+ end
93
+ temp_image_registry[image_id].path
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module BasicReport
5
+ module Generator
6
+ class PDF
7
+ module Graphics
8
+ # @param [String] content
9
+ # @param [Numeric, String] x
10
+ # @param [Numeric, String] y
11
+ # @param [Numeric, String] w
12
+ # @param [Numeric, String] h
13
+ # @param [Hash] attrs ({})
14
+ # @option attrs [String] :font
15
+ # @option attrs [Numeric, String] :size
16
+ # @option attrs [String] :color
17
+ # @option attrs [Array<:bold, :italic, :underline, :strikethrough>]
18
+ # :styles (nil)
19
+ # @option attrs [:left, :center, :right] :align (:left)
20
+ # @option attrs [:top, :center, :bottom] :valign (:top)
21
+ # @option attrs [Numeric, String] :line_height The total height of an text line.
22
+ # @option attrs [Numeric, String] :letter_spacing
23
+ # @option attrs [Boolean] :single (false)
24
+ # @option attrs [:trancate, :shrink_to_fit, :expand] :overflow (:trancate)
25
+ # @option attrs [:none, :break_word] :word_wrap (:none)
26
+ def text_box(content, x, y, w, h, attrs = {}, &block)
27
+ w, h = s2f(w, h)
28
+
29
+ box_attrs = text_box_attrs(
30
+ x, y, w, h,
31
+ single: attrs.delete(:single),
32
+ overflow: attrs[:overflow]
33
+ )
34
+
35
+ # Do not break by word unless :word_wrap is :break_word
36
+ content = text_without_line_wrap(content) if attrs[:word_wrap] == :none
37
+
38
+ with_text_styles(attrs) do |built_attrs, font_styles|
39
+ if block
40
+ block.call [{ text: content, styles: font_styles }],
41
+ built_attrs.merge(box_attrs)
42
+ else
43
+ pdf.formatted_text_box(
44
+ [{ text: content, styles: font_styles }],
45
+ built_attrs.merge(box_attrs)
46
+ )
47
+ end
48
+ end
49
+ rescue Prawn::Errors::CannotFit
50
+ # Nothing to do.
51
+ #
52
+ # When the area is too small compared
53
+ # with the content and the style of the text.
54
+ # (See prawn/core/text/formatted/line_wrap.rb#L185)
55
+ end
56
+
57
+ # @see #text_box
58
+ def text(content, x, y, w, h, attrs = {})
59
+ # Set the :overflow property to :shirink_to_fit.
60
+ text_box(content, x, y, w, h, { overflow: :shirink_to_fit }.merge(attrs))
61
+ end
62
+
63
+ private
64
+
65
+ # @param x (see #text_box)
66
+ # @param y (see #text_box)
67
+ # @param w (see #text_box)
68
+ # @param h (see #text_box)
69
+ # @param [Hash] states
70
+ # @option states [Boolean] :single
71
+ # @option states [Symbold] :overflow
72
+ # @return [Hash]
73
+ def text_box_attrs(x, y, w, h, states = {})
74
+ attrs = {
75
+ at: pos(x, y),
76
+ width: s2f(w)
77
+ }
78
+ if states[:single]
79
+ states[:overflow] != :expand ? attrs.merge(single_line: true) : attrs
80
+ else
81
+ attrs.merge(height: s2f(h))
82
+ end
83
+ end
84
+
85
+ # @param attrs (see #text)
86
+ # @yield [built_attrs, font_styles]
87
+ # @yieldparam [Hash] built_attrs The finalized attributes.
88
+ # @yieldparam [Array] font_styles The finalized styles.
89
+ def with_text_styles(attrs, &block)
90
+ # When no color is given, do not draw.
91
+ return unless attrs.key?(:color) && attrs[:color] != 'none'
92
+
93
+ save_graphics_state
94
+
95
+ fontinfo = {
96
+ name: attrs.delete(:font).to_s,
97
+ color: parse_color(attrs.delete(:color)),
98
+ size: s2f(attrs.delete(:size))
99
+ }
100
+
101
+ # Add the specified value to :leading option.
102
+ line_height = attrs.delete(:line_height)
103
+ if line_height
104
+ attrs[:leading] = text_line_leading(
105
+ s2f(line_height),
106
+ name: fontinfo[:name],
107
+ size: fontinfo[:size]
108
+ )
109
+ end
110
+
111
+ # Set the :character_spacing option.
112
+ spacing = attrs.delete(:letter_spacing)
113
+ attrs[:character_spacing] = s2f(spacing) if spacing
114
+
115
+ # Or... with_font_styles(attrs, fontinfo, &block)
116
+ with_font_styles(attrs, fontinfo) do |modified_attrs, styles|
117
+ block.call(modified_attrs, styles)
118
+ end
119
+
120
+ restore_graphics_state
121
+ end
122
+
123
+ # @param [Numeric] line_height
124
+ # @param [Hash] font
125
+ # @option font [String] :name Name of font.
126
+ # @option font [Numeric] :size Size of font.
127
+ # @return [Numeric]
128
+ def text_line_leading(line_height, font)
129
+ line_height - pdf.font(font[:name], size: font[:size]).height
130
+ end
131
+
132
+ # @param [String] content
133
+ # @return [String]
134
+ def text_without_line_wrap(content)
135
+ content.gsub(/ /, Prawn::Text::NBSP)
136
+ end
137
+
138
+ # @param [Hash] attrs
139
+ # @param [Hash] font
140
+ # @option font [String] :color
141
+ # @option font [Numeric] :size
142
+ # @option font [String] :name
143
+ # @yield [attributes, styles]
144
+ # @yieldparam [Hash] modified_attrs
145
+ # @yieldparam [Array] styles
146
+ def with_font_styles(attrs, font, &block)
147
+ # Building font styles.
148
+ styles = attrs.delete(:styles)
149
+
150
+ if styles
151
+ manual, styles = styles.partition do |style|
152
+ %i[bold italic].include?(style) && !font_has_style?(font[:name], style)
153
+ end
154
+ end
155
+
156
+ # Emulate bold style.
157
+ if manual && manual.include?(:bold)
158
+ pdf.stroke_color(font[:color])
159
+ pdf.line_width(font[:size] * 0.025)
160
+
161
+ # Change rendering mode to :fill_stroke.
162
+ attrs[:mode] = :fill_stroke
163
+ end
164
+
165
+ # Emulate italic style.
166
+ if manual && manual.include?(:italic)
167
+ # FIXME
168
+ # pdf.transformation_matrix(1, 0, 0.26, 1, 0, 0)
169
+ end
170
+
171
+ pdf.font(font[:name], size: font[:size]) do
172
+ pdf.fill_color(font[:color])
173
+ block.call(attrs, styles || [])
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module BasicReport
5
+ module Generator
6
+ class PDF
7
+ module Graphics
8
+ BASE_LINE_WIDTH = 0.9
9
+
10
+ private
11
+
12
+ # Change the default graphic states defined by Prawn.
13
+ def setup_custom_graphic_states
14
+ pdf.line_width(BASE_LINE_WIDTH)
15
+ end
16
+
17
+ # @param [Numeric] width
18
+ def line_width(width)
19
+ pdf.line_width(width * BASE_LINE_WIDTH)
20
+ end
21
+
22
+ # Delegate to Prawn::Document#save_graphic_state
23
+ # @see Prawn::Document#save_graphics_state
24
+ def save_graphics_state
25
+ pdf.save_graphics_state
26
+ end
27
+
28
+ # Delegate to Prawn::Document#restore_graphic_state
29
+ # @see Prawn::Document#restore_graphics_state
30
+ def restore_graphics_state
31
+ pdf.restore_graphics_state
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ require_relative 'graphics/attributes'
40
+ require_relative 'graphics/basic'
41
+ require_relative 'graphics/image'
42
+ require_relative 'graphics/text'
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thinreports
4
+ module BasicReport
5
+ module Generator
6
+ class PDF
7
+ module Page
8
+ JIS_SIZES = {
9
+ 'B4' => [728.5, 1031.8],
10
+ 'B5' => [515.9, 728.5]
11
+ }.freeze
12
+
13
+ # @param [Thinreports::BasicReport::Layout::Format] format
14
+ def start_new_page(format)
15
+ format_id =
16
+ if change_page_format?(format)
17
+ pdf.start_new_page(new_basic_page_options(format))
18
+ @current_page_format = format
19
+
20
+ create_format_stamp(format) unless format_stamp_registry.include?(format.identifier)
21
+ format.identifier
22
+ else
23
+ pdf.start_new_page(new_basic_page_options(current_page_format))
24
+ current_page_format.identifier
25
+ end
26
+
27
+ stamp(format_id.to_s)
28
+ end
29
+
30
+ def start_new_page_for_section_report(format)
31
+ @current_page_format = format
32
+ pdf.start_new_page(new_basic_page_options(current_page_format).merge(
33
+ top_margin: current_page_format.page_margin[0],
34
+ bottom_margin: current_page_format.page_margin[2]
35
+ ))
36
+ end
37
+
38
+ def max_content_height
39
+ pdf.margin_box.height
40
+ end
41
+
42
+ def add_blank_page
43
+ pdf.start_new_page(pdf.page_count.zero? ? { size: 'A4' } : {})
44
+ end
45
+
46
+ private
47
+
48
+ # @return [Thinreports::BasicReport::Layout::Format]
49
+ attr_reader :current_page_format
50
+
51
+ # @param [Thinreports::BasicReport::Layout::Format] new_format
52
+ # @return [Boolean]
53
+ def change_page_format?(new_format)
54
+ !current_page_format ||
55
+ current_page_format.identifier != new_format.identifier
56
+ end
57
+
58
+ # @param [Thinreports::BasicReport::Layout::Format] format
59
+ def create_format_stamp(format)
60
+ create_stamp(format.identifier.to_s) do
61
+ draw_template_items(format.attributes['items'])
62
+ end
63
+ format_stamp_registry << format.identifier
64
+ end
65
+
66
+ # @return [Array]
67
+ def format_stamp_registry
68
+ @format_stamp_registry ||= []
69
+ end
70
+
71
+ # @param [Thinreports::BasicReport::Layout::Format] format
72
+ # @return [Hash]
73
+ def new_basic_page_options(format)
74
+ options = { layout: format.page_orientation.to_sym }
75
+
76
+ options[:size] =
77
+ if format.user_paper_type?
78
+ [format.page_width.to_f, format.page_height.to_f]
79
+ else
80
+ case format.page_paper_type
81
+ # Convert B4(5)_ISO to B4(5)
82
+ when 'B4_ISO', 'B5_ISO'
83
+ format.page_paper_type.delete('_ISO')
84
+ when 'B4', 'B5'
85
+ JIS_SIZES[format.page_paper_type]
86
+ else
87
+ format.page_paper_type
88
+ end
89
+ end
90
+ options
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end