Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format rotated cell text #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Gemfile.lock
manual.pdf
rotate.pdf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like that line wasn't meant to be committed.

3 changes: 3 additions & 0 deletions lib/prawn/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
require_relative 'table/cell/subtable'
require_relative 'table/cell/image'
require_relative 'table/cell/span_dummy'
require_relative 'table/cell/formatted/wrap'
require_relative 'table/cell/formatted/box'
require_relative 'table/cell/box'

module Prawn
module Errors
Expand Down
29 changes: 29 additions & 0 deletions lib/prawn/table/cell/box.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# encoding: utf-8

# box.rb : Implements table cell boxes
#
# Copyright December 2009, Gregory Brown and Brad Ediger. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
#

module Prawn
class Table
class Cell
# Generally, one would use the Prawn::Table#new method to create a table
#
class Box < Prawn::Table::Cell::Formatted::Box

def initialize(string, options={})
super([{ :text => string }], options)
end

def render(flags={})
leftover = super(flags)
leftover.collect { |hash| hash[:text] }.join
end

end
end
end
end
266 changes: 266 additions & 0 deletions lib/prawn/table/cell/formatted/box.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# encoding: utf-8

# formatted/box.rb : Implements formatted table box
#
# Copyright December 2009, Gregory Brown and Brad Ediger. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
#

module Prawn
class Table
class Cell
module Formatted
# @group Stable API

# Generally, one would use the Prawn::Text::Formatted#formatted_text_box
# convenience method. However, using Table::Cell::Formatted::Box.new lets
# you create a formatted box with the table cell rotation algorithm. In
# conjunction with #render(:dry_run => true) you can do look-ahead
# calculations prior to placing text on the page, or to determine how much
# vertical space was consumed by the printed text
#
class Box < Prawn::Text::Formatted::Box
include Prawn::Table::Cell::Formatted::Wrap

@x_correction = @y_correction = nil

def initialize(formatted_text, options={})
super formatted_text, options
# limit rotation to 0-90 until developed
@rotate = 90 if @rotate > 90
@rotate = 0 if @rotate < 0
end

def initialize_wrap(array)
super array
if @baseline_y == 0 && @rotate != 0 && @rotate != 90
# adjust vertical positioning so the first word fits
first_token = @line_wrap.tokenize(@arranger.preview_next_string).first
first_width = @document.width_of first_token
# find out how far down and left the first token
# must be moved so its top edge fits in the corner
hyp = -first_width * rotate_sin
@baseline_y = hyp * rotate_cos - @font_size
@x_correction = hyp * rotate_sin
@y_correction = -@baseline_y
# correct the height running over the bottom padding
# why is this necessary?
@height -= 5
end
end

# The width available at this point in the box
#
def available_width(baseline_y = @baseline_y)
if @rotate == 0
@width
elsif @rotate == 90
@height
else
baseline_top = -baseline_y - @font_size
# if the angle is smaller than the diagonal
if @height > aspect_height
if baseline_top < @width * rotate_sin
# in the top 'corner'
baseline_top / (rotate_sin * rotate_cos)
elsif baseline_top < @height * rotate_cos
# in the middle section
@width / rotate_cos - @line_height * rotate_tan
else
# the bottom 'corner'
(@height * rotate_cos + @width * rotate_sin + baseline_y) /
(rotate_cos * rotate_sin)
end
else # angle is larger than the diagonal
if baseline_top < @height * rotate_cos
# in the top 'corner'
baseline_top * rotate_sin_inv * rotate_cos_inv
elsif baseline_top < @width * rotate_sin
# in the middle section
@height * rotate_sin_inv - @line_height * rotate_tan_inv
else
# the bottom 'corner'
(@height * rotate_cos + @width * rotate_sin + baseline_y) *
(rotate_cos_inv * rotate_sin_inv)
end
end
end
end

# The height actually used during the previous <tt>render</tt>
#
def height(baseline_y = @baseline_y)
return 0 if baseline_y.nil? || @descender.nil?
if @rotate == 0 || @rotate == 90
(baseline_y - @descender).abs
else
((baseline_y - @descender).abs - @width/2) * rotate_cos_inv
end
end

# The height available at this point in the box
#
def available_height(width = @width)
if @rotate == 0
@height
elsif @rotate == 90
@width
else # outside corner to outside corner
@height * rotate_cos + @width * rotate_sin
end
end

# <tt>fragment</tt> is a Prawn::Text::Formatted::Fragment object
#
def draw_fragment(fragment, accumulated_width=0, line_width=0, word_spacing=0) #:nodoc:

last_baseline = @baseline_y+@line_height
case(@align)
when :left
x = @at[0]
when :center
x = @at[0] + (available_width(last_baseline) - line_width) * 0.5
when :right
x = @at[0] + available_width(last_baseline) - line_width
when :justify
if @direction == :ltr
x = @at[0]
else
x = @at[0] + available_width(last_baseline) - line_width
end
end
# @document.circle @at, 3
# bottom_left = [@at[0]-@height*rotate_sin,@at[1]-@height*rotate_cos]
# top_right = [@at[0]+@width*rotate_cos,@at[1]-@width*rotate_sin]
# @document.circle bottom_left, 3
# @document.circle top_right, 3
# @document.circle [bottom_left[0]+top_right[0], bottom_left[1]-(@at[1]-top_right[1])], 3

