prawn-table-html 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +236 -0
  4. data/lib/prawn-html.rb +180 -0
  5. data/lib/prawn_html/attributes.rb +166 -0
  6. data/lib/prawn_html/callbacks/background.rb +19 -0
  7. data/lib/prawn_html/callbacks/strike_through.rb +18 -0
  8. data/lib/prawn_html/context.rb +100 -0
  9. data/lib/prawn_html/document_renderer.rb +172 -0
  10. data/lib/prawn_html/html_parser.rb +104 -0
  11. data/lib/prawn_html/instance.rb +18 -0
  12. data/lib/prawn_html/pdf_wrapper.rb +145 -0
  13. data/lib/prawn_html/tag.rb +93 -0
  14. data/lib/prawn_html/tags/a.rb +20 -0
  15. data/lib/prawn_html/tags/b.rb +13 -0
  16. data/lib/prawn_html/tags/blockquote.rb +25 -0
  17. data/lib/prawn_html/tags/body.rb +13 -0
  18. data/lib/prawn_html/tags/br.rb +21 -0
  19. data/lib/prawn_html/tags/code.rb +13 -0
  20. data/lib/prawn_html/tags/col.rb +37 -0
  21. data/lib/prawn_html/tags/colgroup.rb +13 -0
  22. data/lib/prawn_html/tags/del.rb +13 -0
  23. data/lib/prawn_html/tags/div.rb +13 -0
  24. data/lib/prawn_html/tags/h.rb +49 -0
  25. data/lib/prawn_html/tags/hr.rb +39 -0
  26. data/lib/prawn_html/tags/i.rb +13 -0
  27. data/lib/prawn_html/tags/img.rb +31 -0
  28. data/lib/prawn_html/tags/li.rb +39 -0
  29. data/lib/prawn_html/tags/mark.rb +13 -0
  30. data/lib/prawn_html/tags/ol.rb +43 -0
  31. data/lib/prawn_html/tags/p.rb +23 -0
  32. data/lib/prawn_html/tags/pre.rb +25 -0
  33. data/lib/prawn_html/tags/small.rb +15 -0
  34. data/lib/prawn_html/tags/span.rb +9 -0
  35. data/lib/prawn_html/tags/sub.rb +13 -0
  36. data/lib/prawn_html/tags/sup.rb +13 -0
  37. data/lib/prawn_html/tags/table.rb +53 -0
  38. data/lib/prawn_html/tags/tbody.rb +13 -0
  39. data/lib/prawn_html/tags/td.rb +43 -0
  40. data/lib/prawn_html/tags/th.rb +43 -0
  41. data/lib/prawn_html/tags/tr.rb +37 -0
  42. data/lib/prawn_html/tags/u.rb +13 -0
  43. data/lib/prawn_html/tags/ul.rb +40 -0
  44. data/lib/prawn_html/utils.rb +139 -0
  45. data/lib/prawn_html/version.rb +5 -0
  46. metadata +131 -0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Br < Tag
