How can I generate fonts images using Rmagick? - ruby-on-rails

In my app, there is a controller with a method for generate images from TrueType and OpenType files, which receives parameters like "color", "arbitrary text", "background".
The problem is when the path file contains white space:
def imagefont
font = Font.find params[:font]
file = File.basename font.file, File.extname(font.file)
fontfile = File.path(Pathname.new("public/downloads/#{font.name.slice(0,1).capitalize}/#{file}/#{font.file}"))
options = {
:max_width => 240,
:text_color => params[:color],
:font_size => 35,
:text => params[:text],
:bg_color => params[:background],
:font => fontfile
}
if File.exists?(options[:font])
canvas = Magick::Image.new 50, 50
image = Magick::Draw.new
begin
image.annotate(canvas, 0, 0, 0, 0, options[:text]) do
image.pointsize = options[:font_size]
image.font = options[:font]
end
metrics = image.get_type_metrics canvas, options[:text]
canvas = Magick::Image.new(metrics.width, metrics.height){ self.background_color = options[:bg_color] }
options[:font_size] -= 1
end while metrics.width > options[:max_width]
image = Magick::Draw.new
image.font options[:font]
image.font_size options[:font_size]
image.fill options[:text_color]
image.text 0, 0, options[:text]
image.gravity = Magick::CenterGravity
image.draw canvas
temp = Tempfile.new([font.file, '.png'])
canvas.write(temp.path)
render :text => open(temp.path).read
end
end
The above code works, unless the font name contains whitespaces. In that case it displays the following error:
Magick::ImageMagickError in FontController#imagefont
non-conforming drawing primitive definition `Blick' # error/draw.c/DrawImage/3146
In this case the font name is "A Blick for All Seasons", so I think it's a problem with quotes. I tried put simple quotes, but I wasn't successful.

I hadn't answers, but I got a possible solution. I found and I have modified the method in the gem files that receives the fontfile parameter. Initially appears thus:
# File lib/RMagick.rb, line 335
def font(name)
primitive "font #{name}"
end
I've changed adding simple quotes and I got it, works fine:
# File lib/RMagick.rb, line 335
def font(name)
primitive "font \'#{name}\'"
end
I think I should send a "pull request" with this change. Unless there is another answer.

Related

Prawn/PDF: Multiple formats/text on one line?

When I try the following code:
text "Hello "
text "World"
They render Hello on top of World instead of World right after Hello. I have some complicated formatting (highlighting, different font sizes etc) on text that I need on one line. I know that the :inline_formatting option exists but it seems this is too complicated to use that option.
I have the following code:
highlight_callback.rb:
class HighlightCallback
def initialize(options)
#color = options[:color]
#document = options[:document]
end
def render_behind(fragment)
original_color = #document.fill_color
#document.fill_color = #color
#document.fill_rectangle(fragment.top_left,
fragment.width,
fragment.height)
#document.fill_color = original_color
end
end
order.pdf.prawn:
highlight = HighlightCallback.new(:color => 'ffff00', :document => self)
#code....
text "Authorized Signature: "
formatted_text [{:text => "_" * 15, :callback => highlight }], :size => 20
which is producing the attached image. How can I get the signature line on the same level as the text?
Ruby 2.5.1
Rails 5.2.0
It's enough to change method text to text_box, i.e.:
bounding_box([0, cursor], width: 540, height: 40) do
stroke_color 'FFFF00'
stroke_bounds
date = 'Date: '
text_box date, style: :bold
text_box DateTime.now.strftime('%Y/%m/%d'), at: [bounds.left + width_of(date), cursor]
text_box "Signature ________________", align: :right
end
Example:
To place text at a exact position you can use text_box with the option :at.
You can get the width of your text with pdf.width_of(str) (use the same style optione :size etc. otherwise it will use the default settings to calculate)

Rails: adding to last object if conditions met

I've got a method that scans an HTML string and sort of formats it for prawnpdf:
def format_for_prawn(pdf, string, colour)
body = Nokogiri::HTML::DocumentFragment.parse(string)
result = body.xpath('./*|./text()')
result.each do |breaker|
if breaker.name == "h3"
pdf.fill_color colour
pdf.text breaker.text.to_s, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
pdf.text breaker.text.to_s, :size => 10, :leading => 1
pdf.move_down 10
end
end
end
It works great for <h3>s. In the event that some mid-paragraph <b> (or similar) tags are found it starts a new paragraph because that's where Nokogiri broke the string--which is the correct behaviour.
How could I add the bolded string to the last pdf.text function instead of calling a new pdf.text which results in a new paragraph?
I thought about making an array out of it all but then it'll be out of order with the <h3>s.
Any help would be appreciated.
My first thought was to do a negative match :
body.xpath( './node()[not(self::b)]' )
Sadly, this would exclude <b> rather than ignoring it :
> body = Nokogiri::HTML::DocumentFragment.parse %(<h3><b>foo</b></h3><h3>bar</h3>fooz<b>baz</b>whatever); true
> body.xpath( './node()[not(self::b)]' ).to_a
[
[0] <h3>
<b>foo</b>
</h3>,
[1] <h3>bar</h3>,
[2] fooz,
[3] whatever
]
So, you'll have no choice but using a buffer, here : we can iterate through nodes first, to populate a buffer regarding if we should have a new line or not, then iterate this buffer to have your lines added to pdf :
buffer = []
body.xpath( './node()' ).each do |node|
if %w[text b].include? node.name
# add to previous line or create one
buffer << [] unless buffer.count
buffer.last << { node: node }
else
# set content and create a new line
buffer << [ { node: node, title: node.name == 'h3' } ]
buffer << []
end
end
# Now, each first level item in buffer is a line,
# containing elements we just have to concatenate text of
# to pass to `pdf#text`
buffer.each do |line|
text = line.map do |part|
node = part[ :node ]
inner = node.text.to_s
# restore <b> tag if you want bold style in pdf
node.name == 'b' ? "<b>#{inner}</b>" : inner
end.join
if line.first
if line.first[ :title ]
pdf.fill_color colour
pdf.text text, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
# inline_format ensure basic html formating is used, <b> in our case
# See http://prawn.majesticseacreature.com/docs/0.11.1/Prawn/Text.html#method-i-text
pdf.text text, size: 10, leading: 1, inline_format: true
pdf.move_down 10
end
end
end
Of course, all of this is considering you do not control original html. Else, you should place your text nodes inside <p> or something, and there would not be problems anymore.

