4 # Create and access a database of fonts.
16 # Create and access a database of fonts.
17 # The instance is accessed using FontDB.instance()
20 # Ensures that only one can exist.
23 # Constants for use in a few functions.
26 # Where should they go?
32 # Get this stuff from a textfile or something.
33 FONT_DIRECTORIES = ['/usr/share/fonts','C:\WINDOWS\Fonts']
34 FONT_TYPES = ["ttf","otf"]
36 # Add these tags based on font metadata.
37 AUTO_TAGS = %w(bold serif italic sans-serif
38 sans\ serif hairline modern slab\ serif script)
40 # The associated data cannot coexist. The right side is removed.
41 AUTO_TAGS_EXCLUSIVE = {"sans-serif" => "serif", "sans serif" => "serif"}
43 # The associated data should be combined. Left side becomes right side.
44 AUTO_TAGS_COMBINE = {"sans-serif" => "sans serif"}
46 # Columns to search through for tags.
47 AUTO_TAGS_COLUMNS= %w(family subfamily name description)
50 @db = SQLite3::Database.new('database.db')
53 # Gets data out of the database.
56 # FontDB.instance.get("name","id",DOWN, 5, 10)
58 # will display the names, sorted by decreasing ID starting at #10, and
61 def get(columns, order_by='name', sort_dir = UP, limit = NO_LIMIT, offset = 0)
68 SELECT #{[columns].flatten.join(", ")}
70 ORDER BY #{order_by} #{dir}
76 # Creates the required tables.
79 if !FontDB.instance.tables_exist? then
83 id INTEGER PRIMARY KEY,
84 path VARCHAR(255) UNIQUE ON CONFLICT IGNORE,
86 copyright VARCHAR(2048),
88 subfamily VARCHAR(255),
91 description VARCHAR(2048),
93 defaulttext VARCHAR(255)
98 id INTEGER PRIMARY KEY,
99 name VARCHAR(255) UNIQUE ON CONFLICT IGNORE
102 CREATE TABLE tags_fonts
106 PRIMARY KEY (tag_id, font_id) ON CONFLICT IGNORE
112 id INTEGER PRIMARY KEY,
113 name VARCHAR(255) UNIQUE ON CONFLICT IGNORE
116 CREATE TABLE sets_fonts
120 PRIMARY KEY (set_id, font_id) ON CONFLICT IGNORE
129 # Removes all tables from the database.
132 if FontDB.instance.tables_exist? then
136 DROP TABLE tags_fonts;
138 DROP TABLE sets_fonts;
146 # Adds a font to the database.
148 # This should auto_tag_font.
151 font_data = [path] + Font.meta(path)
155 path, copyright, family, subfamily,
156 name, foundry, description, license, defaulttext
159 '#{font_data.collect{|x|x.to_s}.join(%{','})}'
164 # Populates the table based on system directories.
167 Font.find(FONT_DIRECTORIES, FONT_TYPES).each do |path|
172 # FIXME:: add in sets.
175 return @db.execute(%{
178 ("-------")+"\n" + meta.join("\n") + "\n" + read_tags(meta[0]).join(", ")
182 # Has create_tables been called?
185 return !@db.execute(%{
186 SELECT name FROM sqlite_master WHERE type = 'table'
192 # A set of functions for acting on sets or tags.
194 # Add an array of groups to an array of font ids.
196 def add_groups(font_ids, groups, type)
197 [groups].flatten.each do |group|
198 # Add the group to the global table of groups.
200 INSERT INTO #{type}s (name)
203 [font_ids].flatten.each do |font_id|
204 # Add an association between the font and the group.
206 INSERT INTO #{type}s_fonts
213 (SELECT id FROM #{type}s WHERE name = '#{group}'),
221 # Removes groups from the given fonts.
223 def remove_groups(font_ids, groups, type)
224 [groups].flatten.each do |group|
225 [font_ids].flatten.each do |font_id|
227 DELETE FROM #{type}s_fonts
228 WHERE font_id = '#{font_id}'
231 SELECT id FROM #{type}s
232 WHERE name = '#{group}'
239 # Combines groups named old_names into one named new_name.
241 def combine_groups(new_name, old_names, type)
242 [old_names].flatten.each do |old_name|
243 ids = tag_members(old_name)
244 remove_tags(ids, old_names)
245 add_tags(ids, new_name)
249 # Returns all groups associated with a font ID.
251 def read_groups(font_id, type)
252 return @db.execute(%{
253 SELECT name FROM #{type}s
254 INNER JOIN #{type}s_fonts
255 ON #{type}s.id = #{type}s_fonts.tag_id
256 WHERE #{type}s_fonts.font_id = '#{font_id}'
260 # Returns the font IDs of all members of group.
262 def group_members(group, type)
263 return @db.execute(%{
264 SELECT font_id FROM #{type}s_fonts
266 ON #{type}s_fonts.tag_id = #{type}s.id
267 WHERE #{type}s.name = '#{group}'
271 # Returns a list of all groups.
274 return @db.execute(%{
275 SELECT name FROM #{type}s
281 # Adds the tags to the given fonts.
283 def add_tags(font_ids, tags)
284 add_groups(font_ids, tags, "tag")
287 # Adds the sets to the given fonts.
289 def add_sets(font_ids, set)
290 add_groups(font_ids, tags, "set")
293 # Removes the tags from the given fonts.
295 def remove_tags(font_ids, tags)
296 remove_groups(font_ids, tags, "tag")
299 # Removes the sets from the given fonts.
301 def remove_sets(font_ids, tags)
302 remove_groups(font_ids, tags, "set")
305 # Changes all old_tags to new_tag.
307 def combine_tags(new_tag, *old_tags)
308 combine_groups(new_tag, old_tags, "tag")
311 # Changes all old_sets to new_set.
313 def combine_sets(new_set, *old_sets)
314 combine_groups(new_set, old_sets, "set")
317 # Lists all of the given font's tags.
319 def read_tags(font_id)
320 read_groups(font_id, "tag")
323 # Lists all of the given font's sets.
325 def read_sets(font_id)
326 read_groups(font_id, "set")
329 # Lists all of the tag's members.
331 def tag_members(tag_name)
332 group_members(tag_name, "tag")
335 # Lists all of the set's members.
337 def set_members(set_name)
338 group_members(set_name, "set")
351 # Automatically tags all fonts.
352 # FIXME: should constants be in here?
354 def auto_tag(auto_tags, auto_tags_exclusive,
355 auto_tags_combine, auto_tags_search_columns)
356 auto_tags.each do |tag|
357 # Search through all columns given for tags in the auto_tag list.
358 all_columns = auto_tags_search_columns.join(" LIKE '%#{tag}%' OR ")
367 auto_tags_combine.each_key do |tag_keep|
368 combine_tags(tag_keep, auto_tags_combine[tag_keep])
371 auto_tags_exclusive.each_key do |tag_keep|
372 ids = tag_members(tag_keep) & tag_members(auto_tags_exclusive[tag_keep])
373 remove_tags(ids, (auto_tags_exclusive[tag_keep]))
379 def auto_tag_font(id, auto_tags, auto_tags_exclusive,
380 auto_tags_combine, auto_tags_search_columns)
382 auto_tags.each do |tag|
383 # Search through all columns given for tags in the auto_tag list
384 all_columns = auto_tags_search_columns.join(" LIKE '%#{tag}%' OR ")
388 AND (#{all_columns} LIKE '%#{tag}%')
396 # This shouldn't be global.
397 auto_tags_combine.each_key do |tag_keep|
398 combine_tags(tag_keep, auto_tags_combine[tag_keep])
401 auto_tags_exclusive.each_key do |tag_keep|
402 ids = tag_members(tag_keep) & tag_members(auto_tags_exclusive[tag_keep])
403 remove_tags(ids, (auto_tags_exclusive[tag_keep]))
410 # Should be a transaction to do all starting stuff.
413 # Should it do a full scan every time?
415 db.auto_tag(FontDB::AUTO_TAGS, FontDB::AUTO_TAGS_EXCLUSIVE,
416 FontDB::AUTO_TAGS_COMBINE, FontDB::AUTO_TAGS_COLUMNS)