6
+ ELEMENTS = [:br].freeze
7
+
8
+ BR_SPACING = Utils.convert_size('17')
9
+
10
+ def block?
11
+ true
12
+ end
13
+
14
+ def custom_render(pdf, context)
15
+ return if context.last_text_node || !context.previous_tag.is_a?(Br)
16
+
17
+ pdf.advance_cursor(BR_SPACING)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Code < Tag
6
+ ELEMENTS = [:code].freeze
7
+
8
+ def tag_styles
9
+ 'font-family: Courier'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Col < Tag
6
+ ELEMENTS = [:col].freeze
7
+
8
+ def on_context_add(_context)
9
+ table_parent = find_parent_of_type(PrawnHtml::Tags::Table)
10
+ return unless table_parent
11
+
12
+ width = parse_width
13
+ table_parent.add_col_width(width) if width
14
+ end
15
+
16
+ private
17
+
18
+ def find_parent_of_type(klass)
19
+ p = parent
20
+ p = p.parent while p && !p.is_a?(klass)
21
+ p
22
+ end
23
+
24
+ def parse_width
25
+ return attrs[:colwidth].to_f if attrs[:colwidth]
26
+
27
+ return nil if attrs[:style].include?('min-width:')
28
+
29
+ if attrs[:style] && attrs[:style] =~ /width:\s*(\d+)px/i
30
+ return Regexp.last_match(1).to_f
31
+ end
32
+
33
+ nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Colgroup < Tag
6
+ ELEMENTS = [:colgroup].freeze
7
+
8
+ def block?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Del < Tag
6
+ ELEMENTS = [:del, :s].freeze
7
+
8
+ def tag_styles
9
+ 'text-decoration: line-through'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Div < Tag
6
+ ELEMENTS = [:div].freeze
7
+
8
+ def block?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class H < Tag
6
+ ELEMENTS = [:h1, :h2, :h3, :h4, :h5, :h6].freeze
7
+
8
+ MARGINS_TOP = {
9
+ h1: 25,
10
+ h2: 20.5,
11
+ h3: 18,
12
+ h4: 21.2,
13
+ h5: 21.2,
14
+ h6: 22.8
15
+ }.freeze
16
+
17
+ MARGINS_BOTTOM = {
18
+ h1: 15.8,
19
+ h2: 15.8,
20
+ h3: 15.8,
21
+ h4: 20,
22
+ h5: 21.4,
23
+ h6: 24.8
24
+ }.freeze
25
+
26
+ SIZES = {
27
+ h1: 31.5,
28
+ h2: 24,
29
+ h3: 18.7,
30
+ h4: 15.7,
31
+ h5: 13,
32
+ h6: 10.8
33
+ }.freeze
34
+
35
+ def block?
36
+ true
37
+ end
38
+
39
+ def tag_styles
40
+ <<~STYLES
41
+ font-size: #{SIZES[tag]}px;
42
+ font-weight: bold;
43
+ margin-bottom: #{MARGINS_BOTTOM[tag]}px;
44
+ margin-top: #{MARGINS_TOP[tag]}px;
45
+ STYLES
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Hr < Tag
6
+ ELEMENTS = [:hr].freeze
7
+
8
+ MARGIN_BOTTOM = 12
9
+ MARGIN_TOP = 6
10
+
11
+ def block?
12
+ true
13
+ end
14
+
15
+ def custom_render(pdf, _context)
16
+ dash = attrs.data.include?('dash') ? parse_dash_value(attrs.data['dash']) : nil
17
+ pdf.horizontal_rule(color: attrs.styles[:color], dash: dash)
18
+ end
19
+
20
+ def tag_styles
21
+ <<~STYLES
22
+ margin-bottom: #{MARGIN_BOTTOM}px;
23
+ margin-top: #{MARGIN_TOP}px;
24
+ STYLES
25
+ end
26
+
27
+ private
28
+
29
+ def parse_dash_value(dash_string)
30
+ if dash_string.match? /\A\d+\Z/
31
+ dash_string.to_i
32
+ else
33
+ dash_array = dash_string.split(',')
34
+ dash_array.map(&:to_i) if dash_array.any?
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class I < Tag
6
+ ELEMENTS = [:i, :em].freeze
7
+
8
+ def tag_styles
9
+ 'font-style: italic'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Img < Tag
6
+ ELEMENTS = [:img].freeze
7
+
8
+ def block?
9
+ true
10
+ end
11
+
12
+ def custom_render(pdf, context)
13
+ parsed_styles = Attributes.parse_styles(attrs.style)
14
+ block_styles = context.block_styles
15
+ evaluated_styles = adjust_styles(pdf, block_styles.merge(parsed_styles))
16
+ pdf.image(@attrs.src, evaluated_styles)
17
+ end
18
+
19
+ private
20
+
21
+ def adjust_styles(pdf, img_styles)
22
+ {}.tap do |result|
23
+ w, h = img_styles['width'], img_styles['height']
24
+ result[:width] = Utils.convert_size(w, options: pdf.page_width) if w
25
+ result[:height] = Utils.convert_size(h, options: pdf.page_height) if h
26
+ result[:position] = img_styles[:align] if %i[left center right].include?(img_styles[:align])
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Li < Tag
6
+ ELEMENTS = [:li].freeze
7
+
8
+ INDENT_OL = -12
9
+ INDENT_UL = -6
10
+
11
+ def block?
12
+ true
13
+ end
14
+
15
+ def before_content
16
+ return if @before_content_once
17
+
18
+ @before_content_once = @counter ? "#{@counter}. " : "#{@symbol} "
19
+ end
20
+
21
+ def block_styles
22
+ super.tap do |bs|
23
+ bs[:indent_paragraphs] = @indent
24
+ end
25
+ end
26
+
27
+ def on_context_add(_context)
28
+ case parent.class.to_s
29
+ when 'PrawnHtml::Tags::Ol'
30
+ @indent = INDENT_OL
31
+ @counter = (parent.counter += 1)
32
+ when 'PrawnHtml::Tags::Ul'
33
+ @indent = INDENT_UL
34
+ @symbol = parent.styles[:list_style_type] || '&bullet;'
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Mark < Tag
6
+ ELEMENTS = [:mark].freeze
7
+
8
+ def tag_styles
9
+ 'background: #ff0'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Ol < Tag
6
+ ELEMENTS = [:ol].freeze
7
+
8
+ MARGIN_TOP = 15
9
+ MARGIN_LEFT = 40
10
+ MARGIN_BOTTOM = 15
11
+
12
+ attr_accessor :counter
13
+
14
+ def initialize(tag, attributes: {}, options: {})
15
+ super
16
+ @counter = 0
17
+ @first_level = false
18
+ end
19
+
20
+ def block?
21
+ true
22
+ end
23
+
24
+ def on_context_add(context)
25
+ return if context.map(&:tag).count { |el| el == :ol } > 1
26
+
27
+ @first_level = true
28
+ end
29
+
30
+ def tag_styles
31
+ if @first_level
32
+ <<~STYLES
33
+ margin-top: #{MARGIN_TOP}px;
34
+ margin-left: #{MARGIN_LEFT}px;
35
+ margin-bottom: #{MARGIN_BOTTOM}px;
36
+ STYLES
37
+ else
38
+ "margin-left: #{MARGIN_LEFT}px"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class P < Tag
6
+ ELEMENTS = [:p].freeze
7
+
8
+ MARGIN_BOTTOM = 12.5
9
+ MARGIN_TOP = 12.5
10
+
11
+ def block?
12
+ true
13
+ end
14
+
15
+ def tag_styles
16
+ <<~STYLES
17
+ margin-bottom: #{MARGIN_BOTTOM}px;
18
+ margin-top: #{MARGIN_TOP}px;
19
+ STYLES
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Pre < Tag
6
+ ELEMENTS = [:pre].freeze
7
+
8
+ MARGIN_BOTTOM = 14
9
+ MARGIN_TOP = 14
10
+
11
+ def block?
12
+ true
13
+ end
14
+
15
+ def tag_styles
16
+ <<~STYLES
17
+ font-family: Courier;
18
+ margin-bottom: #{MARGIN_BOTTOM}px;
19
+ margin-top: #{MARGIN_TOP}px;
20
+ white-space: pre;
21
+ STYLES
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Small < Tag
6
+ ELEMENTS = [:small].freeze
7
+
8
+ def update_styles(context_styles)
9
+ size = (context_styles[:size] || Context::DEFAULT_STYLES[:size]) * 0.85
10
+ context_styles[:size] = size
11
+ super(context_styles)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Span < Tag
6
+ ELEMENTS = [:span].freeze
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Sub < Tag
6
+ ELEMENTS = [:sub].freeze
7
+
8
+ def tag_styles
9
+ 'vertical-align: sub'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Sup < Tag
6
+ ELEMENTS = [:sup].freeze
7
+
8
+ def tag_styles
9
+ 'vertical-align: super'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Table < Tag
6
+ ELEMENTS = [:table].freeze
7
+
8
+ def block?
9
+ true
10
+ puts "TABLE: I am a block, watch out for margin"
11
+ end
12
+
13
+ def custom_render(pdf, _context)
14
+ @pdf_wrapper = pdf
15
+ end
16
+
17
+ def on_context_add(_context)
18
+ @rows = []
19
+ @col_widths = []
20
+ end
21
+
22
+ def add_col_width(width)
23
+ @col_widths << width
24
+ end
25
+
26
+ def on_context_remove(_context)
27
+ return if @pdf_wrapper.nil?
28
+
29
+ cell_style = {
30
+ borders: [:top, :bottom, :left, :right],
31
+ border_width: 1,
32
+ border_color: '000000',
33
+ padding: 5,
34
+ inline_format: true
35
+ }
36
+
37
+ # @pdf_wrapper.pdf.move_up(100)
38
+ if @col_widths.any?
39
+ @pdf_wrapper.table(@rows, column_widths: @col_widths, cell_style: cell_style)
40
+ else
41
+ # When providing column_widths, Prawn::Table transform nil into [] and raise error. Better not to provide it at all.
42
+ @pdf_wrapper.table(@rows, cell_style: cell_style)
43
+ end
44
+
45
+ @rows = []
46
+ end
47
+
48
+ def add_row(row_data)
49
+ @rows << row_data
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Tbody < Tag
6
+ ELEMENTS = [:tbody].freeze
7
+
8
+ def block?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Td < Tag
6
+ ELEMENTS = [:td].freeze
7
+
8
+ def on_context_add(_context)
9
+ @text_content = +''
10
+ end
11
+
12
+ def on_text_node(content)
13
+ @text_content ||= +''
14
+ @text_content << content
15
+ end
16
+
17
+ def on_context_remove(_context)
18
+ row_parent = find_parent_of_type(PrawnHtml::Tags::Tr)
19
+ return unless row_parent
20
+
21
+ cell_data = { content: @text_content.strip }
22
+
23
+ cell_data[:colspan] = attrs[:colspan].to_i if attrs[:colspan]
24
+ cell_data[:rowspan] = attrs[:rowspan].to_i if attrs[:rowspan]
25
+
26
+ if attrs[:width]
27
+ cell_data[:width] = attrs[:width].to_i
28
+ end
29
+
30
+ row_parent.add_cell(cell_data)
31
+ @text_content = +''
32
+ end
33
+
34
+ private
35
+
36
+ def find_parent_of_type(klass)
37
+ p = parent
38
+ p = p.parent while p && !p.is_a?(klass)
39
+ p
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Th < Tag
6
+ ELEMENTS = [:th].freeze
7
+
8
+ def on_context_add(_context)
9
+ @text_content = +''
10
+ end
11
+
12
+ def on_text_node(content)
13
+ @text_content ||= +''
14
+ @text_content << content
15
+ end
16
+
17
+ def on_context_remove(_context)
18
+ row_parent = find_parent_of_type(PrawnHtml::Tags::Tr)
19
+ return unless row_parent
20
+
21
+ cell_data = { content: "<b>#{@text_content.strip}</b>" }
22
+
23
+ cell_data[:colspan] = attrs[:colspan].to_i if attrs[:colspan]
24
+ cell_data[:rowspan] = attrs[:rowspan].to_i if attrs[:rowspan]
25
+
26
+ if attrs[:width]
27
+ cell_data[:width] = attrs[:width].to_i
28
+ end
29
+
30
+ row_parent.add_cell(cell_data)
31
+ @text_content = +''
32
+ end
33
+
34
+ private
35
+
36
+ def find_parent_of_type(klass)
37
+ p = parent
38
+ p = p.parent while p && !p.is_a?(klass)
39
+ p
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Tr < Tag
6
+ ELEMENTS = [:tr].freeze
7
+
8
+ def block?
9
+ true
10
+ end
11
+
12
+ def on_context_add(_context)
13
+ @cells = []
14
+ end
15
+
16
+ def on_context_remove(_context)
17
+ table_parent = find_parent_of_type(PrawnHtml::Tags::Table)
18
+ return unless table_parent
19
+
20
+ table_parent.add_row(@cells)
21
+ @cells = []
22
+ end
23
+
24
+ def add_cell(content)
25
+ @cells << content
26
+ end
27
+
28
+ private
29
+
30
+ def find_parent_of_type(klass)
31
+ p = parent
32
+ p = p.parent while p && !p.is_a?(klass)
33
+ p
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class U < Tag
6
+ ELEMENTS = [:ins, :u].freeze
7
+
8
+ def tag_styles
9
+ 'text-decoration: underline'
10
+ end
11
+ end
12
+ end
13
+ end