Class: PDF::Writer::FontMetrics

Inherits:
Object
  • Object
show all
Defined in:
lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb

Overview

– PDF::Writer for Ruby.

http://rubyforge.org/projects/ruby-pdf/
Copyright 2003 - 2005 Austin Ziegler.

Licensed under a MIT-style licence. See LICENCE in the main distribution
for full licensing information.

$Id: fontmetrics.rb 168 2007-11-08 19:04:08Z sandal $ ++

Constant Summary collapse

METRICS_PATH =
[ File.join(File.dirname(File.expand_path(__FILE__)), 'fonts') ]
KEYS =
%w{FontName FullName FamilyName Weight ItalicAngle IsFixedPitch
CharacterSet UnderlinePosition UnderlineThickness Version
EncodingScheme CapHeight XHeight Ascender Descender StdHW StdVW
StartCharMetrics FontBBox C KPX}
NUMBER =
/^[+\-0-9.]+$/o

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFontMetrics

Returns a new instance of FontMetrics.



21
22
23
24
25
26
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 21

def initialize
  @kpx      = {}
  @c        = {}
  @version  = 1
  @font_num = nil
end

Instance Attribute Details

#differencesObject

Returns the value of attribute differences.



30
31
32
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 30

def differences
  @differences
end

#encodingObject

Returns the value of attribute encoding.



31
32
33
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 31

def encoding
  @encoding
end

#font_numObject

Returns the value of attribute font_num.



28
29
30
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 28

def font_num
  @font_num
end

#pathObject

Returns the value of attribute path.



29
30
31
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 29

def path
  @path
end

Class Method Details

.open(font_name) ⇒ Object

Open the font file and return a PDF::Writer::FontMetrics object containing it. The font_name may specify just a font file or a full path. If a path is specified, that is the only place where the font file will be looked for.

Raises:

  • (ArgumentError)


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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 39