x += accumulated_width
y = @at[1] + @baseline_y + fragment.y_offset
# starting spot
# @document.circle [x,y+@y_correction], 1
# text location
# @document.circle [x+last_baseline*rotate_tan+@font_size*rotate_tan,y+@y_correction], 2
# uncorrected rectangle edge:
# @document.circle [x+last_baseline*rotate_tan,y+@y_correction], 2

if @rotate != 0 && @rotate != 90
height_actual = -last_baseline * rotate_cos_inv
# @document.circle [x+last_baseline*rotate_tan+(height_actual-@height)*rotate_sin_inv,y+@y_correction], 2
y += @y_correction
# we have reached the bottom corner of the cell
if height_actual > @height
x += last_baseline*rotate_tan+(height_actual-@height)*rotate_sin_inv
# check if the line overlaps the left side
test_y = Math.tan((90-@rotate)* Math::PI / 180)*(x-@at[0]) + @at[1]
if (y+@font_size) > test_y
x += (y+@font_size-test_y)*rotate_tan
end
else # move left
x += (last_baseline + @y_correction) * rotate_tan + @x_correction
end
end

fragment.left = x
fragment.baseline = y

if @inked
draw_fragment_underlays(fragment)

@document.word_spacing(word_spacing) {
if @draw_text_callback
@draw_text_callback.call(fragment.text, :at => [x, y],
:kerning => @kerning)
else
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
end
}

draw_fragment_overlays(fragment)
end
end

def valid_options
PDF::Core::Text::VALID_OPTIONS + [:at, :height, :width,
:align, :valign,
:rotate,
:overflow, :min_font_size,
:leading, :character_spacing,
:mode, :single_line,
:skip_encoding,
:document,
:direction,
:fallback_fonts,
:draw_text_callback]
end

private

def render_rotated(text)
unprinted_text = ''

if @rotate == 90
x = @at[0] + @height/2.0 - 1.0
y = @at[1] - @height/2.0 + 4.0
else
x = @at[0]
y = @at[1]
end

@document.rotate(@rotate, :origin => [x, y]) do
unprinted_text = wrap(text)
end
unprinted_text
end

private

def aspect_height
@aspect_height ||= @width * rotate_tan
end

def rotate_complement_rads
@rotate_complement_rads ||= (90 - @rotate) * Math::PI / 180
end

def rotate_rads
@rotate_rads ||= @rotate * Math::PI / 180
end

def rotate_atan
Math.atan(@height/@width) * 180 / Math::PI
end

def rotate_tan
@rotate_tan ||= Math.tan(rotate_rads)
end

def rotate_tan_inv
@rotate_tan_inv ||= 1/rotate_tan
end

def rotate_cos
@rotate_cos ||= Math.cos(rotate_rads)
end

def rotate_cos_inv
@rotate_cos_inv ||= 1/rotate_cos
end

def rotate_sin
@rotate_sin ||= Math.sin(rotate_rads)
end

def rotate_sin_inv
@rotate_sin_inv ||= 1/rotate_sin
end

end

end
end
end
end
79 changes: 79 additions & 0 deletions lib/prawn/table/cell/formatted/wrap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# encoding: utf-8

# formatted/wrap.rb : Implements formatted table box
#
# Copyright December 2009, Gregory Brown and Brad Ediger. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
#

module Prawn
class Table
class Cell
module Formatted
# @group Stable API

module Wrap
include Prawn::Text::Formatted::Wrap #:nodoc:

# See the developer documentation for PDF::Core::Text#wrap
#
# Formatted#wrap should set the following variables:
# <tt>@line_height</tt>::
# the height of the tallest fragment in the last printed line
# <tt>@descender</tt>::
# the descender height of the tallest fragment in the last
# printed line
# <tt>@ascender</tt>::
# the ascender heigth of the tallest fragment in the last
# printed line
# <tt>@baseline_y</tt>::
# the baseline of the current line
# <tt>@nothing_printed</tt>::
# set to true until something is printed, then false
# <tt>@everything_printed</tt>::
# set to false until everything printed, then true
#
# Returns any formatted text that was not printed
#
def wrap(array) #:nodoc:
initialize_wrap(array)

stop = false
while !stop
# wrap before testing if enough height for this line because the
# height of the highest fragment on this line will be used to
# determine the line height
begin
cannot_fit = false
@line_wrap.wrap_line(:document => @document,
:kerning => @kerning,
:width => available_width,
:arranger => @arranger,
:rotate => @rotate)
rescue Prawn::Errors::CannotFit
cannot_fit = true
end

if enough_height_for_this_line?
move_baseline_down
print_line unless cannot_fit
elsif cannot_fit
raise Prawn::Errors::CannotFit
else
stop = true
end

stop ||= @single_line || @arranger.finished?
end
@text = @printed_lines.join("\n")
@everything_printed = @arranger.finished?
@arranger.unconsumed
end

end

end
end
end
end
Loading