class Jekyll::Picture

Constants

VERSION

Public Class Methods

new(tag_name, markup, tokens) click to toggle source
Calls superclass method
# File lib/jekyll-plugin-gkpicturetag.rb, line 30
def initialize(tag_name, markup, tokens)
  @markup = markup
  super
end

Public Instance Methods

generate_image(instance, site_source, site_dest, image_source, image_dest, baseurl, debug) click to toggle source

<picture class=“img-responsive”>

<source srcset="/img/illus/home-future-workplace.jpg, /img/illus/home-future-workplace@2x.jpg 2x">      
<img class="img-responsive" src="/img/illus/home-future-workplace.jpg" alt="Future Workplace">

</picture>

# File lib/jekyll-plugin-gkpicturetag.rb, line 176
def generate_image(instance, site_source, site_dest, image_source, image_dest, baseurl, debug)
  begin
    digest = Digest::MD5.hexdigest(File.read(File.join(site_source, image_source, instance[:src]))).slice!(0..5)
  rescue Errno::ENOENT
    warn "Warning:".yellow + " source image #{instance[:src]} is missing."
    return ""
  end

  image_dir = File.dirname(instance[:src])
  ext = File.extname(instance[:src])
  basename = File.basename(instance[:src], ext)

  size = FastImage.size(File.join(site_source, image_source, instance[:src]))
  orig_width = size[0]
  orig_height = size[1]
  orig_ratio = orig_width*1.0/orig_height

  gen_width = if instance[:width]
                instance[:width].to_f
              elsif instance[:height]
                orig_ratio * instance[:height].to_f
              else
                orig_width
              end
  gen_height = if instance[:height]
                 instance[:height].to_f
               elsif instance[:width]
                 instance[:width].to_f / orig_ratio
               else
                 orig_height
               end
  gen_ratio = gen_width/gen_height

  # Don't allow upscaling. If the image is smaller than the requested dimensions, recalculate.
  if orig_width < gen_width || orig_height < gen_height
    undersize = true
    gen_width = if orig_ratio < gen_ratio then orig_width else orig_height * gen_ratio end
    gen_height = if orig_ratio > gen_ratio then orig_height else orig_width/gen_ratio end
  end

  gen_width = gen_width / 2
  gen_height = gen_height / 2

  gen_name = "#{basename}-#{gen_width.round}by#{gen_height.round}-#{digest}#{ext}"
  gen_dest_dir = File.join(site_dest, image_dest, image_dir)
  gen_dest_file = File.join(gen_dest_dir, gen_name)

  # Generate resized files
  unless File.exists?(gen_dest_file)

    warn "Warning:".yellow + " #{instance[:src]} is smaller than the requested output file. It will be resized without upscaling." if undersize

    #  If the destination directory doesn't exist, create it
    FileUtils.mkdir_p(gen_dest_dir) unless File.exist?(gen_dest_dir)

    # Let people know their images are being generated
    puts "Generating #{gen_name}" if !debug

    image = MiniMagick::Image.open(File.join(site_source, image_source, instance[:src]))
    # Scale and crop
    image.combine_options do |i|
      i.resize "#{gen_width}x#{gen_height}^"
      i.gravity "center"
      i.crop "#{gen_width}x#{gen_height}+0+0"
    end

    image.write gen_dest_file
  end

  # Return path relative to the site root for html
  Pathname.new(File.join(baseurl, image_dest, image_dir, gen_name)).cleanpath
