# -*- coding: utf-8 -*- require 'write_xlsx/package/xml_writer_simple' require 'write_xlsx/utility' module Writexlsx class Drawing include Writexlsx::Utility attr_writer :embedded, :orientation def initialize @writer = Package::XMLWriterSimple.new @drawings = [] @embedded = false @orientation = false end def xml_str @writer.string end def set_xml_writer(filename) @writer.set_xml_writer(filename) end # # Assemble and write the XML file. # def assemble_xml_file @writer.xml_decl # Write the xdr:wsDr element. write_drawing_workspace if @embedded index = 0 @drawings.each do |dimensions| # Write the xdr:twoCellAnchor element. index += 1 write_two_cell_anchor(index, *(dimensions.flatten)) end else index = 0 # Write the xdr:absoluteAnchor element. index += 1 write_absolute_anchor(index) end @writer.end_tag('xdr:wsDr') @writer.crlf @writer.close end # # Add a chart, image or shape sub object to the drawing. # def add_drawing_object(*args) @drawings << args.flatten end private # # Write the element. # def write_drawing_workspace schema = 'http://schemas.openxmlformats.org/drawingml/' attributes = [ 'xmlns:xdr', "#{schema}2006/spreadsheetDrawing", 'xmlns:a', "#{schema}2006/main" ] @writer.start_tag('xdr:wsDr', attributes) end # # Write the element. # def write_two_cell_anchor(*args) index, type, col_from, row_from, col_from_offset, row_from_offset, col_to, row_to, col_to_offset, row_to_offset, col_absolute, row_absolute, width, height, description, shape = args attributes = [] # Add attribute for images. attributes << :editAs << 'oneCell' if type == 2 # Add attribute for shapes. attributes << :editAs << shape.edit_as if shape && !shape.edit_as.nil? @writer.tag_elements('xdr:twoCellAnchor', attributes) do # Write the xdr:from element. write_from(col_from, row_from, col_from_offset, row_from_offset) # Write the xdr:from element. write_to(col_to, row_to, col_to_offset, row_to_offset) if type == 1 # Graphic frame. # Write the xdr:graphicFrame element for charts. write_graphic_frame(index, description) elsif type == 2 # Write the xdr:pic element. write_pic(index, col_absolute, row_absolute, width, height, description) else # Write the xdr:sp element for shapes. write_sp(index, col_absolute, row_absolute, width, height, shape) end # Write the xdr:clientData element. write_client_data end end # # Write the element. # def write_absolute_anchor(index) @writer.start_tag('xdr:absoluteAnchor') # Different co-ordinates for horizonatal (= 0) and vertical (= 1). if !ptrue?(@orientation) # Write the xdr:pos element. write_pos(0, 0) # Write the xdr:ext element. write_ext(9308969, 6078325) else # Write the xdr:pos element. write_pos(0, -47625) # Write the xdr:ext element. write_ext(6162675, 6124575) end # Write the xdr:graphicFrame element. write_graphic_frame(index) # Write the xdr:clientData element. write_client_data @writer.end_tag('xdr:absoluteAnchor') end # # Write the element. # def write_from(col, row, col_offset, row_offset) @writer.tag_elements('xdr:from') do # Write the xdr:col element. write_col(col) # Write the xdr:colOff element. write_col_off(col_offset) # Write the xdr:row element. write_row(row) # Write the xdr:rowOff element. write_row_off(row_offset) end end # # Write the element. # def write_to(col, row, col_offset, row_offset) @writer.tag_elements('xdr:to') do # Write the xdr:col element. write_col(col) # Write the xdr:colOff element. write_col_off(col_offset) # Write the xdr:row element. write_row(row) # Write the xdr:rowOff element. write_row_off(row_offset) end end # # Write the element. # def write_col(data) @writer.data_element('xdr:col', data) end # # Write the element. # def write_col_off(data) @writer.data_element('xdr:colOff', data) end # # Write the element. # def write_row(data) @writer.data_element('xdr:row', data) end # # Write the element. # def write_row_off(data) @writer.data_element('xdr:rowOff', data) end # # Write the element. # def write_pos(x, y) attributes = [ 'x', x, 'y', y ] @writer.empty_tag('xdr:pos', attributes) end # # Write the element. # def write_ext(cx, cy) attributes = [ 'cx', cx, 'cy', cy ] @writer.empty_tag('xdr:ext', attributes) end # # Write the element. # def write_graphic_frame(index, name = nil) macro = '' attributes = ['macro', macro] @writer.tag_elements('xdr:graphicFrame', attributes) do # Write the xdr:nvGraphicFramePr element. write_nv_graphic_frame_pr(index, name) # Write the xdr:xfrm element. write_xfrm # Write the a:graphic element. write_atag_graphic(index) end end # # Write the element. # def write_nv_graphic_frame_pr(index, name = nil) name = "Chart #{index}" if name.nil? || name.empty? @writer.tag_elements('xdr:nvGraphicFramePr') do # Write the xdr:cNvPr element. write_c_nv_pr( index + 1, name) # Write the xdr:cNvGraphicFramePr element. write_c_nv_graphic_frame_pr end end # # Write the element. # def write_c_nv_pr(id, name, descr = nil) attributes = [ 'id', id, 'name', name ] # Add description attribute for images. attributes << 'descr' << descr if descr @writer.empty_tag('xdr:cNvPr', attributes) end # # Write the element. # def write_c_nv_graphic_frame_pr if @embedded @writer.empty_tag('xdr:cNvGraphicFramePr') else @writer.tag_elements('xdr:cNvGraphicFramePr') do # Write the a:graphicFrameLocks element. write_a_graphic_frame_locks end end end # # Write the element. # def write_a_graphic_frame_locks no_grp = 1 attributes = ['noGrp', no_grp ] @writer.empty_tag('a:graphicFrameLocks', attributes) end # # Write the element. # def write_xfrm @writer.tag_elements('xdr:xfrm') do # Write the xfrmOffset element. write_xfrm_offset # Write the xfrmOffset element. write_xfrm_extension end end # # Write the xfrm sub-element. # def write_xfrm_offset x = 0 y = 0 attributes = [ 'x', x, 'y', y ] @writer.empty_tag('a:off', attributes) end # # Write the xfrm sub-element. # def write_xfrm_extension x = 0 y = 0 attributes = [ 'cx', x, 'cy', y ] @writer.empty_tag('a:ext', attributes) end # # Write the element. # def write_atag_graphic(index) @writer.tag_elements('a:graphic') do # Write the a:graphicData element. write_atag_graphic_data(index) end end # # Write the element. # def write_atag_graphic_data(index) uri = 'http://schemas.openxmlformats.org/drawingml/2006/chart' attributes = ['uri', uri] @writer.tag_elements('a:graphicData', attributes) do # Write the c:chart element. write_c_chart(index) end end # # Write the element. # def write_c_chart(id) schema = 'http://schemas.openxmlformats.org/' xmlns_c = "#{schema}drawingml/2006/chart" xmlns_r = "#{schema}officeDocument/2006/relationships" attributes = ['xmlns:c', xmlns_c, 'xmlns:r', xmlns_r] attributes += r_id_attributes(id) @writer.empty_tag('c:chart', attributes) end # # Write the element. # def write_client_data @writer.empty_tag('xdr:clientData') end # # Write the element. # def write_sp(index, col_absolute, row_absolute, width, height, shape) if shape.connect != 0 attributes = [:macro, ''] @writer.tag_elements('xdr:cxnSp', attributes) do # Write the xdr:nvCxnSpPr element. write_nv_cxn_sp_pr(index, shape) # Write the xdr:spPr element. write_xdr_sp_pr(index, col_absolute, row_absolute, width, height, shape) end else # Add attribute for shapes. attributes = [:macro, '', :textlink, ''] @writer.tag_elements('xdr:sp', attributes) do # Write the xdr:nvSpPr element. write_nv_sp_pr(index, shape) # Write the xdr:spPr element. write_xdr_sp_pr(index, col_absolute, row_absolute, width, height, shape) # Write the xdr:txBody element. if shape.text != 0 write_txBody(col_absolute, row_absolute, width, height, shape) end end end end # # Write the element. # def write_nv_cxn_sp_pr(index, shape) @writer.tag_elements('xdr:nvCxnSpPr') do shape.name = [shape.type, index].join(' ') unless shape.name write_c_nv_pr(shape.id, shape.name) @writer.tag_elements('xdr:cNvCxnSpPr') do attributes = [:noChangeShapeType, '1'] @writer.empty_tag('a:cxnSpLocks', attributes) if shape.start attributes = ['id', shape.start, 'idx', shape.start_index] @writer.empty_tag('a:stCxn', attributes) end if shape.end attributes = ['id', shape.end, 'idx', shape.end_index] @writer.empty_tag('a:endCxn', attributes) end end end end # # Write the element. # def write_nv_sp_pr(index, shape) attributes = [] attributes << 'txBox' << 1 if shape.tx_box @writer.tag_elements('xdr:nvSpPr') do write_c_nv_pr(shape.id, "#{shape.type} #{index}") @writer.tag_elements('xdr:cNvSpPr', attributes) do @writer.empty_tag('a:spLocks', [:noChangeArrowheads, '1']) end end end # # Write the element. # def write_pic(index, col_absolute, row_absolute, width, height, description) @writer.tag_elements('xdr:pic') do # Write the xdr:nvPicPr element. write_nv_pic_pr(index, description) # Write the xdr:blipFill element. write_blip_fill(index) # Pictures are rectangle shapes by default. shape = Shape.new shape.type = 'rect' # Write the xdr:spPr element. write_sp_pr(col_absolute, row_absolute, width, height, shape) end end # # Write the element. # def write_nv_pic_pr(index, description) @writer.tag_elements('xdr:nvPicPr') do # Write the xdr:cNvPr element. write_c_nv_pr( index + 1, "Picture #{index}", description ) # Write the xdr:cNvPicPr element. write_c_nv_pic_pr end end # # Write the element. # def write_c_nv_pic_pr @writer.tag_elements('xdr:cNvPicPr') do # Write the a:picLocks element. write_a_pic_locks end end # # Write the element. # def write_a_pic_locks no_change_aspect = 1 attributes = ['noChangeAspect', no_change_aspect] @writer.empty_tag('a:picLocks', attributes) end # # Write the element. # def write_blip_fill(index) @writer.tag_elements('xdr:blipFill') do # Write the a:blip element. write_a_blip(index) # Write the a:stretch element. write_a_stretch end end # # Write the element. # def write_a_blip(index) schema = 'http://schemas.openxmlformats.org/officeDocument/' xmlns_r = "#{schema}2006/relationships" r_embed = "rId#{index}" attributes = [ 'xmlns:r', xmlns_r, 'r:embed', r_embed ] @writer.empty_tag('a:blip', attributes) end # # Write the element. # def write_a_stretch @writer.tag_elements('a:stretch') do # Write the a:fillRect element. write_a_fill_rect end end # # Write the element. # def write_a_fill_rect @writer.empty_tag('a:fillRect') end # # Write the element, for charts. # def write_sp_pr(col_absolute, row_absolute, width, height, shape = {}) @writer.tag_elements('xdr:spPr') do # Write the a:xfrm element. write_a_xfrm(col_absolute, row_absolute, width, height) # Write the a:prstGeom element. write_a_prst_geom(shape) end end # # Write the element for shapes. # def write_xdr_sp_pr(index, col_absolute, row_absolute, width, height, shape) attributes = ['bwMode', 'auto'] @writer.tag_elements('xdr:spPr', attributes) do # Write the a:xfrm element. write_a_xfrm(col_absolute, row_absolute, width, height, shape) # Write the a:prstGeom element. write_a_prst_geom(shape) if shape.fill.to_s.bytesize > 1 # Write the a:solidFill element. write_a_solid_fill(shape.fill) else @writer.empty_tag('a:noFill') end # Write the a:ln element. write_a_ln(shape) end end # # Write the element. # def write_a_xfrm(col_absolute, row_absolute, width, height, shape = nil) attributes = [] rotation = shape ? shape.rotation : 0 rotation *= 60000 attributes << 'rot' << rotation if rotation != 0 attributes << 'flipH' << 1 if shape && ptrue?(shape.flip_h) attributes << 'flipV' << 1 if shape && ptrue?(shape.flip_v) @writer.tag_elements('a:xfrm', attributes) do # Write the a:off element. write_a_off( col_absolute, row_absolute ) # Write the a:ext element. write_a_ext( width, height ) end end # # Write the element. # def write_a_off(x, y) attributes = [ 'x', x, 'y', y ] @writer.empty_tag('a:off', attributes) end # # Write the element. # def write_a_ext(cx, cy) attributes = [ 'cx', cx, 'cy', cy ] @writer.empty_tag('a:ext', attributes) end # # Write the element. # def write_a_prst_geom(shape = {}) attributes = [] attributes << 'prst' << shape.type if shape.type @writer.tag_elements('a:prstGeom', attributes) do # Write the a:avLst element. write_a_av_lst(shape) end end # # Write the element. # def write_a_av_lst(shape = {}) if shape.adjustments.respond_to?(:empty?) adjustments = shape.adjustments elsif shape.adjustments.respond_to?(:coerce) adjustments = [shape.adjustments] elsif !shape.adjustments adjustments = [] end if adjustments.respond_to?(:empty?) && !adjustments.empty? @writer.tag_elements('a:avLst') do i = 0 adjustments.each do |adj| i += 1 # Only connectors have multiple adjustments. suffix = shape.connect != 0 ? i : '' # Scale Adjustments: 100,000 = 100%. adj_int = (adj * 1000).to_i attributes = [:name, "adj#{suffix}", :fmla, "val #{adj_int}"] @writer.empty_tag('a:gd', attributes) end end else @writer.empty_tag('a:avLst') end end # # Write the element. # def write_a_solid_fill(rgb = '000000') attributes = ['val', rgb] @writer.tag_elements('a:solidFill') do @writer.empty_tag('a:srgbClr', attributes) end end # # Write the elements. # def write_a_ln(shape = {}) weight = shape.line_weight || 0 attributes = ['w', weight * 9525] @writer.tag_elements('a:ln', attributes) do line = shape.line || 0 if line.to_s.bytesize > 1 # Write the a:solidFill element. write_a_solid_fill(line) else @writer.empty_tag('a:noFill') end if shape.line_type != '' attributes = ['val', shape.line_type] @writer.empty_tag('a:prstDash', attributes) end if shape.connect != 0 @writer.empty_tag('a:round') else attributes = ['lim', 800000] @writer.empty_tag('a:miter', attributes) end @writer.empty_tag('a:headEnd') @writer.empty_tag('a:tailEnd') end end # # Write the element. # def write_txBody(col_absolute, row_absolute, width, height, shape) attributes = [ :vertOverflow, "clip", :wrap, "square", :lIns, "27432", :tIns, "22860", :rIns, "27432", :bIns, "22860", :anchor, shape.valign, :upright, "1" ] @writer.tag_elements('xdr:txBody') do @writer.empty_tag('a:bodyPr', attributes) @writer.empty_tag('a:lstStyle') @writer.tag_elements('a:p') do rotation = shape.format[:rotation] || 0 rotation *= 60000 attributes = [:algn, shape.align, :rtl, rotation] @writer.tag_elements('a:pPr', attributes) do attributes = [:sz, "1000"] @writer.empty_tag('a:defRPr', attributes) end @writer.tag_elements('a:r') do size = shape.format[:size] || 8 size *= 100 bold = shape.format[:bold] || 0 italic = shape.format[:italic] || 0 underline = ptrue?(shape.format[:underline]) ? 'sng' : 'none' strike = ptrue?(shape.format[:font_strikeout]) ? 'Strike' : 'noStrike' attributes = [ :lang, "en-US", :sz, size, :b, bold, :i, italic, :u, underline, :strike, strike, :baseline, 0 ] @writer.tag_elements('a:rPr', attributes) do color = shape.format[:color] if color color = shape.get_palette_color(color) color = color.sub(/^FF/, '') # Remove leading FF from rgb for shape color. else color = '000000' end write_a_solid_fill(color) font = shape.format[:font] || 'Calibri' attributes = [:typeface, font] @writer.empty_tag('a:latin', attributes) @writer.empty_tag('a:cs', attributes) end @writer.tag_elements('a:t') do @writer.characters(shape.text) end end end end end end end