filename = ARGV[0] $eps = File.new filename,"r" $svg = File.new filename[/.*\./] << "svg", "w" $programDirectory = File.dirname($0) + "/" load $programDirectory + "fonttable.rb" load $programDirectory + "standardEncoding.rb" $glyphtable = {} File.new($programDirectory + "glyphlist.txt","r").each_line {|line| line.strip! if line[0]!="#" then line = line.split ";" $glyphtable[line[0].strip] = line[1].to_i(16) end } # Regular expression constants ("re_...") # Why doesn't this work? # $re_delimiter = /[\s\(\)<>\[\]\{\}\/%]/ # $re_number = /(^|(?<=#{$re_delimiter}))([+-]?\d*(\.\d+|\d\.|\d)([eE][+-]?)?\d*|\d+#[\da-fA-F]*)($|(?=#{$re_delimiter}))/ # $re_psname = /(^|(?<=#{$re_delimiter}))(?!#{$re_number})[^\s\(\)<>\[\]\{\}\/%]+($|(?=#{$re_delimiter}))/ # Why does this have to be in parentheses: (.*\) $re_number = /(^|(?<=[\s\(\)<>\[\]\{\}\/%]))([+-]?\d*(\.\d+|\d\.|\d)([eE][+-]?)?\d*|\d+#[\da-fA-F]*)($|(?=[\s\(\)<>\[\]\{\}\/%]))/ $re_psname = /(^|(?<=[\s\(\)<>\[\]\{\}\/%]))(?!#{$re_number})[^\s\(\)<>\[\]\{\}\/%]+($|(?=[\s\(\)<>\[\]\{\}\/%]))/ # TODO: Do better checking of the lines in header rather than blindly assuming correct form # read parameters for global gstate # search for BoundingBox $eps.each_line { |line| if line["%%BoundingBox:"] then $bb_left, $bb_bottom, $bb_right, $bb_top = line.gsub("%%BoundingBox:","").split.map{|value| value.to_i} break end } # search for /SCORE $eps.each_line { |line| if line[/^\s*newpath \/SCORE \{\s*$/] then break end} # read /size and /wdl values from next line splitline = $eps.readline.split $size = splitline[1] $wdl = splitline[4] $currentlw = $wdl $currentfont = "" $current_x_size = 0 $current_y_size = 0 $warning_counter = 0 # skip line $eps.readline # read /lmar and /bmar values from next line splitline = $eps.readline.split $lmar = splitline[1] $bmar = splitline[4] # skip line 2.times{$eps.readline} def root_element $svg << %Q{\n} $svg << %Q{\n\n} $svg << %Q{\n} $svg << %Q{\n} group_count = process_eps group_count.times do |i| $svg << "\n" end $svg << "\n" $svg << "\n" end def process_eps # TODO: # - Recognize outline fonts unrecognized_code = "" group_counter = 0 $eps.each_line { |line| recognized = true case line # line of the form " /EXEC{/exec load}bind def /P00[{g /z exch def /y exch def /x exch def x y tr z z scale" when /^\s*\/EXEC\{\/exec load\}bind def \/P\d*\[\{g \/z exch def \/y exch def \/x exch def x y tr z z scale/ process_def(line) # line of the form "118 -168 m" when /^\s*#{$re_number}\s+#{$re_number}\s+m\s*$/o if $currentlw == $wdl then process_path(line,"") else process_path(line,%Q{stroke-width="#{$currentlw}"}) end # line of the form " 55.639 lw 3743 -24000 .480 P04 wdl lw" when /^\s*#{$re_number}\s+lw\s+#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+P\d+\s+wdl lw\s*$/o process_use(line) $currentlw = $wdl # line of the form " 11112 -23650 1.000 P00" when /^\s*#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+P\d+\s*$/o process_small_use(line) $currentlw = $wdl # line of the form "20.0300 lw" when /^\s*#{$re_number}\s+lw\s*$/o $currentlw = line.split[0] # line of the form "/EXEC {/exec load } bind def /trl [" when /^\s*\/EXEC \{\/exec load \} bind def \/trl \[\s*$/ def_trill # line of the form "15540.3 -17970.0 16950.0 144.5 trl" when /^\s*#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+trl\s*$/o process_trill(line) # line of the form "/Times-Roman f [ 485.082 0 0 485.082 0 0] mkf sf" when /^\s*\/#{$re_psname}\s+f \[\s*#{$re_number}\s+0\s+0\s+#{$re_number}\s+0\s+0\] mkf sf\s*$/o set_font(line) # line of the form " 150 -23832 m save (Contrebasses) show" when /^\s*#{$re_number}\s+#{$re_number}\s+m\s+save\s*\(.*\)\s*show\s*$/o process_text(line) # line of the form " 7190 -20482 m save .00 0 32 22.95 0 (cresc.) aw" when /^\s*#{$re_number}\s+#{$re_number}\s+m\s+save\s*#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+#{$re_number}\s+#{$re_number}\s*\(.*\)\s*aw\s*$/o process_text(line) when /^\s*%svg%/ process_direct_svg(line) when /\/acc(\[|\s|$)/ set_encoding(line) unrecognized_code.clear when /^\s*newpath\s*#{$re_number}\s+#{$re_number}\s+#{$re_number}\s*-270\s*90\s*arc\s*$/o process_circle(line) # line of the form " g 1 2.00000 scale" when /^\s*g\s+1\s+#{$re_number}\s+scale\s*$/o process_ellipse(line) # line of the form " 0 16280 tr" when /^\s*#{$re_number}\s+#{$re_number}\s+tr\s*$/ process_translate(line) group_counter += 1 else recognized = false unrecognized_code << line end if recognized && !unrecognized_code.empty? then $warning_counter = $warning_counter+1 print "WARNING #{$warning_counter}: unrecognized EPS code:\n" print unrecognized_code unrecognized_code.clear end } return group_counter end def def_trill $svg << %Q{\n} $svg << %Q{\n} # skip 2 lines 2.times{$eps.readline} line = $eps.readline process_path(line,'fill="currentColor"') $svg << %Q{\n} $svg << %Q{\n} end def process_trill(line) r3, r4, r6, z = line.split r3 = r3.to_f r4 = r4.to_f r6 = r6.to_f z = z.to_f $svg << %Q{\n} #((r6-r3)/z).floor.times{ |n| ((r6-r3)/z).ceil.times{ |n| x = r3 + n*z $svg << %Q{\n} } $svg << %Q{\n} end def process_use(line) # line of the form " 55.639 lw 3743 -24000 .480 P04 wdl lw" # 0 1 2 3 4 5 6 7 strokeWidth,dummy,x,y,scale,id = line.split write_use(strokeWidth, x, y, scale, id) end def process_small_use(line) # line of the form " 11112 -23650 1.000 P00" # 0 1 2 3 x,y,scale,id = line.split write_use($currentlw, x, y, scale, id) end def write_use(strokeWidth, x, y, scale,id) $svg << %Q{\n} end def process_def(line) $svg << %Q{\n} $svg << %Q{\n} line = $eps.readline begin process_path(line,"") end while (line = $eps.readline) =~ /^\s*#{$re_number}\s+#{$re_number}\s+m\s*$/o $svg << %Q{\n} $svg << %Q{\n} end def process_path(line, attributes) $svg << %Q{ set fill attribute if line =~ /^\s*g e r s\s*$/ then $svg << 'Z" fill="currentColor' end $svg << %Q{"/>\n} end def set_encoding(line) # Position file cursor right after "/acc" $eps.seek(-(line[/(?<=\/acc)[\[|\s|$].*$/].length),IO::SEEK_CUR) $eps.each_char {|c| if c=="[" then break end } # Store whole PostScript array defintion in one string s="" $eps.each_char { |c| if c == "]" then break end if c == "%" then # skip comments $eps.readline s << "\n" else s << c end } while !s.empty? s.lstrip! # next expected token is a PS integer (decimal or octal form) substring = s[/^(8#)?[0-9]+/] if !substring then $warning_counter = $warning_counter+1 print "WARNING #{$warning_counter}: Couldn't complete parsing encoding vector.\nPostScript integer expected, but found:\n" puts s break end if substring[/^8#/] then code = substring[2..-1].to_i(8) else code = substring.to_i end s = s[substring.length,s.length-1].lstrip # remove parsed number # next expected token is a PS name name = s[/(?<=^\/)#{$re_psname}/o] if !name then $warning_counter = $warning_counter+1 print "WARNING #{$warning_counter}: Couldn't complete parsing encoding vector.\nPostScript name with leading slash expected, but found:\n" puts s break end $encoding[code] = name s = s[name.length+1,s.length-1].lstrip # remove parsed name end # skip rest of fontinit.psc $eps.each_line {|s| if s["this must be here to end the file"] then break end } end def set_font(line) # TODO: Error handling if font information wasn't found ps_fontname = line[$re_psname] $currentfont = $fonttable[ps_fontname] line = line.split $current_x_size = line[3].to_i $current_y_size = line[6].to_i end def process_text(line) def write_text_content(string) # TODO: Define precise character positions using x-Attribute def write_unicode_glyph(ps_glyph_code) c = $glyphtable[$encoding[ps_glyph_code]] # if c is undefined, write a kind of missing-glyph rectangle if !c $svg << "▯" # As XML "normalizes" spaces (removes leading/trailing, shrinks sequence of # space characters to one space), we make all spaces non-breaking spaces elsif (c==" ".ord) $svg << " " # check whether c is in the printable ASCII range # (and not "<" which would be interpreted as a tag bracket) elsif (c>31) && (c<127) && (c!="<".ord) && (c!="&".ord) $svg << c.chr elsif # write Unicode $svg << "&#" << c << ";" end end # Iterate through the glyphs. The regexp matches all single chars in literal PostScript strings. # Excption: A continuous whitespace sequence is also matched as it has to be treated specially string.scan(/\\n|\\r|\\t|\\b|\\f|\\\\|\\\(|\\\)|\\[0-3][0-7]{2}|\\.|\s+|./) { |c| case c.length when 1 then write_unicode_glyph(c[0].ord) when 2 then case c[1] when "n" then write_unicode_glyph(10) when "r" then write_unicode_glyph(13) when "t" then write_unicode_glyph(9) when "b" then write_unicode_glyph(8) when "f" then write_unicode_glyph(12) when "\\" then write_unicode_glyph(92) when "(" then write_unicode_glyph(40) when ")" then write_unicode_glyph(41) else write_unicode_glyph(c[1].ord) end # octal codes when 4 then write_unicode_glyph(c[1,3].to_i(8)) end } end # line of the form " 7190 -20482 m save .00 0 32 22.95 0 (cresc.) aw" # or " 150 -23832 m save (Contrebasses) show" x,y = line.split $svg << %Q{} string = line[/(?<=\()(.*)(?=\))/] if (line[/show\s*$/]) then write_text_content(string) else # string with awidthshow spacing x,y,m,save,cx,cy,char,ax = line.split cx = cx.to_f ax = ax.to_f string.scan(/\s+|[^\s]+/) { |substring| $svg << %Q{} else $svg << %Q{letter-spacing="#{ax}">} end write_text_content(substring) $svg << %Q{} } end $svg << %Q{\n} line = $eps.readline if (not line[/^\s*restore\s*$/]) then print %Q{WARNING #{$warning_counter}: Expected line with single "restore" after Text item, but found:\n} puts line end end def process_circle(line) # line of the form " newpath 15487.5 -23475.0 50.8 -270 90 arc" splitline = line.split $svg << %Q{\n} end def process_ellipse(line) #line of the form " g 1 2.00000 scale" yFactor = line.split[2].to_f line = $eps.readline #line of the form " newpath 2475.0 -12000.0 437.5 -270 90 arc" dummy,cx,rawCy,rx = line.split $svg << %Q{\n} end def process_direct_svg(line) $svg << line.partition('%svg%')[2].chomp $svg << "\n" end def process_translate(line) # line of the form " 0 16280 tr" splitline = line.split $svg << %Q{} end root_element print "\nSVG generation was successful with #{$warning_counter} warnings.\n" #$encoding.each{|name| p name}