Skip to content

Commit 56baa11

Browse files
authored
Merge pull request #30 from blocknotes/v0.5.0
v0.5.0
2 parents 8c53801 + 8aa4407 commit 56baa11

63 files changed

Lines changed: 993 additions & 515 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.rubocop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ AllCops:
1515
- vendor/**/*
1616
NewCops: enable
1717

18+
Lint/UnusedMethodArgument:
19+
Exclude:
20+
- lib/prawn_html/utils.rb
21+
1822
Naming/FileName:
1923
Exclude:
2024
- lib/prawn-html.rb
@@ -25,7 +29,7 @@ Naming/MethodParameterName:
2529

2630
RSpec/ExampleLength:
2731
# default: 5
28-
Max: 10
32+
Max: 15
2933

3034
RSpec/MultipleMemoizedHelpers:
3135
# default: 5

examples/headings.pdf

-18 Bytes
Binary file not shown.

examples/misc_elements.pdf

32 Bytes
Binary file not shown.

examples/random_content.pdf

-445 Bytes
Binary file not shown.

examples/styles.pdf

-24 Bytes
Binary file not shown.

lib/prawn-html.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
module PrawnHtml
4-
PX = 0.66 # conversion constant for pixel sixes
4+
PX = 0.6 # conversion constant for pixel sixes
55