end
generate_image2x(instance, site_source, site_dest, image_source, image_dest, baseurl, debug) click to toggle source
# File lib/jekyll-plugin-gkpicturetag.rb, line 249
def generate_image2x(instance, site_source, site_dest, image_source, image_dest, baseurl, debug)
  begin
    digest = Digest::MD5.hexdigest(File.read(File.join(site_source, image_source, instance[:src]))).slice!(0..5)
  rescue Errno::ENOENT
    warn "Warning:".yellow + " source image #{instance[:src]} is missing."
    return ""
  end

  image_dir = File.dirname(instance[:src])
  ext = File.extname(instance[:src])
  basename = File.basename(instance[:src], ext)

  size = FastImage.size(File.join(site_source, image_source, instance[:src]))
  orig_width = size[0]
  orig_height = size[1]
  orig_ratio = orig_width*1.0/orig_height

  gen_width = if instance[:width]
                instance[:width].to_f
              elsif instance[:height]
                orig_ratio * instance[:height].to_f
              else
                orig_width
              end
  gen_height = if instance[:height]
                 instance[:height].to_f
               elsif instance[:width]
                 instance[:width].to_f / orig_ratio
               else
                 orig_height
               end
  gen_ratio = gen_width/gen_height

  # Don't allow upscaling. If the image is smaller than the requested dimensions, recalculate.
  if orig_width < gen_width || orig_height < gen_height
    undersize = true
    gen_width = if orig_ratio < gen_ratio then orig_width else orig_height * gen_ratio end
    gen_height = if orig_ratio > gen_ratio then orig_height else orig_width/gen_ratio end
  end

  gen_name = "#{basename}-#{gen_width.round}by#{gen_height.round}-#{digest}#{ext}"
  gen_dest_dir = File.join(site_dest, image_dest, image_dir)
  gen_dest_file = File.join(gen_dest_dir, gen_name)

  # Generate resized files
  unless File.exists?(gen_dest_file)

    warn "Warning:".yellow + " #{instance[:src]} is smaller than the requested output file. It will be resized without upscaling." if undersize

    #  If the destination directory doesn't exist, create it
    FileUtils.mkdir_p(gen_dest_dir) unless File.exist?(gen_dest_dir)

    # Let people know their images are being generated
    puts "Generating #{gen_name}" if !debug

    image = MiniMagick::Image.open(File.join(site_source, image_source, instance[:src]))
    # Scale and crop
    image.combine_options do |i|
      i.resize "#{gen_width}x#{gen_height}^"
      i.gravity "center"
      i.crop "#{gen_width}x#{gen_height}+0+0"
    end

    image.write gen_dest_file
  end

  # Return path relative to the site root for html
  Pathname.new(File.join(baseurl, image_dest, image_dir, gen_name)).cleanpath
