require 'delegate' require 'spreadsheet/format' require 'spreadsheet/excel/internals' module Spreadsheet module Excel module Writer ## # This class encapsulates everything that is needed to write an XF record. class Format < DelegateClass Spreadsheet::Format include Spreadsheet::Excel::Internals def Format.boolean *args args.each do |key| define_method key do @format.send("#{key}?") ? 1 : 0 end end end def Format.color key, default define_method key do color_code(@format.send(key) || default) end end boolean :hidden, :locked, :merge_range, :shrink, :text_justlast, :text_wrap, :cross_down, :cross_up, :left, :right, :top, :bottom color :left_color, :border color :right_color, :border color :top_color, :border color :bottom_color, :border color :diagonal_color, :border color :pattern_fg_color, :pattern_bg color :pattern_bg_color, :pattern_bg attr_accessor :xf_index attr_reader :format def initialize writer, workbook, format=workbook.default_format, type=:format @type = type.to_s.downcase @format = format @writer = writer @workbook = workbook super format end def color_code color SEDOC_ROLOC[color] end def font_index @writer.font_index @workbook, font.key end def horizontal_align XF_H_ALIGN.fetch @format.horizontal_align, 0 end def num_format @writer.number_format_index @workbook, @format.number_format end def text_direction XF_TEXT_DIRECTION.fetch @format.text_direction, 0 end def vertical_align XF_V_ALIGN.fetch @format.vertical_align, 2 end def write_op writer, op, *args data = args.join writer.write [op,data.size].pack("v2") writer.write data end def write_xf writer, type=@type xf_type = xf_type_prot type data = [ font_index, # Index to FONT record (➜ 6.43) num_format, # Index to FORMAT record (➜ 6.45) xf_type, # Bit Mask Contents # 2-0 0x0007 XF_TYPE_PROT – XF type, cell protection # Bit Mask Contents # 0 0x01 1 = Cell is locked # 1 0x02 1 = Formula is hidden # 2 0x04 0 = Cell XF; 1 = Style XF # 15-4 0xfff0 Index to parent style XF # (always 0xfff in style XFs) xf_align, # Bit Mask Contents # 2-0 0x07 XF_HOR_ALIGN – Horizontal alignment # Value Horizontal alignment # 0x00 General # 0x01 Left # 0x02 Centred # 0x03 Right # 0x04 Filled # 0x05 Justified (BIFF4-BIFF8X) # 0x06 Centred across selection # (BIFF4-BIFF8X) # 0x07 Distributed (BIFF8X) # 3 0x08 1 = Text is wrapped at right border # 6-4 0x70 XF_VERT_ALIGN – Vertical alignment # Value Vertical alignment # 0x00 Top # 0x01 Centred # 0x02 Bottom # 0x03 Justified (BIFF5-BIFF8X) # 0x04 Distributed (BIFF8X) xf_rotation, # XF_ROTATION: Text rotation angle # Value Text rotation # 0 Not rotated # 1-90 1 to 90 degrees counterclockwise # 91-180 1 to 90 degrees clockwise # 255 Letters are stacked top-to-bottom, # but not rotated xf_indent, # Bit Mask Contents # 3-0 0x0f Indent level # 4 0x10 1 = Shrink content to fit into cell # 5 0x40 1 = Merge Range (djberger) # 7-6 0xc0 Text direction (BIFF8X only) # 0 = According to context # 1 = Left-to-right # 2 = Right-to-left xf_used_attr, # Bit Mask Contents # 7-2 0xfc XF_USED_ATTRIB – Used attributes # Each bit describes the validity of a # specific group of attributes. In cell XFs # a cleared bit means the attributes of the # parent style XF are used (but only if the # attributes are valid there), a set bit # means the attributes of this XF are used. # In style XFs a cleared bit means the # attribute setting is valid, a set bit # means the attribute should be ignored. # Bit Mask Contents # 0 0x01 Flag for number format # 1 0x02 Flag for font # 2 0x04 Flag for horizontal and # vertical alignment, text wrap, # indentation, orientation, # rotation, and text direction # 3 0x08 Flag for border lines # 4 0x10 Flag for background area style # 5 0x20 Flag for cell protection (cell # locked and formula hidden) xf_borders, # Cell border lines and background area: # Bit Mask Contents # 3- 0 0x0000000f Left line style (➜ 3.10) # 7- 4 0x000000f0 Right line style (➜ 3.10) # 11- 8 0x00000f00 Top line style (➜ 3.10) # 15-12 0x0000f000 Bottom line style (➜ 3.10) # 22-16 0x007f0000 Colour index (➜ 6.70) # for left line colour # 29-23 0x3f800000 Colour index (➜ 6.70) # for right line colour # 30 0x40000000 1 = Diagonal line # from top left to right bottom # 31 0x80000000 1 = Diagonal line # from bottom left to right top xf_brdcolors, # Bit Mask Contents # 6- 0 0x0000007f Colour index (➜ 6.70) # for top line colour # 13- 7 0x00003f80 Colour index (➜ 6.70) # for bottom line colour # 20-14 0x001fc000 Colour index (➜ 6.70) # for diagonal line colour # 24-21 0x01e00000 Diagonal line style (➜ 3.10) # 31-26 0xfc000000 Fill pattern (➜ 3.11) xf_pattern # Bit Mask Contents # 6-0 0x007f Colour index (➜ 6.70) # for pattern colour # 13-7 0x3f80 Colour index (➜ 6.70) # for pattern background ] write_op writer, 0x00e0, data.pack(binfmt(:xf)) end def xf_align align = horizontal_align align |= text_wrap << 3 align |= vertical_align << 4 align |= text_justlast << 7 align end def xf_borders border = left border |= right << 4 border |= top << 8 border |= bottom << 12 border |= left_color << 16 border |= right_color << 23 border |= cross_down << 30 border |= cross_up << 31 border end def xf_brdcolors border = top_color border |= bottom_color << 7 border |= diagonal_color << 14 border |= pattern << 26 border end def xf_indent indent = indent_level & 0x0f indent |= shrink << 4 indent |= merge_range << 5 indent |= text_direction << 6 indent end def xf_pattern ptrn = pattern_fg_color ptrn |= pattern_bg_color << 7 ptrn end def xf_rotation rot = @format.rotation if @format.rotation_stacked? rot = 255 elsif rot >= -90 or rotation <= 90 rot = -rot + 90 if rot < 0 else warn "rotation outside -90..90; rotation set to 0" rot = 0 end rot end def xf_type_prot type type = type.to_s.downcase == 'style' ? 0xfff5 : 0x0000 type |= locked type |= hidden << 1 type end def xf_used_attr atr_num = num_format & 1 atr_fnt = font_index & 1 atr_fnt = 1 unless @format.font.color == :text atr_alc = 0 if horizontal_align != 0 \ || vertical_align != 2 \ || indent_level > 0 \ || shrink? || merge_range? || text_wrap? then atr_alc = 1 end atr_bdr = [top, bottom, left, right, cross_up, cross_down].max atr_pat = 0 if @format.pattern_fg_color != :border \ || @format.pattern_bg_color != :pattern_bg \ || pattern != 0x00 then atr_pat = 1 end atr_prot = hidden? || locked? ? 1 : 0 attrs = atr_num attrs |= atr_fnt << 1 attrs |= atr_alc << 2 attrs |= atr_bdr << 3 attrs |= atr_pat << 4 attrs |= atr_prot << 5 attrs << 2 end end end end end