66
COLORS = {
77
'aliceblue' => 'f0f8ff',

lib/prawn_html/attributes.rb

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ class Attributes < OpenStruct
77
attr_reader :styles
88

99
STYLES_APPLY = {
10-
block: %i[align leading left margin_left padding_left position top],
10+
block: %i[align bottom leading left margin_left padding_left position right top],
1111
tag_close: %i[margin_bottom padding_bottom break_after],
1212
tag_open: %i[margin_top padding_top break_before],
13-
text_node: %i[background callback character_spacing color font link list_style_type size styles white_space]
13+
text_node: %i[callback character_spacing color font link list_style_type size styles white_space]
1414
}.freeze
1515

1616
STYLES_LIST = {
1717
# text node styles
18-
'background' => { key: :background, set: :convert_color },
19-
'callback' => { key: :callback, set: :copy_value },
18+
'background' => { key: :callback, set: :callback_background },
2019
'color' => { key: :color, set: :convert_color },
2120
'font-family' => { key: :font, set: :unquote },
2221
'font-size' => { key: :size, set: :convert_size },
@@ -25,7 +24,7 @@ class Attributes < OpenStruct
2524
'href' => { key: :link, set: :copy_value },
2625
'letter-spacing' => { key: :character_spacing, set: :convert_float },
2726
'list-style-type' => { key: :list_style_type, set: :unquote },
28-
'text-decoration' => { key: :styles, set: :append_styles },
27+
'text-decoration' => { key: :styles, set: :append_text_decoration },
2928
'vertical-align' => { key: :styles, set: :append_styles },
3029
'white-space' => { key: :white_space, set: :convert_symbol },
3130
# tag opening styles
@@ -37,13 +36,15 @@ class Attributes < OpenStruct
3736
'margin-bottom' => { key: :margin_bottom, set: :convert_size },
3837
'padding-bottom' => { key: :padding_bottom, set: :convert_size },
3938
# block styles
40-
'left' => { key: :left, set: :convert_size },
39+
'bottom' => { key: :bottom, set: :convert_size, options: :height },
40+
'left' => { key: :left, set: :convert_size, options: :width },
4141
'line-height' => { key: :leading, set: :convert_size },
4242
'margin-left' => { key: :margin_left, set: :convert_size },
4343
'padding-left' => { key: :padding_left, set: :convert_size },
4444
'position' => { key: :position, set: :convert_symbol },
45+
'right' => { key: :right, set: :convert_size, options: :width },
4546
'text-align' => { key: :align, set: :convert_symbol },
46-
'top' => { key: :top, set: :convert_size }
47+
'top' => { key: :top, set: :convert_size, options: :height }
4748
}.freeze
4849

4950
STYLES_MERGE = %i[margin_left padding_left].freeze
@@ -67,9 +68,10 @@ def data
6768
# Merge text styles
6869
#
6970
# @param text_styles [String] styles to parse and process
70-
def merge_text_styles!(text_styles)
71+
# @param options [Hash] options (container width/height/etc.)
72+
def merge_text_styles!(text_styles, options: {})
7173
hash_styles = Attributes.parse_styles(text_styles)
72-
process_styles(hash_styles) unless hash_styles.empty?
74+
process_styles(hash_styles, options: options) unless hash_styles.empty?
7375
end
7476

7577
class << self
@@ -100,21 +102,33 @@ def parse_styles(styles)
100102

101103
private
102104

103-
def apply_rule!(result, rule, value)
104-
return unless rule
105+
def process_styles(hash_styles, options:)
106+
hash_styles.each do |key, value|
107+
rule = evaluate_rule(key, value)
108+
apply_rule!(merged_styles: @styles, rule: rule, value: value, options: options)
109+
end
110+
@styles
111+
end
105112

106-
if rule[:set] == :append_styles
107-
(result[rule[:key]] ||= []) << Utils.normalize_style(value)
108-
else
109-
result[rule[:key]] = Utils.send(rule[:set], value)
113+
def evaluate_rule(rule_key, attr_value)
114+
rule = STYLES_LIST[rule_key]
115+
if rule && rule[:set] == :append_text_decoration
116+
return { key: :callback, set: :callback_strike_through } if attr_value == 'line-through'
117+
118+
return { key: :styles, set: :append_styles }
110119
end
120+
rule
111121
end
112122

113-
def process_styles(hash_styles)
114-
hash_styles.each do |key, value|
115-
apply_rule!(@styles, STYLES_LIST[key], value)
123+
def apply_rule!(merged_styles:, rule:, value:, options:)
124+
return unless rule
125+
126+
if rule[:set] == :append_styles
127+
(merged_styles[rule[:key]] ||= []) << Utils.normalize_style(value)
128+
else
129+
opts = rule[:options] ? options[rule[:options]] : nil
130+
merged_styles[rule[:key]] = Utils.send(rule[:set], value, options: opts)
116131
end
117-
@styles
118132
end
119133
end
120134
end
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
module PrawnHtml
44
module Callbacks
5-
class Highlight
5+
class Background
66
DEF_HIGHLIGHT = 'ffff00'
77

8-
def initialize(pdf, item)
8+
def initialize(pdf, color = nil)
99
@pdf = pdf
10-
@color = item.delete(:background) || DEF_HIGHLIGHT
10+
@color = color || DEF_HIGHLIGHT
1111
end
1212

1313
def render_behind(fragment)

lib/prawn_html/context.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22

33
module PrawnHtml
44
class Context < Array
5-
DEF_FONT_SIZE = 10.3
5+
DEF_FONT_SIZE = 16 * PX
66

7-
attr_accessor :last_margin, :last_text_node
7+
attr_reader :previous_tag
8+
attr_accessor :last_text_node
89

910
# Init the Context
1011
def initialize(*_args)
1112
super
12-
@last_margin = 0
1313
@last_text_node = false
14+
@merged_styles = nil
15+
@previous_tag = nil
1416
end
1517

1618
# Add an element to the context
@@ -25,6 +27,7 @@ def add(element)
2527
element.parent = last
2628
push(element)
2729
element.on_context_add(self) if element.respond_to?(:on_context_add)
30+
@merged_styles = nil
2831
self
2932
end
3033

@@ -49,11 +52,21 @@ def block_styles
4952
# Merge the context styles for text nodes
5053
#
5154
# @return [Hash] the hash of merged styles
52-
def text_node_styles
53-
each_with_object(base_styles) do |element, res|
54-
evaluate_element_styles(element, res)
55-
element.update_styles(res) if element.respond_to?(:update_styles)
56-
end
55+
def merged_styles
56+
@merged_styles ||=
57+
each_with_object(base_styles) do |element, res|
58+
evaluate_element_styles(element, res)
59+
element.update_styles(res) if element.respond_to?(:update_styles)
60+
end
61+
end
62+
63+
# Remove the last element from the context
64+
def remove_last
65+
last.on_context_remove(self) if last.respond_to?(:on_context_remove)
66+
@merged_styles = nil
67+
@last_text_node = false
68+
@previous_tag = last.tag
69+
pop
5770
end
5871

5972
private

lib/prawn_html/document_renderer.rb

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class DocumentRenderer
1111
def initialize(pdf)
1212
@buffer = []
1313
@context = Context.new
14+
@last_margin = 0
1415
@pdf = pdf
1516
end
1617

@@ -20,8 +21,7 @@ def initialize(pdf)
2021
def on_tag_close(element)
2122
render_if_needed(element)
2223
apply_tag_close_styles(element)
23-
context.last_text_node = false
24-
context.pop
24+
context.remove_last
2525
end
2626

2727
# On tag open callback
@@ -35,8 +35,9 @@ def on_tag_open(tag_name, attributes:, element_styles: '')
3535
tag_class = Tag.class_for(tag_name)
3636
return unless tag_class
3737

38-
tag_class.new(tag_name, attributes: attributes, element_styles: element_styles).tap do |element|
39-
setup_element(element)
38+
options = { width: pdf.page_width, height: pdf.page_height }
39+
tag_class.new(tag_name, attributes: attributes, options: options).tap do |element|
40+
setup_element(element, element_styles: element_styles)
4041
end
4142
end
4243

@@ -48,7 +49,7 @@ def on_tag_open(tag_name, attributes:, element_styles: '')
4849
def on_text_node(content)
4950
return if content.match?(/\A\s*\Z/)
5051

51-
buffer << context.text_node_styles.merge(text: prepare_text(content))
52+
buffer << context.merged_styles.merge(text: prepare_text(content))
5253
context.last_text_node = true
5354
nil
5455
end
@@ -59,19 +60,20 @@ def render
5960

6061
output_content(buffer.dup, context.block_styles)
6162
buffer.clear
62-
context.last_margin = 0
63+
@last_margin = 0
6364
end
6465

6566
alias_method :flush, :render
6667

6768
private
6869

69-
attr_reader :buffer, :context, :pdf
70+
attr_reader :buffer, :context, :last_margin, :pdf
7071

71-
def setup_element(element)
72+
def setup_element(element, element_styles:)
7273
add_space_if_needed unless render_if_needed(element)
73-
apply_tag_open_styles(element)
7474
context.add(element)
75+
element.process_styles(element_styles: element_styles)
76+
apply_tag_open_styles(element)
7577
element.custom_render(pdf, context) if element.respond_to?(:custom_render)
7678
end
7779

@@ -89,14 +91,14 @@ def render_if_needed(element)
8991

9092
def apply_tag_close_styles(element)
9193
tag_styles = element.tag_close_styles
92-
context.last_margin = tag_styles[:margin_bottom].to_f
93-
pdf.advance_cursor(context.last_margin + tag_styles[:padding_bottom].to_f)
94+
@last_margin = tag_styles[:margin_bottom].to_f
95+
pdf.advance_cursor(last_margin + tag_styles[:padding_bottom].to_f)
9496
pdf.start_new_page if tag_styles[:break_after]
9597
end
9698

9799
def apply_tag_open_styles(element)
98100
tag_styles = element.tag_open_styles
99-
move_down = (tag_styles[:margin_top].to_f - context.last_margin) + tag_styles[:padding_top].to_f
101+
move_down = (tag_styles[:margin_top].to_f - last_margin) + tag_styles[:padding_top].to_f
100102
pdf.advance_cursor(move_down) if move_down > 0
101103
pdf.start_new_page if tag_styles[:break_before]
102104
end
@@ -111,24 +113,41 @@ def prepare_text(content)
111113
def output_content(buffer, block_styles)
112114
apply_callbacks(buffer)
113115
left_indent = block_styles[:margin_left].to_f + block_styles[:padding_left].to_f
114-
options = block_styles.slice(:align, :leading, :mode, :padding_left)
115-
options[:indent_paragraphs] = left_indent if left_indent > 0
116-
pdf.puts(buffer, options, bounding_box: bounds(block_styles))
116+
options = block_styles.slice(:align, :indent_paragraphs, :leading, :mode, :padding_left)
117+
options[:leading] = adjust_leading(buffer, options[:leading])
118+
pdf.puts(buffer, options, bounding_box: bounds(buffer, options, block_styles), left_indent: left_indent)
117119
end
118120

119121
def apply_callbacks(buffer)
120122
buffer.select { |item| item[:callback] }.each do |item|
121-
callback = Tag::CALLBACKS[item[:callback]]
122-
item[:callback] = callback.new(pdf, item)
123+
callback, arg = item[:callback]
124+
callback_class = Tag::CALLBACKS[callback]
125+
item[:callback] = callback_class.new(pdf, arg)
123126
end
124127
end
125128

126-
def bounds(block_styles)
129+
def adjust_leading(buffer, leading)
130+
return leading if leading
131+
132+
(buffer.map { |item| item[:size] || Context::DEF_FONT_SIZE }.max * 0.055).round(4)
133+
end
134+
135+
def bounds(buffer, options, block_styles)
127136
return unless block_styles[:position] == :absolute
128137

129-
y = pdf.bounds.height - (block_styles[:top] || 0)
130-
w = pdf.bounds.width - (block_styles[:left] || 0)
131-
[[block_styles[:left] || 0, y], { width: w }]
138+
x = if block_styles.include?(:right)
139+
x1 = pdf.calc_buffer_width(buffer) + block_styles[:right]
140+
x1 < pdf.page_width ? (pdf.page_width - x1) : 0
141+
else
142+
block_styles[:left] || 0
143+
end
144+
y = if block_styles.include?(:bottom)
145+
pdf.calc_buffer_height(buffer, options) + block_styles[:bottom]
146+
else
147+
pdf.page_height - (block_styles[:top] || 0)
148+
end
149+
150+
[[x, y], { width: pdf.page_width - x }]
132151
end
133152
end
134153
end

0 commit comments

Comments
 (0)