require 'nokogiri' module GalleryItems JPEG_START_OF_FRAME = [0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF].freeze # Read just the dimensions from a local JPEG. The rendered gallery needs # aspect ratios to size each chronological pair without cropping either photo. def jpeg_dimensions(path) File.open(path, 'rb') do |file| return nil unless file.read(2) == "\xFF\xD8".b while (prefix = file.read(1)) next unless prefix.getbyte(0) == 0xFF marker = file.read(1)&.getbyte(0) break unless marker next if marker == 0xFF || marker == 0x01 || (0xD0..0xD7).cover?(marker) break if marker == 0xD9 || marker == 0xDA size = file.read(2)&.unpack1('n') break unless size && size >= 2 if JPEG_START_OF_FRAME.include?(marker) dimensions = file.read(5) return nil unless dimensions&.length == 5 height, width = dimensions.byteslice(1, 4).unpack('n2') return [width, height] if width.positive? && height.positive? return nil end file.seek(size - 2, IO::SEEK_CUR) end end nil rescue Errno::ENOENT, EOFError nil end def gallery_image_ratio(content) image = Nokogiri::HTML::DocumentFragment.parse(content).at_css('img') source = image&.[]('src') return 1.5 unless source&.start_with?('/img/arts/') path = File.join(File.expand_path('..', __dir__), source.delete_prefix('/')) width, height = jpeg_dimensions(path) return 1.5 unless width && height (width.to_f / height).round(4) end # Uploaded batches share one source document, but each photo is a gallery item. # Keeping this at render time also fixes batches that have already been published. def gallery_items(content) fragment = Nokogiri::HTML::DocumentFragment.parse(content) images = fragment.css('img') return [content] unless images.length > 1 && images.all? { |image| image['src'].to_s.start_with?('/img/arts/uploads/') && image.parent.name == 'p' } captions = fragment.css('.image-details').map(&:to_html).join images.each { |image| image.parent.remove } fragment.css('.image-details').each(&:remove) # Preserve the batch's existing anchor exactly once, before the first photo. prefix = fragment.to_html images.each_with_index.map do |image, index| "#{index.zero? ? prefix : ''}

#{image.to_html}

#{captions}" end end end Liquid::Template.register_filter(GalleryItems)