Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c7d42d1
New CSS properties (for position absolute): bottom and right
blocknotes Aug 27, 2021
7e6fdff
Process tag styles outside the initialize
blocknotes Aug 28, 2021
e327e6b
Percent values for position attributes (top, left, bottom, right)
blocknotes Aug 29, 2021
1f6516c
Wrap PDF bounds in page_width and page_height
blocknotes Sep 1, 2021
ae23b23
Merge pull request #22 from blocknotes/improve-position-absolute
blocknotes Sep 1, 2021
c07d947
Move last_margin in DocumentRenderer
blocknotes Sep 2, 2021
985ab99
Wrap Context pop in remove_last
blocknotes Sep 2, 2021
9efc954
Rename text_node_styles in merged_styles and memoize the styles
blocknotes Sep 2, 2021
4892116
Merge pull request #25 from blocknotes/improve-context
blocknotes Sep 2, 2021
6146b5f
Bump to version 0.4.2
blocknotes Sep 3, 2021
0c3d8c8
Improve callbacks handling
blocknotes Sep 2, 2021
f918951
Rename Highlight callback to Background
blocknotes Sep 2, 2021
f7242db
Handle Del tag style using `text-decoration: line-through`
blocknotes Sep 3, 2021
d04ef23
Merge pull request #26 from blocknotes/improve-prawn-callbacks
blocknotes Sep 3, 2021
9111872
Store the previous tag in the context
blocknotes Sep 3, 2021
de81f02
Fix Br tag spacing
blocknotes Sep 3, 2021
2e9d311
Update Br specs
blocknotes Sep 3, 2021
c5966ea
Update default font size
blocknotes Sep 3, 2021
69f471e
Adjust text leading and pixel conversion unit
blocknotes Sep 3, 2021
760a62f
Update tag A default styles
blocknotes Sep 3, 2021
ed1bd00
Fix margin left property
blocknotes Sep 4, 2021
b7e43ca
Update Blockquote tag
blocknotes Sep 4, 2021
0bc804b
Update body tag
blocknotes Sep 4, 2021
e68b1f9
Update H tags
blocknotes Sep 4, 2021
1e97b8d
Add on_context_remove callback to Context
blocknotes Sep 5, 2021
8830c49
Improve Ul, Ol and Li indentation
blocknotes Sep 5, 2021
01c4bda
Minor spec improvements
blocknotes Sep 5, 2021
4f3b2e9
Update P tag
blocknotes Sep 6, 2021
9d1b857
Merge pull request #27 from blocknotes/fix-some-elements-rendering
blocknotes Sep 6, 2021
1b61d71
Merge commit '9d1b85795fa46782495e32629dcd9520873e8fd7' into v0.5.0
blocknotes Sep 9, 2021
8aa4407
Bump to version 0.5.0
blocknotes Sep 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ AllCops:
- vendor/**/*
NewCops: enable

Lint/UnusedMethodArgument:
Exclude:
- lib/prawn_html/utils.rb

Naming/FileName:
Exclude:
- lib/prawn-html.rb
Expand All @@ -25,7 +29,7 @@ Naming/MethodParameterName:

RSpec/ExampleLength:
# default: 5
Max: 10
Max: 15

RSpec/MultipleMemoizedHelpers:
# default: 5
Expand Down
Binary file modified examples/headings.pdf
Binary file not shown.
Binary file modified examples/misc_elements.pdf
Binary file not shown.
Binary file modified examples/random_content.pdf
Binary file not shown.
Binary file modified examples/styles.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/prawn-html.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module PrawnHtml
PX = 0.66 # conversion constant for pixel sixes
PX = 0.6 # conversion constant for pixel sixes

