summaryrefslogtreecommitdiff
path: root/support/rfil/lib/rfil/font
diff options
context:
space:
mode:
Diffstat (limited to 'support/rfil/lib/rfil/font')
-rw-r--r--support/rfil/lib/rfil/font/afm.rb402
-rw-r--r--support/rfil/lib/rfil/font/glyph.rb198
-rw-r--r--support/rfil/lib/rfil/font/metric.rb129
-rw-r--r--support/rfil/lib/rfil/font/truetype.rb29
4 files changed, 758 insertions, 0 deletions
diff --git a/support/rfil/lib/rfil/font/afm.rb b/support/rfil/lib/rfil/font/afm.rb
new file mode 100644
index 0000000000..3a087d36c8
--- /dev/null
+++ b/support/rfil/lib/rfil/font/afm.rb
@@ -0,0 +1,402 @@
+# Last Change: Tue May 16 19:11:20 2006
+
+# require 'rfi'
+
+require 'strscan'
+require 'pathname'
+
+require 'rfil/font/metric'
+
+module RFIL # :nodoc:
+ module Font # :nodoc:
+ # = AFM -- Access type1 font metric files
+ #
+ # == General information
+ #
+ # Read and parse a (type1) afm file. The afm file must be compliant to
+ # the afm specification as described in 'Adobe Font Metrics File
+ # Format Specification' Version 4.1, dated October 7 1998.
+ #
+ # == Example usage
+ #
+ # === Read an afm file
+ # filename = "/opt/tetex/3.0/texmf/fonts/afm/urw/palatino/uplb8a.afm"
+ # afm=AFM.new
+ # afm.read(filename)
+ # afm.filename # => "/opt/..../uplb8a.afm"
+ # afm.count_charmetrics # => 316
+ # afm.encodingscheme # => "AdobeStandardEncoding"
+ # # ....
+ #
+ class AFM < Metric
+
+ # This is set to true if there is something wrong in the afm file.
+ # Diagnostics can be turned on with <tt>:verbose</tt> set to true
+ # when creating the object.
+ attr_reader :something_strange
+
+ # Number of characters found in the afm file.
+ attr_accessor :count_charmetrics
+
+ # Number of encoded character found in the afm file.
+ attr_accessor :count_charmetrics_encoded
+
+ # Number of unencoded character found in the afm file.
+ attr_accessor :count_charmetrics_unencoded
+
+ # The default encoding of the font.
+ attr_accessor :encodingscheme
+
+ # Boundingbox of the font. Array of for elements.
+ attr_accessor :fontbbox
+
+ # Underline position of the font.
+ attr_accessor :underlineposition
+
+ # Underline thickness.
+ attr_accessor :underlinethickness
+
+ # Height of caps.
+ attr_accessor :capheight
+
+ # Height of ascender.
+ attr_accessor :ascender
+
+ # Height of descender.
+ attr_accessor :descender
+
+
+ # Create an empty afm file. If _afm_ is set, use this to initialize
+ # the object. _afm_ is either a string with the contents of an afm
+ # file or a File object that points to the afm file. _options_
+ # currently only accepts <tt>:verbose</tt> (true/false), that prints
+ # out some diagnostic information on STDERR.
+ def initialize(options={})
+ @something_strange = false
+ super()
+ @outlinetype=:type1
+ @comment = ""
+ @verbose=options[:verbose]==true
+ end
+
+ # Read the afm file given with _filename_. _filename_ must be full
+ # path to the afm file, it does not perform any lookups. Returns self.
+ def read (filename)
+ @filename=File.basename(filename)
+ @name=@filename.chomp(".afm")
+ self.pathname=Pathname.new(filename).realpath.to_s
+ parse(File.read(filename))
+ end
+
+ # Return a string representation of the afm file that is compliant
+ # with the afm spec.
+ def to_s
+ s ="StartFontMetrics 2.0\n"
+ s << "Comment Generated using the RFI Library\n"
+ %w( FontName FullName FamilyName Weight Notice ItalicAngle
+ IsFixedPitch UnderlinePosition UnderlineTickness Version
+ EncodingScheme CapHeight XHeight Descender Ascender ).each {|kw|
+
+ meth=kw.downcase.to_sym
+ value=self.send(meth) if self.respond_to?(meth)
+ if value
+ s << kw << " " << value.to_s << "\n"
+ end
+ }
+ s << "FontBBox " << @fontbbox.join(" ") << "\n"
+ s << "StartCharMetrics #@count_charmetrics\n"
+ @chars.sort{ |a,b|
+ # puts "a=#{a[1].c}, b=#{b[1].c}"
+ if a[1].c == -1
+ b[1].c == -1 ? 0 : 1
+ else
+ b[1].c == -1 ? -1 : a[1].c <=> b[1].c
+ end
+ }.each { |a,b|
+ s << "C #{b.c} ; WX #{b.wx} ; N #{a} ; B #{b.b.join(" ")}\n"
+ }
+ s << "EndCharMetrics\nStartKernData\nStartKernPairs"
+ count=0
+ @chars.each_value { |c|
+ count += c.kern_data.size
+ }
+ s << " #{count}\n"
+ @chars.sort{ |a,b| a[0] <=> b[0] }.each { |name,char|
+ char.kern_data.each { |destname, value|
+ s << "KPX #{name} #{destname} #{value[0]}\n"
+ }
+ }
+ s << "EndKernPairs\nEndKernData\nEndFontMetrics\n"
+ s
+ end
+
+ # Parse the contents of the String _txt_. Returns self.
+ def parse(txt)
+ @chars ||= Hash.new
+ @s=StringScanner.new(txt.gsub(/\r\n/,"\n"))
+ @s.scan(/StartFontMetrics/)
+ get_fontmetrics
+ self
+ end
+
+ #######
+ private
+ #######
+
+ def get_keyword
+ @s.skip_until(/\s+/)
+ @s.scan(/[A-Z][A-Za-z0-9]+/)
+ end
+
+ def get_integer
+ @s.skip(/\s+/)
+ @s.scan(/-?\d+/).to_i
+ end
+
+ def get_number
+ @s.skip(/\s+/)
+ @s.scan(/-?\d+(?:\.\d+)?/).to_f
+ end
+
+ def get_boolean
+ @s.skip(/\s+/)
+ @s.scan(/(true|false)/) == 'true'
+ end
+
+ def get_name
+ @s.skip(/\s+/)
+ @s.scan(/[^\s]+/)
+ end
+
+ def get_string
+ @s.skip(/\s+/)
+ @s.scan(/.*/)
+ end
+
+ def get_fontmetrics
+ @version = get_number
+ loop do
+ kw=get_keyword
+ STDERR.puts "KW: " + kw if @verbose
+ case kw
+ when "FontName"
+ @fontname=get_string
+ when "FamilyName"
+ @familyname = get_string
+ when "FullName"
+ @fullname = get_string
+ when "EncodingScheme"
+ @encodingscheme = get_string
+ when "ItalicAngle"
+ @italicangle = get_number
+ when "IsFixedPitch"
+ @isfixedpitch = get_boolean
+ when "Weight"
+ @weight = get_string
+ when "XHeight"
+ @xheight= get_number
+ when "Comment"
+ @comment << get_string << "\n"
+ when "FontBBox"
+ @fontbbox = [get_number,get_number, get_number, get_number]
+ when "Version"
+ @version = get_string
+ when "Notice"
+ @notice = get_string
+ when "MappingScheme"
+ @mappingscheme = get_integer
+ when "EscChar"
+ @escchar = get_integer
+ when "CharacterSet"
+ @characterset = get_string
+ when "Characters"
+ @characters = get_integer
+ when "IsBaseFont"
+ @isbasefont = get_boolean
+ when "VVector"
+ @vvector = [get_number,get_number]
+ when "IsFixedV"
+ @isfixedv = get_boolean
+ when "CapHeight"
+ @capheight = get_number
+ when "Ascender"
+ @ascender = get_number
+ when "Descender"
+ @descender = get_number
+ when "UnderlinePosition"
+ @underlineposition = get_number
+ when "UnderlineThickness"
+ @underlinethickness = get_number
+ when "StartDirection"
+ get_direction
+ when "StartCharMetrics"
+ get_charmetrics
+ when "StartKernData"
+ get_kerndata
+ when "StartComposites"
+ get_composites
+ when "EndFontMetrics"
+ break
+ end
+ end
+ end
+ def get_direction
+ # ignored
+ end
+ def get_charmetrics
+ @count_charmetrics = get_integer
+ @count_charmetrics_encoded = 0
+ @count_charmetrics_unencoded = 0
+ loop do
+ @s.skip_until(/\n/)
+ nextstring = @s.scan_until(/(?:StopCharMetrics|.*)/)
+ return if nextstring=="EndCharMetrics"
+ a=nextstring.split(';')
+ # ["C 32 ", " WX 250 ", " N space ", " B 125 0 125 0 "]
+ a.collect! { |elt|
+ elt.strip.split(/ /,2)
+ }
+ # [["C", "32"], ["WX", "250"], ["N", "space"], ["B", "125 0 125 0"]]
+ char=new_glyph
+ a.each { |elt|
+ key,value = elt
+ case key
+ when "N"
+ char.name=value
+ when "B"
+ #special treatment for bounding box
+ char.b = value.split.collect { |e| e.to_i }
+ char.llx = char.llx
+ char.urx = char.urx
+ # We need to avoid negative heights or depths. They break
+ # accents in math mode, among other things.
+ char.lly = 0 if char.lly > 0
+ char.ury = 0 if char.ury < 0
+ when "C"
+ char.c = value.to_i
+ when "CH"
+ # hex: '<20>' -> '0x20' -> .to_i -> 32
+ char.c = value.sub(/</,'0x').sub(/>/,'').to_i(16)
+ when "WX"
+ char.wx = value.to_i
+ # for "L", check /var/www/mirror/system/tex/texmf-local/fonts/afm/jmn/hans/hans.afm
+ when "L", nil
+ #ignore
+ else
+ char.send((key.downcase + "=").to_sym,value.to_i)
+ end
+ }
+
+ @chars[char.name]=char
+ # update information about encoded/unencoded
+ if char.c > -1
+ @count_charmetrics_encoded += 1
+ else
+ @count_charmetrics_unencoded += 1
+ end
+ end
+ raise "never reached"
+ end
+ def get_kerndata
+ loop do
+ kw = get_keyword
+ STDERR.puts "kw=" + kw if @verbose
+ case kw
+ when "EndKernData"
+ return
+ when "StartKernPairs"
+ get_kernpairs
+ when "StartTrackKern"
+ # TrackKern
+ get_trackkern
+ else
+ # KernPairs0
+ # KernPairs1
+ raise "not implemented"
+ end
+ end
+ raise "never reached"
+ end
+ def get_composites
+ count = get_integer
+ loop do
+ kw = get_keyword
+ STDERR.puts "get_composites keyword = '" + kw + "'" if @verbose
+ case kw
+ when "CC"
+ get_composite
+ when "EndComposites"
+ return
+ else
+ STDERR.puts "next to read = " + @s.string[@s.pos,40]
+ raise "AFM error"
+ end
+ end
+ raise "never reached"
+ end
+ def get_composite
+ glyphname = get_name
+ count = get_integer
+ @s.skip_until(/;\s+/)
+ count.times do
+ nextstring = get_name
+ raise "AFM Error" unless nextstring == "PCC"
+ [get_number,get_number]
+ @s.skip_until(/;/)
+ end
+ end
+
+ def get_trackkern
+ count = get_integer
+ loop do
+ case get_keyword
+ when "EndTrackKern"
+ return
+ when "TrackKern"
+ # TrackKern degree min-ptsize min-kern max-ptsize max-kern
+ [get_integer,get_number,get_number,get_number,get_number]
+ else
+ raise "afm error"
+ end
+ end
+ raise "never reached"
+ end
+
+ def get_kernpairs
+ count = get_integer
+ loop do
+ case get_keyword
+ when "KPX" # y is 0
+ name=get_name
+ # if @info['chars'][name]
+ if @chars[name]
+ # array is [x,y] kerning
+ destname,num=get_name,get_number
+ # somethimes something stupid like
+ # KPX .notdef y -26
+ # KPX A .notdef -43
+ # is in the afm data... :-( -> reject those entries
+ # if @info['chars'][destname]
+ if @chars[destname]
+ @chars[name].kern_data[destname] = [num,0]
+ else
+ STDERR.puts "info: unused kern data for " + name if @verbose
+ end
+ else
+ # ignore this entry, print a message
+ STDERR.puts "info: unused kern data for " + name if @verbose
+ @something_strange=true
+ [get_name,get_number] # ignored
+ end
+ when "EndKernPairs"
+ return
+ else
+ STDERR.puts @s.pos
+ raise "not implmented"
+ end
+ end
+ raise "never reached"
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/glyph.rb b/support/rfil/lib/rfil/font/glyph.rb
new file mode 100644
index 0000000000..91b1d69422
--- /dev/null
+++ b/support/rfil/lib/rfil/font/glyph.rb
@@ -0,0 +1,198 @@
+
+module RFIL
+ module Font
+ class Glyph
+
+ # to make Rdoc and Ruby happy: [ruby-talk:147778]
+ def self.documented_as_accessor(*args) #:nodoc:
+ end
+
+ # Glyphname
+ attr_accessor :name
+
+ # Advance with
+ attr_accessor :wx
+
+ # Standard code slot (0-255 or -1 for unencoded)
+ attr_accessor :c
+
+ # bounding box (llx, lly, urx, ury). Array of size 4.
+ # You should use the methods llx, lly, urx, ury to access the
+ # bounding box.
+ attr_accessor :b
+
+ # Kern_data (Hash). The key is the glyph name, the entries are
+ # _[x,y]_ arrays. For ltr and rtl typesetting only the x entry
+ # should be interesting. This is raw information from the font
+ # metric file. Does not change when efactor et al. are set in any
+ # way.
+ attr_accessor :kern_data
+
+ # Information about ligatures - unknown datatype yet
+ attr_accessor :lig_data
+
+ # Composite characters. Array [['glyph1',xshift,yshift],...]
+ attr_accessor :pcc_data
+
+ # Upper right x value of glyph.
+ documented_as_accessor :urx
+
+ # Upper right y value of glyph.
+ documented_as_accessor :ury
+
+ # Lower left x value of glyph.
+ documented_as_accessor :llx
+
+ # Lower left y value of glyph.
+ documented_as_accessor :lly
+
+ # the name of the uppercase glyph (nil if there is no uppercase glyph)
+ attr_accessor :uc
+
+ # the name of the lowercase glyph (nil if there is no lowercase glyph)
+ attr_accessor :lc
+
+ # Optional argument sets the name of the glyph.
+ def initialize (glyphname=nil)
+ @name=glyphname
+ @lig_data={}
+ @kern_data={}
+ @wx=0
+ @b=[0,0,0,0]
+ @efactor=1.0
+ @slant=0.0
+ end
+
+ # Lower left x position of glyph.
+ def llx # :nodoc:
+ @b[0]
+ end
+ def llx=(value) # :nodoc:
+ @b[0]=value
+ end
+
+ # Lower left y position of glyph.
+ def lly # :nodoc:
+ @b[1]
+ end
+ def lly=(value) # :nodoc:
+ @b[1]=value
+ end
+ # Upper right x position of glyph.
+ def urx # :nodoc:
+ @b[2]
+ end
+ def urx=(value) # :nodoc:
+ @b[2]=value
+ end
+
+ # Upper right y position of glyph.
+ def ury # :nodoc:
+ @b[3]
+ end
+ def ury=(value) # :nodoc:
+ @b[3]=value
+ end
+
+ # Return height of the char used for tfm file.
+ def charht
+ ury
+ end
+
+ # Return width of the char used for tfm file.
+ def charwd
+ wx
+ end
+
+ # Return depth of the char.
+ def chardp
+ lly >= 0 ? 0 : -lly
+ end
+
+ # Return italic correction of the char.
+ def charic
+ (urx - wx) > 0 ? (urx - wx) : 0
+ end
+ # Return an array with all kerning information (x-direction only)
+ # of this glyph. Kerning information is an Array where first
+ # element is the destchar, the second element is the kerning amount.
+ def kerns_x
+ ret=[]
+ @kern_data.each { |destchar,kern|
+ ret.push([destchar,kern[0]])
+ }
+ ret
+ end
+
+ # Return an array with all ligature information (LIG objects) of
+ # this glyph.
+ def ligs
+ ret=[]
+ @lig_data.each { |destchar,lig|
+ ret.push(lig)
+ }
+ ret
+ end
+
+ # Return true if this char has ligature or kerning information. If
+ # glyphindex is supplied, only return true if relevant. This means
+ # that the second parameter of a kerning information or the second
+ # parameter and the result of a ligature information must be in
+ # the glyphindex. glyphindex must respond to <em>include?</em>.
+ def has_ligkern?(glyphindex=nil)
+ if glyphindex and not glyphindex.respond_to? :include?
+ raise ArgumentError, "glyphindex does not respod to include?"
+ end
+ return false if (lig_data == {} and kern_data=={})
+ # this one is easy, just look at lig_data and kern_data
+ # more complicated, we have to take glyphindex into account
+ if glyphindex
+ return false unless glyphindex.include? self.name
+ # right kerningpair not in glyphindex? -> false
+ # right lig not in glyphindex? -> false
+ # result lig not in glyphindex? -> false
+ if lig_data
+ lig_data.each { |otherchar,lig|
+ if (glyphindex.include?(lig.right) and glyphindex.include?(lig.result))
+ return true
+ end
+ }
+ end
+ if kern_data
+ kern_data.each { |otherchar,krn|
+ return true if glyphindex.include?(otherchar)
+ }
+ end
+ return false
+ else
+ # no glyphindex
+ return true
+ end
+ raise "never reached"
+ end # has_ligkern?
+
+ # Return true if glyph is an uppercase char, such as AE.
+ def is_uppercase?
+ return @lc != nil
+ end
+
+ # Return true if glyph is a lowercase char, such as germandbls,
+ # but not hyphen.
+ def is_lowercase?
+ return @uc != nil
+ end
+
+ # Return the uppercase variant of the glyph. Undefined behaviour if
+ # glyph cannot be uppercased.
+ def capitalize
+ @uc
+ end
+
+ # Return the lowercase variant of the glyph. Undefined behaviour if
+ # glyph cannot be lowercased.
+ def downcase
+ @lc
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/metric.rb b/support/rfil/lib/rfil/font/metric.rb
new file mode 100644
index 0000000000..fb9afb66ab
--- /dev/null
+++ b/support/rfil/lib/rfil/font/metric.rb
@@ -0,0 +1,129 @@
+# font/metric.rb - superclass for different font metric formats
+# Last Change: Tue May 16 18:08:49 2006
+
+require 'rfil/font/glyph'
+#require 'font/afm'
+#require 'font/truetype'
+
+module RFIL
+ module Font
+ # FontMetric is the superclass for font metrics. All information that
+ # is not specific to a certain kind of file format is accessible via
+ # this class.
+
+ class Metric
+ # to make Rdoc and Ruby happy: [ruby-talk:147778]
+ def self.documented_as_accessor(*args) # :nodoc:
+ end
+ def self.documented_as_reader(*args) # :nodoc:
+ end
+
+ # :type1, :truetype
+ attr_accessor :outlinetype
+
+ # Hash of glyphs in the font.
+ attr_accessor :chars
+
+ # The filename of the just read metric file. Does not contain the
+ # path. To set, change the pathname
+ documented_as_reader :filename
+
+ # Absolute pathname of the metric file. Not checked when set.
+ attr_accessor :pathname
+
+ # File name of the font containing the outlines (the file that needs
+ # to be put into the pdf-file). The .tt or the .pfb file. If unset
+ # use the value of filename, but changed to the correct extension in
+ # case of Type 1 fonts.
+ documented_as_accessor :fontfilename
+
+ # Some unique name of the font. Use the filename of the font or a
+ # name after the KB naming schema. Do not add an extension such as
+ # afm or tt.
+ attr_accessor :name
+
+ # family name of the font
+ attr_accessor :familyname
+
+ # xheight in 1/1000 of an em
+ attr_accessor :xheight
+
+ attr_accessor :weight
+
+ # natural width of a space
+ documented_as_reader :space
+
+ attr_accessor :italicangle
+
+ # True if the font is a monospaced font (courier for example).
+ attr_accessor :isfixedpitch
+
+ # The official name of the font as supplied by the vendor. Written
+ # as FontName in the afm file.
+ attr_accessor :fontname
+
+ # The full name of the font, whatever this means. Written as
+ # FullName in the afm file.
+ attr_accessor :fullname
+
+ # Class for new glyphs. Default is Glyph
+ attr_accessor :glyph_class
+
+ def Metric.read(filename,options={})
+ case File.extname(filename)
+ when ".afm"
+ require 'rfil/font/afm'
+ return AFM.new(options).read(filename)
+ when ".ttf"
+ require 'rfil/font/truetype'
+ return TrueType.new(options).read(filename)
+ else
+ raise ArgumentError, "Unknown filetype: #{File.basename(filename)}"
+ end
+ end
+
+ def initialize
+ @chars=Hash.new
+ @xheight=nil
+ @glyph_class=Glyph
+ @outlinetype=nil
+ @info={}
+ @fontfilename=nil
+ @efactor=1.0
+ @slantfactor=0.0
+ @pathname=nil
+ end
+
+ # Factory for new glyphs. Return new instance of glyph_class (see
+ # Attributes).
+ def new_glyph
+ @glyph_class.new
+ end
+
+
+ def space # :nodoc:
+ chars['space'].wx
+ end
+
+ def filename # :nodoc:
+ File.basename(@pathname)
+ end
+
+ def fontfilename= (obj) # :nodoc:
+ @fontfilename=obj
+ end
+
+ # This one is documented in the 'attributes' section. If the global
+ # variable is unset, just use @filename, perhaps change afm to pfb
+ def fontfilename # :nodoc:
+ return @fontfilename if @fontfilename
+ case filename
+ when /\.afm$/
+ return filename.chomp(".afm") + ".pfb"
+ when /\.tt$/
+ return filename
+ end
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/truetype.rb b/support/rfil/lib/rfil/font/truetype.rb
new file mode 100644
index 0000000000..6c26287eb4
--- /dev/null
+++ b/support/rfil/lib/rfil/font/truetype.rb
@@ -0,0 +1,29 @@
+# truetype.rb -- read truetype font metrics
+#--
+# Last Change: Tue May 16 17:16:56 2006
+#++
+
+require 'rfil/font/afm'
+
+module RFIL
+ module Font
+ # Read TrueType fonts. Use like the AFM class.
+ class TrueType < AFM
+ def initialize(options={})
+ super
+ @outlinetype=:truetype
+ end
+ def read(filename)
+ @filename=File.basename(filename)
+ @fontfilename=filename
+ @name=@filename.chomp(".ttf")
+ self.pathname=Pathname.new(filename).realpath.to_s
+ a=`ttf2afm #{@fontfilename}`
+ parse(a)
+ # ttf2afm does not give an xheight!?
+ @xheight=@chars['x'].ury unless @xheight
+ self
+ end
+ end
+ end
+end