lib/write_xlsx/shape.rb in write_xlsx-0.62.0 vs lib/write_xlsx/shape.rb in write_xlsx-0.64.0

- old
+ new

@@ -3,20 +3,27 @@ module Writexlsx ############################################################################### # # Shape - A class for writing Excel shapes. # - # Used in conjunction with Excel::Writer::XLSX. + # Used in conjunction with WriteXLSX. # # Copyright 2000-2012, John McNamara, jmcnamara@cpan.org # Converted to ruby by Hideo NAKAMURA, cxn03651@msj.biglobe.ne.jp # class Shape - attr_reader :connect, :editAs, :type, :start, :start_index, :end, :end_index - attr_reader :tx_box, :fill, :rotation, :flip_h, :flip_v - attr_accessor :name + attr_reader :edit_as, :type, :drawing + attr_reader :tx_box, :fill, :line, :format + attr_reader :align, :valign + attr_accessor :name, :connect, :type, :id, :start, :end, :rotation + attr_accessor :flip_h, :flip_v, :adjustments, :palette, :text, :stencil + attr_accessor :row_start, :row_end, :column_start, :column_end + attr_accessor :x1, :x2, :y1, :y2, :x_abs, :y_abs, :start_index, :end_index + attr_accessor :x_offset, :y_offset, :width, :height, :scale_x, :scale_y + attr_accessor :width_emu, :height_emu, :element, :line_weight, :line_type + attr_accessor :start_side, :end_side def initialize(properties = {}) @writer = Package::XMLWriterSimple.new @name = nil @type = 'rect' @@ -26,11 +33,11 @@ # Is a Drawing. Always 0, since a single shape never fills an entire sheet. @drawing = 0 # OneCell or Absolute: options to move and/or size with cells. - @editAs = '' + @edit_as = nil # Auto-incremented, unless supplied by user. @id = 0 # Shape text (usually centered on shape geometry). @@ -68,11 +75,11 @@ # shape rotation (in degrees 0-360). @rotation = 0 # An alternate way to create a text box, because Excel allows it. # It is just a rectangle with text. - @tx_box = 0 + @tx_box = false # Shape outline colour, or 0 for noFill (default black). @line = '000000' # Line type: dash, sysDot, dashDot, lgDash, lgDashDot, lgDashDotDot. @@ -126,36 +133,20 @@ # Override default properties with passed arguments properties.each do |key, value| # Strip leading "-" from Tk style properties e.g. -color => 'red'. k = key.to_s.sub(/^-/, '') self.instance_variable_set("@#{key}", value) -=begin - if key.to_s == 'format' - @format = value - elsif value.respond_to?(:coerce) - eval "@#{k} = #{value}" - else - eval "@#{k} = %!#{value}!" - end -=end end end # # Set the shape adjustments array (as a reference). # def adjustments=(args) @adjustments = *args end - def [](attr) - self.instance_variable_get("@#{attr}") - end - - def []=(attr, value) - self.instance_variable_set("@#{attr}", value) - end # # Convert from an Excel internal colour index to a XML style #RRGGBB index # based on the default or user defined values in the Workbook palette. # Note: This version doesn't add an alpha channel. # @@ -165,8 +156,168 @@ # Palette is passed in from the Workbook class. rgb = @palette[idx] sprintf("%02X%02X%02X", *rgb) + end + + # + # Calculate the vertices that define the position of a shape object within + # the worksheet in EMUs. Save the vertices with the object. + # + # The vertices are expressed as English Metric Units (EMUs). There are 12,700 + # EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel. + # + def calc_position_emus(worksheet) + c_start, r_start, xx1, yy1, c_end, r_end, xx2, yy2, x_abslt, y_abslt = + worksheet.position_object_pixels( + @column_start, + @row_start, + @x_offset, + @y_offset, + @width * @scale_x, + @height * @scale_y, + @drawing + ) + + # Now that x2/y2 have been calculated with a potentially negative + # width/height we use the absolute value and convert to EMUs. + @width_emu = (@width * 9_525).abs.to_i + @height_emu = (@height * 9_525).abs.to_i + + @column_start = c_start.to_i + @row_start = r_start.to_i + @column_end = c_end.to_i + @row_end = r_end.to_i + + # Convert the pixel values to EMUs. See above. + @x1 = (xx1 * 9_525).to_i + @y1 = (yy1 * 9_525).to_i + @x2 = (xx2 * 9_525).to_i + @y2 = (yy2 * 9_525).to_i + @x_abs = (x_abslt * 9_525).to_i + @y_abs = (y_abslt * 9_525).to_i + end + + def set_position(row_start, column_start, x_offset, y_offset, x_scale, y_scale) + @row_start = row_start + @column_start = column_start + @x_offset = x_offset || 0 + @y_offset = y_offset || 0 + + # Override shape scale if supplied as an argument. Otherwise, use the + # existing shape scale factors. + @scale_x = x_scale if x_scale + @scale_y = y_scale if y_scale + end + + # + # Re-size connector shapes if they are connected to other shapes. + # + def auto_locate_connectors(shapes, shape_hash) + # Valid connector shapes. + connector_shapes = { + :straightConnector => 1, + :Connector => 1, + :bentConnector => 1, + :curvedConnector => 1, + :line => 1 + } + + shape_base = @type.chop.to_sym # Remove the number of segments from end of type. + @connect = connector_shapes[shape_base] ? 1 : 0 + return if @connect == 0 + + # Both ends have to be connected to size it. + return if @start == 0 && @end == 0 + + # Both ends need to provide info about where to connect. + return if @start_side == 0 && @end_side == 0 + + sid = @start + eid = @end + + slink_id = shape_hash[sid] || 0 + sls = shapes.fetch(slink_id, Shape.new) + elink_id = shape_hash[eid] || 0 + els = shapes.fetch(elink_id, Shape.new) + + # Assume shape connections are to the middle of an object, and + # not a corner (for now). + connect_type = @start_side + @end_side + smidx = sls.x_offset + sls.width / 2 + emidx = els.x_offset + els.width / 2 + smidy = sls.y_offset + sls.height / 2 + emidy = els.y_offset + els.height / 2 + netx = (smidx - emidx).abs + nety = (smidy - emidy).abs + + if connect_type == 'bt' + sy = sls.y_offset + sls.height + ey = els.y_offset + + @width = (emidx - smidx).to_i.abs + @x_offset = [smidx, emidx].min.to_i + @height = + (els.y_offset - (sls.y_offset + sls.height)).to_i.abs + @y_offset = + [sls.y_offset + sls.height, els.y_offset].min.to_i + @flip_h = smidx < emidx ? 1 : 0 + @rotation = 90 + + if sy > ey + @flip_v = 1 + + # Create 3 adjustments for an end shape vertically above a + # start @ Adjustments count from the upper left object. + if @adjustments.empty? + @adjustments = [-10, 50, 110] + end + @type = 'bentConnector5' + end + elsif connect_type == 'rl' + @width = + (els.x_offset - (sls.x_offset + sls.width)).to_i.abs + @height = (emidy - smidy).to_i.abs + @x_offset = + [sls.x_offset + sls.width, els.x_offset].min + @y_offset = [smidy, emidy].min + + @flip_h = 1 if smidx < emidx && smidy > emidy + @flip_h = 1 if smidx > emidx && smidy < emidy + + if smidx > emidx + # Create 3 adjustments for an end shape to the left of a + # start @ + if @adjustments.empty? + @adjustments = [-10, 50, 110] + end + @type = 'bentConnector5' + end + end + end + + # + # Check shape attributes to ensure they are valid. + # + def validate(index) + unless %w[l ctr r just].include?(@align) + raise "Shape #{index} (#{@type}) alignment (#{@align}) not in ['l', 'ctr', 'r', 'just']\n" + end + + unless %w[t ctr b].include?(@valign) + raise "Shape #{index} (#{@type}) vertical alignment (#{@valign}) not in ['t', 'ctr', 'v']\n" + end + end + + def dimensions + [ + @column_start, @row_start, + @x1, @y1, + @column_end, @row_end, + @x2, @y2, + @x_abs, @y_abs, + @width_emu, @height_emu + ] end end end