# frozen_string_literal: true require 'rmagick' module Lipstick module Images # Creates an AAF standard email banner, with three elements, positioned as # follows: # # 1. AAF Logo. Vertically centered and left-aligned within the content box, # and with a blank space to the right. # 2. Application title. Vertically centered, and left-aligned to the logo's # right margin. # 3. Environment string. Positioned 10 pixels below the app title, and # left-aligned to the logo's right margin. # # The "box" containing these three elements is horizontally centered in the # resulting image. # # Text metrics work as follows, using "Alimony" as the example word: # # * (x, y) is the position of the bottom left tip of 'A' # * `ascent` is the letter height from that point (i.e. the height of 'A') # * `descent` is a negative value; the letter height *below* that point # (i.e. the height of the 'tail' of 'y') # * `height` is greater than `(ascent - descent)`, making it useless for our # purposes here. # # For image compositing, (x, y) is the position of the top-left corner. class EmailBanner def initialize(image:, title:, environment:, background_color:, resize_to:) @title = title @environment = environment @image = image @background_color = background_color @resize_to = resize_to end def to_png bgcolor = @background_color canvas = Magick::ImageList.new canvas.new_image(WIDTH, HEIGHT) do |f| f.format = 'PNG' f.background_color = bgcolor end annotate_title(canvas) annotate_environment(canvas) canvas.composite!(logo, logo_x, logo_y, Magick::SrcOverCompositeOp) canvas.to_blob end private WIDTH = 600 HEIGHT = 150 MID_Y = HEIGHT / 2 private_constant :WIDTH, :HEIGHT, :MID_Y def title_format @title_format ||= Magick::Draw.new do |title| title.font_family = 'Helvetica' title.pointsize = 24 title.gravity = Magick::ForgetGravity title.fill = 'white' end end def environment_format @environment_format ||= Magick::Draw.new do |title| title.font_family = 'Helvetica' title.pointsize = 20 title.gravity = Magick::ForgetGravity title.fill = '#dd7727' end end def blank_environment? !@environment&.present? end def annotate_title(canvas) title_format.annotate(canvas, 0, 0, title_x, title_y, @title) end def annotate_environment(canvas) return if blank_environment? environment_format.annotate(canvas, 0, 0, env_x, env_y, @environment) end def logo @logo ||= Magick::Image.read(@image.pathname.to_s).first .resize_to_fill(@resize_to[0], @resize_to[1]) end def gap @gap ||= environment_format.get_type_metrics('AAF').width end def logo_w logo.bounding_box.width end def logo_x margin_x end # y position to vertically center logo def logo_y ((HEIGHT - logo.bounding_box.height) / 2).round end def title_metrics title_format.get_type_metrics(@title) end def env_metrics return OpenStruct.new(width: 0, height: 0) if blank_environment? environment_format.get_type_metrics(@environment) end # Halve the total margin to horizontally center the whole content area. def margin_x # | whitespace | logo | whitespace | text | whitespace | # |<---------->| ((WIDTH - total_w) / 2).round end def title_x # | whitespace | logo | whitespace | text | whitespace | # ^ this position margin_x + logo_w + gap end # y position to vertically center title def title_y MID_Y + (title_metrics.ascent / 2).round end def env_x title_x end # `ascent` is a positive value, and `descent` is a negative value. They # describe the full height of the letters. See RMagick documentation. def env_y MID_Y + title_metrics.ascent - title_metrics.descent + 10 end # Center using the largest width for aesthetics. def text_w [title_metrics.width, env_metrics.width].max end def total_w # | whitespace | logo | whitespace | text | whitespace | # |<------------------------>| logo_w + gap + text_w end end end end