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