puzzle / prawn-markup

Parse simple HTML markup to include in Prawn PDFs
MIT License
65 stars 16 forks source link

list styles #23

Closed haroldus- closed 1 year ago

haroldus- commented 2 years ago

Nested lists should have a different bullet. It is possible to determine this from the style attribute if present. The type or start attribute is limited. But by looking for list-style-type one is able to style lists with the greatest optionality. I am using the following code in an initializer in a rails app to achieve the foregoing to suit my purposes (perhaps it could form the basis of an enhancement):

module Prawn
  module Markup
    module Elements
      class List
        attr_reader :ordered, :items, :style_properties

        def initialize(ordered, style_properties={})
          @ordered = ordered
          @items = []
          @style_properties = style_properties
        end
      end
    end
  end
end

module Prawn
  module Markup
    module Processor::Lists
      def start_list(ordered)
        if current_list
          add_cell_text_node(current_list_item)
        elsif current_table
          add_cell_text_node(current_cell)
        else
          add_current_text
        end
        @list_stack.push(Elements::List.new(ordered, style_properties))
      end
    end
  end
end

module Prawn
  module Markup
    module Builders
      class ListBuilder < NestableBuilder

        private

        def bullet_text_width
          font = bullet_font
          font_size = column_cell_style(:bullet)[:size] || pdf.font_size
          widths = []
          list.items.size.times do |i|
            encoded = font.normalize_encoding(bullet(i + 1))
            widths << font.compute_width_of(encoded, size: font_size)
          end
          widths.max
        end
        def bullet(index)
          if list.style_properties['list-style-type'].present?
            list.ordered ? "#{int_to_bullet(index, list.style_properties['list-style-type'])}." : bullet_from_style(list.style_properties['list-style-type'])
          else
            list.ordered ? "#{index}." : (column_cell_style(:bullet)[:char] || BULLET_CHAR)
          end
        end
        def int_to_bullet(int, style)
          case style
          when 'decimal'
            int
          when 'lower-alpha' || 'lower-latin'
            int_to_lower_alpha(int)
          when 'lower-roman'
            int_to_upper_roman(int).downcase
          when 'upper-alpha' || 'upper-latin'
            int_to_lower_alpha(int).upcase
          when 'lower-greek'
            int_to_lower_greek(int)
          when 'none'
            ''
          else
            int
          end
        end
        def bullet_from_style(style)
          case style
          when 'disc'
            BULLET_CHAR
          when 'circle'
            '◦'
          when 'square'
            '▪'
          when 'none'
            ''
          else
            BULLET_CHAR
          end
        end
        def int_to_lower_alpha(int)
          chars = ("a".."z").to_a
          return "" if int < 1
          s, q = "", int
          loop do
            q, r = (q - 1).divmod(26)
            s.prepend(chars[r]) 
            break if q.zero?
          end
          s
        end
        def int_to_lower_greek(int)
          chars = ("α".."ω").to_a
          return "" if int < 1
          s, q = "", int
          loop do
            q, r = (q - 1).divmod(24)
            s.prepend(chars[r]) 
            break if q.zero?
          end
          s
        end
        def int_to_upper_roman(int)
          map = {
            1000 => "M",
            900 => "CM",
            500 => "D",
            400 => "CD",
            100 => "C",
            90 => "XC",
            50 => "L",
            40 => "XL",
            10 => "X",
            9 => "IX",
            5 => "V",
            4 => "IV",
            1 => "I"
          }
          return "" if int < 1
          s, n = "", int
          map.keys.each do |d|
            q, r = n.divmod(d)
            s << map[d] * q
            n = r
          end
          s
        end
      end
    end
  end
end

One issue with the above is that the PDF must be using a font-family with adequate glyphs to render the bullets. Windows-1252 doesn't have white bullet or black small square used above and will cause an error. Other fonts don't have these glyphs drawn and will render nothing.

codez commented 2 years ago

Thank you for this feature request. I like the idea of having different bullets. To build a complete feature out of it, the following ideas jump to my mind:

Would you like to work on a pull request to integrate that feature?

codez commented 1 year ago

Closed because of inactivity