def self.open(font_name)
  file  = font_name.gsub(/\\/o, "/")
  dir   = File.dirname(file)
  name  = File.basename(file)

  metrics_path = []

    # Check to see if the directory is "." or a non-path
  if dir == "."
    metrics_path << dir << METRICS_PATH << $LOAD_PATH
  elsif dir !~ %r{^(\w:|/)}o and dir.index("/")
    METRICS_PATH.each { |path| metrics_path << File.join(path, dir) }
    $LOAD_PATH.each { |path| metrics_path << File.join(path, dir) }
  else
    metric_path = [ dir ]
  end
  metrics_path.flatten!

  font = nil
  afm = nil

  metrics_path.each do |path|
    afm_file  = File.join(path, "#{name}.afm").gsub(/\.afm\.afm$/o, ".afm")
    rfm_file  = "#{afm_file}.rfm"

      # Attempt to unmarshal an .afm.rfm file first. If it is loaded,
      # we're in good shape.
    begin
      if File.exists?(rfm_file)
        data = File.open(rfm_file, "rb") { |file| file.read }
        font = Marshal.load(data)
        return font
      end
    rescue
      nil
    end

      # Attempt to open and process the font.
    File.open(afm_file, "rb") do |file|
      font = PDF::Writer::FontMetrics.new

        # An AFM file contains key names followed by valuees.
      file.each do |line|
        line.chomp!
        line.strip!
        key, *values = line.split
        next if key.nil?
        op = "#{key.downcase}=".to_sym

          # I probably need to deal with MetricsSet. The default value is
          # 0, which is writing direction 0 (W0X). If MetricsSet 1 is
          # specified, then only writing direction 1 is present (W1X). If
          # MetricsSet 2 is specified, then both W0X and W1X are present.

          # Measurements are always 1/1000th of a scale factor (point
          # size). So a 12pt character with a width of 222 is going to be
          # 222 * 12 / 1000 or 2.664 points wide.
        case key
        when 'FontName', 'FullName', 'FamilyName', 'Weight',
          'IsFixedPitch', 'CharacterSet', 'Version', 'EncodingScheme'
            # These values are string values.
          font.__send__(op, values.join(" "))
        when 'ItalicAngle', 'UnderlinePosition', 'UnderlineThickness',
          'CapHeight', 'XHeight', 'Ascender', 'Descender', 'StdHW',
          'StdVW', 'StartCharMetrics'
            # These values are floating point values.
          font.__send__(op, values.join(" ").to_f)
        when 'FontBBox'
            # These values are an array of floating point values
          font.fontbbox = values.map { |el| el.to_f }
        when 'C', 'CH'
            # Individual Character Metrics Values:
            #   C  <character number>
            #   CH <hex character number>
            #     One of C or CH must be provided. Specifies the encoding
            #     number for the character. -1 if the character is not
            #     encoded in the font.
            #
            #   WX  <x width number>
            #   W0X <x0 width number>
            #   W1X <x1 width number>
            #     Character width in x for writing direction 0 (WX, W0X)
            #     or 1 (W1X) where y is 0. Optional.
            #
            #   WY  <y width number>
            #   W0Y <y0 width number>
            #   W1Y <y1 width number>
            #     Character width in y for writing direction 0 (WY, W0Y)
            #     or 1 (W1Y) where x is 0. Optional.
            #
            #   W  <x width> <y width>
            #   W0 <x0 width> <y0 width>
            #   W1 <x1 width> <y1 width>
            #     Character width in x, y for writing direction 0 (W, W0)
            #     or 1 (W1). Optional.
            #
            #   VV <x number> <y number>
            #     Same as VVector in the global font definition, but for
            #     this single character. Optional.
            #
            #   N <name>
            #     The PostScript name of the font. Optional.
            #   
            #   B <llx> <lly> <urx> <ury>
            #     Character bounding box for the lower left corner and the
            #     upper right corner. Optional.
            #
            #   L <sucessor> <ligature>
            #     Ligature sequence where both <successor> and <ligature>
            #     are N <names>. For the fragment "N f; L i fi; L l fl",
            #     two ligatures are defined: fi and fl. Optional,
            #     multiples permitted.
            #
            # C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
          bits = line.chomp.strip.split(/;/).collect { |r| r.strip }
          dtmp = {}

          bits.each do |bit|
            b = bit.split
            if b.size > 2
              dtmp[b[0]] = []
              b[1..-1].each do |z|
                if z =~ NUMBER
                  dtmp[b[0]] << z.to_f
                else
                  dtmp[b[0]] << z
                end
              end
            elsif b.size == 2
              if b[0] == 'C' and b[1] =~ NUMBER
                dtmp[b[0]] = b[1].to_i
              elsif b[0] == 'CH'
                dtmp['C'] = b[1].to_i(16)
              elsif b[1] =~ NUMBER
                dtmp[b[0]] = b[1].to_f
              else
                dtmp[b[0]] = b[1]
              end
            end
          end

          font.c[dtmp['N']] = dtmp
          font.c[dtmp['C']] = dtmp unless dtmp['C'].nil?
        when 'KPX' # KPX Adieresis yacute -40
          # KPX: Kerning Pair
          font.kpx[values[0]] = { }
          font.kpx[values[0]][values[1]] = values[2].to_f
        end
      end
      font.path = afm_file
    end rescue nil # Ignore file errors
    break unless font.nil?
  end

  raise ArgumentError, "Font #{font_name} not found." if font.nil?
  font
end

Instance Method Details

#save_as_rfmObject

Save the loaded font metrics file as a binary marshaled value.



198
199
200
201
202
# File 'lib/extensions/pdf-writer/pdf/writer/fontmetrics.rb', line 198

def save_as_rfm
  rfm = File.basename(@path).gsub(/\.afm.*$/, '')
  rfm << ".afm.rfm"
  File.open(rfm, "wb") { |file| file.write Marshal.dump(self) }
end