/* -*- mode: C; c-basic-offset: 4 -*- * fontilus - a collection of font utilities for GNOME * Copyright (C) 2002-2003 James Henstridge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #define FONT_METHOD_DIRECTORY DIRECTORY_DIR "/font-method.directory" /* this is from gnome-vfs-monitor-private.h */ void gnome_vfs_monitor_callback (GnomeVFSMethodHandle *method_handle, GnomeVFSURI *info_uri, GnomeVFSMonitorEventType event_type); static void invoke_monitors(void); /* -------- code for creating the font list -------- */ /* list of fonts in fontconfig database */ G_LOCK_DEFINE_STATIC(font_list); static FcFontSet *font_list = NULL; static gchar **font_names = NULL; static GHashTable *font_hash = NULL; static gchar * get_pango_name(FcPattern *pat) { FcChar8 *family; GString *str; gint i; FcPatternGetString(pat, FC_FAMILY, 0, &family); str = g_string_new(family); g_string_append_c(str, ','); /* add weight word */ if (FcPatternGetInteger(pat, FC_WEIGHT, 0, &i) == FcResultMatch) { gchar *weight = NULL; if (i < FC_WEIGHT_LIGHT) weight = " Ultra-Light"; else if (i < (FC_WEIGHT_LIGHT + FC_WEIGHT_MEDIUM) / 2) weight = " Light"; else if (i < (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) weight = NULL; else if (i < (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) weight = " Semi-Bold"; else if (i < (FC_WEIGHT_BOLD + FC_WEIGHT_BLACK) / 2) weight = " Bold"; else weight = " Ultra-Bold"; if (weight) g_string_append(str, weight); } /* add slant word */ if (FcPatternGetInteger(pat, FC_SLANT, 0, &i) == FcResultMatch) { gchar *style = NULL; if (i == FC_SLANT_ROMAN) style = NULL; else if (i == FC_SLANT_OBLIQUE) style = " Oblique"; else style = " Italic"; if (style) g_string_append(str, style); } /* if ends in a comma, check to see if the last word matches a modifier. * if not, remove the comma. */ if (str->str[str->len-1] == ',') { const gchar *lastword; gint wordlen; gboolean word_matches; const char *modifier_words[] = { "Oblique", "Italic", "Small-Caps", "Ultra-Light", "Light", "Medium", "Semi-Bold", "Bold", "Ultra-Bold", "Heavy", "Ultra-Condensed", "Extra-Condensed", "Condensed", "Semi-Condensed", "Semi-Expanded", "Expanded", "Extra-Expanded", "Ultra-Expanded" }; lastword = strrchr(str->str, ' '); if (lastword) lastword++; else lastword = str->str; wordlen = strlen(lastword) - 1; /* exclude comma */ word_matches = FALSE; for (i = 0; i < G_N_ELEMENTS(modifier_words); i++) { if (g_ascii_strncasecmp(modifier_words[i], lastword, wordlen)==0) { word_matches = TRUE; break; } } /* if the last word doesn't match, then we can remove the comma */ if (!word_matches) g_string_truncate(str, str->len-1); } return g_string_free(str, FALSE); } /* make sure the font_list is valid */ static gboolean ensure_font_list(void) { gboolean result = FALSE; FcPattern *pat; FcObjectSet *os; gint i; G_LOCK(font_list); /* if the config exists, and is up to date, return */ if (font_list != NULL) { if (FcInitBringUptoDate()) { result = TRUE; goto end; } /* otherwise, destroy the fonts list and recreate */ FcFontSetDestroy(font_list); font_list = NULL; g_strfreev(font_names); font_names = NULL; g_hash_table_destroy(font_hash); font_hash = NULL; } pat = FcPatternCreate(); os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_WEIGHT, FC_SLANT, NULL); font_list = FcFontList(NULL, pat, os); FcPatternDestroy(pat); FcObjectSetDestroy(os); if (!font_list) { result = FALSE; goto end; } /* set up name list and hash */ font_names = g_new(gchar *, font_list->nfont); font_hash = g_hash_table_new(g_str_hash, g_str_equal); for (i = 0; i < font_list->nfont; i++) { font_names[i] = get_pango_name(font_list->fonts[i]); g_hash_table_insert(font_hash, font_names[i], font_list->fonts[i]); } result = TRUE; /* invoke monitors */ invoke_monitors(); end: G_UNLOCK(font_list); return result; } static GnomeVFSURI * create_local_uri(const GnomeVFSURI *orig_uri) { gchar *fontsdir, *fontsdir_escaped, *basename; GnomeVFSURI *fontsdir_uri, *new_uri; /* make sure ~/.fonts exists ... */ fontsdir = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, ".fonts",NULL); if (mkdir(fontsdir, 0755) && errno != EEXIST) { g_free(fontsdir); return NULL; } /* get URI for fontsdir */ fontsdir_escaped = gnome_vfs_get_uri_from_local_path(fontsdir); g_free(fontsdir); fontsdir_uri = gnome_vfs_uri_new(fontsdir_escaped); g_free(fontsdir_escaped); basename = gnome_vfs_uri_extract_short_name(orig_uri); new_uri = gnome_vfs_uri_append_file_name(fontsdir_uri, basename); g_free(basename); gnome_vfs_uri_unref(fontsdir_uri); return new_uri; } /* -------- VFS method ------ */ static gchar * get_path_from_uri (GnomeVFSURI const *uri) { gchar *path; gint len; path = gnome_vfs_unescape_string (uri->text, G_DIR_SEPARATOR_S); if (path == NULL) { return NULL; } if (path[0] != G_DIR_SEPARATOR) { g_free (path); return NULL; } len = strlen(path); if (path[len-1] == G_DIR_SEPARATOR) path[len-1] = '\0'; return path; } static GnomeVFSResult fill_file_info(GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, FcChar8 *file, gchar *name) { gchar *uri; GnomeVFSResult result; uri = gnome_vfs_get_uri_from_local_path(file); result = gnome_vfs_get_file_info(uri, file_info, options); g_free (uri); if (result == GNOME_VFS_OK) { g_free(file_info->name); file_info->name = g_strdup(name); file_info->valid_fields &= ~GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME; g_free(file_info->symlink_name); file_info->symlink_name = NULL; file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; GNOME_VFS_FILE_INFO_SET_SYMLINK(file_info, FALSE); } return result; } typedef struct _FontListHandle FontListHandle; struct _FontListHandle { gint font; GnomeVFSFileInfoOptions options; gboolean seen_dotdirectory; }; static GnomeVFSResult do_open_directory(GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_ERROR_NOT_SUPPORTED; char *path = NULL; FontListHandle *handle; path = get_path_from_uri(uri); if (!path) { result = GNOME_VFS_ERROR_INVALID_URI; goto end; } if (strcmp(path, "") != 0) { result = GNOME_VFS_ERROR_NOT_FOUND; goto end; } if (!ensure_font_list()) { result = GNOME_VFS_ERROR_INTERNAL; goto end; } /* handle used to iterate over font names */ handle = g_new0(FontListHandle, 1); handle->font = 0; handle->options = options; handle->seen_dotdirectory = FALSE; *method_handle = (GnomeVFSMethodHandle *)handle; result = GNOME_VFS_OK; end: g_free(path); return result; } static GnomeVFSResult do_close_directory(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { FontListHandle *handle; handle = (FontListHandle *)method_handle; g_free(handle); return GNOME_VFS_OK; } static GnomeVFSResult do_read_directory(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_ERROR_NOT_SUPPORTED; FontListHandle *handle; FcChar8 *file; handle = (FontListHandle *)method_handle; G_LOCK(font_list); if (!font_list) { result = GNOME_VFS_ERROR_INTERNAL; goto end; } /* list the .directory file */ if (!handle->seen_dotdirectory) { g_free(file_info->name); file_info->name = g_strdup(".directory"); file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; file_info->mime_type = g_strdup("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; handle->seen_dotdirectory = TRUE; result = GNOME_VFS_OK; goto end; } if (handle->font >= font_list->nfont) { result = GNOME_VFS_ERROR_EOF; goto end; } /* get information about this font, skipping unfound fonts */ result = GNOME_VFS_ERROR_NOT_FOUND; while (handle->font < font_list->nfont && result == GNOME_VFS_ERROR_NOT_FOUND) { FcPatternGetString(font_list->fonts[handle->font], FC_FILE, 0, &file); result = fill_file_info(file_info, handle->options, file, font_names[handle->font]); /* move on to next font */ handle->font++; } end: G_UNLOCK(font_list); return result; } /* -------- handling of file objects -------- */ static GnomeVFSResult do_open(GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_ERROR_NOT_FOUND; char *path = NULL; FcPattern *font; path = get_path_from_uri(uri); if (!path) { result = GNOME_VFS_ERROR_INVALID_URI; goto end; } if (!ensure_font_list()) { result = GNOME_VFS_ERROR_INTERNAL; goto end; } if (path[0] == '\0') { result = GNOME_VFS_ERROR_IS_DIRECTORY; goto end; } /* we don't support openning existing files for writing */ if ((mode & GNOME_VFS_OPEN_WRITE) != 0) { result = GNOME_VFS_ERROR_READ_ONLY; goto end; } /* handle the .directory file */ if (!strcmp(path, "/.directory")) { uri = gnome_vfs_uri_new(FONT_METHOD_DIRECTORY); result = gnome_vfs_open_uri_cancellable( (GnomeVFSHandle **)method_handle, uri, mode, context); gnome_vfs_uri_unref(uri); goto end; } G_LOCK(font_list); font = g_hash_table_lookup(font_hash, &path[1]); if (font) { FcChar8 *file; gchar *text_uri; GnomeVFSURI *font_uri; FcPatternGetString(font, FC_FILE, 0, &file); text_uri = gnome_vfs_get_uri_from_local_path(file); font_uri = gnome_vfs_uri_new(text_uri); g_free(text_uri); result = gnome_vfs_open_uri_cancellable( (GnomeVFSHandle **)method_handle, font_uri, mode, context); gnome_vfs_uri_unref(font_uri); } else { result = GNOME_VFS_ERROR_NOT_FOUND; } G_UNLOCK(font_list); end: g_free(path); return result; } static GnomeVFSResult do_create(GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, gboolean exclusive, guint perm, GnomeVFSContext *context) { GnomeVFSResult result; GnomeVFSURI *new_uri; new_uri = create_local_uri(uri); if (!new_uri) return gnome_vfs_result_from_errno(); result = gnome_vfs_create_uri_cancellable((GnomeVFSHandle **)method_handle, new_uri, mode, exclusive, perm, context); gnome_vfs_uri_unref(new_uri); return result; } static GnomeVFSResult do_close(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { return gnome_vfs_close_cancellable((GnomeVFSHandle *)method_handle, context); } static GnomeVFSResult do_read(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, gpointer buffer, GnomeVFSFileSize bytes, GnomeVFSFileSize *bytes_read, GnomeVFSContext *context) { return gnome_vfs_read_cancellable((GnomeVFSHandle *)method_handle, buffer, bytes, bytes_read, context); } static GnomeVFSResult do_write(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, gconstpointer buffer, GnomeVFSFileSize bytes, GnomeVFSFileSize *bytes_written, GnomeVFSContext *context) { return gnome_vfs_write_cancellable((GnomeVFSHandle *)method_handle, buffer, bytes, bytes_written, context); } static GnomeVFSResult do_seek(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSSeekPosition whence, GnomeVFSFileOffset offset, GnomeVFSContext *context) { return gnome_vfs_seek_cancellable((GnomeVFSHandle *)method_handle, whence, offset, context); } static GnomeVFSResult do_tell(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileSize *offset_return) { return gnome_vfs_tell((GnomeVFSHandle *)method_handle, offset_return); } static GnomeVFSResult do_get_file_info_from_handle(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { return gnome_vfs_get_file_info_from_handle_cancellable ((GnomeVFSHandle *)method_handle, file_info, options, context); } /* -------- file metadata -------- */ static GnomeVFSResult do_get_file_info(GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_ERROR_NOT_FOUND; char *path = NULL; path = get_path_from_uri(uri); if (!path) { result = GNOME_VFS_ERROR_INVALID_URI; goto end; } if (!ensure_font_list()) { result = GNOME_VFS_ERROR_INTERNAL; goto end; } /* root directory */ if (!strcmp(path, "")) { g_free(file_info->name); file_info->name = g_strdup("Fonts"); file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; g_free(file_info->mime_type); file_info->mime_type = g_strdup("x-directory/normal"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; result = GNOME_VFS_OK; } else if (!strcmp(path, "/.directory")) { g_free(file_info->name); file_info->name = g_strdup(".directory"); file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; g_free(file_info->mime_type); file_info->mime_type = g_strdup("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; result = GNOME_VFS_OK; } else { FcPattern *font; G_LOCK(font_list); font = g_hash_table_lookup(font_hash, &path[1]); if (font) { FcChar8 *file; FcPatternGetString(font, FC_FILE, 0, &file); result = fill_file_info(file_info, options, file, &path[1]); } G_UNLOCK(font_list); } end: G_UNLOCK(font_list); g_free(path); return result; } static gboolean do_is_local(GnomeVFSMethod *method, const GnomeVFSURI *uri) { gboolean result = FALSE; char *path = NULL; path = get_path_from_uri(uri); if (!path) { /* invalid path */ goto end; } if (!ensure_font_list()) { /* could not build font list */ goto end; } /* root directory */ if (!strcmp(path, "")) { /* base dir */ result = TRUE; } else if (!strcmp(path, "/.directory")) { result = TRUE; } else { FcPattern *font; G_LOCK(font_list); font = g_hash_table_lookup(font_hash, &path[1]); if (font) { /* check if underlying uri is local */ FcChar8 *file; gchar *file_text_uri; GnomeVFSURI *file_uri; FcPatternGetString(font, FC_FILE, 0, &file); file_text_uri = gnome_vfs_get_uri_from_local_path(file); file_uri = gnome_vfs_uri_new(file_text_uri); g_free(file_text_uri); result = gnome_vfs_uri_is_local(file_uri); gnome_vfs_uri_unref(file_uri); } G_UNLOCK(font_list); } end: g_free(path); return result; } static GnomeVFSResult do_unlink(GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_ERROR_NOT_SUPPORTED; char *path = NULL; path = get_path_from_uri(uri); if (!path) { /* invalid path */ result = GNOME_VFS_ERROR_INVALID_URI; goto end; } if (!ensure_font_list()) { /* could not build font list */ result = GNOME_VFS_ERROR_INTERNAL; goto end; } if (!strcmp(path, "")) { /* base dir */ result = GNOME_VFS_ERROR_NOT_PERMITTED; } else { FcPattern *font; G_LOCK(font_list); font = g_hash_table_lookup(font_hash, &path[1]); if (font) { /* check if underlying uri is local */ FcChar8 *file; gchar *file_text_uri; GnomeVFSURI *file_uri; FcPatternGetString(font, FC_FILE, 0, &file); file_text_uri = gnome_vfs_get_uri_from_local_path(file); file_uri = gnome_vfs_uri_new(file_text_uri); g_free(file_text_uri); result = gnome_vfs_unlink_from_uri_cancellable(file_uri, context); gnome_vfs_uri_unref(file_uri); } else { result = GNOME_VFS_ERROR_NOT_FOUND; } G_UNLOCK(font_list); } end: g_free(path); return result; } /* -------- Directory monitor -------- */ /* list of monitors attached to fonts:/// */ G_LOCK_DEFINE_STATIC(monitor_list); static GList *monitor_list = NULL; static void invoke_monitors(void) { GList *tmp; G_LOCK(monitor_list); for (tmp = monitor_list; tmp != NULL; tmp = tmp->next) { GnomeVFSURI *uri = tmp->data; gnome_vfs_monitor_callback((GnomeVFSMethodHandle *)uri, uri, GNOME_VFS_MONITOR_EVENT_CHANGED); } G_UNLOCK(monitor_list); } static GnomeVFSResult do_monitor_add(GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSMonitorType monitor_type) { char *path = NULL; GnomeVFSURI *uri_copy; path = get_path_from_uri(uri); if (!path) { /* invalid path */ return GNOME_VFS_ERROR_INVALID_URI; } if (path[0] != '\0' || monitor_type != GNOME_VFS_MONITOR_DIRECTORY) { g_free(path); return GNOME_VFS_ERROR_NOT_SUPPORTED; } g_free(path); /* it is a directory monitor on fonts:/// */ uri_copy = gnome_vfs_uri_dup(uri); *method_handle = (GnomeVFSMethodHandle *)uri_copy; G_LOCK(monitor_list); monitor_list = g_list_prepend(monitor_list, uri_copy); G_UNLOCK(monitor_list); return GNOME_VFS_OK; } static GnomeVFSResult do_monitor_cancel(GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle) { GnomeVFSURI *uri; uri = (GnomeVFSURI *)method_handle; G_LOCK(monitor_list); monitor_list = g_list_remove(monitor_list, uri); G_UNLOCK(monitor_list); gnome_vfs_uri_unref(uri); return GNOME_VFS_OK; } /* -------- Initialisation of the method -------- */ static GnomeVFSMethod method = { sizeof(GnomeVFSMethod), do_open, do_create, do_close, do_read, do_write, do_seek, do_tell, NULL, do_open_directory, do_close_directory, do_read_directory, do_get_file_info, do_get_file_info_from_handle, do_is_local, NULL, NULL, NULL, do_unlink, NULL, NULL, NULL, NULL, NULL, do_monitor_add, do_monitor_cancel, NULL }; GnomeVFSMethod * vfs_module_init(const char *method_name, const char *args) { if (!strcmp(method_name, "fonts")) { if (!FcInit()) { g_warning("can't init fontconfig library"); return NULL; } return &method; } return NULL; } void vfs_module_shutdown(GnomeVFSMethod *method) { /* clean up font list */ if (font_list) FcFontSetDestroy(font_list); if (font_names) g_strfreev(font_names); if (font_hash) g_hash_table_destroy(font_hash); font_list = NULL; font_names = NULL; font_hash = NULL; }