Class: Prawn::Fonts::ToUnicodeCMap

Inherits:
Object
  • Object
show all
Defined in:
lib/prawn/fonts/to_unicode_cmap.rb

Overview

This class generates ToUnicode CMap for embedde TrueType/OpenType fonts. It’s a separate format and is somewhat complicated so it has its own place.

Instance Method Summary collapse

Constructor Details

#initialize(mapping, code_space_size = nil) ⇒ ToUnicodeCMap

mapping is expected to be a hash with keys being character codes (in broad sense, as used in the showing operation strings) and values being Unicode code points.



14
15
16
17
# File 'lib/prawn/fonts/to_unicode_cmap.rb', line 14

def initialize(mapping, code_space_size = nil)
  @mapping = mapping
  @code_space_size = code_space_size
end

Instance Method Details

#generateString

Generate CMap.

Returns:

  • (String)


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/prawn/fonts/to_unicode_cmap.rb', line 22

def generate
  chunks = []

  # Header
  chunks << <<~HEADER.chomp
    /CIDInit /ProcSet findresource begin
    12 dict begin
    begincmap
    /CIDSystemInfo 3 dict dup begin
      /Registry (Adobe) def
      /Ordering (UCS) def
      /Supplement 0 def
    end def
    /CMapName /Adobe-Identity-UCS def
    /CMapType 2 def
  HEADER

  max_glyph_index = mapping.keys.max
  # Range
  code_space_size = (max_glyph_index.bit_length / 8.0).ceil

  used_code_space_size = @code_space_size || code_space_size

  # In CMap codespaces are not sequentional, they're ranges in
  # a multi-dimentional space. Each byte is considered separately. So we
  # have to maximally extend the lower bytes in order to allow for
  # continuos mapping.
  # We only keep the highest byte because usually it's lower than
  # maximally allowed and we don't want to cover that unused space.
  code_space_max = max_glyph_index | ('ff' * (code_space_size - 1)).to_i(16)

  chunks << '1 begincodespacerange'
  chunks << format("<%0#{used_code_space_size * 2}X><%0#{used_code_space_size * 2}X>", 0, code_space_max)
  chunks << 'endcodespacerange'

  # Mapping
  all_spans = mapping_spans(mapping.reject { |gid, cid| gid.zero? || (0xd800..0xdfff).cover?(cid) })

  short_spans, long_spans = all_spans.partition { |span| span[0] == :short }

  long_spans
    .each_slice(100) do |spans|
      chunks << "#{spans.length} beginbfrange"

      spans.each do |type, span|
        # rubocop: disable Lint/FormatParameterMismatch # false positive
        case type
        when :fully_sorted
          chunks << format(
            "<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X><%s>",
            span.first[0],
            span.last[0],
            span.first[1].chr(::Encoding::UTF_16BE).unpack1('H*'),
          )
        when :index_sorted
          chunks << format(
            "<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X>[%s]",
            span.first[0],
            span.last[0],
            span.map { |_, cid| "<#{cid.chr(::Encoding::UTF_16BE).unpack1('H*')}>" }.join(''),
          )
        end
        # rubocop: enable Lint/FormatParameterMismatch
      end

      chunks << 'endbfrange'
    end

  short_spans
    .map { |_type, slice| slice.flatten(1) }
    .each_slice(100) do |mapping|
      chunks << "#{mapping.length} beginbfchar"
      chunks.concat(
        mapping.map { |(gid, cid)|
          # rubocop: disable Lint/FormatParameterMismatch # false positive
          format(
            "<%0#{code_space_size * 2}X><%s>",
            gid,
            cid.chr(::Encoding::UTF_16BE).unpack1('H*'),
          )
          # rubocop: enable Lint/FormatParameterMismatch
        },
      )
      chunks << 'endbfchar'
    end

  # Footer
  chunks << <<~FOOTER.chomp
    endcmap
    CMapName currentdict /CMap defineresource pop
    end
    end
  FOOTER

  chunks.join("\n")
end