require 'net/http' require 'open-uri' require 'yaml' require 'zip/zipfilesystem' require 'fileutils' module Cytoplasm class FontsController < ApplicationController before_filter :load_cache, :only => [:index,:show,:fetch_all] # Configuration @@directories = {"fontsquirrel" => "FontSquirrel","googlewebfonts" => "Google Web Fonts"} @@fontsdir = "public/fonts" @@gwf_file = @@fontsdir + "/googlewebfonts.yml" @@enabled_file = @@fontsdir + "/enabled.yml" # For internal usage, DO NOT MODIFY @@cache = false class << self def directories @@directories end def fonts @@cache = fetch_fonts() if @@cache == false return @fonts end def fetch_fonts @fonts = {"fontsquirrel" => fetch_fs_all(), "googlewebfonts" => fetch_gwf_all(),"imported" => fetch_imported(),"enabled" => fetch_enabled()} return @fonts end def fetch_fs_all fs = {} fetch_json("http://www.fontsquirrel.com/api/fontlist/all").each do |f| fs[f["family_urlname"]] = f end return fs end def fetch_gwf_all gwf = {} fetch_json("https://www.googleapis.com/webfonts/v1/webfonts?key="+Cytoplasm.conf("fontloader.googlewebfonts_apikey"))["items"].each {|f| gwf[f["family"]] = f} return gwf end def fetch_family(dir,fam) return fetch_fs_family(fam) if dir=="fontsquirrel" return fetch_gwf_family(fam) if dir=="googlewebfonts" end def fetch_fs_family(family) fs = (@@cache==false or @@cache["fontsquirrel"]==false) ? fetch_fs_all() : @@cache["fontsquirrel"] return fs[family] unless fs[family].nil? end def fetch_fs_familyinfo(family) fetch_json("http://www.fontsquirrel.com/api/familyinfo/"+family) end def fetch_gwf_family(family) gwf = (@@cache==false) ? fetch_gwf_all() : @@cache["googlewebfonts"] return gwf[family] unless gwf[family].nil? end def fetch_imported imported = {"fontsquirrel" => {}, "googlewebfonts" => {}} # Create @@fontsdir if it doesn't exist unless File.exists?(@@fontsdir) count = 0 @@fontsdir.split("/").each do |dir| Dir.mkdir(dir) unless File.exists?(dir) Dir.chdir(dir) count += 1 end while count > 0 do Dir.chdir("../") count -= 1 end end # Create @@gwf_file and @@enabled_file if it doesn't exist File.open(@@gwf_file,'w') {|f| f.write("")} unless File.exists?(@@gwf_file) File.open(@@enabled_file,'w') {|f| f.write("")} unless File.exists?(@@enabled_file) # Fetch imported FontSquirrel fonts Dir.open(@@fontsdir).each do |f| if File.directory?(@@fontsdir+"/"+f) and f!="." and f!=".." imported["fontsquirrel"][f] = fetch_fs_family(f) imported["fontsquirrel"][f]["familyinfo"] = fetch_fs_familyinfo(f) end end # Fetch imported GoogleWebFonts fonts yaml = fetch_yaml(@@gwf_file) yaml["imported"].each {|f| imported["googlewebfonts"][f] = fetch_gwf_family(f)} unless yaml["imported"].nil? or !yaml["imported"].any? return imported end def fetch_enabled enabled = {"fontsquirrel" => {}, "googlewebfonts" => {}} # Attempt to load YAML from @@enabled_file yaml = fetch_yaml(@@enabled_file) # Check that FontSquirrel fonts actually exist in @@fontsdir notfound = 0 yaml["fontsquirrel"].each do |fam,variants| unless File.exists?(@@fontsdir+"/"+fam) yaml["fontsquirrel"].delete(fam) notfound += 1 end end if notfound>0 open(@@enabled_file,"w+") {|f| f.write(yaml.to_yaml)} return fetch_enabled() end # Fetch enabled fonts lists ["fontsquirrel","googlewebfonts"].each do |dir| unless yaml[dir].nil? yaml[dir].each do |fam,variants| enabled[dir][fam] = fetch_family(dir,fam) end end end return enabled end def is_enabled?(dir,fam) enabled = (@@cache==false) ? fetch_enabled() : @@cache["enabled"] return !enabled[dir][fam].nil? end def import(dir,fam) output = {} @@cache = fetch_fonts() if @@cache == false if dir == "fontsquirrel" output["dir"] = @@fontsdir+"/"+fam output["url"] = "http://www.fontsquirrel.com/fontfacekit/"+fam open(output["url"]) do |f| Zip::ZipFile.open(f) do |zipfile| zipfile.each do |entry| fpath = File.join(output["dir"],entry.to_s) FileUtils.mkdir_p(File.dirname(fpath)) zipfile.extract(entry,fpath){true} end end end fix_fs_stylesheet_family_names(fam) end @@cache["imported"][dir][fam] = fetch_family(dir,fam) write_gwf_file() if dir=="googlewebfonts" @@cache = fetch_fonts() return output; end def remove(dir,fam) output = {} @@cache = fetch_fonts() if @@cache == false disable(dir,fam) if is_enabled?(dir,fam) case dir when "fontsquirrel" output["dir"] = @@fontsdir+"/"+fam FileUtils.rm_rf(output["dir"]) if File.exists?(output["dir"]) when "googlewebfonts" @@cache["imported"]["googlewebfonts"].delete(fam) write_gwf_file() end @@cache = fetch_fonts() return output end def enable(dir,fam) output = {} @@cache = fetch_fonts() if @@cache == false @@cache["enabled"][dir][fam] = fetch_family(dir,fam) write_enabled_file() @@cache = fetch_fonts() return output end def disable(dir,fam) output = {} @@cache = fetch_fonts() if @@cache == false @@cache["enabled"][dir].delete(fam) write_enabled_file() @@cache = fetch_fonts() return output end private def fetch_json(url) return ActiveSupport::JSON.decode(open(url).read) end def fetch_yaml(file) begin c = YAML::load_file(file) rescue puts "Failed to parse YAML in #{file}!" end return c end def write_gwf_file imported = (@@cache==false) ? fetch_imported()["googlewebfonts"] : @@cache["imported"]["googlewebfonts"] open(@@gwf_file,"w+") {|f| f.write({"imported" => imported.keys}.to_yaml)} end def write_enabled_file enabled = (@@cache==false) ? fetch_enabled() : @@cache["enabled"] e = {} ["fontsquirrel","googlewebfonts"].each do |dir| e[dir] = {} enabled[dir].each do |fam,variants| # Make list of variants to enable c = [] c += variants.keys if dir=="googlewebfonts" e[dir][fam] = c end end open(@@enabled_file,"w+") {|f| f.write(e.to_yaml)} end def fix_fs_stylesheet_family_names(fam) sheet = @@fontsdir+"/"+fam+"/stylesheet.css" finfo = fetch_fs_familyinfo(fam) # Fetch contents lines = File.readlines(sheet) lines.each_with_index do |line,index| if line.include?("font-family") # Parse name halves = line.split(":") name = halves[1] [';','"',"'"].each{|char| name = name.gsub(char,'') if name.include?(char)} name = name.strip() # Parse filename filename = false for i in (index+1)..lines.length l = lines[i] if l.include?("src: url(") filename = l.split("(")[1].split(")")[0] ['"',"'"].each{|char| filename = filename.gsub(char,'') if filename.include?(char)} filename = filename.split(".") ext = filename.pop() filename = filename.join(".").strip() break end end filename = filename.chomp("-webfont") if filename.include?("-webfont") unless filename == false # Determine corresponding fontface_name from familyinfo cname = false cindex = false if finfo.length == 1 # If there is only one variant, we choose that one cindex = 0 else # Search for exact matches among the variants finfo.each_with_index do |face,index| ffname = face["filename"].split(".") ext = ffname.pop() ffname = ffname.join(".").strip() if ffname == filename cindex = index break end end # Otherwise, use the Levenshtein algorithm to select the closest match if cindex == false best = false finfo.each_with_index do |face,index| ffname = face["filename"].split(".") ext = ffname.pop() ffname = ffname.join(".").strip() lscore = levenshtein(filename,ffname) if best==false or lscore <= best best = lscore cindex = index end end end end # Fetch fontface_name from cindex and remove the variant from rotation puts "CINDEX: "+cindex.to_s cname = finfo[cindex]["fontface_name"] finfo.delete(cindex) puts "CNAME: " + cname.to_s # Replace if necessary lines[index] = halves[0]+": '"+cname+"';\n" if cname != false and name != cname end end end # Write fixed contents to file open(sheet,"w+") {|f| f.write(lines.join())} end @@damerau = true def levenshtein(s1, s2) d = {} (0..s1.size).each do |row| d[[row, 0]] = row end (0..s2.size).each do |col| d[[0, col]] = col end (1..s1.size).each do |i| (1..s2.size).each do |j| cost = 0 if (s1[i-1] != s2[j-1]) cost = 1 end d[[i, j]] = [d[[i - 1, j]] + 1, d[[i, j - 1]] + 1, d[[i - 1, j - 1]] + cost ].min next unless @@damerau if (i > 1 and j > 1 and s1[i-1] == s2[j-2] and s1[i-2] == s2[j-1]) d[[i, j]] = [d[[i,j]], d[[i-2, j-2]] + cost ].min end end end return d[[s1.size, s2.size]] end end def fetch_all render :text => Cytoplasm::Ajax.success(@@cache) end def index @installed = Cytoplasm::FontsController.fetch_imported() @enabled = Cytoplasm::FontsController.fetch_enabled() end def show @font = Cytoplasm::FontsController.fetch_family(params[:directory],params[:family]) end def import render :text => Cytoplasm::Ajax.success(Cytoplasm::FontsController.import(params[:directory],params[:family])) end def remove render :text => Cytoplasm::Ajax.success(Cytoplasm::FontsController.remove(params[:directory],params[:family])) end def enable render :text => Cytoplasm::Ajax.success(Cytoplasm::FontsController.enable(params[:directory],params[:family])) end def disable render :text => Cytoplasm::Ajax.success(Cytoplasm::FontsController.disable(params[:directory],params[:family])) end private def load_cache @@cache = Cytoplasm::FontsController.fetch_fonts() if @@cache == false end def refresh_cache @@cache = false load_cache() end end end