COLORS = {
'aliceblue' => 'f0f8ff',
Expand Down
52 changes: 33 additions & 19 deletions lib/prawn_html/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ class Attributes < OpenStruct
attr_reader :styles

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

STYLES_LIST = {
# text node styles
'background' => { key: :background, set: :convert_color },
'callback' => { key: :callback, set: :copy_value },
'background' => { key: :callback, set: :callback_background },
'color' => { key: :color, set: :convert_color },
'font-family' => { key: :font, set: :unquote },
'font-size' => { key: :size, set: :convert_size },
Expand All @@ -25,7 +24,7 @@ class Attributes < OpenStruct
'href' => { key: :link, set: :copy_value },
'letter-spacing' => { key: :character_spacing, set: :convert_float },
'list-style-type' => { key: :list_style_type, set: :unquote },
'text-decoration' => { key: :styles, set: :append_styles },
'text-decoration' => { key: :styles, set: :append_text_decoration },
'vertical-align' => { key: :styles, set: :append_styles },
'white-space' => { key: :white_space, set: :convert_symbol },
# tag opening styles
Expand All @@ -37,13 +36,15 @@ class Attributes < OpenStruct
'margin-bottom' => { key: :margin_bottom, set: :convert_size },
'padding-bottom' => { key: :padding_bottom, set: :convert_size },
# block styles
'left' => { key: :left, set: :convert_size },
'bottom' => { key: :bottom, set: :convert_size, options: :height },
'left' => { key: :left, set: :convert_size, options: :width },
'line-height' => { key: :leading, set: :convert_size },
'margin-left' => { key: :margin_left, set: :convert_size },
'padding-left' => { key: :padding_left, set: :convert_size },
'position' => { key: :position, set: :convert_symbol },
'right' => { key: :right, set: :convert_size, options: :width },
'text-align' => { key: :align, set: :convert_symbol },
'top' => { key: :top, set: :convert_size }
'top' => { key: :top, set: :convert_size, options: :height }
}.freeze

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

class << self
Expand Down Expand Up @@ -100,21 +102,33 @@ def parse_styles(styles)

private

def apply_rule!(result, rule, value)
return unless rule
def process_styles(hash_styles, options:)
hash_styles.each do |key, value|
rule = evaluate_rule(key, value)
apply_rule!(merged_styles: @styles, rule: rule, value: value, options: options)
end
@styles
end

if rule[:set] == :append_styles
(result[rule[:key]] ||= []) << Utils.normalize_style(value)
else
result[rule[:key]] = Utils.send(rule[:set], value)
def evaluate_rule(rule_key, attr_value)
rule = STYLES_LIST[rule_key]
if rule && rule[:set] == :append_text_decoration
return { key: :callback, set: :callback_strike_through } if attr_value == 'line-through'

return { key: :styles, set: :append_styles }
end
rule
end

def process_styles(hash_styles)
hash_styles.each do |key, value|
apply_rule!(@styles, STYLES_LIST[key], value)
def apply_rule!(merged_styles:, rule:, value:, options:)
return unless rule

if rule[:set] == :append_styles
(merged_styles[rule[:key]] ||= []) << Utils.normalize_style(value)
else
opts = rule[:options] ? options[rule[:options]] : nil
merged_styles[rule[:key]] = Utils.send(rule[:set], value, options: opts)
end
@styles
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

module PrawnHtml
module Callbacks
class Highlight
class Background
DEF_HIGHLIGHT = 'ffff00'

def initialize(pdf, item)
def initialize(pdf, color = nil)
@pdf = pdf
@color = item.delete(:background) || DEF_HIGHLIGHT
@color = color || DEF_HIGHLIGHT
end

def render_behind(fragment)
Expand Down
29 changes: 21 additions & 8 deletions lib/prawn_html/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

module PrawnHtml
class Context < Array
DEF_FONT_SIZE = 10.3
DEF_FONT_SIZE = 16 * PX

attr_accessor :last_margin, :last_text_node
attr_reader :previous_tag
attr_accessor :last_text_node

# Init the Context
def initialize(*_args)
super
@last_margin = 0
@last_text_node = false
@merged_styles = nil
@previous_tag = nil
end

# Add an element to the context
Expand All @@ -25,6 +27,7 @@ def add(element)
element.parent = last
push(element)
element.on_context_add(self) if element.respond_to?(:on_context_add)
@merged_styles = nil
self
end

Expand All @@ -49,11 +52,21 @@ def block_styles
# Merge the context styles for text nodes
#
# @return [Hash] the hash of merged styles
def text_node_styles
each_with_object(base_styles) do |element, res|
evaluate_element_styles(element, res)
element.update_styles(res) if element.respond_to?(:update_styles)
end
def merged_styles
@merged_styles ||=
each_with_object(base_styles) do |element, res|
evaluate_element_styles(element, res)
element.update_styles(res) if element.respond_to?(:update_styles)
end
end

# Remove the last element from the context
def remove_last
last.on_context_remove(self) if last.respond_to?(:on_context_remove)
@merged_styles = nil
@last_text_node = false
@previous_tag = last.tag
pop
end

private
Expand Down
61 changes: 40 additions & 21 deletions lib/prawn_html/document_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class DocumentRenderer
def initialize(pdf)
@buffer = []
@context = Context.new
@last_margin = 0
@pdf = pdf
end

Expand All @@ -20,8 +21,7 @@ def initialize(pdf)
def on_tag_close(element)
render_if_needed(element)
apply_tag_close_styles(element)
context.last_text_node = false
context.pop
context.remove_last
end

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

tag_class.new(tag_name, attributes: attributes, element_styles: element_styles).tap do |element|
setup_element(element)
options = { width: pdf.page_width, height: pdf.page_height }
tag_class.new(tag_name, attributes: attributes, options: options).tap do |element|
setup_element(element, element_styles: element_styles)
end
end

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

buffer << context.text_node_styles.merge(text: prepare_text(content))
buffer << context.merged_styles.merge(text: prepare_text(content))
context.last_text_node = true
nil
end
Expand All @@ -59,19 +60,20 @@ def render

output_content(buffer.dup, context.block_styles)
buffer.clear
context.last_margin = 0
@last_margin = 0
end

alias_method :flush, :render

private

attr_reader :buffer, :context, :pdf
attr_reader :buffer, :context, :last_margin, :pdf

def setup_element(element)
def setup_element(element, element_styles:)
add_space_if_needed unless render_if_needed(element)
apply_tag_open_styles(element)
context.add(element)
element.process_styles(element_styles: element_styles)
apply_tag_open_styles(element)
element.custom_render(pdf, context) if element.respond_to?(:custom_render)
end

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

def apply_tag_close_styles(element)
tag_styles = element.tag_close_styles
context.last_margin = tag_styles[:margin_bottom].to_f
pdf.advance_cursor(context.last_margin + tag_styles[:padding_bottom].to_f)
@last_margin = tag_styles[:margin_bottom].to_f
pdf.advance_cursor(last_margin + tag_styles[:padding_bottom].to_f)
pdf.start_new_page if tag_styles[:break_after]
end

def apply_tag_open_styles(element)
tag_styles = element.tag_open_styles
move_down = (tag_styles[:margin_top].to_f - context.last_margin) + tag_styles[:padding_top].to_f
move_down = (tag_styles[:margin_top].to_f - last_margin) + tag_styles[:padding_top].to_f
pdf.advance_cursor(move_down) if move_down > 0
pdf.start_new_page if tag_styles[:break_before]
end
Expand All @@ -111,24 +113,41 @@ def prepare_text(content)
def output_content(buffer, block_styles)
apply_callbacks(buffer)
left_indent = block_styles[:margin_left].to_f + block_styles[:padding_left].to_f
options = block_styles.slice(:align, :leading, :mode, :padding_left)
options[:indent_paragraphs] = left_indent if left_indent > 0
pdf.puts(buffer, options, bounding_box: bounds(block_styles))
options = block_styles.slice(:align, :indent_paragraphs, :leading, :mode, :padding_left)
options[:leading] = adjust_leading(buffer, options[:leading])
pdf.puts(buffer, options, bounding_box: bounds(buffer, options, block_styles), left_indent: left_indent)
end

def apply_callbacks(buffer)
buffer.select { |item| item[:callback] }.each do |item|
callback = Tag::CALLBACKS[item[:callback]]
item[:callback] = callback.new(pdf, item)
callback, arg = item[:callback]
callback_class = Tag::CALLBACKS[callback]
item[:callback] = callback_class.new(pdf, arg)
end
end

def bounds(block_styles)
def adjust_leading(buffer, leading)
return leading if leading

(buffer.map { |item| item[:size] || Context::DEF_FONT_SIZE }.max * 0.055).round(4)
end

def bounds(buffer, options, block_styles)
return unless block_styles[:position] == :absolute

y = pdf.bounds.height - (block_styles[:top] || 0)
w = pdf.bounds.width - (block_styles[:left] || 0)
[[block_styles[:left] || 0, y], { width: w }]
x = if block_styles.include?(:right)
x1 = pdf.calc_buffer_width(buffer) + block_styles[:right]
x1 < pdf.page_width ? (pdf.page_width - x1) : 0
else
block_styles[:left] || 0
end
y = if block_styles.include?(:bottom)
pdf.calc_buffer_height(buffer, options) + block_styles[:bottom]
else
pdf.page_height - (block_styles[:top] || 0)
end

[[x, y], { width: pdf.page_width - x }]
end
end
end
Loading