greeting_card/app/models/greeting_card_layout_design.rb

404 lines
12 KiB
Ruby

class GreetingCardLayoutDesign
require 'freetype/api'
include Mongoid::Document
include Mongoid::Timestamps
field :print_format, type: String,default: ''
field :save_name, type: String,default: 'greeting_card_{title}'
field :img_objs, type: Hash, default: {}
field :preserved_keys, type: Array
field :category_id
before_save do
self.preserved_keys = img_objs.map do |i, obj|
get_preserved_keys(obj[:text])
end.flatten
self.preserved_keys += get_preserved_keys(self.save_name)
self.preserved_keys = self.preserved_keys.uniq.map(&:to_sym)
end
def get_preserved_keys(text)
if text.present?
text.scan(/(?<={)[^{}]+(?=})/)
else
[]
end
end
# 微軟正黑體
# need to call f.close manually to prevent memory leak
def get_font(font_name)
font_file = `fc-match '#{font_name}' -f "%{file}" 2>/dev/null`
if font_file.blank?
puts "#{font_name} font not found!"
nil
else
f = FreeType::API::Font.open(font_file)
font_info = {
m_w: 1.0 * f.face[:max_advance_width] / f.face[:units_per_EM],
m_h: 1.0 * f.face[:max_advance_height] / f.face[:units_per_EM],
l_g: 1.0 * (f.face[:height] - f.face[:max_advance_height]) / f.face[:units_per_EM], # line gap
f: f,
file: font_file
}
font_info
end
end
def set_char_size(f, font_size)
f.set_char_size(0, font_size * 64, 0, 0)
end
def get_char_real_width(c, font_size, f=nil, prev_c=nil)
if f.nil?
c_w = get_char_estimate_width(c, font_size)
else
c_w = (f.glyph(c).char_width >> 6) # this size is 64 times larger
if prev_c
c_w += (f.kerning_unscaled(prev_c, c).x >> 6)
end
end
c_w
end
def get_char_estimate_width(c, font_size)
chinese_ratio = 1
sixth_chars = ('0'..'9').to_a
seventh_chars = ('A'..'Z').to_a + ['g', 'h', 'n', 'o', 'p', 'q', 'w']
half_chars = ['-', '_', '?'] + (('a'..'z').to_a - ['m', 't'])
third_chars = [' ', '[', ']', '【', '】', '《', '》', '(', ')', 'i', 'l', 'r', 't', 'f', '!', '.', '-', ':', ';']
point_chars = []
if point_chars.include?(c)
w = 0.1
elsif third_chars.include?(c)
w = 0.3
elsif half_chars.include?(c)
w = 0.5
elsif sixth_chars.include?(c)
w = 0.6
elsif seventh_chars.include?(c)
w = 0.7
elsif c.match(/\p{Han}/)
w = chinese_ratio
else
w = 1
end
return w * font_size
end
def wrap_text_accurate(text, font_info, font_size, width, height, line_height=1, ellipsis=true)
if font_info.nil?
wrap_text_estimate(text, font_size, width, height, line_height, ellipsis)
else
if width == 0.0 && height == 0.0 # auto width and auto height
return text
end
if height == 0.0
max_lines = 0
else
max_lines = [height / (line_height * font_size * font_info[:m_h]).to_i, 1].max
end
f = font_info[:f]
set_char_size(f, font_size)
new_text = ""
last_line = ""
w = 0
lines = 1
prev_c = nil
check_ellipsis = false
text.chars.each do |c|
if c == "\n"
lines += 1
if max_lines != 0 && lines > max_lines
check_ellipsis = true
break
end
new_text += last_line
new_text += c
last_line = ""
prev_c = nil
w = 0
elsif width != 0.0
c_w = get_char_real_width(c, font_size, f, prev_c)
prev_c = c
tmp_w = w + c_w
if tmp_w > width
lines += 1
if max_lines != 0 && lines > max_lines
check_ellipsis = true
break
end
# Wrap the word
last_word = nil
last_line.sub!(/ ([^ ]+)$/){|s| last_word = $1; ' '}
new_text += last_line
new_text += "\n"
if last_word
# last line starts with word
last_line = last_word
w = get_text_real_width(last_word, font_size, f) + c_w
else
w = c_w
last_line = ""
end
else
w = tmp_w
end
last_line += c
end
end
if check_ellipsis && ellipsis # Add ...
last_w = w
ellipsis_text = "..."
ellipsis_width = get_text_real_width(ellipsis_text, font_size, f)
remain_width = width - last_w
if width == 0.0 || remain_width >= ellipsis_width
new_text += ellipsis_text
else
last_line_arr = last_line.split(/ /)
if last_line_arr.count > 1
space_width = get_char_real_width(' ', font_size, f)
while remain_width < ellipsis_width do
if last_line_arr.count > 1
remain_width += space_width
prev_c = ' '.freeze
else
prev_c = nil
end
s = last_line_arr.pop
s_width = get_text_real_width(s, font_size, f, prev_c)
remain_width += s_width
break if last_line_arr.empty?
end
last_line = last_line_arr.join(' ')
else
while remain_width < ellipsis_width do
c = last_line[-1]
last_line.chop!
if last_line.empty?
prev_c = nil
else
prev_c = last_line[-1]
end
remain_width += get_char_real_width(c, font_size, f, prev_c)
if prev_c.nil?
break
end
end
end
new_text += last_line
new_text += ellipsis_text
end
else
new_text += last_line
end
new_text
end
end
def wrap_text_estimate(text, font_size, width, height, line_height=1, ellipsis=true)
if width == 0.0 && height == 0.0 # auto width and auto height
return text
end
if height == 0.0
max_lines = 0
else
max_lines = [height / (line_height * font_size).to_i, 1].max
end
new_text = ""
last_line = ""
w = 0
lines = 1
check_ellipsis = false
text.chars.each do |c|
if c == "\n"
lines += 1
if max_lines != 0 && lines > max_lines
check_ellipsis = true
break
end
new_text += last_line
new_text += c
last_line = ""
prev_c = nil
w = 0
elsif width != 0.0
c_w = get_char_estimate_width(c, font_size)
tmp_w = w + c_w
if tmp_w > width
lines += 1
if max_lines != 0 && lines > max_lines
check_ellipsis = true
break
end
# Wrap the word
last_word = nil
last_line.sub!(/ ([^ ]+)$/){|s| last_word = $1; ' '}
new_text += last_line
new_text += "\n"
if last_word
# last line starts with word
last_line = last_word
w = get_text_estimate_width(last_word, font_size) + c_w
else
w = c_w
last_line = ""
end
else
w = tmp_w
end
last_line += c
end
end
if check_ellipsis && ellipsis
last_w = w
ellipsis_text = "..."
ellipsis_width = get_text_estimate_width(ellipsis_text, font_size)
remain_width = width - last_w
if width == 0.0 || remain_width >= ellipsis_width
new_text += "..."
else
last_line_arr = last_line.split(/ /)
if last_line_arr.count > 1
space_width = get_char_estimate_width(' ', font_size)
while remain_width < ellipsis_width do
if last_line_arr.count > 1
remain_width += space_width
end
s = last_line_arr.pop
s_width = get_text_estimate_width(s, font_size)
remain_width += s_width
break if last_line_arr.empty?
end
last_line = last_line_arr.join(' ')
else
while remain_width < ellipsis_width do
c = last_line[-1]
last_line.chop!
remain_width += get_char_estimate_width(c, font_size)
if last_line.empty?
break
end
end
end
new_text += last_line
new_text += '...'
end
else
new_text += last_line
end
new_text
end
def get_text_real_width(text, font_size, f=nil, prev_c=nil)
if f.nil?
get_text_estimate_width(text, font_size)
else
set_char_size(f, font_size)
w = 0
text.chars.each do |c|
w += get_char_real_width(c, font_size, f, prev_c)
prev_c = c
end
w
end
end
def get_text_estimate_width(text, font_size)
w = 0
text.chars.each do |c|
w += get_char_estimate_width(c, font_size)
end
return w
end
def generate_image(record, image_source, data, saved=true)
# change locale to chinese
ENV['LANG'] = "zh_TW.UTF-8"
begin
img = MiniMagick::Image.open(image_source)
rescue => e
puts "image open: #{image_source} failed!"
return nil
end
data = data.select{|k, v| self.preserved_keys.include?(k)}
img_objs_arr = self.img_objs.map do |i, _obj|
obj = _obj.dup
obj[:text].gsub!(/{([^{}]+)}/){|k| data[$1.to_sym]}
obj[:text].gsub!("\r\n", "\n")
obj[:text].strip!
obj
end
img_width = img[:width]
img_height = img[:height]
font_scale = img_width / 500.0
width_scale = img_width / 100.0
height_scale = img_height / 100.0
image_filename = save_name.gsub!(/{([^{}]+)}/){|k| data[$1.to_sym]}.gsub(CarrierWave::SanitizedFile.sanitize_regexp, '_') + File.extname(image_source)
record.image_will_change!
record[:image] = image_filename # must called
record.image.retrieve_from_store!(image_filename) # must called
dest_image_path = record.image.file.path
image_dir = File.dirname(dest_image_path)
FileUtils.rm_rf image_dir
FileUtils.mkdir_p image_dir
FileUtils.copy_file(image_source, dest_image_path)
record.image.manipulate! do |image|
image.combine_options do |c|
# c << "thumb_榮獲設計競賽-transformed.png"
img_objs_arr.each do |obj|
is_stroke = (obj[:is_stroke] == 'true')
if is_stroke
is_fill = obj[:is_fill] != 'false'
else
is_fill = true
end
x = width_scale * obj[:x].to_f
y = height_scale * obj[:y].to_f
w = width_scale * obj[:w].to_f
h = height_scale * obj[:h].to_f
fontSize = font_scale * obj[:fontSize].to_f
fontFamily = obj[:fontFamily]
font_info = get_font(fontFamily)
if font_info
fontFamily = font_info[:file]
end
line_height = (obj[:l_h] ? obj[:l_h].to_f : 1.0)
wrapped_text = wrap_text_accurate(obj[:text], font_info, fontSize, w, h, line_height)
y = y + fontSize
# y = img_height - y
# x = img_width - x
# c.interline_spacing caption_interline_spacing
line_gap = 0
if font_info
line_gap = -(font_info[:l_g] * fontSize)
end
if obj[:l_h]
line_gap = line_gap + (line_height - 1.0) * fontSize
end
c.interline_spacing line_gap.round(2)
c.font fontFamily
c.pointsize fontSize
c.fill (is_fill ? obj[:fontColor] : 'none')
if is_stroke
c.stroke obj[:stroke]
c.strokewidth (font_scale * obj[:strokeWidth].to_f)
else
c.stroke 'none'
end
# c.kerning -2
c.kerning 1.5
c.annotate "+#{x}+#{y}"
c << wrapped_text #.force_encoding("UTF-8")
# c << "123"
if font_info
font_info[:f].close
end
end
end
end
record.save if saved
end
end