lolcommits/lolcommits-loltext

View on GitHub
lib/lolcommits/plugin/loltext.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

require 'lolcommits/plugin/base'

module Lolcommits
  module Plugin
    class Loltext < Base

      DEFAULT_FONT_PATH = File.join(
        File.dirname(__FILE__), "../../../vendor/fonts/Impact.ttf"
      ).freeze

      # Returns position(s) of when this plugin should run during the capture
      # process. We want to add text to the image after capturing, but before
      # capture is ready for sharing.
      #
      # @return [Array] the position(s) (:post_capture)
      #
      def self.runner_order
        [:post_capture]
      end

      ##
      # Returns true/false indicating if the plugin is enabled or not.
      # Enabled by default (if no configuration exists)
      #
      # @return [Boolean] true/false indicating if plugin is enabled
      #
      def enabled?
        configuration.empty? || super
      end

      ##
      # Returns true/false indicating if the plugin has been correctly
      # configured.
      #
      # Valid by default (if no configuration exists)
      #
      # @return [Boolean] true/false indicating if plugin is correct configured
      #
      def valid_configuration?
        configuration.empty? || super
      end

      ##
      # Prompts the user to configure text options.
      #
      # @return [Hash] a hash of configured plugin options
      #
      def configure_options!
        puts '---------------------------------------------------------------'
        puts '  LolText options '
        puts ''
        puts '  * any blank options will use the (default)'
        puts '  * always use the full absolute path to fonts'
        puts '  * valid text positions are NE, NW, SE, SW, S, C (centered)'
        puts '  * colors can be hex #FC0 value or a string \'white\''
        puts '      - use `none` for no stroke color'
        puts '  * overlay fills your image with a random color'
        puts '      - set one or more overlay_colors with a comma seperator'
        puts '      - overlay_percent (0-100) sets the fill colorize strength'
        puts '---------------------------------------------------------------'

        super
      end

      ##
      #
      # Post-capture hook, runs after lolcommits captures a snapshot.
      #
      # Annotate runner overlay with message, sha, optional border,
      # and semi-transparent colored background (based on config)
      #
      def run_post_capture
        debug "creating annotation overlay with MiniMagick"
        if config_option(:overlay, :enabled)
          color = config_option(:overlay, :overlay_colors).split(',').map(&:strip).sample
          overlay_percent = config_option(:overlay, :overlay_percent).to_f
          debug "adding colorized overlay (#{color} at #{overlay_percent}% opacity)"
          overlay_percent = (255 * (overlay_percent/100)).round

          runner.overlay.combine_options do |c|
            c.fill "#{color}#{sprintf('%02X',overlay_percent)}"
            c.draw "color 0,0 reset"
          end
        end

        if config_option(:border, :enabled)
          debug "adding border (#{config_option(:border, :size)}px #{config_option(:border, :color)})"
          # mogrify doesn't allow compose, format forces use of convert
          runner.overlay.format("PNG32") do |c|
            c.compose "Copy"
            c.shave config_option(:border, :size)
            c.bordercolor config_option(:border, :color)
            c.border config_option(:border, :size)
          end
        end

        annotate(runner.overlay, :message, clean_msg(runner.message))
        annotate(runner.overlay, :sha, runner.sha)
      end

      private

      def annotate(image, type, string)
        debug("annotating #{type} text (#{string})")

        transformed_position = position_transform(config_option(type, :position))
        annotate_location = '0'

        padded_annotation = 10
        if config_option(:border, :enabled)
          padded_annotation = padded_annotation + config_option(:border, :size)
        end

        # move all positions off the edge of the image
        case transformed_position
        when "South"
          annotate_location = "+0+#{padded_annotation}"
        when "NorthWest", "SouthWest", "NorthEast", "SouthEast"
          annotate_location = "+#{padded_annotation}+#{padded_annotation}"
        end

        string.upcase! if config_option(type, :uppercase)

        image.combine_options do |c|
          c.strokewidth 2
          c.interline_spacing(-(config_option(type, :size) / 5))
          c.stroke config_option(type, :stroke_color)
          c.fill config_option(type, :color)
          c.gravity transformed_position
          c.pointsize config_option(type, :size)
          c.font config_option(type, :font)
          c.annotate annotate_location, string
        end
      rescue => error
        debug("mogrify annotate returned non-zero status: #{error.message}")
      end

      # default text styling and positions
      def default_options
        {
          message: {
            color: 'white',
            font: DEFAULT_FONT_PATH,
            position: 'SW',
            size: 48,
            stroke_color: 'black',
            uppercase: false
          },
          sha: {
            color: 'white',
            font: DEFAULT_FONT_PATH,
            position: 'NE',
            size: 32,
            stroke_color: 'black',
            uppercase: false
          },
          overlay: {
            enabled: false,
            overlay_colors: [
              '#2e4970', '#674685', '#ca242f', '#1e7882', '#2884ae', '#4ba000',
              '#187296', '#7e231f', '#017d9f', '#e52d7b', '#0f5eaa', '#e40087',
              '#5566ac', '#ed8833', '#f8991c', '#408c93', '#ba9109'
            ].join(','),
            overlay_percent: 50
          },
          border: {
            enabled: false,
            color: 'white',
            size: 10,
          }
        }
      end

      # explode psuedo-names for text positioning
      def position_transform(position)
        case position
        when 'NE'
          'NorthEast'
        when 'NW'
          'NorthWest'
        when 'SE'
          'SouthEast'
        when 'SW'
          'SouthWest'
        when 'C'
          'Center'
        when 'S'
          'South'
        end
      end

      # do whatever is required to commit message to get it clean and ready for
      # imagemagick
      def clean_msg(text)
        wrapped_text = word_wrap text
        escape_quotes wrapped_text
        escape_ats wrapped_text
      end

      # conversion for quotation marks to avoid shell interpretation
      def escape_quotes(text)
        text.gsub(/"/, "''")
      end

      def escape_ats(text)
        text.gsub(/@/, '\@')
      end

      # convenience method for word wrapping
      # based on https://github.com/cmdrkeene/memegen/blob/master/lib/meme_generator.rb
      def word_wrap(text, col = 27)
        wrapped = text.gsub(/(.{1,#{col + 4}})(\s+|\Z)/, "\\1\n")
        wrapped.chomp!
      end
    end
  end
end