b0VIM 8.2f;bEjfjoshholtzJoshs-MacBook-Air.local~joshholtz/Developer/fastlane/fastlane/frameit/lib/frameit/editor.rbutf-8 3210#"! Utpt]ufV8LUadtuT2b S 0 / o n O  g F o N C o ` ? 5  L X>nL.cb?vuP3O2  end c.geometry(offset['offset']) c.compose("DstOver") @image = frame.composite(image, "png") do |c| end end c.annotate(annotate_offset.to_s, filename.to_s) c.pointsize(font_size) c.fill('white') c.undercolor('#00000080') c.gravity('North') frame.combine_options do |c| annotate_offset = "+0+#{offset_top}" # magic number that works semi well offset_top = offset['offset'].split("+")[2].to_f font_size = width / 20 # magic number that works well width = screenshot.size[0] filename.sub!('Apple', '') # remove 'Apple' filename = File.basename(@frame_path, ".*") if self.debug_mode # Debug Mode: Add filename to frame @image.rotate(-rotation) frame.rotate(-rotation) rotation = self.rotation_for_device_orientation # imagemagick do the hard lifting for landscape screenshots # only. Instead of doing the calculations ourselves, it's much easier to let # We have to rotate the screenshot, since the offset information is for portrait def put_into_frame # puts the screenshot into the frame end UI.success("Added frame: '#{File.expand_path(output_path)}'") Helper.hide_loading_indicator image.write(output_path) image.format("png") output_path = screenshot.output_path def store_result private end return is_complex_framing_mode? && !fetch_text(:title) def should_skip? end return 0 return -90 if self.screenshot.landscape_left? return 90 if self.screenshot.landscape_right? def rotation_for_device_orientation end @image = MiniMagick::Image.open(screenshot.path) def prepare_image end TemplateFinder.get_template(screenshot) end screenshot.color = color if color color = fetch_frame_color def load_frame end store_result # write to file system end put_into_frame # put it in the frame # easy mode from 1.0 - no title or background else @image = complex_framing if is_complex_framing_mode? end return # Message is already shown elsewhere # Couldn't find device frame (probably an iPhone 4, for which there are no images available any more) elsif self.class == Editor self.frame.rotate(self.rotation_for_device_orientation) # Rotate the frame according to the device orientation self.frame = MiniMagick::Image.open(@frame_path) if @frame_path # Mac doesn't need a frame @frame_path = load_frame prepare_image def frame! end self.debug_mode = debug_mode @config = config @screenshot = screenshot def initialize(screenshot, config, debug_mode = false) attr_accessor :space_to_device attr_accessor :image # the current image used for editing attr_accessor :frame # the frame of the device attr_accessor :frame_path attr_accessor :debug_mode attr_accessor :screenshot # reference to the screenshot object to fetch the path, title, etc. class Editor # rubocop:disable Metrics/ClassLength # Currently the class is 2 lines too long. Reevaluate refactoring when it's length changes significantlymodule Frameitrequire_relative 'device_types'require_relative 'strings_parser'require_relative 'config_parser'require_relative 'offsets'require_relative 'module'require_relative 'trim_box'require_relative 'template_finder'require 'mini_magick'adWUZma` ]   =  , + f 3 |rq)_*qIHrN<,eWKA@eend enend endend end end end eend end end return nil UI.verbose("No custom font specified for #{screenshot}, using the default one") end end end return font["font"] UI.verbose("Found a font with no list of supported languages, using this now") # No `supported` array, this will always be true else end end return font["font"] if screenshot.language == language font['supported'].each do |language| if font['supported'] fonts.each do |font| if fonts fonts = @config[key.to_s]['fonts'] return single_font if single_font single_font = @config[key.to_s]['font'] def font(key) # The font we want to use end return nil end end end return constant if color == constant.upcase.gsub(' ', '_') constant = Frameit::Color.const_get(c) Frameit::Color.constants.each do |c| unless color.nil? color = @config['frame'] def fetch_frame_color end return text text = @config[type.to_s]['text'] if @config[type.to_s] && @config[type.to_s]['text'] && @config[type.to_s]['text'].length > 0 # Ignore empty string # No string files, fallback to Framefile config UI.verbose("Falling back to text in Framefile.json as there was nothing specified in the #{type}.strings file") end return text_array.last if text_array && text_array.last.length > 0 # Ignore empty string text_array = parsed.find { |k, v| screenshot.path.upcase.include?(k.upcase) } parsed = StringsParser.parse(strings_path) if File.exist?(strings_path) strings_path = File.join(File.expand_path("../../../", screenshot.path), "#{type}.strings") unless File.exist?(strings_path) strings_path = File.join(File.expand_path("../../", screenshot.path), "#{type}.strings") unless File.exist?(strings_path) strings_path = File.join(File.expand_path("../", screenshot.path), "#{type}.strings") # Try to get it from a keyword.strings or title.strings file UI.user_error!("Valid parameters :keyword, :title") unless [:keyword, :title].include?(type) def fetch_text(type) # Fetches the title + keyword for this particular screenshot end results end results[key].crop(trim_box.string_format) # Crop image with (adjusted) trim box parameters in MiniMagick string format: end UI.verbose("Trim box for key \"#{key}\" is adjusted to align bottom: #{trim_box.json_string_format}") trim_box.height = bottom_vertical_trim_offset - trim_box.offset_y # Set the height of the trim box to the difference between vertical bottom and top offset: if (trim_box.offset_y + trim_box.height) < bottom_vertical_trim_offset # Check if the height needs to be adjusted to reach the bottom offset: end UI.verbose("Trim box for key \"#{key}\" is adjusted to align top: #{trim_box.json_string_format}") trim_box.offset_y = top_vertical_trim_offset # Change the vertical top offset to match that of the others: trim_box.height += trim_box.offset_y - top_vertical_trim_offset # Increase the height of the trim box with the difference in vertical top offset:ad]NMrh U v ) W % n    j i O 0eSSF ui_^('`8}Z end padding = padding.to_i unless padding.end_with?('%') padding = padding.split('x')[1] if padding.kind_of?(String) && padding.split('x').length == 2 padding = @config['padding'] def vertical_frame_padding # Vertical adding around the frames end return scale_padding(padding) end padding = padding.to_i unless padding.end_with?('%') padding = padding.split('x')[0] if padding.kind_of?(String) && padding.split('x').length == 2 padding = @config['padding'] def horizontal_frame_padding # Horizontal adding around the frames end image @image = put_device_into_background(background) end end @image.resize("#{frame_width}x") if frame_width < @image.width # If higher than the frame, the screenshot is cut off at the bottom # the screenshot size is only limited by width. else @image.resize("#{image_width}x#{image_height}") if image_width < @image.width || image_height < @image.height image_width = image_height * image_aspect_ratio image_height = [frame_height, image_width / image_aspect_ratio].min image_width = [frame_width, @image.width].min image_aspect_ratio = @image.width.to_f / @image.height.to_f # it may be limited either by the width or height of the frame # calculate the final size of the screenshot to resize in one go if @config['show_complete_frame'] frame_height = background.height - effective_text_height - vertical_frame_padding frame_width = background.width - horizontal_frame_padding * 2 # Decrease the size of the framed screenshot to fit into the defined padding + background put_into_frame resize_frame! if self.frame # we have no frame on le mac end background = put_title_into_background(background, @config['stack_title']) if @config['title'] self.space_to_device = vertical_frame_padding background = generate_background def complex_framing # more complex mode: background, frame and title end return (@config['background'] and (@config['title'] or @config['keyword'])) def is_complex_framing_mode? # Do we add a background and title as well? end @offset_information['offset'] = new_offset new_offset = "+#{x.round}+#{y.round}" y = hash.split("+")[2].to_f * multiplicator x = hash.split("+")[1].to_f * multiplicator hash = offset['offset'] # Format: "+133+50" def modify_offset(multiplicator) # we need to modify the offset information by a certain factor # since we resize the template images to have higher quality screenshots # the offset information is stored to work for the template images # this is used to correct the 1:1 offset information ######################################################################################### # Everything below is related to title, background, etc. and is not used in the easy mode ######################################################################################### end UI.user_error!("Could not find offset_information for '#{screenshot}'") end return @offset_information if @offset_information && (@offset_information['offset'] || @offset_information['offset']) @offset_information = @config['offset'] || Offsets.image_offset(screenshot).dup return @offset_information if @offset_information def offset end @image.rotate(rotation) frame.rotate(rotation) # Revert the rotation from abovead fuCk/ r j i K    y l L @ 6 . -  B A  &    S % ~vu;%YE=<" KA98 BZY<1 end keyword_top = device_top(background) / 2 - spacing_between_title_and_keyword / 2 - keyword.height else keyword_top = background.height - effective_text_height / 2 - (keyword.height + spacing_between_title_and_keyword + title.height) / 2 if title_below_image self.space_to_device += title.height + keyword.height + spacing_between_title_and_keyword + vertical_padding keyword_left_space = (background.width / 2.0 - keyword.width / 2.0).round title_left_space = (background.width / 2.0 - title.width / 2.0).round spacing_between_title_and_keyword = (actual_font_size('keyword') / 2) vertical_padding = vertical_frame_padding # assign padding to variable resize_text(keyword) resize_text(title) def put_title_into_background_stacked(background, title, keyword) # Add the title above or below the device end end text.resize("#{((1.0 / ratio) * text.width).round}x") # too large - resizing now if ratio > 1.0 ratio = width / (image.width.to_f - horizontal_frame_padding * 2) width = text.width def resize_text(text) end modify_offset(multiplicator) # modify the offset to properly insert the screenshot into the frame later frame.resize("#{new_frame_width.round}x") # resize it to the calculated width new_frame_width = multiplicator * frame.width # the new width for the frame multiplicator = (screenshot_width.to_f / offset['width'].to_f) # by how much do we have to change this? screenshot_width = self.screenshot.portrait? ? screenshot.size[0] : screenshot.size[1] def resize_frame! # Resize the frame as it's too low quality by default end return image end c.geometry("+#{left_space}+#{device_top(background)}") c.compose("Over") c.colorspace(colorspace) if colorspace colorspace = image.data["colorspace"] @image = background.composite(image, "png") do |c| left_space = (background.width / 2.0 - image.width / 2.0).round def put_device_into_background(background) end background end background.merge!(["-gravity", "center", "-crop", "#{screenshot.size[0]}x#{screenshot.size[1]}+0+0"]) # crop from center background.resize("#{screenshot.size[0]}x#{screenshot.size[1]}^") # `^` says it should fill area if background.height != screenshot.size[1] background = MiniMagick::Image.open(@config['background']) def generate_background # Returns a correctly sized background image end @title_below_image ||= @config['title_below_image'] def title_below_image end end end effective_text_height else background.height - effective_text_height - image.height if title_below_image @device_top ||= begin def device_top(background) end [space_to_device, title_min_height].max def effective_text_height end return padding * multi multi = 1.7 if self.screenshot.triple_density? multi = 1.0 end padding = ([image.width, image.height].min * padding.to_f * 0.01).ceil if padding.kind_of?(String) && padding.end_with?('%') def scale_padding(padding) end end height end height = ([image.width, image.height].min * height.to_f * 0.01).ceil if height.kind_of?(String) && height.end_with?('%') height = @config['title_min_height'] || 0 @title_min_height ||= begin def title_min_height # Minimum height for the title end return scale_padding(padding)adV]C|D:)!  [ Z 4   a G = d c , + m 5 {SIHkjOZI^TS wf^]? # The space between the keyword and the title end [@image.width * font_scale_factor].max.round UI.user_error!("Parameter 'font_scale_factor' can not be 0. Please provide a value larger than 0.0 (default = 0.1).") if font_scale_factor == 0.0 font_scale_factor = @config['font_scale_factor'] || 0.1 return font_size if !font_size.nil? && font_size > 0 font_size = @config[key.to_s]['font_size'] def actual_font_size(key) end background end c.geometry("+#{left_space}+#{title_top}") c.compose("Over") background = background.composite(title, "png") do |c| # Then, put the title on top of the screenshot next to the keyword end left_space += keyword.width + (keyword_padding * image_scale_factor) end c.geometry("+#{left_space}+#{title_top}") c.compose("Over") background = background.composite(keyword, "png") do |c| if keyword # First, put the keyword on top of the screenshot, if we have one end title_top = device_top(background) / 2 - title.height / 2 else title_top = background.height - effective_text_height / 2 - title.height / 2 if title_below_image self.space_to_device += actual_font_size('title') + vertical_padding left_space = (background.width / 2.0 - sum_width / 2.0).round vertical_padding = vertical_frame_padding # assign padding to variable end sum_width *= image_scale_factor keyword.resize("#{(image_scale_factor * keyword.width).round}x") if keyword title.resize("#{(image_scale_factor * title.width).round}x") UI.verbose("Text for image #{self.screenshot.path} is quite long, reducing font size by #{(100 * (1.0 - image_scale_factor)).round(1)}%") image_scale_factor = (1.0 / [ratio_horizontal, ratio_vertical].max) # If either is too large, resize with the maximum ratio: if ratio_horizontal > 1.0 || ratio_vertical > 1.0 ratio_vertical = title.height.to_f / effective_text_height # The fraction of the actual height of the images compared to the available space ratio_horizontal = sum_width / (image.width.to_f - horizontal_frame_padding * 2) # The fraction of the text images compared to the left and right padding image_scale_factor = 1.0 # default # Resize the 2 labels if they exceed the available space either horizontally or vertically: title_below_image = @config['title_below_image'] sum_width += keyword.width + keyword_padding if keyword sum_width = title.width # is used to calculate the ratio # sum_width: the width of both labels together including the space in-between end return background background = put_title_into_background_stacked(background, title, keyword) if stack_title && !keyword.nil? && !title.nil? && keyword.width > 0 && title.width > 0 title = text_images[:title] keyword = text_images[:keyword] text_images = build_text_images(image.width - 2 * horizontal_frame_padding, image.height - 2 * vertical_frame_padding, stack_title) def put_title_into_background(background, stack_title) end background end c.geometry("+#{title_left_space}+#{title_top}") c.compose("Over") background = background.composite(title, "png") do |c| # Place the title below the keyword end c.geometry("+#{keyword_left_space}+#{keyword_top}") c.compose("Over") background = background.composite(keyword, "png") do |c| # keyword title_top = keyword_top + keyword.height + spacing_between_title_and_keywordadcL/p Q   o \ [ " !  P 2  k_^<;8wkjyA TtS0/cb if trim_box.offset_y > top_vertical_trim_offset # When the vertical top offset is larger than the smallest vertical top offset, the trim box needs to be adjusted: # Determine the trim area by maintaining the same vertical top offset based on the smallest value from all trim boxes (top_vertical_trim_offset). # Adjust the trim box based on top_vertical_trim_offset and bottom_vertical_trim_offset to maintain the text baseline: trim_box = trim_boxes[key] # Get matching trim box: words.each do |key| # Crop text images: end trim_boxes[key] = trim_box # Store for the crop action: end bottom_vertical_trim_offset = trim_box.offset_y + trim_box.height if (trim_box.offset_y + trim_box.height) > bottom_vertical_trim_offset # Get the maximum bottom offset of the trim box, this is the top offset + height: end top_vertical_trim_offset = trim_box.offset_y if trim_box.offset_y < top_vertical_trim_offset # Get the minimum top offset of the trim box: trim_box = Frameit::Trimbox.new(calculated_trim_box) # Create a Trimbox object from the MiniMagick .identify string with syntax "x++": end b.format("%@") # CALCULATED: trim bounding box (without actually trimming), see: http://www.imagemagick.org/script/escape.php calculated_trim_box = text_image.identify do |b| # Hence retrieve the calculated trim bounding box without actually trimming: # Natively trimming the image with .trim will result in the loss of the common baseline between the text in all images when side-by-side (e.g. stack_title is false). results[key] = text_image end i.fill(@config[key.to_s]['color']) i.interline_spacing(interline_spacing) if interline_spacing i.draw("text 0,0 '#{text}'") i.pointsize(actual_font_size(key)) i.gravity("Center") i.weight(@config[key.to_s]['font_weight']) if @config[key.to_s]['font_weight'] i.font(current_font) if current_font text_image.combine_options do |i| # Add the actual title interline_spacing = @config['interline_spacing'] text.gsub!(/(?