# frozen_string_literal: true
module Axlsx
# a Pic object represents an image in your worksheet
# Worksheet#add_image is the recommended way to manage images in your sheets
# @see Worksheet#add_image
class Pic
include Axlsx::OptionsParser
# Creates a new Pic(ture) object
# @param [Anchor] anchor the anchor that holds this image
# @option options [Boolean] :remote indicates if image_src is a remote URI
# @option options [String] :name
# @option options [String] :descr
# @option options [String] :image_src
# @option options [Array] :start_at
# @option options [Integer] :width
# @option options [Integer] :height
# @option options [Float] :opacity - set the picture opacity, accepts a value between 0.0 and 1.0
def initialize(anchor, options = {})
@anchor = anchor
@hyperlink = nil
@anchor.drawing.worksheet.workbook.images << self
@remote = options[:remote]
parse_options options
start_at(*options[:start_at]) if options[:start_at]
yield self if block_given?
@picture_locking = PictureLocking.new(options)
@opacity = (options[:opacity] * 100000).round if options[:opacity]
end
# allowed mime types
ALLOWED_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze
# The name to use for this picture
# @return [String]
attr_reader :name
# A description of the picture
# @return [String]
attr_reader :descr
# The path to the image you want to include
# Only local images are supported at this time.
# @return [String]
attr_reader :image_src
# The anchor for this image
# @return [OneCellAnchor]
attr_reader :anchor
# The picture locking attributes for this picture
attr_reader :picture_locking
attr_reader :hyperlink
# Picture opacity
# @return [Integer]
attr_reader :opacity
# Flag for remote picture (from URI)
# @return [Boolean]
attr_reader :remote
# sets or updates a hyperlink for this image.
# @param [String] v The href value for the hyper link
# @option options @see Hyperlink#initialize All options available to the Hyperlink class apply - however href will be overridden with the v parameter value.
def hyperlink=(v, options = {})
options[:href] = v
if hyperlink.is_a?(Hyperlink)
options.each do |o|
hyperlink.send("#{o[0]}=", o[1]) if hyperlink.respond_to? "#{o[0]}="
end
else
@hyperlink = Hyperlink.new(self, options)
end
hyperlink
end
def image_src=(v)
Axlsx.validate_string(v)
if remote?
RegexValidator.validate('Pic.image_src', /\A#{URI::DEFAULT_PARSER.make_regexp}\z/, v)
RestrictionValidator.validate 'Pic.image_src', ALLOWED_MIME_TYPES, MimeTypeUtils.get_mime_type_from_uri(v)
else
RestrictionValidator.validate 'Pic.image_src', ALLOWED_MIME_TYPES, MimeTypeUtils.get_mime_type(v)
raise ArgumentError, "File does not exist" unless File.exist?(v)
end
@image_src = v
end
# @see name
def name=(v) Axlsx.validate_string(v); @name = v; end
# @see descr
def descr=(v) Axlsx.validate_string(v); @descr = v; end
# @see remote
def remote=(v) Axlsx.validate_boolean(v); @remote = v; end
def remote?
remote == 1 || remote.to_s == 'true'
end
# The file name of image_src without any path information
# @return [String]
def file_name
File.basename(image_src) unless remote? || image_src.nil?
end
# returns the extension of image_src without the preceeding '.'
# @return [String]
def extname
File.extname(image_src).delete('.') unless image_src.nil?
end
# The index of this image in the workbooks images collections
# @return [Index]
def index
@anchor.drawing.worksheet.workbook.images.index(self)
end
# The part name for this image used in serialization and relationship building
# @return [String]
def pn
format(IMAGE_PN, index + 1, extname)
end
# The relationship object for this pic.
# @return [Relationship]
def relationship
if remote?
Relationship.new(self, IMAGE_R, image_src.to_s, target_mode: :External)
else
Relationship.new(self, IMAGE_R, "../#{pn}")
end
end
# providing access to the anchor's width attribute
# @see OneCellAnchor.width
def width
return unless @anchor.is_a?(OneCellAnchor)
@anchor.width
end
# @see width
def width=(v)
use_one_cell_anchor unless @anchor.is_a?(OneCellAnchor)
@anchor.width = v
end
# providing access to update the anchor's height attribute
# @see OneCellAnchor.width
# @note this is a noop if you are using a TwoCellAnchor
def height
@anchor.height
end
# @see height
# @note This is a noop if you are using a TwoCellAnchor
def height=(v)
use_one_cell_anchor unless @anchor.is_a?(OneCellAnchor)
@anchor.height = v
end
# This is a short cut method to set the start anchor position
# If you need finer granularity in positioning use
# graphic_frame.anchor.from.colOff / rowOff
# @param [Integer] x The column
# @param [Integer] y The row
# @return [Marker]
def start_at(x, y = nil)
@anchor.start_at x, y
@anchor.from
end
# noop if not using a two cell anchor
# @param [Integer] x The column
# @param [Integer] y The row
# @return [Marker]
def end_at(x, y = nil)
use_two_cell_anchor unless @anchor.is_a?(TwoCellAnchor)
@anchor.end_at x, y
@anchor.to
end
# Serializes the object
# @param [String] str
# @return [String]
def to_xml_string(str = +'')
str << ''
str << ''
str << ''
hyperlink.to_xml_string(str) if hyperlink.is_a?(Hyperlink)
str << ''
picture_locking.to_xml_string(str)
str << ''
str << ''
str << relationship_xml_portion
if opacity
str << ""
end
str << ''
str << ''
str << ''
str << ''
end
private
# Return correct xml relationship string portion
def relationship_xml_portion
if remote?
(+'')
else
(+'')
end
end
# Changes the anchor to a one cell anchor.
def use_one_cell_anchor
return if @anchor.is_a?(OneCellAnchor)
new_anchor = OneCellAnchor.new(@anchor.drawing, start_at: [@anchor.from.col, @anchor.from.row])
swap_anchor(new_anchor)
end
# changes the anchor type to a two cell anchor
def use_two_cell_anchor
return if @anchor.is_a?(TwoCellAnchor)
new_anchor = TwoCellAnchor.new(@anchor.drawing, start_at: [@anchor.from.col, @anchor.from.row])
swap_anchor(new_anchor)
end
# refactoring of swapping code, law of demeter be damned!
def swap_anchor(new_anchor)
new_anchor.drawing.anchors.delete(new_anchor)
@anchor.drawing.anchors[@anchor.drawing.anchors.index(@anchor)] = new_anchor
new_anchor.instance_variable_set :@object, @anchor.object
@anchor = new_anchor
end
end
end