Prawn: Table of content with page numbers

I need to create a table of contents with Prawn. I have add_dest function calls in my code and the
right links in the table of content:
add_dest('Komplett', dest_fit(page_count - 1))
and
text "* <link anchor='Komplett'> Vollstaendiges Mitgliederverzeichnis </link>", :inline_format = true
This works and I get clickable links which forward me to the right pages. However, I need to have page numbers in the table of content. How do I get it printed out?
I would suggest a much simpler solution.
Use pdf.page_number to store the page number of all your sections in a hash as you populate the pages
In the code, output the table of contents after populating the rest of your pages. Insert the TOC into the doc in the right spot by navigating in the PDF pdf.go_to_page(page_num).
For example:
render "pdf/frontpage", p: p
toc.merge!(p.page_number => "Section_Title")
p.start_new_page
toc.merge!(p.page_number => "Section_Title")
render "pdf/calendar"
p.start_new_page
toc.merge!(p.page_number => "Section_Title")
render "pdf/another_section"
p.go_to_page(1)
p.start_new_page
toc.merge!(p.page_number => "Table of Contents")
render "pdf/table_of_contents", table_of_contents: toc
you should read the chapter on Outline in this document http://prawn.majesticseacreature.com/manual.pdf, p.96. It explains with examples on how to create TOC.
UPDATE
destinations, page_references = {}, {}
page_count.downto(1).each {|num| page_references[num] = state.store.object_id_for_page(num)}
dests.data.to_hash.each_value do |values|
values.each do |value|
value_array = value.to_s.split(":")
dest_name = value_array[0]
dest_id = value_array[1].split[0]
destinations[dest_name] = Integer(dest_id)
end
end
state.store.each do |reference|
if !(dest_name = destinations.key(reference.identifier)).nil?
puts "Destination - #{dest_name} is on Page #{page_references.key(Integer(reference.data[0].to_s.split[0]))}"
end
end
I also needed to create a dynamic TOC. I put together a quick spike that needs some clean-up but does pretty much what I want. I didn't include click-able links but they could easily be added. The example also assumes the TOC is being placed on the 2nd page of the document.
The basic strategy I used was to store the TOC in a hash. Each time I add a new section to the document that I want to appear in the TOC I add it to the hash, i.e.
#toc[pdf.page_count] = "the toc text for this section"
Then prior to adding the page numbers to the document I iterate thru the hash:
number_of_toc_entries_per_page = 10
offset = (#toc.count.to_f / number_of_toc_entries_per_page).ceil
#toc.each_with_index do |(key, value), index|
pdf.start_new_page if index % number_of_toc_entries_per_page == 0
pdf.text "#{value}.... page #{key + offset}", size: 38
end
Anyway, the full example is below, hope it helps.
require 'prawn'
class TocTest
def self.create
#toc = Hash.new
#current_section_header_number = 0 # used to fake up section header's
pdf = Prawn::Document.new
add_title_page(pdf)
21.times { add_a_content_page(pdf) }
fill_in_toc(pdf)
add_page_numbers(pdf)
pdf.render_file './output/test.pdf'
end
def self.add_title_page(pdf)
pdf.move_down 200
pdf.text "This is my title page", size: 38, style: :bold, align: :center
end
def self.fill_in_toc(pdf)
pdf.go_to_page(1)
number_of_toc_entries_per_page = 10
offset = (#toc.count.to_f / number_of_toc_entries_per_page).ceil
#toc.each_with_index do |(key, value), index|
pdf.start_new_page if index % number_of_toc_entries_per_page == 0
pdf.text "#{value}.... page #{key + offset}", size: 38
end
end
def self.add_a_content_page(pdf)
pdf.start_new_page
toc_heading = grab_some_section_header_text
#toc[pdf.page_count] = toc_heading
pdf.text toc_heading, size: 38, style: :bold
pdf.text "Here is the content for this section"
# randomly span a section over 2 pages
if [true, false].sample
pdf.start_new_page
pdf.text "The content for this section spans 2 pages"
end
end
def self.add_page_numbers(pdf)
page_number_string = 'page <page> of <total>'
options = {
at: [pdf.bounds.right - 175, 9],
width: 150,
align: :right,
size: 10,
page_filter: lambda { |pg| pg > 1 },
start_count_at: 2,
}
pdf.number_pages(page_number_string, options)
end
def self.grab_some_section_header_text
"Section #{#current_section_header_number += 1}"
end
end
I built a report generator featuring a clickable table of contents using code and ideas gathered from this discussion. Here is the relevant parts of the code, in case somebody else needs to do the same.
What it does:
include Prawn::View to use Prawn's methods without having to prefix them with pdf
insert a blank page where the table of contents will be displayed
add the document contents, using h1 and h2 helpers for titles
the h1 and h2 helpers store the position of headings in the document
rewind and generate the actual table of contents
indent subsections in the table of contents
right-align the dots between toc entry and page number for visual consistency
if the table doesn't fit on one page, it adds new pages and increments the relevant page numbers
add a PDF outline with the section and subsection titles for bonus points.
Enjoy!
PDF generator
class ReportPdf
include Prawn::View
COLOR_GRAY = 'BBBBBB' # Color used for the dots in the table of contents
def initialize(report)
#toc = []
#report = report
generate_report
end
private
def generate_report
add_table_of_contents
add_contents
update_table_of_contents
add_outline
end
def add_table_of_contents
# Insert a blank page, which will be filled in later using update_table_of_contents
start_new_page
end
def add_contents
#report.sections.each do |section|
h1(section.title, section.anchor)
section.subsections.each do |subsection|
h2(subsection.title, subsection.anchor)
# subsection contents
end
end
end
def update_table_of_contents
go_to_page(1) # Rewind to where the table needs to be displayed
text 'Table of contents', styles_for(:toc_title)
move_down 20
added_pages = 0
#toc.each do |entry|
unless fits_on_current_page?(entry[:name])
added_pages += 1
start_new_page
end
entry[:page] += added_pages
add_toc_line(entry)
entry[:subsections].each do |subsection_entry|
unless fits_on_current_page?(subsection_entry[:name])
added_pages += 1
start_new_page
end
subsection_entry[:page] += added_pages
add_toc_line(subsection_entry, true)
end
end
end
def add_outline
outline.section 'Table of contents', destination: 2
#toc.each do |entry|
outline.section entry[:name], destination: entry[:page] do
entry[:subsections].each do |subsection|
outline.page title: subsection[:name], destination: subsection[:page]
end
end
end
end
def h1(name, anchor)
add_anchor(anchor, name)
text name, styles_for(:h1)
end
def h2(name, anchor)
add_anchor(anchor, name, true)
text name, styles_for(:h2)
end
def styles_for(element = :p)
case element
when :toc_title then { size: 24, align: :center }
when :h1 then { size: 20, align: :left }
when :h2 then { size: 16, align: :left }
when :p then { size: 12, align: :justify }
end
end
def add_anchor(name, anchor, is_subsection = false)
add_dest anchor, dest_xyz(bounds.absolute_left, y + 20)
if is_subsection
#toc.last[:subsections] << { anchor: anchor, name: name, page: page_count }
else
#toc << { anchor: anchor, name: name, page: page_count, subsections: [] }
end
end
def add_toc_line(entry, is_subsection = false)
anchor = entry[:anchor]
name = entry[:name]
name = "#{Prawn::Text::NBSP * 5}#{name}" if is_subsection
page_number = entry[:page].to_s
dots_info = dots_for(name + ' ' + page_number)
float do
text "<link anchor='#{anchor}'>#{name}</link>", inline_format: true
end
float do
indent(dots_info[:dots_start], dots_info[:right_margin]) do
text "<color rgb='#{COLOR_GRAY}'>#{dots_info[:dots]}</color>", inline_format: true, align: :right
end
end
indent(dots_info[:dots_end]) do
text "<link anchor='#{anchor}'>#{page_number}</link>", inline_format: true, align: :right
end
end
def dots_for(text)
dot_width = text_width('.')
dots_start = text_width(text)
right_margin = text_width(' ') * 6
space_for_dots = bounds.width - dots_start - right_margin
dots = space_for_dots.negative? ? '' : '.' * (space_for_dots / dot_width)
dots_end = space_for_dots - right_margin
{
dots: dots,
dots_start: dots_start,
dots_end: dots_end,
right_margin: right_margin
}
end
def fits_on_current_page?(str)
remaining_height = bounds.top - bounds.absolute_top + y
height_of(str) < remaining_height
end
def text_width(str, size = 12)
font(current_font).compute_width_of(str, size: size)
end
def current_font
#current_font ||= font.inspect.split('<')[1].split(':')[0].strip
end
end
Using the generator
Using Rails, I generate PDFs from a report using the following code:
# app/models/report.rb
class Report < ApplicationRecord
# Additional methods
def pdf
#pdf ||= ReportPdf.new(self)
end
end
# app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def show
respond_to do |format|
format.html
format.pdf do
doc = #report.pdf
send_data doc.render, filename: doc.filename, disposition: :inline, type: Mime::Type.lookup_by_extension(:pdf)
end
end
end

Adding text to posters with RMagick and Carrierwave. uninitialized constant PhotoUploader::Draw

I am trying to do image manipulation as process to add some text to my app.
The effect I'm looking for is : adding text to image, after saving.
The problem I'm dealing here is I get error: uninitialized constant PhotoUploader::Draw.
photo_uploader.rb
process :poster
def poster
manipulate! do |source|
txt = Draw.new
txt.family
txt.pointsize = 12
txt.gravity = Magick::SouthGravity
txt.stroke = "#000000"
title = Demot.last.title
source = source.resize_to_fill(400, 400).border(10, 10, "black")
source.annotate(txt, 0, 0, 0, 40, title)
end
end
Change to
txt = Magick::Draw.new

How to access model in carrierwave?

I did this the way it's below, but it's returning "no text". How to access model properly in carrierwave?
photo_uploader.rb
process :poster
def poster
manipulate! format:"jpg" do |source|
txt = Magick::Draw.new
txt.pointsize = 20
txt.gravity = Magick::SouthGravity
txt.fill = "white"
source.border(50, 50, "black").annotate(txt, 0, 0, 0, 0, "#{model.title}" )
end
end
You can access the model this way. All the error means is that there is actually no text in the title field of model. If you check the params hash, you'll probably see
"my_model"=>{"title"=>"", "image"=> ...)
So you can should check for a non-blank title in the controller:
unless params[:title].blank?
MyModel.create(params[:my_model])
end

Resources