# frozen_string_literal: true module Axlsx # The RichTextRun class creates and self serializing text run. class RichTextRun include Axlsx::OptionsParser attr_reader :value # A list of allowed inline style attributes used for validation INLINE_STYLES = [:font_name, :charset, :family, :b, :i, :strike, :outline, :shadow, :condense, :extend, :u, :vertAlign, :sz, :color, :scheme].freeze def initialize(value, options = {}) self.value = value parse_options(options) end def value=(value) @value = value end attr_accessor :cell # The inline font_name property for the cell # @return [String] attr_reader :font_name # @see font_name def font_name=(v) set_run_style :validate_string, :font_name, v; end # The inline charset property for the cell # As far as I can tell, this is pretty much ignored. However, based on the spec it should be one of the following: # 0  ANSI_CHARSET # 1 DEFAULT_CHARSET # 2 SYMBOL_CHARSET # 77 MAC_CHARSET # 128 SHIFTJIS_CHARSET # 129  HANGUL_CHARSET # 130  JOHAB_CHARSET # 134  GB2312_CHARSET # 136  CHINESEBIG5_CHARSET # 161  GREEK_CHARSET # 162  TURKISH_CHARSET # 163  VIETNAMESE_CHARSET # 177  HEBREW_CHARSET # 178  ARABIC_CHARSET # 186  BALTIC_CHARSET # 204  RUSSIAN_CHARSET # 222  THAI_CHARSET # 238  EASTEUROPE_CHARSET # 255  OEM_CHARSET # @return [String] attr_reader :charset # @see charset def charset=(v) set_run_style :validate_unsigned_int, :charset, v; end # The inline family property for the cell # @return [Integer] # 1 Roman # 2 Swiss # 3 Modern # 4 Script # 5 Decorative attr_reader :family # @see family def family=(v) set_run_style :validate_family, :family, v.to_i end # The inline bold property for the cell # @return [Boolean] attr_reader :b # @see b def b=(v) set_run_style :validate_boolean, :b, v; end # The inline italic property for the cell # @return [Boolean] attr_reader :i # @see i def i=(v) set_run_style :validate_boolean, :i, v; end # The inline strike property for the cell # @return [Boolean] attr_reader :strike # @see strike def strike=(v) set_run_style :validate_boolean, :strike, v; end # The inline outline property for the cell # @return [Boolean] attr_reader :outline # @see outline def outline=(v) set_run_style :validate_boolean, :outline, v; end # The inline shadow property for the cell # @return [Boolean] attr_reader :shadow # @see shadow def shadow=(v) set_run_style :validate_boolean, :shadow, v; end # The inline condense property for the cell # @return [Boolean] attr_reader :condense # @see condense def condense=(v) set_run_style :validate_boolean, :condense, v; end # The inline extend property for the cell # @return [Boolean] attr_reader :extend # @see extend def extend=(v) set_run_style :validate_boolean, :extend, v; end # The inline underline property for the cell. # It must be one of :none, :single, :double, :singleAccounting, :doubleAccounting, true # @return [Boolean] # @return [String] # @note true is for backwards compatability and is reassigned to :single attr_reader :u # @see u def u=(v) v = :single if v == true || v == 1 || v == :true || v == 'true' set_run_style :validate_cell_u, :u, v end # The inline color property for the cell # @return [Color] attr_reader :color # @param [String] v The 8 character representation for an rgb color #FFFFFFFF" def color=(v) @color = v.is_a?(Color) ? v : Color.new(rgb: v) end # The inline sz property for the cell # @return [Inteter] attr_reader :sz # @see sz def sz=(v) set_run_style :validate_unsigned_int, :sz, v; end # The inline vertical alignment property for the cell # this must be one of [:baseline, :subscript, :superscript] # @return [Symbol] attr_reader :vertAlign # @see vertAlign def vertAlign=(v) RestrictionValidator.validate :cell_vertAlign, [:baseline, :subscript, :superscript], v set_run_style nil, :vertAlign, v end # The inline scheme property for the cell # this must be one of [:none, major, minor] # @return [Symbol] attr_reader :scheme # @see scheme def scheme=(v) RestrictionValidator.validate :cell_scheme, [:none, :major, :minor], v set_run_style nil, :scheme, v end # Tries to work out the width of the longest line in the run # @param [Array] widtharray this array is populated with the widths of each line in the run. # @return [Array] def autowidth(widtharray) return if value.nil? if styles.cellXfs[style].alignment && styles.cellXfs[style].alignment.wrap_text first = true value.to_s.split(/\r?\n/, -1).each do |line| if first first = false else widtharray << 0 end widtharray[-1] += string_width(line, font_size) end else widtharray[-1] += string_width(value.to_s, font_size) end widtharray end # Utility method for setting inline style attributes def set_run_style(validator, attr, value) return unless INLINE_STYLES.include?(attr.to_sym) Axlsx.send(validator, value) unless validator.nil? instance_variable_set :"@#{attr}", value end # Serializes the RichTextRun # @param [String] str # @return [String] def to_xml_string(str = +'') valid = RichTextRun::INLINE_STYLES data = Axlsx.instance_values_for(self).transform_keys(&:to_sym) data = data.select { |key, value| !value.nil? && valid.include?(key) } str << '' data.each do |key, val| case key when :font_name str << '' when :color str << val.to_xml_string else str << '<' << key.to_s << ' val="' << xml_value(val) << '"/>' end end clean_value = Axlsx.trust_input ? @value.to_s : ::CGI.escapeHTML(Axlsx.sanitize(@value.to_s)) str << '' << clean_value << '' end private # Returns the width of a string according to the current style # This is still not perfect... # - scaling is not linear as font sizes increase def string_width(string, font_size) font_scale = font_size / 10.0 string.size * font_scale end # we scale the font size if bold style is applied to either the style font or # the cell itself. Yes, it is a bit of a hack, but it is much better than using # imagemagick and loading metrics for every character. def font_size return sz if sz font = styles.fonts[styles.cellXfs[style].fontId] || styles.fonts[0] font.b || (defined?(@b) && @b) ? (font.sz * 1.5) : font.sz end def style cell.style end def styles cell.row.worksheet.styles end # Converts the value to the correct XML representation (fixes issues with # Numbers) def xml_value(value) if value == true 1 elsif value == false 0 else value end.to_s end end end