Skip to content

Commit

Permalink
Merge pull request #30 from blocknotes/v0.5.0
Browse files Browse the repository at this point in the history
v0.5.0
  • Loading branch information
blocknotes committed Sep 9, 2021
2 parents 8c53801 + 8aa4407 commit 56baa11
Show file tree
Hide file tree
Showing 63 changed files with 993 additions and 515 deletions.
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

0 comments on commit 56baa11

Please sign in to comment.