end
render(context) click to toggle source
# File lib/jekyll-plugin-gkpicturetag.rb, line 35
def render(context)

  # Render any liquid variables in tag arguments and unescape template code
  render_markup = Liquid::Template.parse(@markup).render(context).gsub(/\\\{\\\{|\\\{\\%/, '\{\{' => '{{', '\{\%' => '{%')

  # Gather settings
  site = context.registers[:site]
  debug = site.config['debug']      
  settings = site.config['picture']
  url = site.config['url']
  markup = /^(?:(?<preset>[^\s.:\/]+)\s+)?(?<image_src>[^\s]+\.[a-zA-Z0-9]{3,4})\s*(?<source_src>(?:(source_[^\s.:\/]+:\s+[^\s]+\.[a-zA-Z0-9]{3,4})\s*)+)?(?<html_attr>[\s\S]+)?$/.match(render_markup)
  preset = settings['presets'][ markup[:preset] ] || settings['presets']['default']

  raise "Picture Tag can't read this tag. Try {% picture [preset] path/to/img.jpg [source_key: path/to/alt-img.jpg] [attr=\"value\"] %}." unless markup

  # Assign defaults
  settings['source'] ||= '.'
  settings['output'] ||= 'generated'
  settings['markup'] ||= 'picture'

  # Prevent Jekyll from erasing our generated files
  site.config['keep_files'] << settings['output'] unless site.config['keep_files'].include?(settings['output'])

  # Deep copy preset for single instance manipulation
  instance = Marshal.load(Marshal.dump(preset))

  # Process alternate source images
  source_src = if markup[:source_src]
                 Hash[ *markup[:source_src].gsub(/:/, '').split ]
               else
                 {}
               end

  # Process html attributes
  html_attr = if markup[:html_attr]
                Hash[ *markup[:html_attr].scan(/(?<attr>[^\s="]+)(?:="(?<value>[^"]+)")?\s?/).flatten ]
              else
                {}
              end

  if instance['attr']
    html_attr = instance.delete('attr').merge(html_attr)
  end

  html_attr_string = html_attr.inject('') { |string, attrs|
    if attrs[1]
      string << "#{attrs[0]}=\"#{attrs[1]}\" "
    else
      string << "#{attrs[0]} "
    end
  }

  # Prepare ppi variables
  ppi = if instance['ppi'] then instance.delete('ppi').sort.reverse else nil end
  # this might work??? ppi = instance.delete('ppi'){ |ppi|  [nil] }.sort.reverse
  ppi_sources = {}

  # Switch width and height keys to the symbols that generate_image() expects
  begin
    instance.each { |key, source|
      # raise "Preset #{key} is missing a width or a height" if !source['width'] and !source['height']
      instance[key][:width] = instance[key].delete('width') / 2 if source['width']
      instance[key][:height] = instance[key].delete('height') / 2 if source['height']
    }
  rescue
    # warn "Info (Picture-Tag): ".yellow + "No width and height. Take original size."
  end

  # Store keys in an array for ordering the instance sources
  source_keys = instance.keys
  # used to escape markdown parsing rendering below
  markdown_escape = "\ "

  # Raise some exceptions before we start expensive processing
  raise "Picture Tag can't find the \"#{markup[:preset]}\" preset. Check picture: presets in _config.yml for a list of presets." unless preset
  raise "Picture Tag can't find this preset source. Check picture: presets: #{markup[:preset]} in _config.yml for a list of sources." unless (source_src.keys - source_keys).empty?

  # Process instance
  # Add image paths for each source
  instance.each_key { |key|
    instance[key][:src] = source_src[key] || markup[:image_src]
  }

  # Construct ppi sources
  # Generates -webkit-device-ratio and resolution: dpi media value for cross browser support
  # Reference: http://www.brettjankord.com/2012/11/28/cross-browser-retinahigh-resolution-media-queries/
  if ppi
    instance.each { |key, source|
      ppi.each { |p|
        if p != 1
          ppi_key = "#{key}-x#{p}"

          ppi_sources[ppi_key] = {
              :width => if source[:width] then (source[:width].to_f * p).round else nil end,
              :height => if source[:height] then (source[:height].to_f * p).round else nil end,
              'media' => if source['media']
                           "#{source['media']} and (-webkit-min-device-pixel-ratio: #{p}), #{source['media']} and (min-resolution: #{(p * 96).round}dpi)"
                         else
                           "(-webkit-min-device-pixel-ratio: #{p}), (min-resolution: #{(p * 96).to_i}dpi)"
                         end,
              :src => source[:src]
          }

          # Add ppi_key to the source keys order
          source_keys.insert(source_keys.index(key), ppi_key)
        end
      }
    }
    instance.merge!(ppi_sources)
  end

  # Generate resized images
  instance.each { |key, source|
    instance[key][:generated_src] = generate_image(source, site.source, site.dest, settings['source'], settings['output'], site.config["baseurl"], debug)
    instance[key][:generated2x_src] = generate_image2x(source, site.source, site.dest, settings['source'], settings['output'], site.config["baseurl"], debug)
  }

  # Construct and return tag
  source_tags = ''
  source_keys.each do |source|
    warn "Info (Picture-Tag): ".yellow + "" + instance[source][:generated_src].to_s.green + "\n                  | ".yellow + instance[source][:generated2x_src].to_s.blue if debug
    media = " media=\"#{instance[source]['media']}\"" unless source == 'source_default'
    source_tags += "#{markdown_escape * 4}<source srcset=\"#{instance[source][:generated_src]}, #{instance[source][:generated2x_src]} 2x\"#{media}>\n"
  end

  # Note: we can't indent html output because markdown parsers will turn 4 spaces into code blocks
  # Note: Added backslash+space escapes to bypass markdown parsing of indented code below -WD
  picture_tag = "<picture>\n"\
                "#{source_tags}"\
                "#{markdown_escape * 4}<img src=\"#{instance['source_default'][:generated_src]}\" #{html_attr_string}>\n"\
                "#{markdown_escape * 2}</picture>\n"

  # Return the markup!
  picture_tag
end