404 lines
12 KiB
Ruby
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
|