systemfonts/0000755000176200001440000000000014743422652012661 5ustar liggesuserssystemfonts/tests/0000755000176200001440000000000014672302505014016 5ustar liggesuserssystemfonts/tests/testthat/0000755000176200001440000000000014743422652015663 5ustar liggesuserssystemfonts/tests/testthat/test-match_font.R0000644000176200001440000000135614672302530021103 0ustar liggesuserscontext("Font Matching") sysname <- tolower(Sys.info()[["sysname"]]) font <- switch( sysname, darwin = "Helvetica", linux = "DejaVuSans", freebsd = "DejaVuSans", windows = "arial" ) test_that("Font files can be found", { font_path <- match_font("sans")$path expect_true(file.exists(font_path)) skip_on_os("linux") # Different fonts for different distros skip_on_os("solaris") # Have no idea what it is supposed to give expect_equal(tools::file_path_sans_ext(basename(font_path)), font) }) test_that("Default font is correct", { font_path <- match_font("sans")$path expect_true(file.exists(font_path)) skip_on_os("linux") skip_on_os("solaris") expect_equal(tools::file_path_sans_ext(basename(font_path)), font) }) systemfonts/tests/testthat/test-system_fonts.R0000644000176200001440000000044414672302530021513 0ustar liggesuserscontext("Font listing") test_that("System fonts can be listed", { fonts <- system_fonts() expect_is(fonts, 'data.frame') expect_gt(nrow(fonts), 0) expect_named(fonts, c("path", "index", "name", "family", "style", "weight", "width", "italic", "monospace")) }) systemfonts/tests/testthat.R0000644000176200001440000000010214672302530015770 0ustar liggesuserslibrary(testthat) library(systemfonts) test_check("systemfonts") systemfonts/MD50000644000176200001440000001315414743422652013175 0ustar liggesuserse97d8c3cc7f0dd0a352c4eaa7126bb53 *DESCRIPTION c31ee18d335f7158ed985f5939319c1f *LICENSE cac314ab717f9fb17616b36897ae51f7 *NAMESPACE 090f26bf0df0fb32f195251b6a50a4e4 *NEWS.md 709c6a25247c8310b8efb02e0cd58261 *R/cpp11.R 74786ff5d1f5dde3454d17d8a2de1ccb *R/dev_strings.R f05e4863b9d51041c160b1533d185691 *R/emoji.R 756a917a22903b90f6cbc19c485f213d *R/font_fallback.R 34d5198062d37ff883ddcb0d3b421df4 *R/font_feature.R 78c825e3b97fa9f9b3a898878dee225c *R/font_info.R 4d764f77d401bd491cedabc1972cbfbc *R/font_outline.R 292a8f9703cf273d4aa5c4a564649ad8 *R/match_fonts.R 436cabc78263b08ffd6ff87d13165b2e *R/register_font.R b4f920654381d72ea038be7a2d961ba3 *R/shape_string.R 450a8e74d61467c86628109be3551119 *R/sysdata.rda 357b9eefcdcaf5dccba0486039a0ac0c *R/system_fonts.R 4006c34d424b9058b0e445202a5c0574 *R/systemfonts-package.R 19dc64e299e3a4e62f7f350e8212de07 *R/web_fonts.R 456746e7e89f15addcd767443bc7c344 *R/zzz.R 737c1cdf8dcba9b0c6166c952f2e70e0 *README.md c4f778f389c990af2640160fb3b7d626 *build/vignette.rds 0d45fe9e64d559e4275a84df252448fd *cleanup 7c5b3c7c335263478b8d247dca3b7e10 *configure 98c02890677ae28193e06316508ef5c9 *inst/doc/c_interface.R ff709403f071158d8ee8d30650dc5057 *inst/doc/c_interface.Rmd de05e31fa3b2bb82dbfb530396f3e462 *inst/doc/c_interface.html c758b62be4379a2a6849dc262f68a9df *inst/include/systemfonts-ft.h ac1dcc9a59f1cdc313832d20d4b7760e *inst/include/systemfonts.h a11ff11ac7db5df57660f50d65378bf2 *inst/unfont.ttf a87de191fdd5715acdcf9f2347057de6 *man/add_fonts.Rd 3aecec3a3318491d947f300fe50e80b2 *man/as_font_weight.Rd a1cbaf3f328e8d74e747faacf640c7fc *man/figures/lifecycle-archived.svg 6f521fb1819410630e279d1abf88685a *man/figures/lifecycle-defunct.svg 391f696f961e28914508628a7af31b74 *man/figures/lifecycle-deprecated.svg 691b1eb2aec9e1bec96b79d11ba5e631 *man/figures/lifecycle-experimental.svg 405e252e54a79b33522e9699e4e9051c *man/figures/lifecycle-maturing.svg f41ed996be135fb35afe00641621da61 *man/figures/lifecycle-questioning.svg 306bef67d1c636f209024cf2403846fd *man/figures/lifecycle-soft-deprecated.svg ed42e3fbd7cc30bc6ca8fa9b658e24a8 *man/figures/lifecycle-stable.svg bf2f1ad432ecccee3400afe533404113 *man/figures/lifecycle-superseded.svg 3ff40779076f4cd0531b92ee242e9386 *man/font_fallback.Rd 15ed44c9c419c96ae22dc32a09dc5b98 *man/font_feature.Rd 750186ef9c33fdc0f916d518b1e19de4 *man/font_info.Rd c4332e3048542cbeda8a4a7f440fc124 *man/get_fallback.Rd 15a615103df3479cd249423af2d36b4d *man/glyph_info.Rd e09f76c937bfdd66f63eb496f6680fc3 *man/glyph_outline.Rd 76fb4c6a7b6074ece6f55bb7ee2bada2 *man/glyph_raster.Rd 2c7d4769d94f80d5a5a83839ecce4c84 *man/glyph_raster_grob.Rd 74a37354f73dd54adbdf806db5b67df4 *man/match_fonts.Rd 23fd7c8f59004ee2edb6fc597c67476c *man/register_font.Rd 4abcf3f2e11548211e3481819704da30 *man/register_variant.Rd 7946ecb8e5d4e926089a3c8c931d2e41 *man/require_font.Rd 410487ad9c7fce4f2351ebf1569f6e2b *man/reset_font_cache.Rd 4d37b67861429eb3f49def6ad1df8dad *man/search_web_fonts.Rd 066ee2a2aa9f96f7a210cd2cbc145a71 *man/shape_string.Rd 90c6a72cba2eb04583949c6efa2340ba *man/str_split_emoji.Rd 74916bb9316fb3b3ef6f9f9b6358dba4 *man/string_metrics_dev.Rd 704aec4ec7bec5301d1934c3f266c714 *man/string_width.Rd c72ad2a644aa183945b66164aee58ec6 *man/string_widths_dev.Rd 8c3dc7bc1e63c3195611e49364bea5da *man/system_fonts.Rd 02abb9adb8993d574cdd9c0fe75335e7 *man/systemfonts-package.Rd 78f2132e8d8317f8ece0660e8983f845 *man/web-fonts.Rd f2417364c9b5c1750566dc8108cdf044 *src/FontDescriptor.h 9e130557dcc58b165a9322cd38213016 *src/Makevars.in 16f5b4b0ad7d24d07ad422698f8ee636 *src/Makevars.win f21f62b8a7e300c356c8ac326552f0c2 *src/cache_lru.h a2c2da6400632a7bc5fdfd14d73b6a8c *src/cache_store.cpp 69b85b17e10134f34f962d12c6bb9f5e *src/cache_store.h e025e2a68ee4977237aa21ceaf281687 *src/caches.cpp 1a997562bc4c7564e8b1fdc12c449d07 *src/caches.h f7b3cf4b7abb3513d75460289c0f04b2 *src/cpp11.cpp 8e340c25049f84658747dc1ec0f0fd18 *src/dev_metrics.cpp 689e955be1d6b4baf0c9a0811ae3b9aa *src/dev_metrics.h 04b170936547e799a6875fda13577cb5 *src/emoji.cpp a82f523519ac1dd5809e8e5bdd6505d2 *src/emoji.h 54a478790815a2f59d9a76372547f18b *src/font_fallback.cpp 94411cf3eafd1c8ec3c59dcd24053197 *src/font_fallback.h eee86504e27dc7f64dd698f57c97641d *src/font_local.cpp 9b94ae92d464a14a982428d925de2328 *src/font_local.h 5d0bdbff4b483a7172db8b069b8ef0f4 *src/font_matching.cpp 3a54be92f22931681faa9e04a11506c9 *src/font_matching.h 432e0b3844da87427f41a60176d91ad6 *src/font_metrics.cpp f21e79dd4b048af55cae1ff00e42ad68 *src/font_metrics.h 69df4814d17f7481a4954d0c6458f830 *src/font_outlines.cpp 1a4b46f0d54901b600647c671eae9d4e *src/font_outlines.h 88c81da41f0db9ca0bafb64805a0996e *src/font_registry.cpp 8b56ef67884fa3b41f6205d30e8afcdd *src/font_registry.h 5690b5e717044767515a561a9698dc62 *src/ft_cache.cpp a17c509992a372a2e4aa6c90592c3008 *src/ft_cache.h cf5c2dd2dcccb5d26dcbe464e60136d9 *src/init.cpp eb3f484b0d0d2e2bc8872b5a46f79eaa *src/mac/FontManagerMac.mm b3bedef5c2657632a2d7c4c0e6be898c *src/string_metrics.cpp e2a636534334f3ce1bf42187f2055476 *src/string_metrics.h 06432554ddaac25eb3bff7a5fd9db68e *src/string_shape.cpp 8feb4ff7fa00b41bdb430718e9cddaf3 *src/string_shape.h 0eeb90f13c58d94f22e1057477360ce4 *src/types.h 84d3b18636b8ad6fef0d5e399d539dae *src/unix/FontManagerLinux.cpp e16168b323a80d8ae3b3043f0bcf3650 *src/utils.h 882d23bb7c881e87eb82a0de7952d0da *src/win/DirectWriteFontManagerWindows.cpp 1333b0c1fe6b5c1a1a48d5e5e5f8ee14 *src/win/FontManagerWindows.cpp 2b7eaaf1f6572e72dde8052c8c14412a *tests/testthat.R 6084292b94e5c54bbceb89bdd8edda9d *tests/testthat/test-match_font.R 928495dc1e966e04e0c4fc7c89e56056 *tests/testthat/test-system_fonts.R c9004bea8c7973c97dba5a813e98aa93 *tools/winlibs.R ff709403f071158d8ee8d30650dc5057 *vignettes/c_interface.Rmd systemfonts/R/0000755000176200001440000000000014742430050013050 5ustar liggesuserssystemfonts/R/system_fonts.R0000644000176200001440000000205614675503504015745 0ustar liggesusers#' List all fonts installed on your system #' #' @return A data frame with a row for each font and various information in each #' column #' #' @export #' #' @examples #' # See all monospace fonts #' fonts <- system_fonts() #' fonts[fonts$monospace, ] #' system_fonts <- function() { system_fonts_c() } #' Reset the system font cache #' #' Building the list of system fonts is time consuming and is therefore cached. #' This, in turn, means that changes to the system fonts (i.e. installing new #' fonts), will not propagate to systemfonts. The solution is to reset the #' cache, which will result in the next call to e.g. [match_fonts()] will #' trigger a rebuild of the cache. #' #' @export #' #' @examples #' all_fonts <- system_fonts() #' #' ##-- Install a new font on the system --## #' #' all_fonts_new <- system_fonts() #' #' ## all_fonts_new will be equal to all_fonts #' #' reset_font_cache() #' #' all_fonts_new <- system_fonts() #' #' ## all_fonts_new will now contain the new font #' reset_font_cache <- function() { reset_font_cache_c() } systemfonts/R/dev_strings.R0000644000176200001440000000575514672302530015541 0ustar liggesusers#' Get string widths as measured by the current device #' #' For certain composition tasks it is beneficial to get the width of a string #' as interpreted by the device that is going to plot it. grid provides this #' through construction of a `textGrob` and then converting the corresponding #' grob width to e.g. cm, but this comes with a huge overhead. #' `string_widths_dev()` provides direct, vectorised, access to the graphic #' device for as high performance as possible. #' #' @param strings A character vector of strings to measure #' @param family The font families to use. Will get recycled #' @param face The font faces to use. Will get recycled #' @param size The font size to use. Will get recycled #' @param cex The cex multiplier to use. Will get recycled #' @param unit The unit to return the width in. Either `"cm"`, `"inches"`, #' `"device"`, or `"relative"` #' #' @return A numeric vector with the width of each of the strings given in #' `strings` in the unit given in `unit` #' #' @export #' #' @family device metrics #' #' @examples #' # Get the widths as measured in cm (default) #' string_widths_dev(c('a string', 'an even longer string')) #' string_widths_dev <- function(strings, family = '', face = 1, size = 12, cex = 1, unit = 'cm') { unit <- match.arg(unit, possible_units) unit <- match(unit, possible_units) - 1L n_total <- length(strings) if (length(family) != 1) family <- rep_len(family, n_total) if (any(c(length(face), length(size), length(cex)) != 1)) { face <- rep_len(face, n_total) size <- rep_len(size, n_total) cex <- rep_len(cex, n_total) } dev_string_widths_c(as.character(strings), as.character(family), as.integer(face), as.numeric(size), as.numeric(cex), unit) } #' Get string metrics as measured by the current device #' #' This function is much like [string_widths_dev()] but also returns the ascent #' and descent of the string making it possible to construct a tight bounding #' box around the string. #' #' @inheritParams string_widths_dev #' #' @return A data.frame with `width`, `ascent`, and `descent` columns giving the #' metrics in the requested unit. #' #' @family device metrics #' #' @export #' #' @examples #' # Get the metrics as measured in cm (default) #' string_metrics_dev(c('some text', 'a string with descenders')) #' string_metrics_dev <- function(strings, family = '', face = 1, size = 12, cex = 1, unit = 'cm') { unit <- match.arg(unit, possible_units) unit <- match(unit, possible_units) - 1L n_total <- length(strings) if (length(family) != 1) family <- rep_len(family, n_total) if (any(c(length(face), length(size), length(cex)) != 1)) { face <- rep_len(face, n_total) size <- rep_len(size, n_total) cex <- rep_len(cex, n_total) } dev_string_metrics_c(as.character(strings), as.character(family), as.integer(face), as.numeric(size), as.numeric(cex), unit) } # Order important. Will get converted to 0-indexed unit identity for C code possible_units <- c('cm', 'inches', 'device', 'relative') systemfonts/R/cpp11.R0000644000176200001440000000532514742430050014124 0ustar liggesusers# Generated by cpp11: do not edit by hand dev_string_widths_c <- function(string, family, face, size, cex, unit) { .Call(`_systemfonts_dev_string_widths_c`, string, family, face, size, cex, unit) } dev_string_metrics_c <- function(string, family, face, size, cex, unit) { .Call(`_systemfonts_dev_string_metrics_c`, string, family, face, size, cex, unit) } load_emoji_codes_c <- function(all, default_text, base_mod) { invisible(.Call(`_systemfonts_load_emoji_codes_c`, all, default_text, base_mod)) } emoji_split_c <- function(string, path, index) { .Call(`_systemfonts_emoji_split_c`, string, path, index) } get_fallback_c <- function(path, index, string) { .Call(`_systemfonts_get_fallback_c`, path, index, string) } add_local_fonts <- function(paths) { .Call(`_systemfonts_add_local_fonts`, paths) } clear_local_fonts_c <- function() { invisible(.Call(`_systemfonts_clear_local_fonts_c`)) } match_font_c <- function(family, italic, bold) { .Call(`_systemfonts_match_font_c`, family, italic, bold) } locate_fonts_c <- function(family, italic, weight, width) { .Call(`_systemfonts_locate_fonts_c`, family, italic, weight, width) } system_fonts_c <- function() { .Call(`_systemfonts_system_fonts_c`) } reset_font_cache_c <- function() { invisible(.Call(`_systemfonts_reset_font_cache_c`)) } get_font_info_c <- function(path, index, size, res) { .Call(`_systemfonts_get_font_info_c`, path, index, size, res) } get_glyph_info_c <- function(glyphs, path, index, size, res) { .Call(`_systemfonts_get_glyph_info_c`, glyphs, path, index, size, res) } get_glyph_outlines <- function(glyph, path, index, size, tolerance, verbose) { .Call(`_systemfonts_get_glyph_outlines`, glyph, path, index, size, tolerance, verbose) } get_glyph_bitmap <- function(glyph, path, index, size, res, color, verbose) { .Call(`_systemfonts_get_glyph_bitmap`, glyph, path, index, size, res, color, verbose) } register_font_c <- function(family, paths, indices, features, settings) { invisible(.Call(`_systemfonts_register_font_c`, family, paths, indices, features, settings)) } clear_registry_c <- function() { invisible(.Call(`_systemfonts_clear_registry_c`)) } registry_fonts_c <- function() { .Call(`_systemfonts_registry_fonts_c`) } get_string_shape_c <- function(string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) { .Call(`_systemfonts_get_string_shape_c`, string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) } get_line_width_c <- function(string, path, index, size, res, include_bearing) { .Call(`_systemfonts_get_line_width_c`, string, path, index, size, res, include_bearing) } systemfonts/R/match_fonts.R0000644000176200001440000001476014741276724015527 0ustar liggesusers#' Find a system font by name and style #' #' This function locates the font file (and index) best matching a name and #' optional style. A font file will be returned even if a perfect match #' isn't found, but it is not necessarily similar to the requested family and #' it should not be relied on for font substitution. The aliases `"sans"`, #' `"serif"`, `"mono"`, `"symbol"`, and `"emoji"` match to their respective #' system defaults (`""` is equivalent to `"sans"`). `match_font()` has been #' deprecated in favour of `match_fonts()` which provides vectorisation, as well #' as querying for different weights (rather than just "normal" and "bold") as #' well as different widths. #' #' # Font matching #' During font matching, systemfonts has to look in three different locations. #' The font registry (populated by [register_font()]/[register_variant()]), the #' local fonts (populated with [add_fonts()]/[scan_local_fonts()]), and the #' fonts installed on the system. It does so in that order: registry > local > #' installed. #' #' The matching performed at each step also differs. The fonts in the registry #' is only matched by family name. The local fonts are matched based on all the #' provided parameters (family, weight, italic, etc) in a way that is local to #' systemfonts, but try to emulate the system native matching. The installed #' fonts are matched using the system native matching functionality on macOS and #' Linux. On Windows the installed fonts are read from the system registry and #' matched using the same approach as for local fonts. Matching will always find #' a font no matter what you throw at it, defaulting to "sans" if nothing else #' is found. #' #' @param family The name of the font families to match #' @param italic logical indicating the font slant #' @param weight The weight to query for, either in numbers (`0`, `100`, `200`, #' `300`, `400`, `500`, `600`, `700`, `800`, or `900`) or strings (`"undefined"`, #' `"thin"`, `"ultralight"`, `"light"`, `"normal"`, `"medium"`, `"semibold"`, #' `"bold"`, `"ultrabold"`, or `"heavy"`). `NA` will be interpreted as #' `"undefined"`/`0` #' @param width The width to query for either in numbers (`0`, `1`, `2`, #' `3`, `4`, `5`, `6`, `7`, `8`, or `9`) or strings (`"undefined"`, #' `"ultracondensed"`, `"extracondensed"`, `"condensed"`, `"semicondensed"`, #' `"normal"`, `"semiexpanded"`, `"expanded"`, `"extraexpanded"`, or #' `"ultraexpanded"`). `NA` will be interpreted as `"undefined"`/`0` #' @param bold logical indicating whether the font weight #' #' @return A list containing the paths locating the font files, the 0-based #' index of the font in the files and the features for the font in case a #' registered font was located. #' #' @export #' #' @examples #' # Get the system default sans-serif font in italic #' match_fonts('sans', italic = TRUE) #' #' # Try to match it to a thin variant #' match_fonts(c('sans', 'serif'), weight = "thin") #' match_fonts <- function(family, italic = FALSE, weight = "normal", width = "undefined") { if (!is.character(family) || anyNA(family)) stop("`family` must be a character vector without NA values", call. = FALSE) weight <- as_font_weight(weight) width <- as_font_width(width) n_max <- max(length(family), length(italic), length(weight), length(width)) locate_fonts_c( rep_len(family, n_max), rep_len(as.logical(italic), n_max), rep_len(weight, n_max), rep_len(width, n_max) ) } #' @rdname match_fonts #' @export match_font <- function(family, italic = FALSE, bold = FALSE) { lifecycle::deprecate_soft("1.1.0", "match_font()", "match_fonts()") if (!is.character(family)) stop("family must be a string", call. = FALSE) match_font_c(family, as.logical(italic), as.logical(bold)) } weights <- c("undefined", "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy") #' Convert weight and width to numerics #' #' It is often more natural to describe font weight and width with names rather #' than numbers (e.g. "bold" or "condensed"), but underneath these names are #' matched to numeric values. These two functions are used to retrieve the #' numeric counterparts to names #' #' @param weight,width character vectors with valid names for weight or width #' #' @return An integer vector matching the length of the input #' #' @keywords internal #' #' @export #' #' @examples #' as_font_weight( #' c("undefined", "thin", "ultralight", "light", "normal", "medium", "semibold", #' "bold", "ultrabold", "heavy") #' ) #' as_font_weight <- function(weight) { if (is.factor(weight)) weight <- as.character(weight) if (is.logical(weight) && all(is.na(weight))) weight <- "undefined" if (is.character(weight)) { weight[is.na(weight)] <- "undefined" weight <- match(tolower(weight), weights) if (anyNA(weight)) { stop(paste0("`weight` must be one of ", paste('"', weights[1:9], '"', collapse = ", ", sep = ''), ', or "', weights[10], '"'), call. = FALSE) } weight <- (weight - 1L) * 100L } else if (is.numeric(weight) || is.integer(weight)) { weight[is.na(weight)] <- 0 if (any((weight %% 100) != 0)) { stop(paste0("`weight` must be one of ", paste((seq_len(9)-1)*100, collapse = ", "), ", or ", 900), call. = FALSE) } weight <- as.integer(weight) } else { stop("Can't convert this object to a font weight", call. = FALSE) } weight } widths <- c("undefined", "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded") #' @rdname as_font_weight #' @export #' #' @examples #' as_font_width( #' c("undefined", "ultracondensed", "extracondensed", "condensed", "semicondensed", #' "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded") #' ) #' as_font_width <- function(width) { if (is.factor(width)) width <- as.character(width) if (is.logical(width) && all(is.na(width))) width <- "undefined" if (is.character(width)) { width[is.na(width)] <- "undefined" width <- match(tolower(width), widths) if (anyNA(width)) { stop(paste0("`width` must be one of ", paste('"', widths[1:9], '"', collapse = ", ", sep = ''), ', or "', widths[10], '"'), call. = FALSE) } width <- width - 1L } else if (is.numeric(width) || is.integer(width)) { width[is.na(width)] <- 0 if (any((width %% 1) != 0)) { stop(paste0("`width` must be one of ", paste((seq_len(9)-1), collapse = ", "), ", or ", 9), call. = FALSE) } width <- as.integer(width) } else { stop("Can't convert this object to a font width", call. = FALSE) } width } systemfonts/R/register_font.R0000644000176200001440000002236514742425066016070 0ustar liggesusers#' Register font collections as families #' #' By design, systemfonts searches the fonts installed natively on the system. #' It is possible, however, to register other fonts from e.g. font packages or #' local font files, that will get searched before searching any installed #' fonts. You can always get an overview over all registered fonts with the #' `registry_fonts()` function that works as a registry focused analogue to #' [system_fonts()]. If you wish to clear out the registry, you can either #' restart the R session or call `clear_registry()`. #' #' @inheritSection match_fonts Font matching #' #' @param name The name the collection will be known under (i.e. *family*) #' @param plain,bold,italic,bolditalic Fontfiles for the different faces of the #' collection. can either be a filepath or a list containing a filepath and an #' index (only for font files containing multiple fonts). If not given it will #' default to the `plain` specification. #' @param features A [`font_feature`] object describing the specific OpenType #' font features to turn on for the registered font. #' #' @return `register_font()` and `clear_registry()` returns `NULL` invisibly. #' `registry_fonts()` returns a data table in the same style as [system_fonts()] #' though less detailed and not based on information in the font file. #' #' @details #' `register_font` also makes it possible to use system fonts with traits that #' is not covered by the graphic engine in R. In plotting operations it is only #' possible to specify a family name and whether or not the font should be bold #' and/or italic. There are numerous fonts that will never get matched to this, #' especially because bold is only one of many weights. #' #' Apart from granting a way to use new varieties of fonts, font registration #' also allows you to override the default `sans`, `serif`, and `mono` mappings, #' simply by registering a collection to the relevant default name. As #' registered fonts are searched first it will take precedence over the default. #' #' @export #' #' @examples #' # Create a random font collection #' fonts <- system_fonts() #' plain <- sample(which(!fonts$italic & fonts$weight <= 'normal'), 1) #' bold <- sample(which(!fonts$italic & fonts$weight > 'normal'), 1) #' italic <- sample(which(fonts$italic & fonts$weight <= 'normal'), 1) #' bolditalic <- sample(which(fonts$italic & fonts$weight > 'normal'), 1) #' register_font( #' 'random', #' plain = list(fonts$path[plain], fonts$index[plain]), #' bold = list(fonts$path[bold], fonts$index[bold]), #' italic = list(fonts$path[italic], fonts$index[italic]), #' bolditalic = list(fonts$path[bolditalic], fonts$index[bolditalic]) #' ) #' #' # Look at your creation #' registry_fonts() #' #' # Reset #' clear_registry() #' register_font <- function(name, plain, bold = plain, italic = plain, bolditalic = plain, features = font_feature()) { if (name %in% system_fonts()$family) { stop("A system font with that family name already exists", call. = FALSE) } if (is.character(plain)) plain <- list(plain, 0) if (is.character(bold)) bold <- list(bold, 0) if (is.character(italic)) italic <- list(italic, 0) if (is.character(bolditalic)) bolditalic <- list(bolditalic, 0) files <- c(plain[[1]], bold[[1]], italic[[1]], bolditalic[[1]]) indices <- c(plain[[2]], bold[[2]], italic[[2]], bolditalic[[2]]) if (!all(file.exists(files))) { stop("reference to non-existing font file", call. = FALSE) } register_font_c(as.character(name), as.character(files), as.integer(indices), features[[1]], features[[2]]) } #' @rdname register_font #' @export registry_fonts <- function() { registry_fonts_c() } #' @rdname register_font #' @export clear_registry <- function() { clear_registry_c() } #' Register a font as a variant as an existing one #' #' This function is a wrapper around [register_font()] that allows you to easily #' create variants of existing system fonts, e.g. to target different weights #' and/or widths, or for attaching OpenType features to a font. #' #' @inheritSection match_fonts Font matching #' #' @param name The new family name the variant should respond to #' @param family The name of an existing font family that this is a variant of #' @param weight One or two of `"thin"`, `"ultralight"`, `"light"`, `"normal"`, #' `"medium"`, `"semibold"`, `"bold"`, `"ultrabold"`, or `"heavy"`. If one is #' given it sets the weight for the whole variant. If two is given the first #' one defines the plain weight and the second the bold weight. If `NULL` then #' the variants of the given family closest to `"normal"` and `"bold"` will be #' chosen. #' @param width One of `"ultracondensed"`, `"extracondensed"`, `"condensed"`, #' `"semicondensed"`, `"normal"`, `"semiexpanded"`, `"expanded"`, #' `"extraexpanded"`, or `"ultraexpanded"` giving the width of the variant. If #' `NULL` then the width closest to `"normal"` will be chosen. #' @param features A [`font_feature`] object describing the specific OpenType #' font features to turn on for the registered font variant. #' #' @export #' #' @examples #' # Get the default "sans" family #' sans <- match_fonts("sans")$path #' sans <- system_fonts()$family[system_fonts()$path == sans][1] #' #' # Register a variant of it: #' register_variant( #' "sans_ligature", #' sans, #' features = font_feature(ligatures = "discretionary") #' ) #' #' registry_fonts() #' #' # clean up #' clear_registry() #' register_variant <- function(name, family, weight = NULL, width = NULL, features = font_feature()) { sys_fonts <- system_fonts() sys_fonts <- sys_fonts[grepl(tolower(family), tolower(sys_fonts$family)), , drop = FALSE] if (!is.null(width)) { sys_fonts <- sys_fonts[sys_fonts$width == tolower(width), , drop = FALSE] } if (!is.null(weight)) { sys_fonts <- sys_fonts[sys_fonts$weight %in% tolower(weight), , drop = FALSE] } if (nrow(sys_fonts) == 0) { stop("No font with the given family name and weight/width settings available", call. = FALSE) } if (any(tolower(family) == tolower(sys_fonts$family))) { sys_fonts <- sys_fonts[tolower(family) == tolower(sys_fonts$family), , drop = FALSE] } if (is.null(width)) { width <- sys_fonts$width[which.min(abs(as.integer(sys_fonts$width) - 5))] sys_fonts <- sys_fonts[sys_fonts$width == tolower(width), , drop = FALSE] } if (is.null(weight)) { normal <- which.min(abs(as.integer(sys_fonts$weight) - 4)) bold <- which.min(abs(as.integer(sys_fonts$weight) - 7)) weight <- sys_fonts$weight[unique(c(normal, bold))] } plain <- sys_fonts[which(sys_fonts$weight == weight[1] & !sys_fonts$italic), , drop = FALSE] bold <- if (length(weight) == 2) sys_fonts[which(sys_fonts$weight == weight[2] & !sys_fonts$italic), , drop = FALSE] else plain italic <- sys_fonts[which(sys_fonts$weight == weight[1] & sys_fonts$italic), , drop = FALSE] bolditalic <- if (length(weight) == 2) sys_fonts[which(sys_fonts$weight == weight[2] & sys_fonts$italic), , drop = FALSE] else italic if (nrow(plain) == 0) plain <- italic if (nrow(bold) == 0) bold <- plain if (nrow(italic) == 0) italic <- plain if (nrow(bolditalic) == 0) bolditalic <- if (length(weight) == 2) bold else italic register_font( name, as.list(plain[1, c('path', 'index')]), as.list(bold[1, c('path', 'index')]), as.list(italic[1, c('path', 'index')]), as.list(bolditalic[1, c('path', 'index')]), features ) } #' Add local font files to the search path #' #' systemfonts is mainly about getting system native access to the fonts #' installed on the OS you are executing the code on. However, you may want to #' access fonts without doing a full installation, either because you want your #' project to be reproducible on all systems, because you don't have #' administrator priviliges on the system, or for a different reason entirely. #' `add_fonts()` provide a way to side load font files so that they are found #' during font matching. The function differs from [register_font()] and #' [register_variant()] in that they add the font file as-is using the family #' name etc that are provided by the font. `scan_local_fonts()` is run when #' systemfonts is loaded and will automatically add font files stored in #' `./fonts` (project local) and `~/fonts` (user local). #' #' @inheritSection match_fonts Font matching #' #' @param files A character vector of font file paths to add #' #' @return This function is called for its sideeffects #' #' @export #' #' @examples #' # example code #' empty_font <- system.file("unfont.ttf", package = "systemfonts") #' #' add_fonts(empty_font) #' #' clear_local_fonts() #' add_fonts <- function(files) { if (!is.character(files)) { stop("`files` must be a character vector") } if (!all(file.exists(files))) { stop("all elements in `files` must be paths to existing files") } if (length(files) > 0) { add_local_fonts(vapply(files, tools::file_path_as_absolute, character(1))) } invisible(NULL) } #' @rdname add_fonts #' @export #' scan_local_fonts <- function() { files <- unique(c( list.files("./fonts", all.files = TRUE, full.names = TRUE, recursive = TRUE), list.files("~/fonts", all.files = TRUE, full.names = TRUE, recursive = TRUE) )) add_fonts(files) } #' @rdname add_fonts #' @export #' clear_local_fonts <- function() { message("Run `scan_local_fonts()` in order to re-add automatically added fonts") clear_local_fonts_c() } systemfonts/R/zzz.R0000644000176200001440000000126614741276724014054 0ustar liggesusersrelease_bullets <- function() { c( '`rhub::check_on_solaris(env_vars = c("_R_CHECK_FORCE_SUGGESTS_" = "false"))`', '`rhub::check_with_valgrind(env_vars = c(VALGRIND_OPTS = "--leak-check=full --track-origins=yes"))`' ) } .onLoad <- function(...) { load_emoji_codes() scan_local_fonts() } warn_env <- new.env(parent = emptyenv()) warn_env$warned <- FALSE #' Get location of the fallback font #' #' @export #' @keywords internal get_fallback <- function() { if (!warn_env$warned) { warning("No fonts detected on your system. Using an empty font.", call. = FALSE) warn_env$warned <- TRUE } list( system.file("unfont.ttf", package = "systemfonts"), 0L ) } systemfonts/R/font_feature.R0000644000176200001440000001532014672302530015660 0ustar liggesusers#' Define OpenType font feature settings #' #' This function encapsulates the specification of OpenType font features. Some #' specific features have named arguments, but all available features can be #' set by using its specific 4-letter tag For a list of the 4-letter tags #' available see e.g. the overview on #' [Wikipedia](https://en.wikipedia.org/wiki/List_of_typographic_features). #' #' @param ligatures Settings related to ligatures. One or more types of #' ligatures to turn on (see details). #' @param letters Settings related to the appearance of single #' letters (as opposed to ligatures that substitutes multiple letters). See #' details for supported values. #' @param numbers Settings related to the appearance of numbers. See details for #' supported values. #' @param ... key-value pairs with the key being the 4-letter tag and the value #' being the setting (usually `TRUE` to turn it on). #' #' @details #' OpenType features are defined by a 4-letter tag along with an integer value. #' Often that value is a simple `0` (off) or `1` (on), but some features support #' additional values, e.g. stylistic alternates (`salt`) where a font may #' provide multiple variants of a letter and the value will be used to chose #' which one to use. #' #' Common features related to appearance may be given with a long form name to #' either the `ligatures`, `letters`, or `numbers` argument to avoid remembering #' the often arbitrary 4-letter tag. Providing a long form name is the same as #' setting the tag to `1` and can thus not be used to set tags to other values. #' #' The possible long form names are given below with the tag in parenthesis: #' #' **Ligatures** #' - `standard` (*liga*): Turns on standard multiple letter substitution #' - `historical` (*hlig*): Use obsolete historical ligatures #' - `contextual` (*clig*): Apply secondary ligatures based on the character #' patterns surrounding the potential ligature #' - `discretionary` (*dlig*): Use ornamental ligatures #' #' **Letters** #' - `swash` (*cswh*): Use contextual swashes (ornamental decorations) #' - `alternates` (*calt*): Use alternate letter forms based on the sourrounding #' pattern #' - `historical` (*hist*): Use obsolete historical forms of the letters #' - `localized` (*locl*): Use alternate forms preferred by the script language #' - `randomize` (*rand*): Use random variants of the letters (e.g. to mimick #' handwriting) #' - `alt_annotation` (*nalt*): Use alternate annotations (e.g. circled digits) #' - `stylistic` (*salt*): Use a stylistic alternative form of the letter #' - `subscript` (*subs*): Set letter in subscript #' - `superscript` (*sups*): Set letter in superscript #' - `titling` (*titl*): Use letter forms well suited for large text and titles #' - `small_caps` (*smcp*): Use small caps variants of the letters #' #' **Numbers** #' - `lining` (*lnum*): Use number variants that rest on the baseline #' - `oldstyle` (*onum*): Use old style numbers that use descender and ascender #' for various numbers #' - `proportional` (*pnum*): Let numbers take up width based on the visual #' width of the glyph #' - `tabular` (*tnum*): Enforce all numbers to take up the same width #' - `fractions` (*frac*): Convert numbers separated by `/` into a fraction #' glyph #' - `fractions_alt` (*afrc*): Use alternate fraction form with a horizontal #' divider #' #' @return A `font_feature` object #' #' @export #' #' @examples #' font_feature(letters = "stylistic", numbers = c("lining", "tabular")) #' #' # Use the tag directly to access additional stylistic variants #' font_feature(numbers = c("lining", "tabular"), salt = 2) #' font_feature <- function(ligatures = NULL, letters = NULL, numbers = NULL , ...) { features <- list(...) if (!is.null(ligatures)) { for (lig in ligatures) { features[get_ligature_tag(lig)] <- 1L } } if (!is.null(letters)) { for (let in letters) { features[get_letter_tag(let)] <- 1L } } if (!is.null(numbers)) { for (num in numbers) { features[get_number_tag(num)] <- 1L } } if (length(features) == 0) { features <- list(character(), integer()) } else { feature_names <- names(features) if (any(vapply(features, length, integer(1)) != 1)) { stop("A feature setting can only be of length 1", call. = FALSE) } if (anyDuplicated(feature_names)) { stop("Features can only be given once", call. = FALSE) } if (any(nchar(feature_names) != 4)) { stop("Feature tags must be 4 character long", call. = FALSE) } feature_settings <- vapply(features, as.integer, integer(1)) features <- list(feature_names, feature_settings) } class(features) <- "font_feature" features } is_font_feature <- function(x) inherits(x, "font_feature") #' @export length.font_feature <- function(x) { length(x[[1]]) } #' @export print.font_feature <- function(x, ...) { if (length(x) == 0) { cat("An empty font_feature object\n") } else { cat("A list of OpenType font feature settings\n") cat(paste(paste0("- ", x[[1]], ": ", x[[2]]), collapse = "\n")) } invisible(x) } #' @export format.font_feature <- function(x, ...) { if (length(x) == 0) { return("") } paste(x[[1]], ': ', x[[2]], collapse = '; ', sep = '') } #' @export c.font_feature <- function(x, ...) { features <- rev(list(x, ...)) names <- unlist(lapply(features, `[[`, 1)) keep <- !duplicated(names) values <- unlist(lapply(features, `[[`, 2))[keep] structure(list(names[keep], values), class = "font_feature") } get_ligature_tag <- function(x) { name <- c("standard", "historical", "contextual", "discretionary") tags <- c("liga", "hlig", "clig", "dlig" ) ind <- match(tolower(x), name) if (is.na(ind)) { stop("No ligature setting called '", x, "'. Use one of ", paste(name, collapse = ", "), call. = FALSE) } tags[ind] } get_letter_tag <- function(x) { name <- c("swash", "alternates", "historical", "localized", "randomize", "alt_annotation", "stylistic", "subscript", "superscript", "titling", "small_caps") tags <- c("cswh", "calt", "hist", "locl", "rand", "nalt", "salt", "subs", "sups", "titl", "smcp" ) ind <- match(tolower(x), name) if (is.na(ind)) { stop("No letter setting called '", x, "'. Use one of ", paste(name, collapse = ", "), call. = FALSE) } tags[ind] } get_number_tag <- function(x) { name <- c("lining", "oldstyle", "proportional", "tabular", "fractions", "fractions_alt") tags <- c("lnum", "onum", "pnum", "tnum", "frac", "afrc" ) ind <- match(tolower(x), name) if (is.na(ind)) { stop("No number setting called '", x, "'. Use one of ", paste(name, collapse = ", "), call. = FALSE) } tags[ind] } systemfonts/R/font_fallback.R0000644000176200001440000000342014672302530015762 0ustar liggesusers#' Get the fallback font for a given string #' #' A fallback font is a font to use as a substitute if the chosen font does not #' contain the requested characters. Using font fallbacks means that the user #' doesn't have to worry about mixing characters from different scripts or #' mixing text and emojies. Fallback is calculated for the full string and the #' result is platform specific. If no font covers all the characters in the #' string an undefined "best match" is returned. The best approach is to figure #' out which characters are not covered by your chosen font and figure out #' fallbacks for these, rather than just request a fallback for the full string. #' #' @param string The strings to find fallbacks for #' @inheritParams font_info #' #' @return A data frame with a `path` and `index` column giving fallback for the #' specified string and font combinations #' #' @export #' #' @examples #' font_fallback("\U0001f604") # Smile emoji #' font_fallback <- function(string, family = '', italic = FALSE, bold = FALSE, path = NULL, index = 0) { full_length <- length(string) if (is.null(path)) { fonts <- match_fonts( rep_len(family, full_length), rep_len(italic, full_length), ifelse(rep_len(bold, full_length), "bold", "normal") ) path <- fonts$path index <- fonts$index } else { full_length <- max(length(path), length(index), full_length) if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, full_length) index <- rep_len(index, full_length) } } if (length(string) != 1) string <- rep_len(string, full_length) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_fallback_c(path, as.integer(index), as.character(string)) } systemfonts/R/font_info.R0000644000176200001440000001312614672302530015162 0ustar liggesusers#' Query font-specific information #' #' Get general information about a font, relative to a given size. Size specific #' measures will be returned in pixel units. The function is vectorised to the #' length of the longest argument. #' #' @inheritParams match_font #' @param size The pointsize of the font to use for size related measures #' @param res The ppi of the size related mesures #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return #' A data.frame giving info on the requested font + size combinations. The #' data.frame will contain the following columns: #' #' \describe{ #' \item{path}{The path to the font file} #' \item{index}{The 0-based index of the font in the fontfile} #' \item{family}{The family name of the font} #' \item{style}{The style name of the font} #' \item{italic}{A logical giving if the font is italic} #' \item{bold}{A logical giving if the font is bold} #' \item{monospace}{A logical giving if the font is monospace} #' \item{weight}{A factor giving the weight of the font} #' \item{width}{A factor giving the width of the font} #' \item{kerning}{A logical giving if the font supports kerning} #' \item{color}{A logical giving if the font has color glyphs} #' \item{scalable}{A logical giving if the font is scalable} #' \item{vertical}{A logical giving if the font is vertical} #' \item{n_glyphs}{The number of glyphs in the font} #' \item{n_sizes}{The number of predefined sizes in the font} #' \item{n_charmaps}{The number of character mappings in the font file} #' \item{bbox}{A bounding box large enough to contain any of the glyphs in the font} #' \item{max_ascend}{The maximum ascend of the tallest glyph in the font} #' \item{max_descent}{The maximum descend of the most descending glyph in the font} #' \item{max_advance_width}{The maximum horizontal advance a glyph can make} #' \item{max_advance_height}{The maximum vertical advance a glyph can make} #' \item{lineheight}{The height of a single line of text in the font} #' \item{underline_pos}{The position of a potential underlining segment} #' \item{underline_size}{The width the the underline} #' } #' #' @export #' #' @examples #' font_info('serif') #' #' # Avoid lookup if font file is already known #' sans <- match_fonts('sans') #' font_info(path = sans$path, index = sans$index) #' font_info <- function(family = '', italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0) { full_length <- max(length(size), length(res)) if (is.null(path)) { full_length <- max(length(family), length(italic), length(bold), full_length) fonts <- match_fonts( rep_len(family, full_length), rep_len(italic, full_length), ifelse(rep_len(bold, full_length), "bold", "normal") ) path <- fonts$path index <- fonts$index } else { full_length <- max(length(path), length(index), full_length) if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, full_length) index <- rep_len(index, full_length) } } if (length(size) != 1) size <- rep_len(size, full_length) if (length(res) != 1) res <- rep_len(res, full_length) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_font_info_c(path, as.integer(index), as.numeric(size), as.numeric(res)) } #' Query glyph-specific information from fonts #' #' This function allows you to extract information about the individual glyphs #' in a font, based on a specified size. All size related measures are in #' pixel-units. The function is vectorised to the length of the `glyphs` vector. #' #' @param glyphs A vector of glyphs. Strings will be split into separate glyphs #' automatically #' @inheritParams font_info #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return #' A data.frame with information about each glyph, containing the following #' columns: #' #' \describe{ #' \item{glyph}{The glyph as a character} #' \item{index}{The index of the glyph in the font file} #' \item{width}{The width of the glyph} #' \item{height}{The height of the glyph} #' \item{x_bearing}{The horizontal distance from the origin to the leftmost part of the glyph} #' \item{y_bearing}{The vertical distance from the origin to the top part of the glyph} #' \item{x_advance}{The horizontal distance to move the cursor after adding the glyph} #' \item{y_advance}{The vertical distance to move the cursor after adding the glyph} #' \item{bbox}{The tight bounding box surrounding the glyph} #' } #' #' @export glyph_info <- function(glyphs, family = '', italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0) { n_strings <- length(glyphs) glyphs <- strsplit(glyphs, '') n_glyphs <- lengths(glyphs) glyphs <- unlist(glyphs) if (is.null(path)) { fonts <- match_fonts( rep_len(family, n_strings), rep_len(italic, n_strings), ifelse(rep_len(bold, n_strings), "bold", "normal") ) path <- rep(fonts$path, n_glyphs) index <- rep(fonts$index, n_glyphs) } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep(rep_len(path, n_strings), n_glyphs) index <- rep(rep_len(index, n_strings), n_glyphs) } } if (length(size) != 1) size <- rep(rep_len(size, n_strings), n_glyphs) if (length(res) != 1) res <- rep(rep_len(res, n_strings), n_glyphs) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_glyph_info_c(glyphs, path, as.integer(index), as.numeric(size), as.numeric(res)) } systemfonts/R/font_outline.R0000644000176200001440000001231714742201111015676 0ustar liggesusers#' Get the outline of glyphs #' #' This function allows you to retrieve the outline of glyphs as polygon #' coordinates. The glyphs are given as indexes into a font file and not as #' characters allowing you to retrieve outlines for glyphs that doesn't have a #' character counterpoint. Glyphs that are given as bitmaps are ignored. #' #' @param glyph The index of the glyph in the font file #' @param path The path to the font file encoding the glyph #' @param index The index of the font in the font file #' @param size The size of the font in big points (1/72 inch) #' @param tolerance The deviation tolerance for decomposing bezier curves of the #' glyph. Given in the same unit as size. Smaller values give more detailed #' polygons #' @param verbose Should font and glyph loading errors be reported as warnings #' #' @return A data frame giving the outlines of the glyphs provide in `glyph`. It #' contains the columns `glyph` pointing to the element in the input it relates #' to, `contour` enumerating the contours the glyph consists of, and `x` and `y` #' giving the coordinates in big points #' #' @export #' #' @examples #' # Get the shape of s in the default font #' font <- font_info() #' glyph <- glyph_info("s", path = font$path, index = font$index) #' #' s <- glyph_outline(glyph$index, font$path, font$index, size = 150) #' #' plot(s$x, s$y, type = 'l') #' glyph_outline <- function(glyph, path, index = 0, size = 12, tolerance = 0.2, verbose = FALSE) { n_glyphs <- length(glyph) glyph <- as.integer(glyph) path <- rep_len(as.character(path), n_glyphs) index <- rep_len(as.integer(index), n_glyphs) size <- rep_len(as.numeric(size), n_glyphs) tolerance <- as.numeric(tolerance) get_glyph_outlines(glyph, path, index, size, tolerance, verbose) } #' Render glyphs to raster image #' #' Not all glyphs are encoded as vector outlines (emojis often not). Even for #' fonts that provide an outline you might be interested in a raster version. #' This function gives you just that. It converts a glyph into an optimized #' raster object that can be plotted with e.g. [graphics::rasterImage()] or #' [grid::grid.raster()]. For convenience, you can also use #' [glyph_raster_grob()] for plotting the result. #' #' @inheritParams glyph_outline #' @param res The resolution to render the glyphs to #' @param col The color of the glyph assuming the glyph doesn't have a native #' coloring #' #' @return A list of nativeRaster objects (or `NULL` if it failed to render a #' given glyph). The nativeRasters have additional attributes attached. `"size"` #' will give the size of the glyph in big points and `"offset"` will give the #' location of the top-left corner of the raster with respect to where it should #' be rendered. #' #' @export #' #' @examples #' font <- font_info() #' glyph <- glyph_info("R", path = font$path, index = font$index) #' #' R <- glyph_raster(glyph$index, font$path, font$index, size = 150) #' #' plot.new() #' plot.window(c(0,150), c(0, 150), asp = 1) #' rasterImage(R[[1]], 0, 0, attr(R[[1]], "size")[2], attr(R[[1]], "size")[1]) #' glyph_raster <- function(glyph, path, index = 0, size = 12, res = 300, col = "black", verbose = FALSE) { n_glyphs <- length(glyph) if (all(col == "black" | col == "#000000")) { col <- rep_len(-16777216L, n_glyphs) } else { if (!requireNamespace("farver", quietly = TRUE)) { stop("The farver package is required to tint glyphs with a color") } col <- rep_len(farver::encode_native(col), n_glyphs) } glyph <- as.integer(glyph) path <- rep_len(as.character(path), n_glyphs) index <- rep_len(as.integer(index), n_glyphs) size <- rep_len(as.numeric(size), n_glyphs) res <- rep_len(as.numeric(res), n_glyphs) get_glyph_bitmap(glyph, path, index, size, res, col, verbose) } #' Convert an extracted glyph raster to a grob #' #' This is a convenience function that helps in creating [rasterGrob] with the #' correct settings for the glyph. It takes inot account the sizing and offset #' returned by [glyph_raster()] and allows you to only consider the baseline #' position of the glyph. #' #' @param glyph The nativeRaster object returned as one of the elements by #' [glyph_raster()] #' @param x,y The baseline location of the glyph #' @inheritDotParams grid::rasterGrob -x -y #' @inheritParams grid::rasterGrob #' #' @return A rasterGrob object #' #' @export #' #' @examples #' font <- font_info() #' glyph <- glyph_info("R", path = font$path, index = font$index) #' #' R <- glyph_raster(glyph$index, font$path, font$index, size = 150) #' #' grob <- glyph_raster_grob(R[[1]], 50, 50) #' #' grid::grid.newpage() #' # Mark the baseline location #' grid::grid.points(50, 50, default.units = "bigpts") #' # Draw the glyph #' grid::grid.draw(grob) #' glyph_raster_grob <- function(glyph, x, y, ..., default.units = "bigpts") { if (length(glyph) == 0) return(grid::nullGrob()) if (!grid::is.unit(x)) x <- grid::unit(x, default.units) if (!grid::is.unit(y)) y <- grid::unit(y, default.units) size <- attr(glyph, "size") offset <- attr(glyph, "offset") grid::rasterGrob( glyph, x = x + grid::unit(offset[2], "bigpts"), y = y + grid::unit(offset[1], "bigpts"), width = grid::unit(size[2], "bigpts"), height = grid::unit(size[1], "bigpts"), just = c("left", "top"), ... ) } systemfonts/R/sysdata.rda0000644000176200001440000001774314672302530015227 0ustar liggesusersgIY&A#4FQ&4irNM9Ӥ&69gdLY0c,bĈs ծ]U\_4Ӳ\f͏szv~cϻcxttt3.{tg\ƿg>./|suXΕ[yrKZn#mIwttu~Wțk-u5הk !|D>*Ave/TiGG |C%.Cѱx|Jcp|%Wϐϔϖϕpfrs񞏽;r_/ʃyq;c{|d9>vy8ÓDNJreѡŹ\C9щ/guN#Qq6'{?OGG73nx|\^tk>9WϽkS Sɋ FU5fZyw_// ($,ɇ#Q ({IA6ceȔut*5rRn%mvr{r__ Ƀ!y͇7|؛{ao.ͅ7fL؛ {3ao&̈́7Su:qN\'׉u:qN\'׉u:qN\'׉u:qN\'׉t]N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;N|'߉w;{.q8Ozb=QIO'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'|b>1O'擝 ~$?O[Ne3~f?g3~f?g3~f?g3~f?g3~f?g3~f?g3~f?g3~f?yW}\sU>W]\urU.W]fc5Xj6Vlfc5Xj6Vlfc5Xj6VljϩjϩjϩjnVss=g5?YjvV;NT;NT;NrX9V+arX9V+arX9V+arX9V+arX9V+arX9V+arX9V+arX9V+arX9V+ՎS8ՎS8ՎS٬\V.+e岺KY|VMTMTwMuTwMT*o[VyU*oz~S7TٮjW+xe2^Wƫ;ê;Mӛ7Mo4]iڦ/M7n4htFӍ7Ͻyso{ܛ<7Ͻyso{ܛ<7Ͻyso{ܛ<7϶yͳmmlg<5ϭynshC<9?<:UoU:-וr,'rCXΑMfrs9Wn!-VrkVn';NrgU&w{=|[#@yHy4i4g3ML|i>4i4g3M󙦙 m>4i4g3ML|i>4i4|i>4i4g3ML|i>4i4>#>#g3ML|i>4sM7=nzqg3M4K4K4K4KMߛ7}o....̇f>4 LhfB3Tc1՘jL5SLofz3ӛY㬱Xj,5KRci4~?OiOi4F#Hc1i4F#Hc1i4F#Hc1i4F#Hc1i4F#Hc1lt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:Fglt6:FgnvnKݧNgt;Vcu;Vcu;Vcu;Vsչ\u:WUݫ۽ݫ۽뻽ź];ngvwdwGvwdwGvwdwGvwdsݹ\w;םu纻#;#;xg3w;xg3ݑݑݑݑw;w;w;w;w;?????????????????????????????????O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'?O'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_ _μ.|.xK.|饿y_Ѽ䫙*%_7~~O_T_') } } import_from_font_library <- function(family, ...) { family_name <- gsub(" ", "-", tolower(family)) url <- paste0("https://fontlibrary.org/face/", family_name) if (length(readLines(url, n = 1)) != 0) { paste0('') } else { NULL } } # REGISTRIES ------------------------------------------------------------------- google_fonts_registry <- new.env(parent = emptyenv()) get_google_fonts_registry <- function(woff2 = FALSE) { loc <- if (woff2) "woff2" else "ttf" if (is.null(google_fonts_registry[[loc]])) { url <- "https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyBkOYsZREsyZWvbSR_d03SI5XX30cIapYo&sort=popularity" if (woff2) { url <- paste0(url, "&capability=WOFF2") } fonts <- jsonlite::read_json(url)$items google_fonts_registry[[loc]] <- do.call(rbind, lapply(fonts, function(x) { x <- data.frame( family = x$family, variant = unlist(x$variants), url = unlist(x$files), file = NA_character_, version = x$version, modified = x$lastModified, category = I(rep(list(x$category), length(x$variants))) ) x$file <- paste0(x$family, "-", x$variant, sub("^.*(\\.\\w+)$", "\\1", x$url)) attr(x, "row.names") <- .set_row_names(nrow(x)) x })) } google_fonts_registry[[loc]] } font_squirrel_registry <- new.env(parent = emptyenv()) get_font_squirrel_registry <- function() { if (is.null(font_squirrel_registry$fonts)) { fonts <- jsonlite::read_json("https://www.fontsquirrel.com/api/fontlist/all") font_squirrel_registry$fonts <- do.call(rbind, lapply(fonts, function(x) { x <- data.frame( family = x$family_name, n_variants = x$family_count, url = paste0("https://www.fontsquirrel.com/fonts/download/", x$family_urlname), category = I(list(x$classification)) ) attr(x, "row.names") <- .set_row_names(nrow(x)) x })) } font_squirrel_registry$fonts } systemfonts/cleanup0000755000176200001440000000007014743404477014240 0ustar liggesusers#!/bin/sh rm -f src/Makevars configure.log rm -Rf .deps systemfonts/vignettes/0000755000176200001440000000000014743404476014675 5ustar liggesuserssystemfonts/vignettes/c_interface.Rmd0000644000176200001440000001720414672302530017574 0ustar liggesusers--- title: "systemfonts C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{systemfonts C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(systemfonts) ``` Most of the functionality in systemfonts is intended to be used from compiled code to help e.g. graphic devices to resolve font specifications to a font file prior to rendering. systemfonts provide key functionality to get called at the C level by putting systemfonts in the `LinkingTo` field in the description and adding `#include ` to your C code. Make sure systemfonts is loaded before using it, e.g. by having `match_font()` imported into your package namespace. The different functionality will be discussed below ## Font matching The C equivalent of the `match_font()` R function is `locate_font()` with the following signature: ```C int locate_font( const char *family, int italic, int bold, char *path, int max_path_length ) ``` It takes a UTF-8 encoded string with the font family name, an int setting both italic and bold styles along with a char pointer to be filled with the located path and the maximum length it can hold. The return value is an int giving the index of the font in the font file. With the advent of systemfonts 0.3.0 fonts can now also have OpenType features attached to them through the use of `register_font()` or `register_variant()`. If you wish to support such features you can use an alternative to the above: ```C FontSettings locate_font_with_features( const char *family, int italic, int bold ) ``` The returned `FontSettings` struct will contain both the font location and index along with any OpenType feature settings. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings` struct as the `features` array may be cleared at any time after the call has ended. systemfonts itself takes care of caching so this is not something you should be concerned with in your code. ```C struct FontFeature { char feature[4]; int setting; }; struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width ) ``` It takes the glyph to measure as an int giving the UTF code of the glyph, along with a fontfile and index to identify the font to measure with. Further it takes a size in pt and a resolution in ppi. It will write the ascent, descent, and width in pts to the pointers passed in, and return `0` if the operation was successful. ## String width The C equivalent of the `string_width()` R function is also called `string_width()` with the following signature: ```C string_width( const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width ) ``` This function calculates the width of a string, ignoring any newlines (these are automatically being handled by the graphic engine). It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the width in pts to the passed in pointer and return 0 if successful. ## String shape A parred down version of `shape_string()` is accessible at the C level with `string_shape()`. It behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. ```C string_shape( const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length ) ``` `string_shape()` behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the x and y location of each glyph in pts to the passed in arrays, stopping before the provided max_length and return 0 if successful. ## Retrieving cached freetype face A heavy part of text layouting is reading and parsing font files. systemfonts contains its own cache to make sure that parsing is kept at a minimum. If you want to use this cache to load and cache freetype face object (FT_Face) you can use `get_cached_face()`. This resides in a separate header (`systemfonts-ft.h`) because it requires FreeType to be linked in your package, which the rest of the C api does not. It will look in the cache for a face and size that matches your request and return that if found. If not, it will load it for you and add it to the cache, before returning it to you. `get_cached_face()` sets the passed in error pointer to 0 if successful. ```C get_cached_face( const char* fontfile, int index, double size, double res, int * error ) ``` Freetype uses reference counting to keep track of objects and the count is increased by a call to `get_cached_face()`. It is the responsiblity of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Font fallback When rendering text it is not given that all the requested characters have a glyph in the given font. While one can elect to render a "missing glyph" glyph (often either an empty square or a questionmark in a tilted square) a better approach is often to find a font substitute that does contain the character and use that for rendering it. This function allows you to find a fallback font for a given string and font. The string should be stripped of characters that you already know how to render. The fallback font is returned as a FontSettings object, though features are always empty. ```C FontSettings get_fallback( const char* string, const char* path, int index ) ``` ## Font Weight When encoding text with CSS it may be necessary to know the exact weight of the font given by a file so that it may be reflected in the style sheet. This function takes a path and an index and returns the weight (100-900 in steps of 100) or 0 if it is undefined by the font. ```C int get_font_weight( const char* path, int index ) ``` ## Family name It may be beneficial to know the family name from a given path and index into a font. This can be obtained with `get_font_family()` which will write the name to the provided `char*` argument. It will return 0 if it was somehow unsuccessful. ```C int get_font_family( const char* path, int index, char* family, int max_length ) ``` ## Emoji location Figuring out which character in a string should be treated as an emoji is non-trivial due to the existence of emojis with text representation default etc. systemfonts allow you to get the embedding of emojis in a string based on the correct rules. ```C void detect_emoji_embedding( const uint32_t* string, int n, int* embedding, const char *path, int index ) ``` systemfonts/src/0000755000176200001440000000000014743404476013454 5ustar liggesuserssystemfonts/src/win/0000755000176200001440000000000014672302505014240 5ustar liggesuserssystemfonts/src/win/DirectWriteFontManagerWindows.cpp0000744000176200001440000003043414672302530022671 0ustar liggesusers#define WINVER 0x0600 #include "../FontDescriptor.h" #include #include #include // throws a JS error when there is some exception in DirectWrite #define HR(hr) \ if (FAILED(hr)) throw "Font loading error"; WCHAR *utf8ToUtf16(const char *input) { unsigned int len = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); WCHAR *output = new WCHAR[len]; MultiByteToWideChar(CP_UTF8, 0, input, -1, output, len); return output; } char *utf16ToUtf8(const WCHAR *input) { unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); char *output = new char[len]; WideCharToMultiByte(CP_UTF8, 0, input, -1, output, len, NULL, NULL); return output; } // returns the index of the user's locale in the set of localized strings unsigned int getLocaleIndex(IDWriteLocalizedStrings *strings) { unsigned int index = 0; BOOL exists = false; wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; // Get the default locale for this user. int success = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH); // If the default locale is returned, find that locale name, otherwise use "en-us". if (success) { HR(strings->FindLocaleName(localeName, &index, &exists)); } // if the above find did not find a match, retry with US English if (!exists) { HR(strings->FindLocaleName(L"en-us", &index, &exists)); } if (!exists) index = 0; return index; } // gets a localized string for a font char *getString(IDWriteFont *font, DWRITE_INFORMATIONAL_STRING_ID string_id) { char *res = NULL; IDWriteLocalizedStrings *strings = NULL; BOOL exists = false; HR(font->GetInformationalStrings( string_id, &strings, &exists )); if (exists) { unsigned int index = getLocaleIndex(strings); unsigned int len = 0; WCHAR *str = NULL; HR(strings->GetStringLength(index, &len)); str = new WCHAR[len + 1]; HR(strings->GetString(index, str, len + 1)); // convert to utf8 res = utf16ToUtf8(str); delete str; strings->Release(); } if (!res) { res = new char[1]; res[0] = '\0'; } return res; } FontDescriptor *resultFromFont(IDWriteFont *font) { FontDescriptor *res = NULL; IDWriteFontFace *face = NULL; unsigned int numFiles = 0; HR(font->CreateFontFace(&face)); // get the font files from this font face IDWriteFontFile *files = NULL; HR(face->GetFiles(&numFiles, NULL)); HR(face->GetFiles(&numFiles, &files)); // return the first one if (numFiles > 0) { IDWriteFontFileLoader *loader = NULL; IDWriteLocalFontFileLoader *fileLoader = NULL; unsigned int nameLength = 0; const void *referenceKey = NULL; unsigned int referenceKeySize = 0; WCHAR *name = NULL; HR(files[0].GetLoader(&loader)); // check if this is a local font file HRESULT hr = loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void **)&fileLoader); if (SUCCEEDED(hr)) { // get the file path HR(files[0].GetReferenceKey(&referenceKey, &referenceKeySize)); HR(fileLoader->GetFilePathLengthFromKey(referenceKey, referenceKeySize, &nameLength)); name = new WCHAR[nameLength + 1]; HR(fileLoader->GetFilePathFromKey(referenceKey, referenceKeySize, name, nameLength + 1)); char *psName = utf16ToUtf8(name); char *postscriptName = getString(font, DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME); char *family = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES); char *style = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES); // this method requires windows 7, so we need to cast to an IDWriteFontFace1 IDWriteFontFace1 *face1 = static_cast(face); bool monospace = face1->IsMonospacedFont() == TRUE; res = new FontDescriptor( psName, postscriptName, family, style, (FontWeight) font->GetWeight(), (FontWidth) font->GetStretch(), font->GetStyle() == DWRITE_FONT_STYLE_ITALIC, monospace ); delete psName; delete name; delete postscriptName; delete family; delete style; fileLoader->Release(); } loader->Release(); } face->Release(); files->Release(); return res; } ResultSet *getAvailableFonts() { ResultSet *res = new ResultSet(); int count = 0; IDWriteFactory *factory = NULL; HR(DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&factory) )); // Get the system font collection. IDWriteFontCollection *collection = NULL; HR(factory->GetSystemFontCollection(&collection)); // Get the number of font families in the collection. int familyCount = collection->GetFontFamilyCount(); // track postscript names we've already added // using a set so we don't get any duplicates. std::unordered_set psNames; for (int i = 0; i < familyCount; i++) { IDWriteFontFamily *family = NULL; // Get the font family. HR(collection->GetFontFamily(i, &family)); int fontCount = family->GetFontCount(); for (int j = 0; j < fontCount; j++) { IDWriteFont *font = NULL; HR(family->GetFont(j, &font)); FontDescriptor *result = resultFromFont(font); if (psNames.count(result->postscriptName) == 0) { res->push_back(resultFromFont(font)); psNames.insert(result->postscriptName); } } family->Release(); } collection->Release(); factory->Release(); return res; } bool resultMatches(FontDescriptor *result, FontDescriptor *desc) { if (desc->postscriptName && strcmp(desc->postscriptName, result->postscriptName) != 0) return false; if (desc->family && strcmp(desc->family, result->family) != 0) return false; if (desc->style && strcmp(desc->style, result->style) != 0) return false; if (desc->weight && desc->weight != result->weight) return false; if (desc->width && desc->width != result->width) return false; if (desc->italic != result->italic) return false; if (desc->monospace != result->monospace) return false; return true; } ResultSet *findFonts(FontDescriptor *desc) { ResultSet *fonts = getAvailableFonts(); for (ResultSet::iterator it = fonts->begin(); it != fonts->end();) { if (!resultMatches(*it, desc)) { delete *it; it = fonts->erase(it); } else { it++; } } return fonts; } FontDescriptor *findFont(FontDescriptor *desc) { ResultSet *fonts = findFonts(desc); // if we didn't find anything, try again with only the font traits, no string names if (fonts->size() == 0) { delete fonts; FontDescriptor *fallback = new FontDescriptor( NULL, NULL, NULL, NULL, desc->weight, desc->width, desc->italic, false ); fonts = findFonts(fallback); } // ok, nothing. shouldn't happen often. // just return the first available font if (fonts->size() == 0) { delete fonts; fonts = getAvailableFonts(); } // hopefully we found something now. // copy and return the first result if (fonts->size() > 0) { FontDescriptor *res = new FontDescriptor(fonts->front()); delete fonts; return res; } // whoa, weird. no fonts installed or something went wrong. delete fonts; return NULL; } // custom text renderer used to determine the fallback font for a given char class FontFallbackRenderer : public IDWriteTextRenderer { public: IDWriteFontCollection *systemFonts; IDWriteFont *font; unsigned long refCount; FontFallbackRenderer(IDWriteFontCollection *collection) { refCount = 0; collection->AddRef(); systemFonts = collection; font = NULL; } ~FontFallbackRenderer() { if (systemFonts) systemFonts->Release(); if (font) font->Release(); } // IDWriteTextRenderer methods IFACEMETHOD(DrawGlyphRun)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const *glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription, IUnknown *clientDrawingEffect) { // save the font that was actually rendered return systemFonts->GetFontFromFontFace(glyphRun->fontFace, &font); } IFACEMETHOD(DrawUnderline)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_UNDERLINE const *underline, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } IFACEMETHOD(DrawStrikethrough)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_STRIKETHROUGH const *strikethrough, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } IFACEMETHOD(DrawInlineObject)( void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } // IDWritePixelSnapping methods IFACEMETHOD(IsPixelSnappingDisabled)(void *clientDrawingContext, BOOL *isDisabled) { *isDisabled = FALSE; return S_OK; } IFACEMETHOD(GetCurrentTransform)(void *clientDrawingContext, DWRITE_MATRIX *transform) { const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; *transform = ident; return S_OK; } IFACEMETHOD(GetPixelsPerDip)(void *clientDrawingContext, FLOAT *pixelsPerDip) { *pixelsPerDip = 1.0f; return S_OK; } // IUnknown methods IFACEMETHOD_(unsigned long, AddRef)() { return InterlockedIncrement(&refCount); } IFACEMETHOD_(unsigned long, Release)() { unsigned long newCount = InterlockedDecrement(&refCount); if (newCount == 0) { delete this; return 0; } return newCount; } IFACEMETHOD(QueryInterface)(IID const& riid, void **ppvObject) { if (__uuidof(IDWriteTextRenderer) == riid) { *ppvObject = this; } else if (__uuidof(IDWritePixelSnapping) == riid) { *ppvObject = this; } else if (__uuidof(IUnknown) == riid) { *ppvObject = this; } else { *ppvObject = nullptr; return E_FAIL; } this->AddRef(); return S_OK; } }; FontDescriptor *substituteFont(char *postscriptName, char *string) { FontDescriptor *res = NULL; IDWriteFactory *factory = NULL; HR(DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&factory) )); // Get the system font collection. IDWriteFontCollection *collection = NULL; HR(factory->GetSystemFontCollection(&collection)); // find the font for the given postscript name FontDescriptor *desc = new FontDescriptor(); desc->postscriptName = postscriptName; FontDescriptor *font = findFont(desc); // create a text format object for this font IDWriteTextFormat *format = NULL; if (font) { WCHAR *familyName = utf8ToUtf16(font->family); // create a text format HR(factory->CreateTextFormat( familyName, collection, (DWRITE_FONT_WEIGHT) font->weight, font->italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, (DWRITE_FONT_STRETCH) font->width, 12.0, L"en-us", &format )); delete familyName; delete font; } else { // this should never happen, but just in case, let the system // decide the default font in case findFont returned nothing. HR(factory->CreateTextFormat( L"", collection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 12.0, L"en-us", &format )); } // convert utf8 string for substitution to utf16 WCHAR *str = utf8ToUtf16(string); // create a text layout for the substitution string IDWriteTextLayout *layout = NULL; HR(factory->CreateTextLayout( str, wcslen(str), format, 100.0, 100.0, &layout )); // render it using a custom renderer that saves the physical font being used FontFallbackRenderer *renderer = new FontFallbackRenderer(collection); HR(layout->Draw(NULL, renderer, 100.0, 100.0)); // if we found something, create a result object if (renderer->font) { res = resultFromFont(renderer->font); } // free all the things delete renderer; layout->Release(); format->Release(); desc->postscriptName = NULL; delete desc; delete str; collection->Release(); factory->Release(); return res; } systemfonts/src/win/FontManagerWindows.cpp0000644000176200001440000003164114742171465020534 0ustar liggesusers#include #include #include #include #include #include FT_FREETYPE_H #include "../FontDescriptor.h" #include "../utils.h" #include "../font_matching.h" #include "../emoji.h" // A map for keeping font linking on Windows typedef std::unordered_map > WinLinkMap; ResultSet& get_font_list(); WinLinkMap& get_win_link_map(); WCHAR *utf8ToUtf16(const char *input) { unsigned int len = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); WCHAR *output = new WCHAR[len]; MultiByteToWideChar(CP_UTF8, 0, input, -1, output, len); return output; } char *utf16ToUtf8(const WCHAR *input) { unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); char *output = new char[len]; WideCharToMultiByte(CP_UTF8, 0, input, -1, output, len, NULL, NULL); return output; } int scan_font_dir(HKEY which, bool data_is_path, bool last_chance = false) { char win_dir[MAX_PATH]; GetWindowsDirectoryA(win_dir, MAX_PATH); std::string font_dir; if (last_chance) { font_dir = "C:\\WINDOWS"; } else { font_dir += win_dir; } font_dir += "\\Fonts\\"; static const LPCSTR font_registry_path = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; HKEY h_key; LONG result; result = RegOpenKeyExA(which, font_registry_path, 0, KEY_READ, &h_key); if (result != ERROR_SUCCESS) { return 1; } DWORD max_value_name_size, max_value_data_size; result = RegQueryInfoKey(h_key, 0, 0, 0, 0, 0, 0, 0, &max_value_name_size, &max_value_data_size, 0, 0); if (result != ERROR_SUCCESS) { return 1; } DWORD value_index = 0; LPSTR value_name = new CHAR[max_value_name_size]; LPBYTE value_data = new BYTE[max_value_data_size]; DWORD value_name_size, value_data_size, value_type; std::string font_path; ResultSet& font_list = get_font_list(); FT_Library library; FT_Face face; FT_Error error; error = FT_Init_FreeType(&library); if (error) { return 1; } do { // Loop over font registry, construct file path and parse with freetype value_data_size = max_value_data_size; value_name_size = max_value_name_size; result = RegEnumValueA(h_key, value_index, value_name, &value_name_size, 0, &value_type, value_data, &value_data_size); value_index++; if (!(result == ERROR_SUCCESS || result == ERROR_MORE_DATA) || value_type != REG_SZ) { continue; } font_path.clear(); if (!data_is_path) { font_path += font_dir; } font_path.append((LPSTR) value_data, value_data_size); error = FT_New_Face(library, font_path.c_str(), 0, &face); if (error) { continue; } font_list.push_back(new FontDescriptor(face, font_path.c_str(), 0)); int n_fonts = face->num_faces; FT_Done_Face(face); for (int i = 1; i < n_fonts; ++i) { error = FT_New_Face(library, font_path.c_str(), i, &face); if (error) { continue; } font_list.push_back(new FontDescriptor(face, font_path.c_str(), i)); FT_Done_Face(face); } } while (result != ERROR_NO_MORE_ITEMS); // Cleanup delete[] value_name; delete[] value_data; FT_Done_FreeType(library); return 0; } int scan_font_reg() { scan_font_dir(HKEY_LOCAL_MACHINE, false); scan_font_dir(HKEY_CURRENT_USER, true); // Move Arial Regular to front ResultSet& font_list = get_font_list(); if (font_list.n_fonts() == 0) { scan_font_dir(HKEY_LOCAL_MACHINE, false, true); } for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { if (strcmp((*it)->family, "Arial") == 0 && strcmp((*it)->style, "Regular") == 0) { FontDescriptor* arial = *it; font_list.erase(it); font_list.insert(font_list.begin(), arial); break; } } return 0; } void resetFontCache() { ResultSet& font_list = get_font_list(); font_list.clear(); WinLinkMap& font_links = get_win_link_map(); font_links.clear(); } ResultSet *getAvailableFonts() { ResultSet *res = new ResultSet(); ResultSet& font_list = get_font_list(); if (font_list.size() == 0) scan_font_reg(); for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { FontDescriptor* font = new FontDescriptor(*it); res->push_back(font); } return res; } bool resultMatches(FontDescriptor *result, FontDescriptor *desc) { if (desc->postscriptName && !strcmp_no_case(desc->postscriptName, result->postscriptName)) return false; if (desc->family && !strcmp_no_case(desc->family, result->family)) return false; if (desc->style && !strcmp_no_case(desc->style, result->style)) return false; if (desc->weight && desc->weight != result->weight) return false; if (desc->width && desc->width != result->width) return false; if (desc->italic != result->italic) return false; return true; } ResultSet *findFonts(FontDescriptor *desc) { ResultSet *res = new ResultSet(); ResultSet& font_list = get_font_list(); if (font_list.size() == 0) scan_font_reg(); for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { if (!resultMatches(*it, desc)) { continue; } FontDescriptor* font = new FontDescriptor(*it); res->push_back(font); } return res; } FontDescriptor *findFont(FontDescriptor *desc) { ResultSet *fonts = findFonts(desc); // if we didn't find anything, try again with postscriptName as family if (fonts->size() == 0) { delete fonts; desc->postscriptName = desc->family; desc->family = NULL; fonts = findFonts(desc); desc->family = desc->postscriptName; desc->postscriptName = NULL; } // if we didn't find anything, try again with only the font traits, no string names if (fonts->size() == 0) { delete fonts; FontDescriptor *fallback = new FontDescriptor( NULL, NULL, NULL, NULL, desc->weight, desc->width, desc->italic, false ); fonts = findFonts(fallback); } // ok, nothing. shouldn't happen often. // just return the first available font if (fonts->size() == 0) { delete fonts; fonts = getAvailableFonts(); } // hopefully we found something now. // copy and return the first result if (fonts->size() > 0) { FontDescriptor *res = new FontDescriptor(fonts->front()); delete fonts; return res; } // whoa, weird. no fonts installed or something went wrong. delete fonts; return NULL; } bool font_has_glyphs(const char * font_path, int index, FT_Library &library, uint32_t * str, int n_chars) { FT_Face face; FT_Error error; error = FT_New_Face(library, font_path, index, &face); if (error) { return false; } bool has_glyph = false; for (int i = 0; i < n_chars; ++i) { if (FT_Get_Char_Index( face, str[i])) { has_glyph = true; break; } } FT_Done_Face(face); return has_glyph; } int scan_link_reg() { WinLinkMap& font_links = get_win_link_map(); if (font_links.size() != 0) { return 0; } static const LPCSTR link_registry_path = "Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink"; HKEY h_key; LONG result; result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, link_registry_path, 0, KEY_READ, &h_key); if (result != ERROR_SUCCESS) { return 1; } DWORD max_value_name_size, max_value_data_size; result = RegQueryInfoKey(h_key, 0, 0, 0, 0, 0, 0, 0, &max_value_name_size, &max_value_data_size, 0, 0); if (result != ERROR_SUCCESS) { return 1; } DWORD value_index = 0; LPSTR value_name = new CHAR[max_value_name_size]; LPBYTE value_data = new BYTE[max_value_data_size]; DWORD value_name_size, value_data_size, value_type; do { // Loop over font registry, construct file path and parse with freetype value_data_size = max_value_data_size; value_name_size = max_value_name_size; result = RegEnumValueA(h_key, value_index, value_name, &value_name_size, 0, &value_type, value_data, &value_data_size); value_index++; if (!(result == ERROR_SUCCESS || result == ERROR_MORE_DATA) || value_type != REG_MULTI_SZ) { continue; } std::string name((LPSTR) value_name, value_name_size); std::vector values; unsigned char* value_cast = value_data; DWORD value_counter = 0; bool at_font_name = false; DWORD value_start = 0; bool ignore_subvalue = false; while (value_counter <= value_data_size) { if (value_cast[value_counter] == ',') { if (at_font_name) { ignore_subvalue = true; } else { at_font_name = true; value_start = value_counter + 1; } } else if (value_cast[value_counter] == '\0') { if (!ignore_subvalue) { values.emplace_back(reinterpret_cast(value_cast + value_start)); } if (value_cast[value_counter + 1] == '\0') { break; } ignore_subvalue = false; at_font_name = false; } ++value_counter; } if (values.size() != 0) { font_links[name] = values; } } while (result != ERROR_NO_MORE_ITEMS); // Cleanup delete[] value_name; delete[] value_data; return 0; } FontDescriptor *substituteFont(char *postscriptName, char *string) { scan_link_reg(); FontDescriptor *res = NULL; // find the font for the given postscript name FontDescriptor *desc = new FontDescriptor(); desc->postscriptName = postscriptName; FontDescriptor *font = findFont(desc); desc->postscriptName = NULL; if (font == NULL) { delete desc; return font; } FT_Library library; FT_Error error; error = FT_Init_FreeType( &library ); if (error) { delete desc; return font; } UTF_UCS conv; int n_chars = 0; uint32_t* str = conv.convert(string, n_chars); // Does the provided one work? if (font->path != NULL && font_has_glyphs(font->path, font->index, library, str, n_chars)) { FT_Done_FreeType(library); delete desc; return font; } // Try emoji desc->family = EMOJI; res = findFont(desc); desc->family = NULL; if (res != NULL && font_has_glyphs(res->get_path(), res->index, library, str, n_chars)) { FT_Done_FreeType(library); delete desc; delete font; return res; } delete res; desc->weight = font->weight; desc->italic = font->italic; // Look for links WinLinkMap& font_links = get_win_link_map(); std::string family(font->get_family()); auto link = font_links.find(family); // If the font doesn't have links, try the different standard system fonts if (link == font_links.end()) { link = font_links.find("Segoe UI"); if (link == font_links.end()) { link = font_links.find("Tahoma"); if (link == font_links.end()) { link = font_links.find("Lucida Sans Unicode"); } } } // hopefully some links were found if (link != font_links.end()) { for (auto it = link->second.begin(); it != link->second.end(); ++it) { desc->family = it->c_str(); res = findFont(desc); desc->family = NULL; if (res != NULL && font_has_glyphs(res->get_path(), res->index, library, str, n_chars)) { FT_Done_FreeType(library); delete desc; delete font; return res; } delete res; } } // Still no match -> try some standard unicode fonts static std::vector fallbacks = { "Segoe UI", // Latin, Greek, Cyrillic, Arabic "Arial Unicode MS", // Only installed with office AFAIK "Tahoma", // Latin, Greek, Cyrillic, Arabic, Hebrew, Thai "Meiryo UI", // CJK (Japanese) "MS UI Gothic", // CJK (Japanese) "Microsoft JhengHei UI", // CJK (Traditional Chinese) "Microsoft YaHei UI", // CJK (Simplified Chinese) "Microsoft Himalaya", // Tibetan "Microsoft New Tai Lue", // New Tai Lue "Microsoft PhagsPa", // Phags-Pa "Microsoft Sans Serif", // Latin, Greek, Cyrillic, Arabic, Hebrew, Thai, Vietnamese, Baltic, Turkish "Microsoft Tai Le", // Tai Le "Microsoft Yi Baiti", // Yi "Nirmala UI", // Many central asian scripts "Malgun Gothic", // CJK (Korean) "PMingLiU", // CJK (Traditional Chinese) "SimSun", // CJK (Simplified Chinese) "Gulim", // CJK (Korean) "Yu Gothic", // CJK (Japanese) "Leelawadee UI", // Thai "Ebrima", // African "Gadugi", // Cherokee "Jawanese Text", // Javanese "Mongolian Baiti", // Mongolian "Myanmar Text", // Myanmar "Segoe UI Symbol"// Symbols }; for (auto it = fallbacks.begin(); it != fallbacks.end(); ++it) { desc->family = it->c_str(); res = findFont(desc); desc->family = NULL; if (res != NULL && font_has_glyphs(res->get_path(), res->index, library, str, n_chars)) { FT_Done_FreeType(library); delete desc; delete font; return res; } delete res; } // Really? We just return the input font FT_Done_FreeType(library); delete desc; return font; } systemfonts/src/types.h0000644000176200001440000000371414672302530014763 0ustar liggesusers#pragma once #include #include #include #include #include // The exact location of a single file struct FontLoc { std::string file; unsigned int index; }; // Settings related to a single OpenType feature (all feature ids are 4 char long) struct FontFeature { char feature[4]; int setting; }; // A collection of four fonts (plain, bold, italic, bold-italic) along with optional features struct FontCollection { FontLoc fonts[4]; std::vector features; }; // A structure to pass around a single font with features (used by the C interface) struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; // A collection of registered fonts typedef std::unordered_map FontReg; // A map of Emoji unicode points typedef std::unordered_map EmojiMap; // A map for keeping font linking on Windows typedef std::unordered_map > WinLinkMap; // Key for looking up cached font locations struct FontKey { std::string family; int weight; int width; int italic; FontKey() : family(""), weight(400), width(5), italic(0) {} FontKey(std::string _family) : family(_family), weight(400), width(5), italic(0) {} FontKey(std::string _family, int _weight, int _width, int _italic) : family(_family), weight(_weight), width(_width), italic(_italic) {} inline bool operator==(const FontKey &other) const { return (weight == other.weight && width == other.width && italic == other.italic && family == other.family); } }; namespace std { template <> struct hash { size_t operator()(const FontKey & x) const { return std::hash()(x.family) ^ std::hash()(x.weight) ^ std::hash()(x.width) ^ std::hash()(x.italic); } }; } // Map for keeping already resolved font locations typedef std::unordered_map FontMap; systemfonts/src/caches.h0000644000176200001440000000066214741276724015060 0ustar liggesusers#pragma once #include #include #include "types.h" #include "FontDescriptor.h" #include "ft_cache.h" ResultSet& get_font_list(); ResultSet& get_local_font_list(); FontReg& get_font_registry(); FreetypeCache& get_font_cache(); EmojiMap& get_emoji_map(); FontMap& get_font_map(); WinLinkMap& get_win_link_map(); [[cpp11::init]] void init_caches(DllInfo* dll); void unload_caches(DllInfo* dll); systemfonts/src/font_fallback.cpp0000644000176200001440000000473114672302530016737 0ustar liggesusers#include #include #include "font_fallback.h" #include "FontDescriptor.h" #include "ft_cache.h" #include "caches.h" using namespace cpp11::literals; // these functions are implemented by the platform FontDescriptor *substituteFont(char *, char *); FontDescriptor *fallback_font(const char* file, int index, const char* string) { FreetypeCache& cache = get_font_cache(); if (!cache.load_font(file, index)) { return NULL; } std::string font_name = cache.cur_name(); std::vector writable_name(font_name.begin(), font_name.end()); writable_name.push_back('\0'); std::vector writable_string(string, string + std::strlen(string)); writable_string.push_back('\0'); return substituteFont(writable_name.data(), writable_string.data()); } cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string) { bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_string = string.size() == 1; const char* first_string = Rf_translateCharUTF8(string[0]); int full_length = 1; if (!one_path) full_length = path.size(); else if (!one_string) full_length = string.size(); cpp11::writable::strings paths; paths.reserve(full_length); cpp11::writable::integers indices; indices.reserve(full_length); for (int i = 0; i < full_length; ++i) { FontDescriptor* fallback = fallback_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_string ? first_string : Rf_translateCharUTF8(string[i]) ); if (fallback == NULL) { paths.push_back(R_NaString); indices.push_back(R_NaInt); } else { paths.push_back(fallback->path); indices.push_back(fallback->index); } delete fallback; } return cpp11::writable::data_frame({ "path"_nm = paths, "index"_nm = indices }); } FontSettings request_fallback(const char *string, const char *path, int index) { FontDescriptor *fallback = fallback_font(path, index, string); FontSettings result = {}; if (fallback == NULL) { std::strncpy(result.file, path, PATH_MAX); result.index = index; } else { std::strncpy(result.file, fallback->path, PATH_MAX); result.index = fallback->index; } delete fallback; return result; } void export_font_fallback(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_fallback", (DL_FUNC)request_fallback); } systemfonts/src/caches.cpp0000644000176200001440000000212114741276724015403 0ustar liggesusers#include "caches.h" static ResultSet* fonts; ResultSet& get_font_list() { return *fonts; } static ResultSet* fonts_local; ResultSet& get_local_font_list() { return *fonts_local; } static FontReg* font_registry; FontReg& get_font_registry() { return *font_registry; } static FreetypeCache* font_cache; FreetypeCache& get_font_cache() { return *font_cache; } static EmojiMap* emoji_map; EmojiMap& get_emoji_map() { return *emoji_map; } static FontMap* font_locations; FontMap& get_font_map() { return *font_locations; } static WinLinkMap* win_font_linking; WinLinkMap& get_win_link_map() { return *win_font_linking; } void init_caches(DllInfo* dll) { fonts = new ResultSet(); fonts_local = new ResultSet(); font_registry = new FontReg(); font_cache = new FreetypeCache(); emoji_map = new EmojiMap(); font_locations = new FontMap(); win_font_linking = new WinLinkMap(); } void unload_caches(DllInfo* dll) { delete fonts; delete fonts_local; delete font_registry; delete font_cache; delete emoji_map; delete font_locations; delete win_font_linking; } systemfonts/src/font_local.h0000644000176200001440000000053714742302060015733 0ustar liggesusers#pragma once #include "types.h" #include "caches.h" #include "utils.h" #include FontDescriptor *find_first_match(FontDescriptor *desc, ResultSet& font_list); FontDescriptor *match_local_fonts(FontDescriptor *desc); [[cpp11::register]] int add_local_fonts(cpp11::strings paths); [[cpp11::register]] void clear_local_fonts_c(); systemfonts/src/font_registry.cpp0000644000176200001440000000742014672302530017046 0ustar liggesusers#include "font_registry.h" #include "caches.h" #include #include #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using namespace cpp11::literals; void register_font_c(strings_t family, strings_t paths, integers_t indices, strings_t features, integers_t settings) { FontReg& registry = get_font_registry(); std::string name(family[0]); FontCollection col = {}; for (int i = 0; i < features.size(); ++i) { const char* f = Rf_translateCharUTF8(features[i]); col.features.push_back({{f[0], f[1], f[2], f[3]}, settings[i]}); } for (int i = 0; i < Rf_length(paths); ++i) { if (i > 3) continue; col.fonts[i] = {paths[i], (unsigned int) indices[i]}; } registry[name] = col; FontMap& font_map = get_font_map(); font_map.clear(); } void clear_registry_c() { FontReg& registry = get_font_registry(); registry.clear(); FontMap& font_map = get_font_map(); font_map.clear(); } data_frame_w registry_fonts_c() { FontReg& registry = get_font_registry(); int n_reg = registry.size(); int n = n_reg * 4; strings_w path(n); integers_w index(n); strings_w family(n); strings_w style(n); integers_w weight(n); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = {"normal", "bold"}; logicals_w italic(n); list_w features(n); int i = 0; for (auto it = registry.begin(); it != registry.end(); ++it) { for (int j = 0; j < 4; j++) { path[i] = it->second.fonts[j].file; index[i] = it->second.fonts[j].index; family[i] = it->first; switch (j) { case 0: style[i] = "Regular"; break; case 1: style[i] = "Bold"; break; case 2: style[i] = "Italic"; break; case 3: style[i] = "Bold Italic"; break; } weight[i] = 1 + (int) (j == 1 || j == 3); italic[i] = (Rboolean) (j > 1); if (it->second.features.empty()) { features[i] = integers_w(); } else { int n_features = it->second.features.size(); integers_w feat(n_features); strings_w tag(n_features); for (int k = 0; k < n_features; ++k) { feat[k] = it->second.features[k].setting; tag[k] = cpp11::r_string({ it->second.features[k].feature[0], it->second.features[k].feature[1], it->second.features[k].feature[2], it->second.features[k].feature[3] }); } feat.names() = tag; features[i] = feat; } ++i; } } data_frame_w res({ "path"_nm = path, "index"_nm = index, "family"_nm = family, "style"_nm = style, "weight"_nm = weight, "italic"_nm = italic, "features"_nm = features }); res.attr("class") = {"tbl_df", "tbl", "data.frame"}; return res; } bool locate_in_registry(const char *family, int italic, int bold, FontSettings& res) { FontReg& registry = get_font_registry(); if (registry.empty()) return false; auto search = registry.find(std::string(family)); if (search == registry.end()) { return false; } int index = bold ? (italic ? 3 : 1) : (italic ? 2 : 0); strncpy(res.file, search->second.fonts[index].file.c_str(), PATH_MAX); res.file[PATH_MAX] = '\0'; res.index = search->second.fonts[index].index; res.features = search->second.features.data(); res.n_features = search->second.features.size(); return true; } systemfonts/src/Makevars.win0000644000176200001440000000152314741701356015740 0ustar liggesusersPKG_CONFIG_NAME = freetype2 PKG_CONFIG ?= $(BINPREF)pkg-config PKG_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_CONFIG_NAME)) OBJECTS = caches.o cpp11.o dev_metrics.o font_matching.o font_local.o \ font_registry.o ft_cache.o string_shape.o font_metrics.o font_outlines.o \ font_fallback.o string_metrics.o emoji.o cache_store.o init.o win/FontManagerWindows.o ifneq ($(PKG_LIBS),) $(info using $(PKG_CONFIG_NAME) from Rtools) PKG_CPPFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_CONFIG_NAME)) else RWINLIB = ../windows/freetype2 PKG_CPPFLAGS = -I$(RWINLIB)/include/freetype2 PKG_LIBS = -L$(RWINLIB)/lib$(R_ARCH) -L$(RWINLIB)/lib -lfreetype -lharfbuzz -lpng -lbz2 -lz -lrpcrt4 -lgdi32 -luuid endif all: $(SHLIB) $(OBJECTS): $(RWINLIB) $(RWINLIB): "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" clean: rm -f $(SHLIB) $(OBJECTS) systemfonts/src/init.cpp0000644000176200001440000000017114672302530015107 0ustar liggesusers#include #include "caches.h" extern "C" void R_unload_systemfonts(DllInfo *dll) { unload_caches(dll); } systemfonts/src/string_metrics.h0000644000176200001440000000267514672302530016660 0ustar liggesusers#pragma once #include #include #include #include #include #include [[cpp11::register]] cpp11::list get_string_shape_c(cpp11::strings string, cpp11::integers id, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::doubles lineheight, cpp11::integers align, cpp11::doubles hjust, cpp11::doubles vjust, cpp11::doubles width, cpp11::doubles tracking, cpp11::doubles indent, cpp11::doubles hanging, cpp11::doubles space_before, cpp11::doubles space_after); [[cpp11::register]] cpp11::doubles get_line_width_c(cpp11::strings string, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::logicals include_bearing); int string_width(const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width); int string_shape(const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length); [[cpp11::init]] void export_string_metrics(DllInfo* dll); systemfonts/src/string_metrics.cpp0000644000176200001440000002203214672302530017200 0ustar liggesusers#include "string_metrics.h" #include "string_shape.h" #include "utils.h" #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using doubles_t = cpp11::doubles; using doubles_w = cpp11::writable::doubles; using namespace cpp11::literals; list_t get_string_shape_c(strings_t string, integers_t id, strings_t path, integers_t index, doubles_t size, doubles_t res, doubles_t lineheight, integers_t align, doubles_t hjust, doubles_t vjust, doubles_t width, doubles_t tracking, doubles_t indent, doubles_t hanging, doubles_t space_before, doubles_t space_after) { int n_strings = string.size(); bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; bool one_lht = lineheight.size() == 1; double first_lht = lineheight[0]; bool one_align = align.size() == 1; int first_align = align[0]; bool one_hjust = hjust.size() == 1; double first_hjust = hjust[0]; bool one_vjust = vjust.size() == 1; double first_vjust = vjust[0]; bool one_width = width.size() == 1; double first_width = width[0] * 64; bool one_tracking = tracking.size() == 1; double first_tracking = tracking[0]; bool one_indent = indent.size() == 1; double first_indent = indent[0] * 64; bool one_hanging = hanging.size() == 1; double first_hanging = hanging[0] * 64; bool one_before = space_before.size() == 1; double first_before = space_before[0] * 64; bool one_after = space_after.size() == 1; double first_after = space_after[0] * 64; integers_w glyph; integers_w glyph_id; integers_w metric_id; integers_w string_id; doubles_w x_offset; doubles_w y_offset; doubles_w x_midpoint; doubles_w widths; doubles_w heights; doubles_w left_bearings; doubles_w right_bearings; doubles_w top_bearings; doubles_w bottom_bearings; doubles_w left_border; doubles_w top_border; doubles_w pen_x; doubles_w pen_y; // Shape the text int cur_id = id[0] - 1; // make sure it differs from first bool success = false; FreetypeShaper shaper; for (int i = 0; i < n_strings; ++i) { const char* this_string = Rf_translateCharUTF8(string[i]); int this_id = id[i]; if (cur_id == this_id) { success = shaper.add_string( this_string, one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_tracking ? first_tracking : tracking[i] ); if (!success) { cpp11::stop("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(path[i]), shaper.error_code); } } else { cur_id = this_id; success = shaper.shape_string( this_string, one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i], one_lht ? first_lht : lineheight[i], one_align ? first_align : align[i], one_hjust ? first_hjust : hjust[i], one_vjust ? first_vjust : vjust[i], one_width ? first_width : width[i] * 64, one_tracking ? first_tracking : tracking[i], one_indent ? first_indent : indent[i] * 64, one_hanging ? first_hanging : hanging[i] * 64, one_before ? first_before : space_before[i] * 64, one_after ? first_after : space_after[i] * 64 ); if (!success) { cpp11::stop("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(path[i]), shaper.error_code); } } bool store_string = i == n_strings - 1 || cur_id != id[i + 1]; if (store_string) { success = shaper.finish_string(); if (!success) { cpp11::stop("Failed to finalise string shaping"); } int n_glyphs = shaper.glyph_id.size(); for (int j = 0; j < n_glyphs; j++) { glyph.push_back((int) shaper.glyph_uc[j]); glyph_id.push_back((int) shaper.glyph_id[j]); metric_id.push_back(widths.size()); string_id.push_back(shaper.string_id[j] + 1); x_offset.push_back(double(shaper.x_pos[j]) / 64.0); y_offset.push_back(double(shaper.y_pos[j]) / 64.0); x_midpoint.push_back(double(shaper.x_mid[j]) / 64.0); } widths.push_back(double(shaper.width) / 64.0); heights.push_back(double(shaper.height) / 64.0); left_bearings.push_back(double(shaper.left_bearing) / 64.0); right_bearings.push_back(double(shaper.right_bearing) / 64.0); top_bearings.push_back(double(shaper.top_bearing) / 64.0); bottom_bearings.push_back(double(shaper.bottom_bearing) / 64.0); left_border.push_back(double(shaper.left_border) / 64.0); top_border.push_back(double(shaper.top_border) / 64.0); pen_x.push_back(double(shaper.pen_x) / 64.0); pen_y.push_back(double(shaper.pen_y) / 64.0); } } data_frame_w shape_df({ "glyph"_nm = (SEXP) glyph, "index"_nm = (SEXP) glyph_id, "metric_id"_nm = (SEXP) metric_id, "string_id"_nm = (SEXP) string_id, "x_offset"_nm = (SEXP) x_offset, "y_offset"_nm = (SEXP) y_offset, "x_midpoint"_nm = (SEXP) x_midpoint }); shape_df.attr("class") = {"tbl_df", "tbl", "data.frame"}; data_frame_w metrics_df({ "string"_nm = (SEXP) strings_w(widths.size()), "width"_nm = (SEXP) widths, "height"_nm = (SEXP) heights, "left_bearing"_nm = (SEXP) left_bearings, "right_bearing"_nm = (SEXP) right_bearings, "top_bearing"_nm = (SEXP) top_bearings, "bottom_bearing"_nm = (SEXP) bottom_bearings, "left_border"_nm = (SEXP) left_border, "top_border"_nm = (SEXP) top_border, "pen_x"_nm = (SEXP) pen_x, "pen_y"_nm = (SEXP) pen_y }); metrics_df.attr("class") = {"tbl_df", "tbl", "data.frame"}; return list_w({ "shape"_nm = shape_df, "metrics"_nm = metrics_df }); } doubles_t get_line_width_c(strings_t string, strings_t path, integers_t index, doubles_t size, doubles_t res, logicals_t include_bearing) { int n_strings = string.size(); bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; bool one_bear = include_bearing.size() == 1; int first_bear = include_bearing[0]; doubles_w widths(n_strings); bool success = false; long width = 0; FreetypeShaper shaper; for (int i = 0; i < n_strings; ++i) { success = shaper.single_line_width( Rf_translateCharUTF8(string[i]), one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i], one_bear ? first_bear : static_cast(include_bearing[0]), width ); if (!success) { cpp11::stop("Failed to calculate width of string (%s) with font file (%s) with freetype error %i", Rf_translateCharUTF8(string[i]), Rf_translateCharUTF8(path[i]), shaper.error_code); } widths[i] = (double) width / 64.0; } return widths; } int string_width(const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width) { BEGIN_CPP FreetypeShaper shaper; long width_tmp = 0; bool success = shaper.single_line_width( string, fontfile, index, size, res, (bool) include_bearing, width_tmp ); if (!success) { return shaper.error_code; } *width = (double) width_tmp / 64.0; END_CPP return 0; } int string_shape(const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length) { BEGIN_CPP FreetypeShaper shaper; bool success = shaper.shape_string(string, fontfile, index, size, res, 0.0, 0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0); if (!success) { return shaper.error_code; } success = shaper.finish_string(); if (!success) { return shaper.error_code; } max_length = max_length < shaper.x_pos.size() ? max_length : shaper.x_pos.size(); for (unsigned int i = 0; i < max_length; ++i) { x[i] = shaper.x_pos[i]; y[i] = shaper.y_pos[i]; } END_CPP return 0; } void export_string_metrics(DllInfo* dll){ R_RegisterCCallable("systemfonts", "string_width", (DL_FUNC)string_width); R_RegisterCCallable("systemfonts", "string_shape", (DL_FUNC)string_shape); } systemfonts/src/font_matching.h0000644000176200001440000000272714675503430016447 0ustar liggesusers#pragma once #include #include #include #include #include #include #include "types.h" #include "caches.h" // Default fonts based on browser behaviour #if defined _WIN32 #define SANS "Arial" #define SERIF "Times New Roman" #define MONO "Courier New" #define EMOJI "Segoe UI Emoji" #define SYMBOL "Segoe UI Symbol" #elif defined __APPLE__ #define SANS "Helvetica" #define SERIF "Times" #define MONO "Courier New" #define EMOJI "Apple Color Emoji" #define SYMBOL "Symbol" #else #define SANS "sans" #define SERIF "serif" #define MONO "mono" #define EMOJI "emoji" #define SYMBOL "symbol" #endif int locate_font(const char *family, int italic, int bold, char *path, int max_path_length); FontSettings locate_font_with_features(const char *family, int italic, int bold); [[cpp11::register]] cpp11::list match_font_c(cpp11::strings family, cpp11::logicals italic, cpp11::logicals bold); [[cpp11::register]] cpp11::writable::data_frame locate_fonts_c(cpp11::strings family, cpp11::logicals italic, cpp11::integers weight, cpp11::integers width); [[cpp11::register]] cpp11::writable::data_frame system_fonts_c(); [[cpp11::register]] void reset_font_cache_c(); [[cpp11::init]] void export_font_matching(DllInfo* dll); systemfonts/src/emoji.cpp0000644000176200001440000001307414672302530015255 0ustar liggesusers#include "emoji.h" #include "types.h" #include "caches.h" #include "utils.h" #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using namespace cpp11::literals; bool has_emoji(const char* string) { UTF_UCS utf_converter; int n_glyphs = 0; uint32_t* codepoints = utf_converter.convert(string, n_glyphs); EmojiMap& emoji_map = get_emoji_map(); for (int i = 0; i < n_glyphs; ++i) { EmojiMap::iterator it = emoji_map.find(codepoints[i]); if (it == emoji_map.end()) { // Not an emoji continue; } switch (it->second) { case 0: // Fully qualified emoji codepoint return true; case 1: // Emoji with text presentation default if (i != n_glyphs - 1 && codepoints[i + 1] == 0xFE0F) { return true; } break; case 2: // Emoji with text presentation default that can take modifier if (i != n_glyphs - 1 && codepoints[i + 1] >= 0x1F3FB && codepoints[i + 1] <= 0x1F3FF) { return true; } break; } } return false; } void detect_emoji_embedding(const uint32_t* codepoints, int n, int* embedding, const char* fontpath, int index) { EmojiMap& emoji_map = get_emoji_map(); FreetypeCache& cache = get_font_cache(); bool loaded = cache.load_font(fontpath, index, 12.0, 72.0); // We don't care about sizing for (int i = 0; i < n; ++i) { EmojiMap::iterator it = emoji_map.find(codepoints[i]); if (it == emoji_map.end()) { // Not an emoji embedding[i] = 0; continue; } switch (it->second) { case 0: // Fully qualified emoji codepoint embedding[i] = 1; break; case 1: // Emoji with text presentation default if (i == n - 1) { embedding[i] = 0; break; } if (codepoints[i + 1] == 0xFE0F) { embedding[i] = 1; embedding[i + 1] = 1; ++i; } else if (loaded && cache.has_glyph(codepoints[i])) { embedding[i] = 0; } else { embedding[i] = 1; } break; case 2: // Emoji with text presentation default that can take modifier if (i == n - 1) { embedding[i] = 0; break; } if (codepoints[i + 1] >= 0x1F3FB && codepoints[i + 1] <= 0x1F3FF) { embedding[i] = 1; embedding[i + 1] = 1; ++i; } else if (loaded && cache.has_glyph(codepoints[i])) { embedding[i] = 0; } else { embedding[i] = 1; } break; default: // should not be reached embedding[i] = 0; } } } bool is_emoji(uint32_t* codepoints, int n, logicals_w &result, const char* fontpath, int index) { EmojiMap& emoji_map = get_emoji_map(); FreetypeCache& cache = get_font_cache(); bool loaded = cache.load_font(fontpath, index, 12.0, 72.0); // We don't care about sizing if (!loaded) { return false; } for (int i = 0; i < n; ++i) { EmojiMap::iterator it = emoji_map.find(codepoints[i]); if (it == emoji_map.end()) { // Not an emoji result.push_back(FALSE); continue; } switch (it->second) { case 0: // Fully qualified emoji codepoint result.push_back(TRUE); break; case 1: // Emoji with text presentation default if (i == n - 1) { result.push_back(FALSE); break; } if (codepoints[i + 1] == 0xFE0F) { result.push_back(TRUE); result.push_back(TRUE); ++i; } else if (cache.has_glyph(codepoints[i])) { result.push_back(FALSE); } else { result.push_back(TRUE); } break; case 2: // Emoji with text presentation default that can take modifier if (i == n - 1) { result.push_back(FALSE); break; } if (codepoints[i + 1] >= 0x1F3FB && codepoints[i + 1] <= 0x1F3FF) { result.push_back(TRUE); result.push_back(TRUE); ++i; } else if (cache.has_glyph(codepoints[i])) { result.push_back(FALSE); } else { result.push_back(TRUE); } break; default: // should not be reached result.push_back(FALSE); } } return true; } void load_emoji_codes_c(integers_t all, integers_t default_text, integers_t base_mod) { EmojiMap& emoji_map = get_emoji_map(); for (int i = 0; i < all.size(); ++i) { emoji_map[all[i]] = 0; } for (int i = 0; i < default_text.size(); ++i) { emoji_map[default_text[i]] = 1; } for (int i = 0; i < base_mod.size(); ++i) { emoji_map[base_mod[i]] = 2; } } list_t emoji_split_c(strings_t string, strings_t path, integers_t index) { int n_strings = string.size(); bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; integers_w glyph; integers_w id; logicals_w emoji; UTF_UCS utf_converter; for (int i = 0; i < n_strings; ++i) { int n_glyphs = 0; uint32_t* glyphs = utf_converter.convert(Rf_translateCharUTF8(string[i]), n_glyphs); is_emoji(glyphs, n_glyphs, emoji, one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i]); for (int j = 0; j < n_glyphs; j++) { glyph.push_back(glyphs[j]); id.push_back(i); } } return list_w({(SEXP) glyph, (SEXP) id, (SEXP) emoji}); } void export_emoji_detection(DllInfo* dll){ R_RegisterCCallable("systemfonts", "detect_emoji_embedding", (DL_FUNC)detect_emoji_embedding); } systemfonts/src/dev_metrics.h0000644000176200001440000000151614672302530016121 0ustar liggesusers#pragma once #include #include #include #include [[cpp11::register]] cpp11::doubles dev_string_widths_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); [[cpp11::register]] cpp11::writable::data_frame dev_string_metrics_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); systemfonts/src/emoji.h0000644000176200001440000000124614672302530014720 0ustar liggesusers#pragma once #include #include #include #include #include #include typedef std::unordered_map EmojiMap; bool has_emoji(const char* string); void detect_emoji_embedding(const uint32_t* codepoints, int n, int* embedding, const char* fontpath, int index); [[cpp11::register]] void load_emoji_codes_c(cpp11::integers all, cpp11::integers default_text, cpp11::integers base_mod); [[cpp11::register]] cpp11::list emoji_split_c(cpp11::strings string, cpp11::strings path, cpp11::integers index); [[cpp11::init]] void export_emoji_detection(DllInfo* dll); systemfonts/src/cache_lru.h0000644000176200001440000001010514672302530015534 0ustar liggesusers#pragma once #include #include #include template class LRU_Cache { public: LRU_Cache() : _max_size(32) { } LRU_Cache(size_t max_size) : _max_size(max_size) { } ~LRU_Cache() { clear(); } // Add a key-value pair, potentially passing a removed key and value back // through the removed_key and removed_value argument. Returns true if a value // was removed and false otherwise inline bool add(key_t key, value_t value, key_t& removed_key, value_t& removed_value) { cache_map_it_t it = _cache_map.find(key); _cache_list.push_front(key_value_t(key, value)); if (it != _cache_map.end()) { _cache_list.erase(it->second); _cache_map.erase(it); } _cache_map[key] = _cache_list.begin(); if (_cache_map.size() > _max_size) { cache_list_it_t last = _cache_list.end(); last--; removed_key = last->first; removed_value = last->second; _cache_map.erase(last->first); _cache_list.pop_back(); return true; } return false; } // Add a key-value pair, potentially passing a value back through the // removed_value argument. Returns true if a value was removed and false // otherwise inline bool add(key_t key, value_t value, value_t& removed_value) { key_t removed_key; return add(key, value, removed_key, removed_value); } // Add a key-value pair, potentially passing a key back through the // removed_key argument. Returns true if a value was removed and false // otherwise. Will destroy the value inline bool add(key_t key, value_t value, key_t& removed_key) { value_t removed_value; bool overflow = add(key, value, removed_key, removed_value); if (overflow) { value_dtor(removed_value); } return overflow; } // Add a key-value pair, automatically destroying any removed value inline void add(key_t key, value_t value) { value_t removed_value; key_t removed_key; if (add(key, value, removed_key, removed_value)) { value_dtor(removed_value); } } // Retrieve a value based on a key, returning true if a value was found. Will // move the key-value pair to the top of the list inline bool get(key_t key, value_t& value) { cache_map_it_t it = _cache_map.find(key); if (it == _cache_map.end()) { return false; } value = it->second->second; _cache_list.splice(_cache_list.begin(), _cache_list, it->second); return true; } // Retrieve a value based on a key without bumping the pair to the top of the // list inline bool steal(key_t key, value_t& value) { cache_map_it_t it = _cache_map.find(key); if (it == _cache_map.end()) { return false; } value = it->second->second; return true; } // Check for the existence of a key-value pair inline bool exist(key_t key) { return _cache_map.find(key) != _cache_map.end(); } // Remove a key-value pair, destroying the value inline void remove(key_t key) { cache_map_it_t it = _cache_map.find(key); if (it == _cache_map.end()) { return; } value_dtor(it->second->second); _cache_list.erase(it->second); _cache_map.erase(it); } // Clear the cache, destroying all values with it inline void clear() { for (cache_list_it_t it = _cache_list.begin(); it != _cache_list.end(); ++it) { value_dtor(it->second); } _cache_list.clear(); _cache_map.clear(); } private: size_t _max_size; // Should be overridden for children with value types that needs special // dtor handling inline virtual void value_dtor(value_t& value) { // Allow children to destroy values properly } protected: typedef typename std::pair key_value_t; typedef typename std::list list_t; typedef typename list_t::iterator cache_list_it_t; typedef typename std::unordered_map map_t; typedef typename std::unordered_map::iterator cache_map_it_t; list_t _cache_list; map_t _cache_map; }; systemfonts/src/FontDescriptor.h0000755000176200001440000001653414742430040016567 0ustar liggesusers#ifndef FONT_DESCRIPTOR_H #define FONT_DESCRIPTOR_H #include #include #include #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H #include "utils.h" enum FontWeight { FontWeightUndefined = 0, FontWeightThin = 100, FontWeightUltraLight = 200, FontWeightLight = 300, FontWeightNormal = 400, FontWeightMedium = 500, FontWeightSemiBold = 600, FontWeightBold = 700, FontWeightUltraBold = 800, FontWeightHeavy = 900 }; enum FontWidth { FontWidthUndefined = 0, FontWidthUltraCondensed = 1, FontWidthExtraCondensed = 2, FontWidthCondensed = 3, FontWidthSemiCondensed = 4, FontWidthNormal = 5, FontWidthSemiExpanded = 6, FontWidthExpanded = 7, FontWidthExtraExpanded = 8, FontWidthUltraExpanded = 9 }; inline FontWeight resolve_font_weight(FT_UShort weight) { if (weight == 0) return FontWeightUndefined; if (weight < 150) return FontWeightThin; if (weight < 250) return FontWeightUltraLight; if (weight < 350) return FontWeightLight; if (weight < 450) return FontWeightNormal; if (weight < 550) return FontWeightMedium; if (weight < 650) return FontWeightSemiBold; if (weight < 750) return FontWeightBold; if (weight < 850) return FontWeightUltraBold; return FontWeightHeavy; } inline FontWeight get_font_weight(FT_Face face) { void* table = FT_Get_Sfnt_Table(face, FT_SFNT_OS2); if (table == NULL) { return FontWeightUndefined; } TT_OS2* os2_table = (TT_OS2*) table; return resolve_font_weight(os2_table->usWeightClass); } inline FontWidth get_font_width(FT_Face face) { void* table = FT_Get_Sfnt_Table(face, FT_SFNT_OS2); if (table == NULL) { return FontWidthUndefined; } TT_OS2* os2_table = (TT_OS2*) table; return (FontWidth) os2_table->usWidthClass; } struct FontDescriptor { public: const char *path; int index; const char *postscriptName; const char *family; const char *style; FontWeight weight; FontWidth width; bool italic; bool monospace; FontDescriptor() { path = NULL; index = -1; postscriptName = NULL; family = NULL; style = NULL; weight = FontWeightUndefined; width = FontWidthUndefined; italic = false; monospace = false; } // Constructor added by Thomas Lin Pedersen FontDescriptor(const char *family, bool italic, bool bold) { this->path = NULL; this->index = -1; this->postscriptName = NULL; this->family = copyString(family); this->style = NULL; this->weight = bold ? FontWeightBold : FontWeightNormal; this->width = FontWidthUndefined; this->italic = italic; this->monospace = false; } // Constructor added by Thomas Lin Pedersen FontDescriptor(const char *family, bool italic, FontWeight weight, FontWidth width) { this->path = NULL; this->index = -1; this->postscriptName = NULL; this->family = copyString(family); this->style = NULL; this->weight = weight; this->width = width; this->italic = italic; this->monospace = false; } // Constructor added by Thomas Lin Pedersen FontDescriptor(FT_Face face, const char* path, int index) { this->path = copyString(path); this->index = index; this->postscriptName = FT_Get_Postscript_Name(face) == NULL ? "" : copyString(FT_Get_Postscript_Name(face)); this->family = copyString(face->family_name); this->style = copyString(face->style_name); this->weight = get_font_weight(face); this->width = get_font_width(face); this->italic = face->style_flags & FT_STYLE_FLAG_ITALIC; this->monospace = FT_IS_FIXED_WIDTH(face); } FontDescriptor(const char *path, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace) { this->path = copyString(path); this->index = 0; this->postscriptName = copyString(postscriptName); this->family = copyString(family); this->style = copyString(style); this->weight = weight; this->width = width; this->italic = italic; this->monospace = monospace; } FontDescriptor(const char *path, int index, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace) { this->path = copyString(path); this->index = index; this->postscriptName = copyString(postscriptName); this->family = copyString(family); this->style = copyString(style); this->weight = weight; this->width = width; this->italic = italic; this->monospace = monospace; } FontDescriptor(FontDescriptor *desc) { path = copyString(desc->path); index = desc->index; postscriptName = copyString(desc->postscriptName); family = copyString(desc->family); style = copyString(desc->style); weight = desc->weight; width = desc->width; italic = desc->italic; monospace = desc->monospace; } const char* get_path() { return path == NULL ? "" : path; } const char* get_psname() { return postscriptName == NULL ? "" : postscriptName; } const char* get_family() { return family == NULL ? "" : family; } const char* get_style() { return style == NULL ? "" : style; } int get_weight() { switch (weight) { case FontWeightThin: return 1; case FontWeightUltraLight: return 2; case FontWeightLight: return 3; case FontWeightNormal: return 4; case FontWeightMedium: return 5; case FontWeightSemiBold: return 6; case FontWeightBold: return 7; case FontWeightUltraBold: return 8; case FontWeightHeavy: return 9; case FontWeightUndefined: return 0; } return 0; } int get_width() { switch (width) { case FontWidthUltraCondensed: return 1; case FontWidthExtraCondensed: return 2; case FontWidthCondensed: return 3; case FontWidthSemiCondensed: return 4; case FontWidthNormal: return 5; case FontWidthSemiExpanded: return 6; case FontWidthExpanded: return 7; case FontWidthExtraExpanded: return 8; case FontWidthUltraExpanded: return 9; case FontWidthUndefined: return 0; } return 0; } ~FontDescriptor() { if (path) delete[] path; if (postscriptName) delete[] postscriptName; if (family) delete[] family; if (style) delete[] style; postscriptName = NULL; family = NULL; style = NULL; } bool operator==(FontDescriptor& other) { if (postscriptName && !strcmp_no_case(postscriptName, other.postscriptName)) return false; if (family && !strcmp_no_case(family, other.family)) return false; if (style && !strcmp_no_case(style, other.style)) return false; if (weight && weight != other.weight) return false; if (width && width != other.width) return false; if (italic != other.italic) return false; return true; } bool operator!=(FontDescriptor& other) { return !this->operator==(other); } private: char *copyString(const char *input) { if (input == NULL) { return NULL; } char *str = new char[strlen(input) + 1]; strcpy(str, input); return str; } }; class ResultSet : public std::vector { public: ~ResultSet() { for (ResultSet::iterator it = this->begin(); it != this->end(); it++) { delete *it; } } int n_fonts() { return size(); } }; #endif systemfonts/src/font_metrics.h0000644000176200001440000000152714672302530016313 0ustar liggesusers#pragma once #include #include #include #include #include #include [[cpp11::register]] cpp11::writable::data_frame get_font_info_c(cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); [[cpp11::register]] cpp11::writable::data_frame get_glyph_info_c(cpp11::strings glyphs, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width); int font_weight(const char* fontfile, int index); int font_family(const char* fontfile, int index, char* family, int max_length); [[cpp11::init]] void export_font_metrics(DllInfo* dll); systemfonts/src/ft_cache.cpp0000644000176200001440000002241314741745027015714 0ustar liggesusers#include "ft_cache.h" #include #include FreetypeCache::FreetypeCache() : error_code(0), glyphstore(), face_cache(16), size_cache(32), cur_id(), cur_size(-1), cur_res(-1), cur_can_kern(false), cur_glyph(0) { FT_Error err = FT_Init_FreeType(&library); if (err != 0) { cpp11::stop("systemfonts failed to initialise the freetype font cache"); } } FreetypeCache::~FreetypeCache() { FT_Done_FreeType(library); } bool FreetypeCache::load_font(const char* file, int index, double size, double res) { FaceID id(std::string(file), index); if (current_face(id, size, res)) { return true; } if (!load_face(id)) { return false; } if (!load_size(id, size, res)) { return false; } cur_id = id; cur_size = size; cur_res = res; glyphstore.clear(); cur_can_kern = FT_HAS_KERNING(face); return true; } bool FreetypeCache::load_font(const char* file, int index) { std::string file_str(file); FaceID id(file_str, index); if (id == cur_id) { return true; } if (!load_face(id)) { return false; } cur_id = id; cur_size = -1; cur_res = -1; glyphstore.clear(); cur_can_kern = FT_HAS_KERNING(face); return true; } bool FreetypeCache::load_face(FaceID face) { if (face == cur_id) { return true; } FaceStore cached_face; if (face_cache.get(face, cached_face)) { this->face = cached_face.face; cur_is_scalable = FT_IS_SCALABLE(this->face); return true; } FT_Face new_face; FT_Error err = FT_New_Face(this->library, face.file.c_str(), face.index, &new_face); if (err != 0) { error_code = err; return false; } this->face = new_face; cur_is_scalable = FT_IS_SCALABLE(new_face); if (face_cache.add(face, FaceStore(new_face), cached_face)) { for(std::unordered_set::iterator it = cached_face.sizes.begin(); it != cached_face.sizes.end(); ++it) { size_cache.remove(*it); } FT_Done_Face(cached_face.face); } return true; } bool FreetypeCache::load_size(FaceID face, double size, double res) { SizeID id(face, size, res); FT_Size cached_size; SizeID cached_id; FaceStore cached_face; if (size_cache.get(id, cached_size)) { FT_Activate_Size(cached_size); this->size = cached_size; return true; } FT_Size new_size; FT_Error err = FT_New_Size(this->face, &new_size); if (err != 0) { error_code = err; return false; } FT_Size old_size = this->face->size; FT_Activate_Size(new_size); if (cur_is_scalable) { err = FT_Set_Char_Size(this->face, 0, size * 64, res, res); if (err != 0) { error_code = err; FT_Activate_Size(old_size); return false; } } else { if (this->face->num_fixed_sizes == 0) { error_code = 23; FT_Activate_Size(old_size); return false; } int best_match = 0; int diff = 1e6; int scaled_size = 64 * size * res / 72; int largest_size = 0; int largest_ind = -1; bool found_match = false; for (int i = 0; i < this->face->num_fixed_sizes; ++i) { if (this->face->available_sizes[i].size > largest_size) { largest_ind = i; } int ndiff = this->face->available_sizes[i].size - scaled_size; if (ndiff >= 0 && ndiff < diff) { best_match = i; diff = ndiff; found_match = true; } } if (!found_match && scaled_size >= largest_size) { best_match = largest_ind; } err = FT_Select_Size(this->face, best_match); if (err != 0) { error_code = err; FT_Activate_Size(old_size); return false; } unscaled_scaling = 1; } if (size_cache.add(id, new_size, cached_id)) { if (face_cache.get(cached_id.face, cached_face)) { cached_face.sizes.erase(cached_id); } } face_cache.add_size_id(face, id); this->size = new_size; return true; } bool FreetypeCache::has_glyph(uint32_t index) { FT_UInt glyph_id = FT_Get_Char_Index(face, index); return glyph_id != 0; } bool FreetypeCache::load_unicode(uint32_t index) { FT_UInt glyph_id = FT_Get_Char_Index(face, index); return load_glyph(glyph_id); } bool FreetypeCache::load_glyph(FT_UInt id, int flags) { FT_Error err = 0; err = FT_Load_Glyph(face, id, flags); error_code = err; if (err == 0) { cur_glyph = id; } return err == 0; } FontInfo FreetypeCache::font_info() { FontInfo res = {}; res.family = std::string(face->family_name); res.style = std::string(face->style_name); res.is_italic = face->style_flags & FT_STYLE_FLAG_ITALIC; res.is_bold = face->style_flags & FT_STYLE_FLAG_BOLD; res.is_monospace = FT_IS_FIXED_WIDTH(face); res.is_vertical = FT_HAS_VERTICAL(face); res.has_kerning = cur_can_kern; #ifdef FT_HAS_COLOR res.has_color = FT_HAS_COLOR(face); #else res.has_color = false; #endif res.is_scalable = cur_is_scalable; res.n_glyphs = face->num_glyphs; res.n_sizes = face->num_fixed_sizes; res.n_charmaps = face->num_charmaps; res.bbox = { FT_MulFix(face->bbox.xMin, size->metrics.x_scale), FT_MulFix(face->bbox.xMax, size->metrics.x_scale), FT_MulFix(face->bbox.yMin, size->metrics.y_scale), FT_MulFix(face->bbox.yMax, size->metrics.y_scale) }; res.max_ascend = FT_MulFix(face->ascender, size->metrics.y_scale); res.max_descend = FT_MulFix(face->descender, size->metrics.y_scale); res.max_advance_h = FT_MulFix(face->max_advance_height, size->metrics.y_scale); res.max_advance_w = FT_MulFix(face->max_advance_width, size->metrics.x_scale); res.lineheight = FT_MulFix(face->height, size->metrics.y_scale); res.underline_pos = FT_MulFix(face->underline_position, size->metrics.y_scale); res.underline_size = FT_MulFix(face->underline_thickness, size->metrics.y_scale); return res; } GlyphInfo FreetypeCache::glyph_info() { GlyphInfo res = {}; res.index = cur_glyph; res.width = face->glyph->metrics.width; res.height = face->glyph->metrics.height; res.x_advance = face->glyph->advance.x; res.y_advance = face->glyph->advance.y; if (res.y_advance != 0) { // Vertical res.x_bearing = face->glyph->metrics.vertBearingX; res.y_bearing = face->glyph->metrics.vertBearingY; } else { res.x_bearing = face->glyph->metrics.horiBearingX; res.y_bearing = face->glyph->metrics.horiBearingY; } res.bbox = {res.x_bearing, res.x_bearing + res.width, res.y_bearing - res.height, res.y_bearing}; if (!cur_is_scalable) { res.width *= unscaled_scaling; res.height *= unscaled_scaling; res.x_advance *= unscaled_scaling; res.y_advance *= unscaled_scaling; res.x_bearing *= unscaled_scaling; res.y_bearing *= unscaled_scaling; res.bbox[0] *= unscaled_scaling; res.bbox[1] *= unscaled_scaling; res.bbox[2] *= unscaled_scaling; res.bbox[3] *= unscaled_scaling; } return res; } GlyphInfo FreetypeCache::cached_glyph_info(uint32_t index, int& error) { std::map::iterator cached_gi = glyphstore.find(index); GlyphInfo info = {}; error = 0; if (cached_gi == glyphstore.end()) { if (load_unicode(index)) { info = glyph_info(); glyphstore[index] = info; } else { error = error_code; } } else { info = cached_gi->second; } return info; } long FreetypeCache::cur_lineheight() { return FT_MulFix(face->height, size->metrics.y_scale); } long FreetypeCache::cur_ascender() { return FT_MulFix(face->ascender, size->metrics.y_scale); } long FreetypeCache::cur_descender() { return FT_MulFix(face->descender, size->metrics.y_scale); } bool FreetypeCache::get_kerning(uint32_t left, uint32_t right, long &x, long &y) { x = 0; y = 0; // Early exit if (!cur_can_kern) return true; FT_UInt left_id = FT_Get_Char_Index(face, left); FT_UInt right_id = FT_Get_Char_Index(face, right); FT_Vector delta = {}; FT_Error error = FT_Get_Kerning(face, left_id, right_id, FT_KERNING_DEFAULT, &delta); if (error != 0) { error_code = error; return false; } x = delta.x; y = delta.y; return true; } bool FreetypeCache::apply_kerning(uint32_t left, uint32_t right, long &x, long &y) { long delta_x = 0, delta_y = 0; if (!get_kerning(left, right, delta_x, delta_y)) { return false; } x += delta_x; y += delta_y; return true; } double FreetypeCache::tracking_diff(double tracking) { return (double) FT_MulFix(face->units_per_EM, size->metrics.x_scale) * tracking / 1000; } FT_Face FreetypeCache::get_face() { //FT_Reference_Face(face); return face; } std::string FreetypeCache::cur_name() { const char* ps_name = FT_Get_Postscript_Name(face); if (ps_name == NULL) { const char* f_name = face->family_name; if (f_name == NULL) f_name = ""; return {f_name}; } return {ps_name}; } int FreetypeCache::get_weight() { void* table = FT_Get_Sfnt_Table(face, ft_sfnt_os2); // [1] ft_sfnt_os2 is deprecated and should be replaced by FT_SFNT_OS2 (only) in the remote future for compatibilty (2021-03-04) if (table == NULL) { return 0; } TT_OS2* os2_table = (TT_OS2*) table; return os2_table->usWeightClass; } int FreetypeCache::get_width() { void* table = FT_Get_Sfnt_Table(face, ft_sfnt_os2); // see comment [1] above if (table == NULL) { return 0; } TT_OS2* os2_table = (TT_OS2*) table; return os2_table->usWidthClass; } void FreetypeCache::get_family_name(char* family, int max_length) { strncpy(family, face->family_name, max_length); } systemfonts/src/cache_store.cpp0000644000176200001440000000106314672302530016424 0ustar liggesusers#include "cache_store.h" #include "types.h" #include "caches.h" #include "utils.h" FT_Face get_cached_face(const char* file, int index, double size, double res, int* error) { FT_Face face = nullptr; BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(file, index, size, res)) { *error = cache.error_code; return face; } face = cache.get_face(); END_CPP *error = 0; return face; } void export_cache_store(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_cached_face", (DL_FUNC)get_cached_face); } systemfonts/src/utils.h0000644000176200001440000001140114724004735014753 0ustar liggesusers#pragma once #include #include #include #include #include #include #define BEGIN_CPP \ SEXP err = R_NilValue; \ const size_t ERROR_SIZE = 8192; \ char buf[ERROR_SIZE] = ""; \ try { #define END_CPP \ } \ catch (cpp11::unwind_exception & e) { \ err = e.token; \ } \ catch (std::exception & e) { \ strncpy(buf, e.what(), ERROR_SIZE - 1); \ } \ catch (...) { \ strncpy(buf, "C++ error (unknown cause)", ERROR_SIZE - 1); \ } \ if (buf[0] != '\0') { \ Rf_error("%s", buf); \ } else if (err != R_NilValue) { \ R_ContinueUnwind(err); \ } inline bool strcmp_no_case(const char * A, const char * B) { if (A == NULL && B == NULL) return true; if (A == NULL || B == NULL) return false; unsigned int a_len = strlen(A); if (strlen(B) != a_len) return false; for (unsigned int i = 0; i < a_len; ++i) if (tolower(A[i]) != tolower(B[i])) return false; return true; } /* Basic UTF-8 manipulation routines by Jeff Bezanson placed in the public domain Fall 2005 This code is designed to provide the utilities you need to manipulate UTF-8 as an internal string encoding. These functions do not perform the error checking normally needed when handling UTF-8 data, so if you happen to be from the Unicode Consortium you will want to flay me alive. I do this because error checking can be performed at the boundaries (I/O), with these routines reserved for higher performance on data known to be valid. Source: https://www.cprogramming.com/tutorial/utf8.c Modified 2019 by Thomas Lin Pedersen to work with const char* */ static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; static const char trailingBytesForUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /* conversions without error checking only works for valid UTF-8, i.e. no 5- or 6-byte sequences srcsz = source size in bytes, or -1 if 0-terminated sz = dest size in # of wide characters returns # characters converted dest will always be L'\0'-terminated, even if there isn't enough room for all the characters. if sz = srcsz+1 (i.e. 4*srcsz+4 bytes), there will always be enough space. */ static int u8_toucs(uint32_t *dest, int sz, const char *src, int srcsz) { uint32_t ch; const char *src_end = src + srcsz; int nb; int i=0; while (i < sz-1) { nb = trailingBytesForUTF8[(unsigned char)*src]; if (srcsz == -1) { if (*src == 0) goto done_toucs; } else { if (src + nb >= src_end) goto done_toucs; } ch = 0; switch (nb) { /* these fall through deliberately */ case 5: ch += (unsigned char)*src++; ch <<= 6; case 4: ch += (unsigned char)*src++; ch <<= 6; case 3: ch += (unsigned char)*src++; ch <<= 6; case 2: ch += (unsigned char)*src++; ch <<= 6; case 1: ch += (unsigned char)*src++; ch <<= 6; case 0: ch += (unsigned char)*src++; } ch -= offsetsFromUTF8[nb]; dest[i++] = ch; } done_toucs: dest[i] = 0; return i; } /* End of Basic UTF-8 manipulation routines by Jeff Bezanson */ class UTF_UCS { std::vector buffer; public: UTF_UCS() { // Allocate space in buffer buffer.resize(1024); } ~UTF_UCS() { } uint32_t * convert(const char * string, int &n_conv) { if (string == NULL) { n_conv = 0; return buffer.data(); } int n_bytes = strlen(string) + 1; unsigned int max_size = n_bytes * 4; if (buffer.size() < max_size) { buffer.resize(max_size); } n_conv = u8_toucs(buffer.data(), max_size, string, -1); return buffer.data(); } }; systemfonts/src/cpp11.cpp0000644000176200001440000003164614742430050015100 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "cpp11/declarations.hpp" #include // dev_metrics.h cpp11::doubles dev_string_widths_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); extern "C" SEXP _systemfonts_dev_string_widths_c(SEXP string, SEXP family, SEXP face, SEXP size, SEXP cex, SEXP unit) { BEGIN_CPP11 return cpp11::as_sexp(dev_string_widths_c(cpp11::as_cpp>(string), cpp11::as_cpp>(family), cpp11::as_cpp>(face), cpp11::as_cpp>(size), cpp11::as_cpp>(cex), cpp11::as_cpp>(unit))); END_CPP11 } // dev_metrics.h cpp11::writable::data_frame dev_string_metrics_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); extern "C" SEXP _systemfonts_dev_string_metrics_c(SEXP string, SEXP family, SEXP face, SEXP size, SEXP cex, SEXP unit) { BEGIN_CPP11 return cpp11::as_sexp(dev_string_metrics_c(cpp11::as_cpp>(string), cpp11::as_cpp>(family), cpp11::as_cpp>(face), cpp11::as_cpp>(size), cpp11::as_cpp>(cex), cpp11::as_cpp>(unit))); END_CPP11 } // emoji.h void load_emoji_codes_c(cpp11::integers all, cpp11::integers default_text, cpp11::integers base_mod); extern "C" SEXP _systemfonts_load_emoji_codes_c(SEXP all, SEXP default_text, SEXP base_mod) { BEGIN_CPP11 load_emoji_codes_c(cpp11::as_cpp>(all), cpp11::as_cpp>(default_text), cpp11::as_cpp>(base_mod)); return R_NilValue; END_CPP11 } // emoji.h cpp11::list emoji_split_c(cpp11::strings string, cpp11::strings path, cpp11::integers index); extern "C" SEXP _systemfonts_emoji_split_c(SEXP string, SEXP path, SEXP index) { BEGIN_CPP11 return cpp11::as_sexp(emoji_split_c(cpp11::as_cpp>(string), cpp11::as_cpp>(path), cpp11::as_cpp>(index))); END_CPP11 } // font_fallback.h cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string); extern "C" SEXP _systemfonts_get_fallback_c(SEXP path, SEXP index, SEXP string) { BEGIN_CPP11 return cpp11::as_sexp(get_fallback_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(string))); END_CPP11 } // font_local.h int add_local_fonts(cpp11::strings paths); extern "C" SEXP _systemfonts_add_local_fonts(SEXP paths) { BEGIN_CPP11 return cpp11::as_sexp(add_local_fonts(cpp11::as_cpp>(paths))); END_CPP11 } // font_local.h void clear_local_fonts_c(); extern "C" SEXP _systemfonts_clear_local_fonts_c() { BEGIN_CPP11 clear_local_fonts_c(); return R_NilValue; END_CPP11 } // font_matching.h cpp11::list match_font_c(cpp11::strings family, cpp11::logicals italic, cpp11::logicals bold); extern "C" SEXP _systemfonts_match_font_c(SEXP family, SEXP italic, SEXP bold) { BEGIN_CPP11 return cpp11::as_sexp(match_font_c(cpp11::as_cpp>(family), cpp11::as_cpp>(italic), cpp11::as_cpp>(bold))); END_CPP11 } // font_matching.h cpp11::writable::data_frame locate_fonts_c(cpp11::strings family, cpp11::logicals italic, cpp11::integers weight, cpp11::integers width); extern "C" SEXP _systemfonts_locate_fonts_c(SEXP family, SEXP italic, SEXP weight, SEXP width) { BEGIN_CPP11 return cpp11::as_sexp(locate_fonts_c(cpp11::as_cpp>(family), cpp11::as_cpp>(italic), cpp11::as_cpp>(weight), cpp11::as_cpp>(width))); END_CPP11 } // font_matching.h cpp11::writable::data_frame system_fonts_c(); extern "C" SEXP _systemfonts_system_fonts_c() { BEGIN_CPP11 return cpp11::as_sexp(system_fonts_c()); END_CPP11 } // font_matching.h void reset_font_cache_c(); extern "C" SEXP _systemfonts_reset_font_cache_c() { BEGIN_CPP11 reset_font_cache_c(); return R_NilValue; END_CPP11 } // font_metrics.h cpp11::writable::data_frame get_font_info_c(cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); extern "C" SEXP _systemfonts_get_font_info_c(SEXP path, SEXP index, SEXP size, SEXP res) { BEGIN_CPP11 return cpp11::as_sexp(get_font_info_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res))); END_CPP11 } // font_metrics.h cpp11::writable::data_frame get_glyph_info_c(cpp11::strings glyphs, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); extern "C" SEXP _systemfonts_get_glyph_info_c(SEXP glyphs, SEXP path, SEXP index, SEXP size, SEXP res) { BEGIN_CPP11 return cpp11::as_sexp(get_glyph_info_c(cpp11::as_cpp>(glyphs), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res))); END_CPP11 } // font_outlines.h cpp11::writable::data_frame get_glyph_outlines(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, double tolerance, bool verbose); extern "C" SEXP _systemfonts_get_glyph_outlines(SEXP glyph, SEXP path, SEXP index, SEXP size, SEXP tolerance, SEXP verbose) { BEGIN_CPP11 return cpp11::as_sexp(get_glyph_outlines(cpp11::as_cpp>(glyph), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(tolerance), cpp11::as_cpp>(verbose))); END_CPP11 } // font_outlines.h cpp11::writable::list get_glyph_bitmap(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::integers color, bool verbose); extern "C" SEXP _systemfonts_get_glyph_bitmap(SEXP glyph, SEXP path, SEXP index, SEXP size, SEXP res, SEXP color, SEXP verbose) { BEGIN_CPP11 return cpp11::as_sexp(get_glyph_bitmap(cpp11::as_cpp>(glyph), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(color), cpp11::as_cpp>(verbose))); END_CPP11 } // font_registry.h void register_font_c(cpp11::strings family, cpp11::strings paths, cpp11::integers indices, cpp11::strings features, cpp11::integers settings); extern "C" SEXP _systemfonts_register_font_c(SEXP family, SEXP paths, SEXP indices, SEXP features, SEXP settings) { BEGIN_CPP11 register_font_c(cpp11::as_cpp>(family), cpp11::as_cpp>(paths), cpp11::as_cpp>(indices), cpp11::as_cpp>(features), cpp11::as_cpp>(settings)); return R_NilValue; END_CPP11 } // font_registry.h void clear_registry_c(); extern "C" SEXP _systemfonts_clear_registry_c() { BEGIN_CPP11 clear_registry_c(); return R_NilValue; END_CPP11 } // font_registry.h cpp11::writable::data_frame registry_fonts_c(); extern "C" SEXP _systemfonts_registry_fonts_c() { BEGIN_CPP11 return cpp11::as_sexp(registry_fonts_c()); END_CPP11 } // string_metrics.h cpp11::list get_string_shape_c(cpp11::strings string, cpp11::integers id, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::doubles lineheight, cpp11::integers align, cpp11::doubles hjust, cpp11::doubles vjust, cpp11::doubles width, cpp11::doubles tracking, cpp11::doubles indent, cpp11::doubles hanging, cpp11::doubles space_before, cpp11::doubles space_after); extern "C" SEXP _systemfonts_get_string_shape_c(SEXP string, SEXP id, SEXP path, SEXP index, SEXP size, SEXP res, SEXP lineheight, SEXP align, SEXP hjust, SEXP vjust, SEXP width, SEXP tracking, SEXP indent, SEXP hanging, SEXP space_before, SEXP space_after) { BEGIN_CPP11 return cpp11::as_sexp(get_string_shape_c(cpp11::as_cpp>(string), cpp11::as_cpp>(id), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(lineheight), cpp11::as_cpp>(align), cpp11::as_cpp>(hjust), cpp11::as_cpp>(vjust), cpp11::as_cpp>(width), cpp11::as_cpp>(tracking), cpp11::as_cpp>(indent), cpp11::as_cpp>(hanging), cpp11::as_cpp>(space_before), cpp11::as_cpp>(space_after))); END_CPP11 } // string_metrics.h cpp11::doubles get_line_width_c(cpp11::strings string, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::logicals include_bearing); extern "C" SEXP _systemfonts_get_line_width_c(SEXP string, SEXP path, SEXP index, SEXP size, SEXP res, SEXP include_bearing) { BEGIN_CPP11 return cpp11::as_sexp(get_line_width_c(cpp11::as_cpp>(string), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(include_bearing))); END_CPP11 } extern "C" { static const R_CallMethodDef CallEntries[] = { {"_systemfonts_add_local_fonts", (DL_FUNC) &_systemfonts_add_local_fonts, 1}, {"_systemfonts_clear_local_fonts_c", (DL_FUNC) &_systemfonts_clear_local_fonts_c, 0}, {"_systemfonts_clear_registry_c", (DL_FUNC) &_systemfonts_clear_registry_c, 0}, {"_systemfonts_dev_string_metrics_c", (DL_FUNC) &_systemfonts_dev_string_metrics_c, 6}, {"_systemfonts_dev_string_widths_c", (DL_FUNC) &_systemfonts_dev_string_widths_c, 6}, {"_systemfonts_emoji_split_c", (DL_FUNC) &_systemfonts_emoji_split_c, 3}, {"_systemfonts_get_fallback_c", (DL_FUNC) &_systemfonts_get_fallback_c, 3}, {"_systemfonts_get_font_info_c", (DL_FUNC) &_systemfonts_get_font_info_c, 4}, {"_systemfonts_get_glyph_bitmap", (DL_FUNC) &_systemfonts_get_glyph_bitmap, 7}, {"_systemfonts_get_glyph_info_c", (DL_FUNC) &_systemfonts_get_glyph_info_c, 5}, {"_systemfonts_get_glyph_outlines", (DL_FUNC) &_systemfonts_get_glyph_outlines, 6}, {"_systemfonts_get_line_width_c", (DL_FUNC) &_systemfonts_get_line_width_c, 6}, {"_systemfonts_get_string_shape_c", (DL_FUNC) &_systemfonts_get_string_shape_c, 16}, {"_systemfonts_load_emoji_codes_c", (DL_FUNC) &_systemfonts_load_emoji_codes_c, 3}, {"_systemfonts_locate_fonts_c", (DL_FUNC) &_systemfonts_locate_fonts_c, 4}, {"_systemfonts_match_font_c", (DL_FUNC) &_systemfonts_match_font_c, 3}, {"_systemfonts_register_font_c", (DL_FUNC) &_systemfonts_register_font_c, 5}, {"_systemfonts_registry_fonts_c", (DL_FUNC) &_systemfonts_registry_fonts_c, 0}, {"_systemfonts_reset_font_cache_c", (DL_FUNC) &_systemfonts_reset_font_cache_c, 0}, {"_systemfonts_system_fonts_c", (DL_FUNC) &_systemfonts_system_fonts_c, 0}, {NULL, NULL, 0} }; } void export_cache_store(DllInfo* dll); void init_caches(DllInfo* dll); void export_emoji_detection(DllInfo* dll); void export_font_fallback(DllInfo* dll); void export_font_matching(DllInfo* dll); void export_font_metrics(DllInfo* dll); void export_string_metrics(DllInfo* dll); extern "C" attribute_visible void R_init_systemfonts(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); export_cache_store(dll); init_caches(dll); export_emoji_detection(dll); export_font_fallback(dll); export_font_matching(dll); export_font_metrics(dll); export_string_metrics(dll); R_forceSymbols(dll, TRUE); } systemfonts/src/font_metrics.cpp0000644000176200001440000002131114672302530016637 0ustar liggesusers#include "font_metrics.h" #include "types.h" #include "caches.h" #include "utils.h" #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using doubles_t = cpp11::doubles; using doubles_w = cpp11::writable::doubles; using namespace cpp11::literals; data_frame_w get_font_info_c(strings_t path, integers_t index, doubles_t size, doubles_t res) { bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; int full_length = 1; if (!one_path) full_length = path.size(); else if (!one_size) full_length = size.size(); else if (!one_res) full_length = res.size(); FreetypeCache& cache = get_font_cache(); strings_w path_col(full_length); integers_w index_col(full_length); strings_w family(full_length); strings_w style(full_length); logicals_w italic(full_length); logicals_w bold(full_length); logicals_w monospace(full_length); integers_w weight(full_length); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = { "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy" }; integers_w width(full_length); width.attr("class") = {"ordered", "factor"}; width.attr("levels") = { "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded" }; logicals_w kerning(full_length); logicals_w color(full_length); logicals_w scalable(full_length); logicals_w vertical(full_length); integers_w nglyphs(full_length); integers_w nsizes(full_length); integers_w ncharmaps(full_length); list_w bbox(full_length); doubles_w ascend(full_length); doubles_w descend(full_length); doubles_w advance_w(full_length); doubles_w advance_h(full_length); doubles_w lineheight(full_length); doubles_w u_pos(full_length); doubles_w u_size(full_length); for (int i = 0; i < full_length; ++i) { bool success = cache.load_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i] ); if (!success) { cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code); } FontInfo info = cache.font_info(); path_col[i] = one_path ? first_path : path[i]; index_col[i] = one_path ? first_index : index[i]; family[i] = info.family; style[i] = info.style; italic[i] = (Rboolean) info.is_italic; bold[i] = (Rboolean) info.is_bold; monospace[i] = (Rboolean) info.is_monospace; weight[i] = cache.get_weight() / 100; if (weight[i] == 0) { weight[i] = NA_INTEGER; } width[i] = cache.get_width(); if (width[i] == 0) { width[i] = NA_INTEGER; } kerning[i] = (Rboolean) info.has_kerning; color[i] = (Rboolean) info.has_color; scalable[i] = (Rboolean) info.is_scalable; vertical[i] = (Rboolean) info.is_vertical; nglyphs[i] = info.n_glyphs; nsizes[i] = info.n_sizes; ncharmaps[i] = info.n_charmaps; bbox[i] = doubles_w({ "xmin"_nm = double(info.bbox[0]) / 64.0, "xmax"_nm = double(info.bbox[1]) / 64.0, "ymin"_nm = double(info.bbox[2]) / 64.0, "ymax"_nm = double(info.bbox[3]) / 64.0 }); ascend[i] = info.max_ascend / 64.0; descend[i] = info.max_descend / 64.0; advance_w[i] = info.max_advance_w / 64.0; advance_h[i] = info.max_advance_h / 64.0; lineheight[i] = info.lineheight / 64.0; u_pos[i] = info.underline_pos / 64.0; u_size[i] = info.underline_size / 64.0; } data_frame_w info({ "path"_nm = path_col, "index"_nm = index_col, "family"_nm = family, "style"_nm = style, "italic"_nm = italic, "bold"_nm = bold, "monospace"_nm = monospace, "weight"_nm = weight, "width"_nm = width, "kerning"_nm = kerning, "color"_nm = color, "scalable"_nm = scalable, "vertical"_nm = vertical, "n_glyphs"_nm = nglyphs, "n_sizes"_nm = nsizes, "n_charmaps"_nm = ncharmaps, "bbox"_nm = bbox, "max_ascend"_nm = ascend, "max_descend"_nm = descend, "max_advance_width"_nm = advance_w, "max_advance_height"_nm = advance_h, "lineheight"_nm = lineheight, "underline_pos"_nm = u_pos, "underline_size"_nm = u_size }); info.attr("class") = {"tbl_df", "tbl", "data.frame"}; return info; } data_frame_w get_glyph_info_c(strings_t glyphs, strings_t path, integers_t index, doubles_t size, doubles_t res) { int n_glyphs = glyphs.size(); bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; FreetypeCache& cache = get_font_cache(); integers_w glyph_ids(n_glyphs); doubles_w widths(n_glyphs); doubles_w heights(n_glyphs); doubles_w x_bearings(n_glyphs); doubles_w y_bearings(n_glyphs); doubles_w x_advances(n_glyphs); doubles_w y_advances(n_glyphs); list_w bboxes(n_glyphs); UTF_UCS utf_converter; int length = 0; int error_c = 0; for (int i = 0; i < n_glyphs; ++i) { bool success = cache.load_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i] ); if (!success) { cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code); } const char* glyph = Rf_translateCharUTF8(glyphs[i]); uint32_t* glyph_code = utf_converter.convert(glyph, length); GlyphInfo glyph_info = cache.cached_glyph_info(glyph_code[0], error_c); if (error_c != 0) { cpp11::stop("Failed to load `%s` from font (%s) with freetype error %i", glyph, Rf_translateCharUTF8(path[i]), error_c); } glyph_ids[i] = glyph_info.index; widths[i] = glyph_info.width / 64.0; heights[i] = glyph_info.height / 64.0; x_bearings[i] = glyph_info.x_bearing / 64.0; y_bearings[i] = glyph_info.y_bearing / 64.0; x_advances[i] = glyph_info.x_advance / 64.0; y_advances[i] = glyph_info.y_advance / 64.0; bboxes[i] = doubles_w({ "xmin"_nm = double(glyph_info.bbox[0]) / 64.0, "xmax"_nm = double(glyph_info.bbox[1]) / 64.0, "ymin"_nm = double(glyph_info.bbox[2]) / 64.0, "ymax"_nm = double(glyph_info.bbox[3]) / 64.0 }); } data_frame_w info({ "glyph"_nm = glyphs, "index"_nm = glyph_ids, "width"_nm = widths, "height"_nm = heights, "x_bearing"_nm = x_bearings, "y_bearing"_nm = y_bearings, "x_advance"_nm = x_advances, "y_advance"_nm = y_advances, "bbox"_nm = bboxes }); info.attr("class") = {"tbl_df", "tbl", "data.frame"}; return info; } int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index, size, res)) { return cache.error_code; } int error = 0; GlyphInfo metrics = cache.cached_glyph_info(code, error); if (error != 0) { return error; } *width = metrics.x_advance / 64.0; *ascent = metrics.bbox[3] / 64.0; *descent = -metrics.bbox[2] / 64.0; END_CPP return 0; } int font_weight(const char* fontfile, int index) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index)) { return 0; } return cache.get_weight(); END_CPP return 0; } int font_family(const char* fontfile, int index, char* family, int max_length) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index)) { return 0; } cache.get_family_name(family, max_length); END_CPP return 1; } void export_font_metrics(DllInfo* dll) { R_RegisterCCallable("systemfonts", "glyph_metrics", (DL_FUNC)glyph_metrics); R_RegisterCCallable("systemfonts", "font_weight", (DL_FUNC)font_weight); R_RegisterCCallable("systemfonts", "font_family", (DL_FUNC)font_family); } systemfonts/src/unix/0000755000176200001440000000000014672302505014426 5ustar liggesuserssystemfonts/src/unix/FontManagerLinux.cpp0000744000176200001440000001712014672302530020353 0ustar liggesusers#include #include "../FontDescriptor.h" int convertWeight(FontWeight weight) { switch (weight) { case FontWeightThin: return FC_WEIGHT_THIN; case FontWeightUltraLight: return FC_WEIGHT_ULTRALIGHT; case FontWeightLight: return FC_WEIGHT_LIGHT; case FontWeightNormal: return FC_WEIGHT_REGULAR; case FontWeightMedium: return FC_WEIGHT_MEDIUM; case FontWeightSemiBold: return FC_WEIGHT_SEMIBOLD; case FontWeightBold: return FC_WEIGHT_BOLD; case FontWeightUltraBold: return FC_WEIGHT_EXTRABOLD; case FontWeightHeavy: return FC_WEIGHT_ULTRABLACK; default: return FC_WEIGHT_REGULAR; } } FontWeight convertWeight(int weight) { switch (weight) { case FC_WEIGHT_THIN: return FontWeightThin; case FC_WEIGHT_ULTRALIGHT: return FontWeightUltraLight; case FC_WEIGHT_LIGHT: return FontWeightLight; case FC_WEIGHT_REGULAR: return FontWeightNormal; case FC_WEIGHT_MEDIUM: return FontWeightMedium; case FC_WEIGHT_SEMIBOLD: return FontWeightSemiBold; case FC_WEIGHT_BOLD: return FontWeightBold; case FC_WEIGHT_EXTRABOLD: return FontWeightUltraBold; case FC_WEIGHT_ULTRABLACK: return FontWeightHeavy; default: return FontWeightNormal; } } int convertWidth(FontWidth width) { switch (width) { case FontWidthUltraCondensed: return FC_WIDTH_ULTRACONDENSED; case FontWidthExtraCondensed: return FC_WIDTH_EXTRACONDENSED; case FontWidthCondensed: return FC_WIDTH_CONDENSED; case FontWidthSemiCondensed: return FC_WIDTH_SEMICONDENSED; case FontWidthNormal: return FC_WIDTH_NORMAL; case FontWidthSemiExpanded: return FC_WIDTH_SEMIEXPANDED; case FontWidthExpanded: return FC_WIDTH_EXPANDED; case FontWidthExtraExpanded: return FC_WIDTH_EXTRAEXPANDED; case FontWidthUltraExpanded: return FC_WIDTH_ULTRAEXPANDED; default: return FC_WIDTH_NORMAL; } } FontWidth convertWidth(int width) { switch (width) { case FC_WIDTH_ULTRACONDENSED: return FontWidthUltraCondensed; case FC_WIDTH_EXTRACONDENSED: return FontWidthExtraCondensed; case FC_WIDTH_CONDENSED: return FontWidthCondensed; case FC_WIDTH_SEMICONDENSED: return FontWidthSemiCondensed; case FC_WIDTH_NORMAL: return FontWidthNormal; case FC_WIDTH_SEMIEXPANDED: return FontWidthSemiExpanded; case FC_WIDTH_EXPANDED: return FontWidthExpanded; case FC_WIDTH_EXTRAEXPANDED: return FontWidthExtraExpanded; case FC_WIDTH_ULTRAEXPANDED: return FontWidthUltraExpanded; default: return FontWidthNormal; } } FontDescriptor *createFontDescriptor(FcPattern *pattern) { FcChar8 *path = NULL, *psName = NULL, *family = NULL, *style = NULL; int index = 0, weight = 0, width = 0, slant = 0, spacing = 0; FcPatternGetString(pattern, FC_FILE, 0, &path); #ifdef FC_POSTSCRIPT_NAME FcPatternGetString(pattern, FC_POSTSCRIPT_NAME, 0, &psName); #else psName = (FcChar8*) ""; #endif FcPatternGetString(pattern, FC_FAMILY, 0, &family); FcPatternGetString(pattern, FC_STYLE, 0, &style); FcPatternGetInteger(pattern, FC_INDEX, 0, &index); FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight); FcPatternGetInteger(pattern, FC_WIDTH, 0, &width); FcPatternGetInteger(pattern, FC_SLANT, 0, &slant); FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing); return new FontDescriptor( (char *) path, index, (char *) psName, (char *) family, (char *) style, convertWeight(weight), convertWidth(width), slant == FC_SLANT_ITALIC, spacing == FC_MONO ); } ResultSet *getResultSet(FcFontSet *fs) { ResultSet *res = new ResultSet(); if (!fs) return res; for (int i = 0; i < fs->nfont; i++) { res->push_back(createFontDescriptor(fs->fonts[i])); } return res; } void resetFontCache() { FcInitReinitialize(); } ResultSet *getAvailableFonts() { FcInit(); FcPattern *pattern = FcPatternCreate(); #ifdef FC_POSTSCRIPT_NAME FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #else FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #endif FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); FcFontSetDestroy(fs); return res; } FcPattern *createPattern(FontDescriptor *desc) { FcInit(); FcPattern *pattern = FcPatternCreate(); #ifdef FC_POSTSCRIPT_NAME if (desc->postscriptName) FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) desc->postscriptName); #endif if (desc->family) FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *) desc->family); if (desc->style) FcPatternAddString(pattern, FC_STYLE, (FcChar8 *) desc->style); if (desc->italic) FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); if (desc->weight) FcPatternAddInteger(pattern, FC_WEIGHT, convertWeight(desc->weight)); if (desc->width) FcPatternAddInteger(pattern, FC_WIDTH, convertWidth(desc->width)); if (desc->monospace) FcPatternAddInteger(pattern, FC_SPACING, FC_MONO); return pattern; } ResultSet *findFonts(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); #ifdef FC_POSTSCRIPT_NAME FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #else FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #endif FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcFontSetDestroy(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); return res; } FontDescriptor *findFont(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); FcConfigSubstitute(NULL, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *font = FcFontMatch(NULL, pattern, &result); FontDescriptor *res = font ? createFontDescriptor(font) : NULL; FcPatternDestroy(pattern); FcPatternDestroy(font); // No match try using family as postscriptName if (res == NULL) { desc->postscriptName = desc->family; desc->family = NULL; pattern = createPattern(desc); FcConfigSubstitute(NULL, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); font = FcFontMatch(NULL, pattern, &result); res = font ? createFontDescriptor(font) : NULL; FcPatternDestroy(pattern); FcPatternDestroy(font); } return res; } FontDescriptor *substituteFont(char *postscriptName, char *string) { FcInit(); // create a pattern with the postscript name FcPattern* pattern = FcPatternCreate(); #ifdef FC_POSTSCRIPT_NAME FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) postscriptName); #endif // create a charset with each character in the string FcCharSet* charset = FcCharSetCreate(); int len = strlen(string); for (int i = 0; i < len;) { FcChar32 c; i += FcUtf8ToUcs4((FcChar8 *)string + i, &c, len - i); FcCharSetAddChar(charset, c); } FcPatternAddCharSet(pattern, FC_CHARSET, charset); FcCharSetDestroy(charset); FcConfigSubstitute(0, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); // find the best match font FcResult result; FcPattern *font = FcFontMatch(NULL, pattern, &result); FontDescriptor *res = font ? createFontDescriptor(font) : NULL; FcPatternDestroy(pattern); FcPatternDestroy(font); return res; } systemfonts/src/cache_store.h0000644000176200001440000000040214672302530016065 0ustar liggesusers#pragma once #include #include FT_FREETYPE_H #include #include FT_Face get_cached_face(const char* file, int index, double size, double res, int* error); [[cpp11::init]] void export_cache_store(DllInfo* dll); systemfonts/src/Makevars.in0000644000176200001440000000075614741701336015556 0ustar liggesusersPKG_CPPFLAGS=@cflags@ PKG_OBJCXXFLAGS=@objcflags@ DARWIN_LIBS = -framework CoreText -framework Foundation DARWIN_OBJECTS = mac/FontManagerMac.o UNIX_OBJECTS = unix/FontManagerLinux.o PKG_LIBS = @libs@ $(@SYS@_LIBS) OBJECTS = caches.o cpp11.o dev_metrics.o font_matching.o font_local.o \ font_registry.o ft_cache.o string_shape.o font_metrics.o font_outlines.o \ font_fallback.o string_metrics.o emoji.o cache_store.o init.o $(@SYS@_OBJECTS) all: clean clean: rm -f $(SHLIB) $(OBJECTS) systemfonts/src/string_shape.h0000644000176200001440000000722714672302530016310 0ustar liggesusers#pragma once #include #include #include #include #include FT_FREETYPE_H #include FT_TYPES_H #include FT_CACHE_H #ifdef __EMSCRIPTEN__ #undef TYPEOF #endif #include "utils.h" #include "ft_cache.h" class FreetypeShaper { public: FreetypeShaper() : width(0), height(0), left_bearing(0), right_bearing(0), top_bearing(0), bottom_bearing(0), top_border(0), left_border(0), pen_x(0), pen_y(0), error_code(0), cur_lineheight(0.0), cur_align(0), cur_string(0), cur_hjust(0.0), cur_vjust(0.0), cur_res(0.0), line_left_bear(), line_right_bear(), line_width(), line_id(), top(0), bottom(0), ascend(0), descend(0), max_width(0), indent(0), hanging(0), space_before(0), space_after(0) {}; ~FreetypeShaper() {}; static std::vector glyph_uc; static std::vector glyph_id; static std::vector string_id; static std::vector x_pos; static std::vector y_pos; static std::vector x_mid; long width; long height; long left_bearing; long right_bearing; long top_bearing; long bottom_bearing; long top_border; long left_border; long pen_x; long pen_y; int error_code; bool shape_string(const char* string, const char* fontfile, int index, double size, double res, double lineheight, int align, double hjust, double vjust, double width, double tracking, double ind, double hang, double before, double after); bool add_string(const char* string, const char* fontfile, int index, double size, double tracking); bool finish_string(); bool single_line_width(const char* string, const char* fontfile, int index, double size, double res, bool include_bearing, long& width); private: static UTF_UCS utf_converter; double cur_lineheight; int cur_align; unsigned int cur_string; double cur_hjust; double cur_vjust; double cur_res; static std::vector x_advance; static std::vector x_offset; static std::vector left_bear; static std::vector right_bear; static std::vector top_extend; static std::vector bottom_extend; static std::vector ascenders; static std::vector descenders; std::vector line_left_bear; std::vector line_right_bear; std::vector line_width; std::vector line_id; long top; long bottom; long ascend; long descend; long max_width; long indent; long hanging; long space_before; long space_after; void reset(); bool shape_glyphs(uint32_t* glyphs, int n_glyphs, FreetypeCache& cache, double tracking); inline bool glyph_is_linebreak(int id) { switch (id) { case 10: return true; case 11: return true; case 12: return true; case 13: return true; case 133: return true; case 8232: return true; case 8233: return true; } return false; } inline bool glyph_is_breaker(int id) { switch (id) { case 9: return true; case 32: return true; case 5760: return true; case 6158: return true; case 8192: return true; case 8193: return true; case 8194: return true; case 8195: return true; case 8196: return true; case 8197: return true; case 8198: return true; case 8200: return true; case 8201: return true; case 8202: return true; case 8203: return true; case 8204: return true; case 8205: return true; case 8287: return true; case 12288: return true; } return false; } }; systemfonts/src/font_local.cpp0000644000176200001440000000456014742302036016271 0ustar liggesusers#include "font_local.h" #include "FontDescriptor.h" #include "Rinternals.h" #include "caches.h" #include #include #include FontDescriptor *find_first_match(FontDescriptor *desc, ResultSet& font_list) { for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { if ((*desc) == (**it)) { FontDescriptor* font = new FontDescriptor(*it); return font; } } return NULL; } FontDescriptor *match_local_fonts(FontDescriptor *desc) { FontDescriptor *font = find_first_match(desc, get_local_font_list()); // if we didn't find anything, try again with postscriptName as family if (!font) { const char* tmp_psn = desc->postscriptName; desc->postscriptName = desc->family; desc->family = NULL; font = find_first_match(desc, get_local_font_list()); desc->family = desc->postscriptName; desc->postscriptName = tmp_psn; } // might be NULL but that is ok return font; } int add_local_fonts(cpp11::strings paths) { ResultSet& font_list = get_local_font_list(); std::set current_files; for (size_t i = 0; i < font_list.size(); ++i) { current_files.insert(std::string(font_list[i]->get_path())); } FT_Library library; FT_Face face; FT_Error error; error = FT_Init_FreeType(&library); if (error) { return 1; } for (R_xlen_t i = 0; i < paths.size(); ++i) { std::string path(paths[i]); if (current_files.find(path) != current_files.end()) { continue; } error = FT_New_Face(library, path.c_str(), 0, &face); if (error) { continue; } font_list.push_back(new FontDescriptor(face, path.c_str(), 0)); int n_fonts = face->num_faces; FT_Done_Face(face); for (int i = 1; i < n_fonts; ++i) { error = FT_New_Face(library, path.c_str(), i, &face); if (error) { continue; } font_list.push_back(new FontDescriptor(face, path.c_str(), i)); FT_Done_Face(face); } } FT_Done_FreeType(library); FontMap& font_map = get_font_map(); font_map.clear(); return 0; } void clear_local_fonts_c() { ResultSet& font_list = get_local_font_list(); font_list.clear(); FontMap& font_map = get_font_map(); font_map.clear(); } systemfonts/src/font_outlines.cpp0000644000176200001440000003015514742167723017054 0ustar liggesusers#include "font_outlines.h" #include "Rinternals.h" #include "caches.h" #include "cpp11/data_frame.hpp" #include "cpp11/doubles.hpp" #include "cpp11/integers.hpp" #include "cpp11/list.hpp" #include #include #include #include #include #include FT_OUTLINE_H #include using namespace cpp11::literals; struct Outline { cpp11::writable::integers glyph; cpp11::writable::integers contour; cpp11::writable::doubles x; cpp11::writable::doubles y; double last_x; double last_y; int current_glyph; int current_contour; double tolerance; cpp11::writable::data_frame to_df() { return { "glyph"_nm = glyph, "contour"_nm = contour, "x"_nm = x, "y"_nm = y }; } }; void recurse_conic(double x0, double y0, double x1, double y1, double x2, double y2, cpp11::writable::doubles& x, cpp11::writable::doubles& y, double tolerance) { double td2x = std::abs(x0 + x2 - x1 - x1); double td2y = std::abs(y0 + y2 - y1 - y1); double dist = td2x + td2y; if (dist * 2.0 <= tolerance) { x.push_back(x2 / 64.0); y.push_back(y2 / 64.0); return; } double x01 = (x0 + x1) * 0.5; double y01 = (y0 + y1) * 0.5; double x12 = (x1 + x2) * 0.5; double y12 = (y1 + y2) * 0.5; double x012 = (x01 + x12) * 0.5; double y012 = (y01 + y12) * 0.5; recurse_conic(x0, y0, x01, y01, x012, y012, x, y, tolerance); recurse_conic(x012, y012, x12, y12, x2, y2, x, y, tolerance); } void recurse_cubic(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, cpp11::writable::doubles& x, cpp11::writable::doubles& y, double tolerance) { double td2x = std::abs(x0 + x0 + x3 - x1 - x1 - x1); double td2y = std::abs(y0 + y0 + y3 - y1 - y1 - y1); double td3x = std::abs(x0 + x3 + x3 - x2 - x2 - x2); double td3y = std::abs(y0 + y3 + y3 - y2 - y2 - y2); double dist = td2x + td2y + td3x + td3y; if (dist <= tolerance) { x.push_back(x3 / 64.0); y.push_back(y3 / 64.0); return; } double x01 = (x0 + x1) * 0.5; double y01 = (y0 + y1) * 0.5; double x12 = (x1 + x2) * 0.5; double y12 = (y1 + y2) * 0.5; double x23 = (x2 + x3) * 0.5; double y23 = (y2 + y3) * 0.5; double x012 = (x01 + x12) * 0.5; double y012 = (y01 + y12) * 0.5; double x123 = (x12 + x23) * 0.5; double y123 = (y12 + y23) * 0.5; double x0123 = (x012 + x123) * 0.5; double y0123 = (y012 + y123) * 0.5; recurse_cubic(x0, y0, x01, y01, x012, y012, x0123, y0123, x, y, tolerance); recurse_cubic(x0123, y0123, x123, y123, x23, y23, x3, y3, x, y, tolerance); } static int move_func(const FT_Vector *to, void *user) { Outline *outlines = static_cast(user); outlines->current_contour++; outlines->last_x = to->x; outlines->last_y = to->y; return 0; } static int line_func(const FT_Vector *to, void *user) { Outline *outlines = static_cast(user); outlines->last_x = to->x; outlines->last_y = to->y; outlines->glyph.push_back(outlines->current_glyph); outlines->contour.push_back(outlines->current_contour); outlines->x.push_back(double(to->x) / 64.0); outlines->y.push_back(double(to->y) / 64.0); return 0; } static int conic_func(const FT_Vector *control, const FT_Vector *to, void *user) { Outline *outlines = static_cast(user); R_xlen_t last = outlines->x.size(); recurse_conic(outlines->last_x, outlines->last_y, control->x, control->y, to->x, to->y, outlines->x, outlines->y, outlines->tolerance); for (R_xlen_t i = last; i < outlines->x.size(); ++i) { outlines->glyph.push_back(outlines->current_glyph); outlines->contour.push_back(outlines->current_contour); } outlines->last_x = to->x; outlines->last_y = to->y; return 0; } static int cubic_func(const FT_Vector *controlOne, const FT_Vector *controlTwo, const FT_Vector *to, void *user) { Outline *outlines = static_cast(user); R_xlen_t last = outlines->x.size(); recurse_cubic(outlines->last_x, outlines->last_y, controlOne->x, controlOne->y, controlTwo->x, controlTwo->y, to->x, to->y, outlines->x, outlines->y, outlines->tolerance); for (R_xlen_t i = last; i < outlines->x.size(); ++i) { outlines->glyph.push_back(outlines->current_glyph); outlines->contour.push_back(outlines->current_contour); } outlines->last_x = to->x; outlines->last_y = to->y; return 0; } cpp11::writable::data_frame get_glyph_outlines(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, double tolerance, bool verbose) { Outline outlines; // We double the tolerance rather than halve it in the quadratic bezier // as that is the most common curve: 64*2 (the 64 takes care of scaling it to // glyph units) outlines.tolerance = tolerance * 128.0; FreetypeCache& cache = get_font_cache(); FT_Outline_Funcs callbacks; callbacks.move_to = move_func; callbacks.line_to = line_func; callbacks.conic_to = conic_func; callbacks.cubic_to = cubic_func; callbacks.delta = 0; callbacks.shift = 0; cpp11::writable::integers unscallable; for (R_xlen_t i = 0; i < glyph.size(); ++i) { if (!cache.load_font(std::string(path[i]).c_str(), index[i], size[i], 72.0)) { if (verbose) { cpp11::warning("Failed to load %s:%i with freetype error %i", std::string(path[i]).c_str(), index[i], cache.error_code); } continue; } if (!FT_IS_SCALABLE(cache.get_face())) { if (verbose) { cpp11::warning("%s:%i does not provide outlines", glyph[i], std::string(path[i]).c_str(), index[i], cache.error_code); } unscallable.push_back(i+1); continue; } if (!cache.load_glyph(glyph[i])) { if (verbose) { cpp11::warning("Failed to load glyph %i in %s:%i with freetype error %i", glyph[i], std::string(path[i]).c_str(), index[i], cache.error_code); } continue; } FT_GlyphSlot& slot = cache.get_face()->glyph; FT_Outline& outline = slot->outline; if (slot->format != FT_GLYPH_FORMAT_OUTLINE) { if (verbose) { cpp11::warning("Glyph %i in %s:%i does not provide an outline", glyph[i], std::string(path[i]).c_str(), index[i]); } unscallable.push_back(i+1); continue; } if (outline.n_contours <= 0 || outline.n_points <= 0) { continue; } outlines.current_glyph = i + 1; outlines.current_contour = 0; R_xlen_t last_size = outlines.glyph.size(); FT_Error error = FT_Outline_Decompose(&outline, &callbacks, &outlines); if (error) { if (verbose) { cpp11::warning("Couldn't extract outline from glyph %i in %s:%i with freetype error %i", glyph[i], std::string(path[i]).c_str(), index[i], error); } outlines.glyph.resize(last_size); outlines.contour.resize(last_size); outlines.x.resize(last_size); outlines.y.resize(last_size); unscallable.push_back(i+1); continue; } if (outlines.contour[outlines.contour.size() - 1] != outlines.contour[outlines.contour.size() - 2]) { // Terminal point is singular outlines.glyph.pop_back(); outlines.contour.pop_back(); outlines.x.pop_back(); outlines.y.pop_back(); } } cpp11::writable::data_frame outlines_df = outlines.to_df(); outlines_df.attr("missing") = unscallable; return outlines_df; } inline uint8_t demultiply(uint8_t a, uint8_t b) { if (a * b == 0) return 0; if (a > b) return 255; return (a * 255 + (b >> 1)) / b; } inline uint8_t multiply(uint8_t a, uint8_t b) { uint32_t t = a * b + 127; return ((t >> 8) + t) >> 8; } double set_font_size(FT_Face face, int size) { int best_match = 0; int diff = 1e6; int largest_size = 0; int largest_ind = -1; bool found_match = false; for (int i = 0; i < face->num_fixed_sizes; ++i) { if (face->available_sizes[i].size > largest_size) { largest_ind = i; } int ndiff = face->available_sizes[i].size - size; if (ndiff >= 0 && ndiff < diff) { best_match = i; diff = ndiff; found_match = true; } } if (!found_match && size >= largest_size) { best_match = largest_ind; } FT_Select_Size(face, best_match); return double(size) / double(face->size->metrics.height); } cpp11::writable::list get_glyph_bitmap(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::integers color, bool verbose) { cpp11::writable::list bitmaps; FreetypeCache& cache = get_font_cache(); for (R_xlen_t i = 0; i < glyph.size(); ++i) { if (!cache.load_font(std::string(path[i]).c_str(), index[i], size[i], res[i])) { if (verbose) { cpp11::warning("Failed to load %s:%i with freetype error %i", std::string(path[i]).c_str(), index[i], cache.error_code); } bitmaps.push_back(R_NilValue); continue; } double scaling = 72.0 / res[i]; if (!FT_IS_SCALABLE(cache.get_face())) { scaling *= set_font_size(cache.get_face(), size[i] * res[i] * 64.0 / 72.0); } if (!cache.load_glyph(glyph[i], FT_HAS_COLOR(cache.get_face()) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT)) { if (verbose) { cpp11::warning("Failed to load glyph %i in %s:%i with freetype error %i", glyph[i], std::string(path[i]).c_str(), index[i], cache.error_code); } bitmaps.push_back(R_NilValue); continue; } FT_GlyphSlot& slot = cache.get_face()->glyph; FT_Error error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); if (error != 0) { if (verbose) { cpp11::warning("Failed to render glyph %i in %s:%i with freetype error %i", glyph[i], std::string(path[i]).c_str(), index[i], error); } bitmaps.push_back(R_NilValue); continue; } int red = R_RED(color[i]); int green = R_GREEN(color[i]); int blue = R_BLUE(color[i]); int alpha = R_ALPHA(color[i]); FT_Bitmap& bitmap = slot->bitmap; if (bitmap.pixel_mode != FT_PIXEL_MODE_GRAY && bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) { if (verbose) { cpp11::warning("Unsupported pixel mode for glyph %i in %s:%i", glyph[i], std::string(path[i]).c_str(), index[i]); } bitmaps.push_back(R_NilValue); continue; } const unsigned char* buffer = bitmap.buffer; cpp11::writable::integers_matrix<> raster(bitmap.width, bitmap.rows); size_t offset = 0; for (size_t j = 0; j < bitmap.rows; ++j) { for (int k = 0; k < bitmap.pitch; ++k) { switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_GRAY: { if (buffer[offset + k] == 0) raster(k, j) = R_RGBA(0, 0, 0, 0); else raster(k, j) = R_RGBA(red, green, blue, multiply(alpha, buffer[offset + k])); break; }; case FT_PIXEL_MODE_BGRA: { size_t index = offset + k * 4; raster(k, j) = R_RGBA( demultiply(buffer[index + 2], buffer[index + 3]), demultiply(buffer[index + 1], buffer[index + 3]), demultiply(buffer[index + 0], buffer[index + 3]), buffer[index + 3] ); break; }; } } offset += bitmap.pitch; } double offset_top = slot->bitmap_top; if (!strcmp("Apple Color Emoji", cache.get_face()->family_name)) { offset_top -= bitmap.rows * 0.1; } SEXP dims = PROTECT(Rf_allocVector(INTSXP, 2)); INTEGER(dims)[0] = bitmap.rows; INTEGER(dims)[1] = bitmap.width; Rf_setAttrib(raster.data(), R_DimSymbol, dims); Rf_setAttrib(raster.data(), Rf_mkString("channels"), Rf_ScalarInteger(4)); Rf_classgets(raster.data(), Rf_mkString("nativeRaster")); SEXP raster_offset = PROTECT(Rf_allocVector(REALSXP, 2)); REAL(raster_offset)[0] = offset_top * scaling; REAL(raster_offset)[1] = double(slot->bitmap_left) * scaling; Rf_setAttrib(raster.data(), Rf_mkString("offset"), raster_offset); SEXP raster_size = PROTECT(Rf_allocVector(REALSXP, 2)); REAL(raster_size)[0] = double(bitmap.rows) * scaling; REAL(raster_size)[1] = double(bitmap.width) * scaling; Rf_setAttrib(raster.data(), Rf_mkString("size"), raster_size); bitmaps.push_back(raster); UNPROTECT(3); } return bitmaps; } systemfonts/src/string_shape.cpp0000644000176200001440000002644414672302530016645 0ustar liggesusers#include #include #include #include "string_shape.h" #include "types.h" #include "caches.h" UTF_UCS FreetypeShaper::utf_converter = UTF_UCS(); std::vector FreetypeShaper::glyph_uc = {}; std::vector FreetypeShaper::glyph_id = {}; std::vector FreetypeShaper::string_id = {}; std::vector FreetypeShaper::x_pos = {}; std::vector FreetypeShaper::y_pos = {}; std::vector FreetypeShaper::x_mid = {}; std::vector FreetypeShaper::x_advance = {}; std::vector FreetypeShaper::x_offset = {}; std::vector FreetypeShaper::left_bear = {}; std::vector FreetypeShaper::right_bear = {}; std::vector FreetypeShaper::top_extend = {}; std::vector FreetypeShaper::bottom_extend = {}; std::vector FreetypeShaper::ascenders = {}; std::vector FreetypeShaper::descenders = {}; bool FreetypeShaper::shape_string(const char* string, const char* fontfile, int index, double size, double res, double lineheight, int align, double hjust, double vjust, double width, double tracking, double ind, double hang, double before, double after) { reset(); FreetypeCache& cache = get_font_cache(); bool success = cache.load_font(fontfile, index, size, res); if (!success) { error_code = cache.error_code; return false; } int n_glyphs = 0; uint32_t* glyphs = utf_converter.convert(string, n_glyphs); if (n_glyphs == 0) return true; max_width = width; indent = ind; pen_x = indent; hanging = hang; space_before = before; space_after = after; glyph_uc.reserve(n_glyphs); glyph_id.reserve(n_glyphs); string_id.reserve(n_glyphs); x_pos.reserve(n_glyphs); y_pos.reserve(n_glyphs); cur_res = res; cur_lineheight = lineheight; cur_align = align; cur_hjust = hjust; cur_vjust = vjust; ascend = cache.cur_ascender(); descend = cache.cur_descender(); success = shape_glyphs(glyphs, n_glyphs, cache, tracking); return success; } bool FreetypeShaper::add_string(const char* string, const char* fontfile, int index, double size, double tracking) { cur_string++; int n_glyphs = 0; uint32_t* glyphs = utf_converter.convert(string, n_glyphs); if (n_glyphs == 0) return true; FreetypeCache& cache = get_font_cache(); bool success = cache.load_font(fontfile, index, size, cur_res); if (!success) { error_code = cache.error_code; return false; } ascend = cache.cur_ascender(); descend = cache.cur_descender(); success = shape_glyphs(glyphs, n_glyphs, cache, tracking); return success; } bool FreetypeShaper::finish_string() { if (glyph_id.size() == 0) { return true; } bool first_char = true; bool first_line = true; pen_x += indent; int last_space = -1; long last_nonspace_width = 0; long last_nonspace_bear = 0; int cur_line = 0; double line_height = 0; size_t glyph_counter = 0; long max_descend = 0; long max_ascend = 0; long max_top_extend = 0; long max_bottom_extend = 0; long last_max_descend = 0; bool no_break_last = true; for (unsigned int i = 0; i < glyph_id.size(); ++i) { bool linebreak = glyph_is_linebreak(glyph_uc[i]); bool may_break = glyph_is_breaker(glyph_uc[i]); bool last = i == glyph_id.size() - 1; bool soft_wrap = false; if (may_break || linebreak) { last_space = i; if (no_break_last) { last_nonspace_width = pen_x; last_nonspace_bear = i == 0 ? 0 : right_bear[i - 1]; } } no_break_last = !may_break; // Apply kerning if not the first glyph on the line if (!first_char) { pen_x += x_offset[i]; } // Calculate top and bottom extend and ascender/descender // Soft wrapping? if (max_width > 0 && !first_char && pen_x + x_advance[i] > max_width && !may_break && !linebreak) { // Rewind to last breaking char and set the soft_wrap flag i = last_space >= 0 ? last_space : i - 1; x_pos.resize(i + 1); x_mid.resize(i + 1); line_id.resize(i + 1); soft_wrap = true; last = false; } else { // No soft wrap, record pen position x_pos.push_back(pen_x); x_mid.push_back(x_advance[i] / 2); line_id.push_back(cur_line); } // If last char update terminal line info if (last) { last_nonspace_width = pen_x + x_advance[i]; last_nonspace_bear = right_bear[i]; } if (first_char) { line_left_bear.push_back(left_bear[i]); pen_y -= space_before; } // Handle new lines if (linebreak || soft_wrap || last) { // Record and reset line dim info line_right_bear.push_back(last_nonspace_bear); line_width.push_back(last_nonspace_width); last_nonspace_bear = 0; last_nonspace_width = 0; last_space = -1; no_break_last = true; // Calculate line dimensions for (size_t j = glyph_counter; j < x_pos.size(); ++j) { if (max_ascend < ascenders[j]) { max_ascend = ascenders[j]; } if (max_top_extend < top_extend[j]) { max_top_extend = top_extend[j]; } if (max_descend > descenders[j]) { max_descend = descenders[j]; } if (max_bottom_extend > bottom_extend[j]) { max_bottom_extend = bottom_extend[j]; } } // Move pen based on indent and line height line_height = (max_ascend - last_max_descend) * cur_lineheight; if (last) { pen_x = (linebreak || soft_wrap) ? 0 : pen_x + x_advance[i]; } else { pen_x = soft_wrap ? hanging : indent; } pen_y = first_line ? 0 : pen_y - line_height; bottom -= line_height; // Fill up y_pos based on calculated pen position for (; glyph_counter < x_pos.size(); ++glyph_counter) { y_pos.push_back(pen_y); } // Move pen_y further down based on paragraph spacing // TODO: Add per string paragraph spacing if (linebreak) { pen_y -= space_after; if (last) { pen_y -= line_height; bottom -= line_height; } } if (first_line) { top_border = max_ascend; top_bearing = top_border - max_top_extend; } // Reset flags and counters last_max_descend = max_descend; if (!last) { max_ascend = 0; max_descend = 0; max_top_extend = 0; max_bottom_extend = 0; first_line = false; cur_line++; first_char = true; } } else { // No line break - advance the pen pen_x += x_advance[i]; first_char = false; } } height = top_border - bottom - max_descend; bottom_bearing = max_bottom_extend - max_descend; int max_width_ind = std::max_element(line_width.begin(), line_width.end()) - line_width.begin(); width = max_width < 0 ? line_width[max_width_ind] : max_width; if (cur_align != 0) { for (unsigned int i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; int lwd = line_width[index]; x_pos[i] = cur_align == 1 ? x_pos[i] + width/2 - lwd/2 : x_pos[i] + width - lwd; } } double width_diff = width - line_width[max_width_ind]; if (cur_align == 1) { width_diff /= 2; } left_bearing = cur_align == 0 ? *std::min_element(line_left_bear.begin(), line_left_bear.end()) : line_left_bear[max_width_ind] + width_diff; right_bearing = cur_align == 2 ? *std::min_element(line_right_bear.begin(), line_right_bear.end()) : line_right_bear[max_width_ind] + width_diff; if (cur_hjust != 0.0) { left_border = - cur_hjust * width; pen_x += left_border; for (unsigned int i = 0; i < x_pos.size(); ++i) { x_pos[i] += left_border; } } if (cur_vjust != 0.0) { long just_height = top_border - pen_y; for (unsigned int i = 0; i < x_pos.size(); ++i) { y_pos[i] += - pen_y - cur_vjust * just_height; } top_border += - pen_y - cur_vjust * just_height; pen_y += - pen_y - cur_vjust * just_height; } return true; } bool FreetypeShaper::single_line_width(const char* string, const char* fontfile, int index, double size, double res, bool include_bearing, long& width) { long x = 0; long y = 0; long left_bear = 0; int error_c = 0; GlyphInfo metrics = {}; int n_glyphs = 0; uint32_t* glyphs = utf_converter.convert(string, n_glyphs); if (n_glyphs == 0) { width = x; return true; } FreetypeCache& cache = get_font_cache(); bool success = cache.load_font(fontfile, index, size, res); if (!success) { error_code = cache.error_code; return false; } for (int i = 0; i < n_glyphs; ++i) { metrics = cache.cached_glyph_info(glyphs[i], error_c); if (error_c != 0) { error_code = error_c; return false; } if (i != 0) { success = cache.apply_kerning(glyphs[i - 1], glyphs[i], x, y); if (!success) { error_code = cache.error_code; return false; } } else { left_bear = metrics.x_bearing; } x += metrics.x_advance; } if (!include_bearing) { x -= left_bear; x -= metrics.x_advance - metrics.bbox[1]; } width = x; return true; } void FreetypeShaper::reset() { glyph_uc.clear(); glyph_id.clear(); string_id.clear(); x_pos.clear(); y_pos.clear(); x_mid.clear(); x_advance.clear(); x_offset.clear(); left_bear.clear(); right_bear.clear(); top_extend.clear(); bottom_extend.clear(); line_left_bear.clear(); line_right_bear.clear(); line_width.clear(); line_id.clear(); ascenders.clear(); descenders.clear(); pen_x = 0; pen_y = 0; top = 0; bottom = 0; ascend = 0; descend = 0; left_bearing = 0; right_bearing = 0; width = 0; height = 0; top_border = 0; left_border = 0; cur_string = 0; } bool FreetypeShaper::shape_glyphs(uint32_t* glyphs, int n_glyphs, FreetypeCache& cache, double tracking) { if (n_glyphs == 0) return true; int error_c = 0; bool success = false; GlyphInfo old_metrics = cache.cached_glyph_info(glyphs[0], error_c); if (error_c != 0) { error_code = error_c; return false; } GlyphInfo metrics = old_metrics; tracking = cache.tracking_diff(tracking); long delta_x = 0; long delta_y = 0; for (int i = 0; i < n_glyphs; ++i) { x_advance.push_back(metrics.x_advance + tracking); left_bear.push_back(metrics.bbox[0]); right_bear.push_back(old_metrics.x_advance - old_metrics.bbox[1]); top_extend.push_back(metrics.bbox[3]); bottom_extend.push_back(metrics.bbox[2]); ascenders.push_back(ascend); descenders.push_back(descend); if (i == 0) { x_offset.push_back(0); } else { success = cache.get_kerning(glyphs[i - 1], glyphs[i], delta_x, delta_y); if (!success) { error_code = cache.error_code; return false; } x_offset.push_back(delta_x); } glyph_uc.push_back(glyphs[i]); glyph_id.push_back(metrics.index); string_id.push_back(cur_string); if (i != n_glyphs - 1) { old_metrics = metrics; metrics = cache.cached_glyph_info(glyphs[i + 1], error_c); if (error_c != 0) { error_code = error_c; return false; } } } return true; } systemfonts/src/font_matching.cpp0000755000176200001440000001770214742166114017003 0ustar liggesusers#include "font_matching.h" #include "types.h" #include "caches.h" #include "utils.h" #include "FontDescriptor.h" #include "font_registry.h" #include "font_local.h" #include #include #include #include #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using namespace cpp11::literals; // these functions are implemented by the platform ResultSet *getAvailableFonts(); ResultSet *findFonts(FontDescriptor *); FontDescriptor *findFont(FontDescriptor *); void resetFontCache(); int locate_systemfont(const char *family, int italic, int weight, int width, char *path, int max_path_length) { const char* resolved_family = family; if (strcmp_no_case(family, "") || strcmp_no_case(family, "sans")) { resolved_family = SANS; } else if (strcmp_no_case(family, "serif")) { resolved_family = SERIF; } else if (strcmp_no_case(family, "mono")) { resolved_family = MONO; } else if (strcmp_no_case(family, "emoji")) { resolved_family = EMOJI; } else if (strcmp_no_case(family, "symbol")) { resolved_family = SYMBOL; } FontMap& font_map = get_font_map(); static FontKey key; key.family.assign(resolved_family); key.weight = weight; key.width = width; key.italic = italic; FontMap::iterator font_it = font_map.find(key); if (font_it != font_map.end()) { strncpy(path, font_it->second.file.c_str(), max_path_length); path[max_path_length] = '\0'; return font_it->second.index; } FontDescriptor font_desc(resolved_family, italic, FontWeight(weight), FontWidth(width)); std::unique_ptr font_loc(match_local_fonts(&font_desc)); if (!font_loc) { font_loc = std::unique_ptr(findFont(&font_desc)); } int index = 0; if (!font_loc) { list_t fallback = cpp11::as_cpp(cpp11::package("systemfonts")["get_fallback"]()); strncpy(path, CHAR(STRING_ELT(fallback[0], 0)), max_path_length); index = INTEGER(fallback[1])[0]; } else { strncpy(path, font_loc->path, max_path_length); index = font_loc->index; } path[max_path_length] = '\0'; font_map[key] = {std::string(path), (unsigned int) index}; return index; } int locate_font(const char *family, int italic, int bold, char *path, int max_path_length) { BEGIN_CPP FontSettings registry_match; if (locate_in_registry(family, italic, bold, registry_match)) { strncpy(path, registry_match.file, max_path_length); return registry_match.index; } return locate_systemfont(family, italic, bold ? FontWeightBold : FontWeightNormal, FontWidthUndefined, path, max_path_length); END_CPP return 0; } FontSettings locate_font_with_features(const char *family, int italic, int bold) { FontSettings registry_match = {}; registry_match.features = NULL; registry_match.n_features = 0; BEGIN_CPP if (locate_in_registry(family, italic, bold, registry_match)) { return registry_match; } registry_match.index = locate_systemfont(family, italic, bold ? FontWeightBold : FontWeightNormal, FontWidthUndefined, registry_match.file, PATH_MAX); END_CPP registry_match.file[PATH_MAX] = '\0'; return registry_match; } list_t match_font_c(strings_t family, logicals_t italic, logicals_t bold) { FontSettings loc = locate_font_with_features( Rf_translateCharUTF8(family[0]), italic[0], bold[0] ); integers_w feat(loc.n_features); if (loc.n_features == 0) { return list_w({ "path"_nm = cpp11::r_string(loc.file), "index"_nm = loc.index, "features"_nm = feat }); } strings_w tag(loc.n_features); for (int i = 0; i < loc.n_features; ++i) { feat[i] = loc.features[i].setting; tag[i] = cpp11::r_string({ loc.features[i].feature[0], loc.features[i].feature[1], loc.features[i].feature[2], loc.features[i].feature[3] }); } feat.names() = tag; return list_w({ "path"_nm = cpp11::r_string(loc.file), "index"_nm = loc.index, "features"_nm = feat }); } data_frame_w locate_fonts_c(strings_t family, logicals_t italic, integers_t weight, integers_t width) { strings_w paths; integers_w indices; list_w features; char file[PATH_MAX + 1]; for (R_xlen_t i = 0; i < family.size(); ++i) { if ((width[i] == FontWidthUndefined || width[i] == FontWidthNormal) && (weight[i] == FontWeightNormal || weight[i] == FontWeightBold || weight[i] == FontWeightUndefined)) { list_t match = match_font_c( strings_t(Rf_ScalarString(family[i])), logicals_t(Rf_ScalarLogical(italic[i])), logicals_t(Rf_ScalarLogical(weight[i] != FontWeightNormal)) ); paths.push_back(cpp11::as_cpp(match[0])[0]); indices.push_back(cpp11::as_cpp(match[1])[0]); integers_t feat = cpp11::as_cpp(match[2]); list_w f_feat({feat.names(), feat}); f_feat.attr("class") = {"font_feature"}; features.push_back(f_feat); } else { int index = locate_systemfont(Rf_translateCharUTF8(family[i]), italic[i], weight[i], width[i], file, PATH_MAX); paths.push_back(file); indices.push_back(index); list_w f_feat({strings_w(), integers_w()}); f_feat.attr("class") = {"font_feature"}; features.push_back(f_feat); } } data_frame_w res({ "path"_nm = paths, "index"_nm = indices, "features"_nm = features }); res.attr("class") = {"tbl_df", "tbl", "data.frame"}; return res; } data_frame_w system_fonts_c() { int n = 0; std::unique_ptr all_fonts(getAvailableFonts()); all_fonts->insert(all_fonts->begin(), get_local_font_list().begin(), get_local_font_list().end()); n = all_fonts->n_fonts(); strings_w path(n); integers_w index(n); strings_w name(n); strings_w family(n); strings_w style(n); integers_w weight(n); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = { "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy" }; integers_w width(n); width.attr("class") = {"ordered", "factor"}; width.attr("levels") = { "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded" }; logicals_w italic(n); logicals_w monospace(n); int i = 0; for (ResultSet::iterator it = all_fonts->begin(); it != all_fonts->end(); it++) { path[i] = (*it)->get_path(); index[i] = (*it)->index; name[i] = (*it)->get_psname(); family[i] = (*it)->get_family(); style[i] = (*it)->get_style(); weight[i] = (*it)->get_weight(); if (weight[i] == 0) { weight[i] = NA_INTEGER; } width[i] = (*it)->get_width(); if (width[i] == 0) { width[i] = NA_INTEGER; } italic[i] = (Rboolean) (*it)->italic; monospace[i] = (Rboolean) (*it)->monospace; ++i; } // Remove local fonts so they don't get deleted at cleanup all_fonts->erase(all_fonts->begin(), all_fonts->begin() + get_local_font_list().size()); data_frame_w res({ "path"_nm = path, "index"_nm = index, "name"_nm = name, "family"_nm = family, "style"_nm = style, "weight"_nm = weight, "width"_nm = width, "italic"_nm = italic, "monospace"_nm = monospace }); res.attr("class") = {"tbl_df", "tbl", "data.frame"}; return res; } void reset_font_cache_c() { resetFontCache(); get_font_map().clear(); } void export_font_matching(DllInfo* dll) { R_RegisterCCallable("systemfonts", "locate_font", (DL_FUNC)locate_font); R_RegisterCCallable("systemfonts", "locate_font_with_features", (DL_FUNC)locate_font_with_features); } systemfonts/src/ft_cache.h0000644000176200001440000001126614741745051015362 0ustar liggesusers#pragma once #include #include #include #include #include #include #include #include #include FT_FREETYPE_H #include FT_TYPES_H #include FT_SIZES_H #include FT_TRUETYPE_TABLES_H #ifdef __EMSCRIPTEN__ #undef TYPEOF #endif #include "cache_lru.h" struct FaceID { std::string file; unsigned int index; inline FaceID() : file(""), index(0) {} inline FaceID(std::string f) : file(f), index(0) {} inline FaceID(std::string f, unsigned int i) : file(f), index(i) {} inline FaceID(const FaceID& face) : file(face.file), index(face.index) {} inline bool operator==(const FaceID &other) const { return (index == other.index && file == other.file); } }; struct SizeID { FaceID face; double size; double res; inline SizeID() : face(), size(-1.0), res(-1.0) {} inline SizeID(FaceID f, double s, double r) : face(f), size(s), res(r) {} inline SizeID(const SizeID& s) : face(s.face), size(s.size), res(s.res) {} inline bool operator==(const SizeID &other) const { return (size == other.size && res == other.res && face == other.face); } }; namespace std { template <> struct hash { size_t operator()(const FaceID & x) const { return std::hash()(x.file) ^ std::hash()(x.index); } }; template<> struct hash { size_t operator()(const SizeID & x) const { return std::hash()(x.face) ^ std::hash()(x.size) ^ std::hash()(x.res); } }; } struct FaceStore { FT_Face face; std::unordered_set sizes; FaceStore() : sizes() {}; FaceStore(FT_Face f) : face(f), sizes() {} }; struct FontInfo { std::string family; std::string style; bool is_italic; bool is_bold; bool is_monospace; bool is_vertical; bool has_kerning; bool has_color; bool is_scalable; int n_glyphs; int n_sizes; int n_charmaps; std::vector bbox; long max_ascend; long max_descend; long max_advance_h; long max_advance_w; long lineheight; long underline_pos; long underline_size; }; struct GlyphInfo { unsigned index; long x_bearing; long y_bearing; long width; long height; long x_advance; long y_advance; std::vector bbox; }; class FaceCache : public LRU_Cache { using typename LRU_Cache::key_value_t; using typename LRU_Cache::list_t; using typename LRU_Cache::cache_list_it_t; using typename LRU_Cache::map_t; using typename LRU_Cache::cache_map_it_t; public: FaceCache() : LRU_Cache() { } FaceCache(size_t max_size) : LRU_Cache(max_size) { } void add_size_id(FaceID fid, SizeID sid) { cache_map_it_t it = _cache_map.find(fid); if (it == _cache_map.end()) { return; } it->second->second.sizes.insert(sid); } private: inline virtual void value_dtor(FaceStore& value) { FT_Done_Face(value.face); } }; class SizeCache : public LRU_Cache { public: SizeCache() : LRU_Cache() { } SizeCache(size_t max_size) : LRU_Cache(max_size) { } private: inline virtual void value_dtor(FT_Size& value) { FT_Done_Size(value); } }; class FreetypeCache { public: FreetypeCache(); ~FreetypeCache(); bool load_font(const char* file, int index, double size, double res); bool load_font(const char* file, int index); FontInfo font_info(); bool has_glyph(uint32_t index); bool load_unicode(uint32_t index); bool load_glyph(FT_UInt index, int flags = FT_LOAD_DEFAULT); GlyphInfo glyph_info(); GlyphInfo cached_glyph_info(uint32_t index, int& error); double string_width(uint32_t* string, int length, bool add_kern); long cur_lineheight(); long cur_ascender(); long cur_descender(); bool get_kerning(uint32_t left, uint32_t right, long &x, long &y); bool apply_kerning(uint32_t left, uint32_t right, long &x, long &y); double tracking_diff(double tracking); FT_Face get_face(); int get_weight(); int get_width(); void get_family_name(char* family, int max_length); std::string cur_name(); int error_code; private: FT_Library library; std::map glyphstore; FaceCache face_cache; SizeCache size_cache; FaceID cur_id; double cur_size; double cur_res; bool cur_can_kern; unsigned int cur_glyph; bool cur_is_scalable; double unscaled_scaling; FT_Face face; FT_Size size; bool load_face(FaceID face); bool load_size(FaceID face, double size, double res); inline bool current_face(FaceID id, double size, double res) { return size == cur_size && res == cur_res && id == cur_id; }; }; systemfonts/src/dev_metrics.cpp0000644000176200001440000000555214672302530016460 0ustar liggesusers#include "dev_metrics.h" #include #include #include using doubles_t = cpp11::doubles; using doubles_w = cpp11::writable::doubles; using strings_t = cpp11::strings; using integers_t = cpp11::integers; using data_frame_w = cpp11::writable::data_frame; using namespace cpp11::literals; doubles_t dev_string_widths_c(strings_t string, strings_t family, integers_t face, doubles_t size, doubles_t cex, integers_t unit) { GEUnit u = GE_INCHES; switch (INTEGER(unit)[0]) { case 0: u = GE_CM; break; case 1: u = GE_INCHES; break; case 2: u = GE_DEVICE; break; case 3: u = GE_NDC; break; } pGEDevDesc dev = GEcurrentDevice(); R_GE_gcontext gc = {}; double width = 0; int n_total = string.size(); int scalar_family = family.size() == 1; int scalar_rest = face.size() == 1; strcpy(gc.fontfamily, Rf_translateCharUTF8(family[0])); gc.fontface = face[0]; gc.ps = size[0]; gc.cex = cex[0]; doubles_w res(n_total); for (int i = 0; i < n_total; ++i) { if (i > 0 && !scalar_family) { strcpy(gc.fontfamily, Rf_translateCharUTF8(family[i])); } if (i > 0 && !scalar_rest) { gc.fontface = face[i]; gc.ps = size[i]; gc.cex = cex[i]; } width = GEStrWidth( CHAR(string[i]), Rf_getCharCE(string[i]), &gc, dev ); res[i] = GEfromDeviceWidth(width, u, dev); } return res; } data_frame_w dev_string_metrics_c(strings_t string, strings_t family, integers_t face, doubles_t size, doubles_t cex, integers_t unit) { GEUnit u = GE_INCHES; switch (INTEGER(unit)[0]) { case 0: u = GE_CM; break; case 1: u = GE_INCHES; break; case 2: u = GE_DEVICE; break; case 3: u = GE_NDC; break; } pGEDevDesc dev = GEcurrentDevice(); R_GE_gcontext gc = {}; double width = 0, ascent = 0, descent = 0; int n_total = string.size(); int scalar_family = family.size() == 1; int scalar_rest = face.size() == 1; strcpy(gc.fontfamily, Rf_translateCharUTF8(family[0])); gc.fontface = face[0]; gc.ps = size[0]; gc.cex = cex[0]; doubles_w w(n_total); doubles_w a(n_total); doubles_w d(n_total); for (int i = 0; i < n_total; ++i) { if (i > 0 && !scalar_family) { strcpy(gc.fontfamily, Rf_translateCharUTF8(family[i])); } if (i > 0 && !scalar_rest) { gc.fontface = face[i]; gc.ps = size[i]; gc.cex = cex[i]; } GEStrMetric( CHAR(string[i]), Rf_getCharCE(string[i]), &gc, &ascent, &descent, &width, dev ); w[i] = GEfromDeviceWidth(width, u, dev); a[i] = GEfromDeviceWidth(ascent, u, dev); d[i] = GEfromDeviceWidth(descent, u, dev); } data_frame_w res({ "width"_nm = w, "ascent"_nm = a, "descent"_nm = d }); res.attr("class") = {"tbl_df", "tbl", "data.frame"}; return res; } systemfonts/src/font_fallback.h0000644000176200001440000000065014672302530016400 0ustar liggesusers#pragma once #include #include #include #include #include "types.h" [[cpp11::register]] cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string); FontSettings request_fallback(const char *string, const char *path, int index); [[cpp11::init]] void export_font_fallback(DllInfo* dll); systemfonts/src/font_registry.h0000644000176200001440000000075314672302530016515 0ustar liggesusers#pragma once #include "types.h" #include #include #include [[cpp11::register]] void register_font_c(cpp11::strings family, cpp11::strings paths, cpp11::integers indices, cpp11::strings features, cpp11::integers settings); [[cpp11::register]] void clear_registry_c(); [[cpp11::register]] cpp11::writable::data_frame registry_fonts_c(); bool locate_in_registry(const char *family, int italic, int bold, FontSettings& res); systemfonts/src/mac/0000755000176200001440000000000014743404476014214 5ustar liggesuserssystemfonts/src/mac/FontManagerMac.mm0000755000176200001440000002377714675503401017404 0ustar liggesusers#include #include #include #include #include "../FontDescriptor.h" // converts a CoreText weight (-1 to +1) to a standard weight (100 to 900) static int convertWeight(float weight) { if (weight <= -0.8f) return 100; else if (weight <= -0.6f) return 200; else if (weight <= -0.4f) return 300; else if (weight <= 0.0f) return 400; else if (weight <= 0.25f) return 500; else if (weight <= 0.35f) return 600; else if (weight <= 0.4f) return 700; else if (weight <= 0.6f) return 800; else return 900; } // converts a CoreText width (-1 to +1) to a standard width (1 to 9) static int convertWidth(float unit) { if (unit < 0) { return 1 + (1 + unit) * 4; } else { return 5 + unit * 4; } } void addFontIndex(FontDescriptor* font) { @autoreleasepool { static std::map font_index; std::string font_name(font->postscriptName); int font_no; std::map::iterator it = font_index.find(font_name); if (it == font_index.end()) { NSString *font_path = [NSString stringWithUTF8String:font->path]; NSURL *font_url = [NSURL fileURLWithPath: font_path]; CFArrayRef font_descriptors = CTFontManagerCreateFontDescriptorsFromURL((CFURLRef) font_url); if (font_descriptors == NULL) { font_no = 0; font_index[font_name] = 0; } else { int n_fonts = CFArrayGetCount(font_descriptors); if (n_fonts == 1) { font_no = 0; font_index[font_name] = 0; } else { for (int i = 0; i < n_fonts; i++) { CTFontDescriptorRef font_at_i = (CTFontDescriptorRef) CFArrayGetValueAtIndex(font_descriptors, i); std::string font_name_at_i = [(__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(font_at_i, kCTFontNameAttribute) UTF8String]; font_index[font_name_at_i] = i; if (font_name.compare(font_name_at_i) == 0) { font_no = i; } } } CFRelease(font_descriptors); } } else { font_no = (*it).second; } font->index = font_no; }} FontDescriptor *createFontDescriptor(CTFontDescriptorRef descriptor) { @autoreleasepool { NSURL *url = (__bridge_transfer NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); NSString *psName = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); NSString *family = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); NSString *style = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); NSDictionary *traits = (__bridge_transfer NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); NSNumber *weightVal = traits[(id)kCTFontWeightTrait]; FontWeight weight = (FontWeight) convertWeight([weightVal floatValue]); NSNumber *widthVal = traits[(id)kCTFontWidthTrait]; FontWidth width = (FontWidth) convertWidth([widthVal floatValue]); NSNumber *symbolicTraitsVal = traits[(id)kCTFontSymbolicTrait]; unsigned int symbolicTraits = [symbolicTraitsVal unsignedIntValue]; FontDescriptor *res = new FontDescriptor( [[url path] UTF8String], [psName UTF8String], [family UTF8String], [style UTF8String], weight, width, (symbolicTraits & kCTFontItalicTrait) != 0, (symbolicTraits & kCTFontMonoSpaceTrait) != 0 ); addFontIndex(res); return res; }} static CTFontCollectionRef collection = NULL; void resetFontCache() { if (collection != NULL) { CTFontCollectionRef temp = collection; collection = NULL; CFRelease(temp); } } ResultSet *getAvailableFonts() { @autoreleasepool { // cache font collection for fast use in future calls if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL); NSArray *matches = (__bridge_transfer NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); ResultSet *results = new ResultSet(); for (id m in matches) { CTFontDescriptorRef match = (__bridge CTFontDescriptorRef) m; results->push_back(createFontDescriptor(match)); } return results; }} // helper to square a value static inline int sqr(int value) { return value * value; } CTFontDescriptorRef getFontDescriptor(FontDescriptor *desc) { // build a dictionary of font attributes NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; CTFontSymbolicTraits symbolicTraits = 0; if (desc->postscriptName) { NSString *postscriptName = [NSString stringWithUTF8String:desc->postscriptName]; attrs[(id)kCTFontNameAttribute] = postscriptName; } if (desc->family) { NSString *family = [NSString stringWithUTF8String:desc->family]; attrs[(id)kCTFontFamilyNameAttribute] = family; } if (desc->style) { NSString *style = [NSString stringWithUTF8String:desc->style]; attrs[(id)kCTFontStyleNameAttribute] = style; } // build symbolic traits if (desc->italic) symbolicTraits |= kCTFontItalicTrait; if (desc->weight == FontWeightBold) symbolicTraits |= kCTFontBoldTrait; if (desc->monospace) symbolicTraits |= kCTFontMonoSpaceTrait; if (desc->width == FontWidthCondensed) symbolicTraits |= kCTFontCondensedTrait; if (desc->width == FontWidthExpanded) symbolicTraits |= kCTFontExpandedTrait; if (symbolicTraits) { NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolicTraits]}; attrs[(id)kCTFontTraitsAttribute] = traits; } // create a font descriptor and search for matches return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); } int metricForMatch(CTFontDescriptorRef match, FontDescriptor *desc) { @autoreleasepool { NSDictionary *dict = (__bridge_transfer NSDictionary *)CTFontDescriptorCopyAttribute(match, kCTFontTraitsAttribute); bool italic = ([dict[(id)kCTFontSymbolicTrait] unsignedIntValue] & kCTFontItalicTrait); // normalize everything to base-900 int metric = 0; if (desc->weight) metric += sqr(convertWeight([dict[(id)kCTFontWeightTrait] floatValue]) - desc->weight); if (desc->width) metric += sqr((convertWidth([dict[(id)kCTFontWidthTrait] floatValue]) - desc->width) * 100); metric += sqr((italic != desc->italic) * 900); return metric; }} ResultSet *findFonts(FontDescriptor *desc) { @autoreleasepool { CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); ResultSet *results = new ResultSet(); NSArray *sorted = [matches sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { int ma = metricForMatch((__bridge CTFontDescriptorRef) a, desc); int mb = metricForMatch((__bridge CTFontDescriptorRef) b, desc); return ma < mb ? NSOrderedAscending : ma > mb ? NSOrderedDescending : NSOrderedSame; }]; for (id m in sorted) { CTFontDescriptorRef match = (__bridge CTFontDescriptorRef) m; int mb = metricForMatch((__bridge CTFontDescriptorRef) m, desc); if (mb < 10000) { results->push_back(createFontDescriptor(match)); } } CFRelease(descriptor); return results; }} CTFontDescriptorRef findBest(FontDescriptor *desc, NSArray *matches) { // find the closest match for width and weight attributes CTFontDescriptorRef best = NULL; int bestMetric = INT_MAX; for (id m in matches) { int metric = metricForMatch((__bridge CTFontDescriptorRef) m, desc); if (metric < bestMetric) { bestMetric = metric; best = (__bridge CTFontDescriptorRef) m; } // break if this is an exact match if (metric == 0) break; } return best; } FontDescriptor *findFont(FontDescriptor *desc) { @autoreleasepool { FontDescriptor *res = NULL; CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); // if there was no match, try again but use family as postscriptName if ([matches count] == 0) { desc->postscriptName = desc->family; desc->family = NULL; CTFontDescriptorRef descriptor_ps = getFontDescriptor(desc); matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor_ps, NULL); desc->family = desc->postscriptName; desc->postscriptName = NULL; } // if there was no match, try again but only try to match traits if ([matches count] == 0) { NSSet *set = [NSSet setWithObjects:(id)kCTFontTraitsAttribute, nil]; matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, (CFSetRef) set); } // find the closest match for width and weight attributes CTFontDescriptorRef best = findBest(desc, matches); // if we found a match, generate and return a URL for it if (best) { res = createFontDescriptor(best); } CFRelease(descriptor); return res; }} FontDescriptor *substituteFont(char *postscriptName, char *string) { @autoreleasepool { FontDescriptor *res = NULL; // create a font descriptor to find the font by its postscript name // we don't use CTFontCreateWithName because that supports font // names other than the postscript name but prints warnings. NSString *ps = [NSString stringWithUTF8String:postscriptName]; NSDictionary *attrs = @{(id)kCTFontNameAttribute: ps}; CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 12.0, NULL); // find a substitute font that support the given characters NSString *str = [NSString stringWithUTF8String:string]; CTFontRef substituteFont = CTFontCreateForString(font, (CFStringRef) str, CFRangeMake(0, [str length])); CTFontDescriptorRef substituteDescriptor = CTFontCopyFontDescriptor(substituteFont); // finally, create and return a result object for this substitute font res = createFontDescriptor(substituteDescriptor); CFRelease(descriptor); CFRelease(font); CFRelease(substituteFont); CFRelease(substituteDescriptor); return res; }} systemfonts/src/font_outlines.h0000644000176200001440000000102114742131016016471 0ustar liggesusers#pragma once #include #include #include #include [[cpp11::register]] cpp11::writable::data_frame get_glyph_outlines(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, double tolerance, bool verbose); [[cpp11::register]] cpp11::writable::list get_glyph_bitmap(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::integers color, bool verbose); systemfonts/NAMESPACE0000644000176200001440000000163614742302351014076 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(c,font_feature) S3method(format,font_feature) S3method(length,font_feature) S3method(print,font_feature) export(add_fonts) export(as_font_weight) export(as_font_width) export(clear_local_fonts) export(clear_registry) export(font_fallback) export(font_feature) export(font_info) export(get_fallback) export(get_from_font_squirrel) export(get_from_google_fonts) export(glyph_info) export(glyph_outline) export(glyph_raster) export(glyph_raster_grob) export(match_font) export(match_fonts) export(register_font) export(register_variant) export(registry_fonts) export(require_font) export(reset_font_cache) export(scan_local_fonts) export(search_web_fonts) export(shape_string) export(str_split_emoji) export(string_metrics_dev) export(string_width) export(string_widths_dev) export(system_fonts) importFrom(lifecycle,deprecated) useDynLib(systemfonts, .registration = TRUE) systemfonts/LICENSE0000644000176200001440000000006114672302530013654 0ustar liggesusersYEAR: 2019 COPYRIGHT HOLDER: Thomas Lin Pedersen systemfonts/NEWS.md0000644000176200001440000001134314743404163013756 0ustar liggesusers# systemfonts 1.2.1 * Fix a memory issue when adding new fonts with `add_fonts()` * Default to not downloading woff2 files from Google Fonts since it is poorly supported on many systems * Fixed a bug in `get_from_font_squirrel()` where the font wasn't placed in the user specified location # systemfonts 1.2.0 * Providing the font name as the family should now result in better matching * Improved the fallback options for Windows so that as many scripts are now covered * Add infrastructure to add uninstalled font files to the search path used for font matching * Add facilities to download and register fonts from web repositories such as Google Fonts and Font Squirrel * Add `require_font()` that does it's best to ensure that a given font is available after being called. * Added functions for extracting outline and raster representation of glyphs # systemfonts 1.1.0 * `match_fonts()` have been added as a vectorized and generalized version of `match_font()`. In the process `match_font()` has been deprecated in favour of `match_fonts()` * Two internal functions for converting weight and width names to integers have been exported * Fix a segfault on macOS when the system encounters a corrupted font collection (#113) # systemfonts 1.0.6 * Fix a bug in `shape_string()` using `vjust = 1` (#85) # systemfonts 1.0.4 * Use Courier New as default mono font on macOS instead of Courier to avoid issues between FreeType and Courier (#105) # systemfonts 1.0.4 * Provide a fallback solution to the setup of the CRAN windows builder so that fonts can be discovered (#87) # systemfonts 1.0.3 * Avoid warning when including the systemfonts header (#77) * Fix size selection of non-scalable fonts when the requested size is bigger than the available * Fix compilation bug when systemfont is used in C packages (#76) # systemfonts 1.0.2 * Ensure compitability with freetype <= 2.4.11 (#70, @jan-glx) * Prepare for UCRT compilation # systemfonts 1.0.1 * Fix a bug in font matching on Windows when matching monospace fonts * Fix a bug in `reset_font_cache()` on mac that would cause a system crash if the cache was not filled in advance (#67) # systemfonts 1.0.0 * Tweak size determination for non-scalable fonts * Fix bug when switching between scalable and non-scalable fonts in the cache * Add utility for querying font fallbacks at both the R and C level * Add C-level API for finding emoji embeddings in strings * Add utility for getting weight of font from C code * Add utility for getting family name of font from C code * Add font weight and width to the output of `font_info()` # systemfonts 0.3.2 * Fix compiled code for old R versions * Changes to comply with next cpp11 version # systemfonts 0.3.1 * Fixed warnings on CRAN LTO machine # systemfonts 0.3.0 * Added `get_cached_face()` so that other packages might retrieve FT_Face objects from the cache. * Adapted cpp11 * Add infrastructure for setting OpenType font features on a registered font with either `register_font()` or the new `register_variant()`, along with the `font_feature()` function. # systemfonts 0.2.3 * Replace the buggy Freetype cache subsystem with own implementation * Fix indexing bug in `glyph_metrics()` # systemfonts 0.2.2 * Fix remaining valgrind issues by fixing the included font-manager code * Rewrite the text shaping algorithm to make it more future proof * Work around a nasty freetype bug in their cache subsystem # systemfonts 0.2.1 * Various fixes to the correctness of compiled code # systemfonts 0.2.0 * Add `string_widths_dev()` and `string_metrics_dev()` to request the current graphic device for string widths and metrics. * Add system for registering non-system fonts for look-up. * systemfonts will now detect user-installed fonts on Windows (possible after the 1806 update) * Font lookup is now cached for faster performance. The caching will get flushed when new fonts are added to the registry, or manually with `reset_font_cache()` * Systemfonts now provide querying of font information with `font_info()` and `glyph_info()` * Basic string shaping is now provided with `shape_string()` * Line width calculation is now available with `string_width()` (ignores presence of newlines, use `shape_string()` for more complicated strings) * Added `str_split_emoji()` for splitting of strings into substrings of emoji and non-emoji glyphs * Provide a header file for easy use from within C in other packages * Fix memory management issues on Mac * Fix handling of erroneous font files on windows # systemfonts 0.1.1 * Fix compilation on systems with a very old fontconfig version (Solaris) # systemfonts 0.1.0 * First version with `match_font()` and `system_fonts()` capabilities. More to come. * Added a `NEWS.md` file to track changes to the package. systemfonts/inst/0000755000176200001440000000000014743404476013642 5ustar liggesuserssystemfonts/inst/include/0000755000176200001440000000000014741276717015270 5ustar liggesuserssystemfonts/inst/include/systemfonts-ft.h0000644000176200001440000000152614741276717020452 0ustar liggesusers#ifndef SYSTEMFONTS_FT_H #define SYSTEMFONTS_FT_H #ifndef R_NO_REMAP #define R_NO_REMAP #endif #include #include FT_FREETYPE_H #include #include // Retrieve an FT_Face from the cache and assigns it to the face pointer. The // retrieved face should be destroyed with FT_Done_Face once no longer needed. // Returns 0 if successful. static inline FT_Face get_cached_face(const char* fontfile, int index, double size, double res, int* error) { static FT_Face (*p_get_cached_face)(const char*, int, double, double, int*) = NULL; if (p_get_cached_face == NULL) { p_get_cached_face = (FT_Face (*)(const char*, int, double, double, int*)) R_GetCCallable("systemfonts", "get_cached_face"); } return p_get_cached_face(fontfile, index, size, res, error); } #endif systemfonts/inst/include/systemfonts.h0000644000176200001440000001320414741276717020037 0ustar liggesusers#pragma once #ifndef R_NO_REMAP #define R_NO_REMAP #endif #include #include #include #include #include struct FontFeature { char feature[4]; int setting; }; typedef struct FontFeature FontFeature; // A structure to pass around a single font with features (used by the C interface) struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; typedef struct FontSettings FontSettings; // Get the file and index of a font given by its name, along with italic and // bold status. Writes filepath to `path` and returns the index static inline int locate_font(const char *family, int italic, int bold, char *path, int max_path_length) { static int (*p_locate_font)(const char*, int, int, char*, int) = NULL; if (p_locate_font == NULL) { p_locate_font = (int (*)(const char *, int, int, char *, int)) R_GetCCallable("systemfonts", "locate_font"); } return p_locate_font(family, italic, bold, path, max_path_length); } // Get the file and index of a font along with possible registered OpenType // features, returned as a FontSettings object. static inline FontSettings locate_font_with_features(const char *family, int italic, int bold) { static FontSettings (*p_locate_font_with_features)(const char*, int, int) = NULL; if (p_locate_font_with_features == NULL) { p_locate_font_with_features = (FontSettings (*)(const char *, int, int)) R_GetCCallable("systemfonts", "locate_font_with_features"); } return p_locate_font_with_features(family, italic, bold); } // Get ascent, descent, and width of a glyph, given by its unicode number, // fontfile and index, along with its size and the resolution. Returns 0 if // successful static inline int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width) { static int (*p_glyph_metrics)(uint32_t, const char*, int, double, double, double*, double*, double*) = NULL; if (p_glyph_metrics == NULL) { p_glyph_metrics = (int (*)(uint32_t, const char*, int, double, double, double*, double*, double*)) R_GetCCallable("systemfonts", "glyph_metrics"); } return p_glyph_metrics(code, fontfile, index, size, res, ascent, descent, width); } // Calculate the width of a string based on a fontfile, index, size, and // resolution. Writes it to width, and returns 0 if successful static inline int string_width(const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width) { static int (*p_string_width)(const char*, const char*, int, double, double, int, double*) = NULL; if (p_string_width == NULL) { p_string_width = (int (*)(const char*, const char*, int, double, double, int, double*)) R_GetCCallable("systemfonts", "string_width"); } return p_string_width(string, fontfile, index, size, res, include_bearing, width); } // Calculate glyph positions for a string based on a fontfile, index, size, and // resolution, and writes it to the x and y arrays. Returns 0 if successful. static inline int string_shape(const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length) { static int (*p_string_shape)(const char*, const char*, int, double, double, double*, double*, unsigned int) = NULL; if (p_string_shape == NULL) { p_string_shape = (int (*)(const char*, const char*, int, double, double, double*, double*, unsigned int)) R_GetCCallable("systemfonts", "string_shape"); } return p_string_shape(string, fontfile, index, size, res, x, y, max_length); } // Get the file and index of a fallback font for the given string based on the // given font and index static inline FontSettings get_fallback(const char *string, const char *path, int index) { static FontSettings (*p_get_fallback)(const char*, const char*, int) = NULL; if (p_get_fallback == NULL) { p_get_fallback = (FontSettings (*)(const char*, const char*, int)) R_GetCCallable("systemfonts", "get_fallback"); } return p_get_fallback(string, path, index); } // Get the weight of the font as encoded in the OTT/2 table static inline int get_font_weight(const char *path, int index) { static int (*p_get_weight)(const char*, int) = NULL; if (p_get_weight == NULL) { p_get_weight = (int (*)(const char*, int)) R_GetCCallable("systemfonts", "font_weight"); } return p_get_weight(path, index); } // Get the family name of the font as encoded in the font file. The name is // written to the family argument, not exceeding `max_length` static inline int get_font_family(const char *path, int index, char* family, int max_length) { static int (*p_get_family)(const char*, int, char*, int) = NULL; if (p_get_family == NULL) { p_get_family = (int (*)(const char*, int, char*, int)) R_GetCCallable("systemfonts", "font_family"); } return p_get_family(path, index, family, max_length); } // Get the location of emojis written to the embedding array. A 0 indicate that // the codepoint is not to be treated as emoji, a 1 indicate that it should, static inline void detect_emoji_embedding(const uint32_t* string, int n, int* embedding, const char *path, int index) { static void (*p_detect_emoji_embedding)(const uint32_t*, int, int*, const char*, int) = NULL; if (p_detect_emoji_embedding == NULL) { p_detect_emoji_embedding = (void (*)(const uint32_t*, int, int*, const char*, int)) R_GetCCallable("systemfonts", "detect_emoji_embedding"); } p_detect_emoji_embedding(string, n, embedding, path, index); } systemfonts/inst/doc/0000755000176200001440000000000014743404476014407 5ustar liggesuserssystemfonts/inst/doc/c_interface.R0000644000176200001440000000036614743404475017000 0ustar liggesusers## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----setup-------------------------------------------------------------------- library(systemfonts) systemfonts/inst/doc/c_interface.html0000644000176200001440000006474314743404476017555 0ustar liggesusers systemfonts C interface

systemfonts C interface

library(systemfonts)

Most of the functionality in systemfonts is intended to be used from compiled code to help e.g. graphic devices to resolve font specifications to a font file prior to rendering. systemfonts provide key functionality to get called at the C level by putting systemfonts in the LinkingTo field in the description and adding #include <systemfonts.h> to your C code. Make sure systemfonts is loaded before using it, e.g. by having match_font() imported into your package namespace. The different functionality will be discussed below

Font matching

The C equivalent of the match_font() R function is locate_font() with the following signature:

int locate_font(
  const char *family, 
  int italic, 
  int bold, 
  char *path, 
  int max_path_length
)

It takes a UTF-8 encoded string with the font family name, an int setting both italic and bold styles along with a char pointer to be filled with the located path and the maximum length it can hold. The return value is an int giving the index of the font in the font file.

With the advent of systemfonts 0.3.0 fonts can now also have OpenType features attached to them through the use of register_font() or register_variant(). If you wish to support such features you can use an alternative to the above:

FontSettings locate_font_with_features(
  const char *family, 
  int italic, 
  int bold
)

The returned FontSettings struct will contain both the font location and index along with any OpenType feature settings. The struct (along with its FontFeature struct dependency) is shown below and is pretty self-documenting.

Do not cache the FontSettings struct as the features array may be cleared at any time after the call has ended. systemfonts itself takes care of caching so this is not something you should be concerned with in your code.

struct FontFeature {
  char feature[4];
  int setting;
};
struct FontSettings {
  char file[PATH_MAX + 1];
  unsigned int index;
  const FontFeature* features;
  int n_features;
};

Glyph metrics

The C equivalent of glyph_info() is glyph_metrics() with the following signature:

int glyph_metrics(
  uint32_t code, 
  const char* fontfile, 
  int index, 
  double size, 
  double res, 
  double* ascent, 
  double* descent, 
  double* width
)

It takes the glyph to measure as an int giving the UTF code of the glyph, along with a fontfile and index to identify the font to measure with. Further it takes a size in pt and a resolution in ppi. It will write the ascent, descent, and width in pts to the pointers passed in, and return 0 if the operation was successful.

String width

The C equivalent of the string_width() R function is also called string_width() with the following signature:

string_width(
  const char* string, 
  const char* fontfile, 
  int index, 
  double size, 
  double res, 
  int include_bearing, 
  double* width
)

This function calculates the width of a string, ignoring any newlines (these are automatically being handled by the graphic engine). It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the width in pts to the passed in pointer and return 0 if successful.

String shape

A parred down version of shape_string() is accessible at the C level with string_shape(). It behaves more or less like string_width() above, but instead returns the location to write each glyph at relative to a (0, 0) origin.

string_shape(
  const char* string, 
  const char* fontfile, 
  int index, 
  double size, 
  double res, 
  double* x, 
  double* y, 
  unsigned int max_length
)

string_shape() behaves more or less like string_width() above, but instead returns the location to write each glyph at relative to a (0, 0) origin. It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the x and y location of each glyph in pts to the passed in arrays, stopping before the provided max_length and return 0 if successful.

Retrieving cached freetype face

A heavy part of text layouting is reading and parsing font files. systemfonts contains its own cache to make sure that parsing is kept at a minimum. If you want to use this cache to load and cache freetype face object (FT_Face) you can use get_cached_face(). This resides in a separate header (systemfonts-ft.h) because it requires FreeType to be linked in your package, which the rest of the C api does not. It will look in the cache for a face and size that matches your request and return that if found. If not, it will load it for you and add it to the cache, before returning it to you. get_cached_face() sets the passed in error pointer to 0 if successful.

get_cached_face(
  const char* fontfile, 
  int index, 
  double size, 
  double res,
  int * error
)

Freetype uses reference counting to keep track of objects and the count is increased by a call to get_cached_face(). It is the responsiblity of the caller to decrease it once the face is no longer needed using FT_Done_Face().

Font fallback

When rendering text it is not given that all the requested characters have a glyph in the given font. While one can elect to render a “missing glyph” glyph (often either an empty square or a questionmark in a tilted square) a better approach is often to find a font substitute that does contain the character and use that for rendering it. This function allows you to find a fallback font for a given string and font. The string should be stripped of characters that you already know how to render. The fallback font is returned as a FontSettings object, though features are always empty.

FontSettings get_fallback(
  const char* string,
  const char* path,
  int index
)

Font Weight

When encoding text with CSS it may be necessary to know the exact weight of the font given by a file so that it may be reflected in the style sheet. This function takes a path and an index and returns the weight (100-900 in steps of 100) or 0 if it is undefined by the font.

int get_font_weight(
  const char* path,
  int index
)

Family name

It may be beneficial to know the family name from a given path and index into a font. This can be obtained with get_font_family() which will write the name to the provided char* argument. It will return 0 if it was somehow unsuccessful.

int get_font_family(
  const char* path,
  int index,
  char* family,
  int max_length
)

Emoji location

Figuring out which character in a string should be treated as an emoji is non-trivial due to the existence of emojis with text representation default etc. systemfonts allow you to get the embedding of emojis in a string based on the correct rules.

void detect_emoji_embedding(
  const uint32_t* string, 
  int n, 
  int* embedding, 
  const char *path, 
  int index
)
systemfonts/inst/doc/c_interface.Rmd0000644000176200001440000001720414672302530017306 0ustar liggesusers--- title: "systemfonts C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{systemfonts C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(systemfonts) ``` Most of the functionality in systemfonts is intended to be used from compiled code to help e.g. graphic devices to resolve font specifications to a font file prior to rendering. systemfonts provide key functionality to get called at the C level by putting systemfonts in the `LinkingTo` field in the description and adding `#include ` to your C code. Make sure systemfonts is loaded before using it, e.g. by having `match_font()` imported into your package namespace. The different functionality will be discussed below ## Font matching The C equivalent of the `match_font()` R function is `locate_font()` with the following signature: ```C int locate_font( const char *family, int italic, int bold, char *path, int max_path_length ) ``` It takes a UTF-8 encoded string with the font family name, an int setting both italic and bold styles along with a char pointer to be filled with the located path and the maximum length it can hold. The return value is an int giving the index of the font in the font file. With the advent of systemfonts 0.3.0 fonts can now also have OpenType features attached to them through the use of `register_font()` or `register_variant()`. If you wish to support such features you can use an alternative to the above: ```C FontSettings locate_font_with_features( const char *family, int italic, int bold ) ``` The returned `FontSettings` struct will contain both the font location and index along with any OpenType feature settings. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings` struct as the `features` array may be cleared at any time after the call has ended. systemfonts itself takes care of caching so this is not something you should be concerned with in your code. ```C struct FontFeature { char feature[4]; int setting; }; struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width ) ``` It takes the glyph to measure as an int giving the UTF code of the glyph, along with a fontfile and index to identify the font to measure with. Further it takes a size in pt and a resolution in ppi. It will write the ascent, descent, and width in pts to the pointers passed in, and return `0` if the operation was successful. ## String width The C equivalent of the `string_width()` R function is also called `string_width()` with the following signature: ```C string_width( const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width ) ``` This function calculates the width of a string, ignoring any newlines (these are automatically being handled by the graphic engine). It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the width in pts to the passed in pointer and return 0 if successful. ## String shape A parred down version of `shape_string()` is accessible at the C level with `string_shape()`. It behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. ```C string_shape( const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length ) ``` `string_shape()` behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the x and y location of each glyph in pts to the passed in arrays, stopping before the provided max_length and return 0 if successful. ## Retrieving cached freetype face A heavy part of text layouting is reading and parsing font files. systemfonts contains its own cache to make sure that parsing is kept at a minimum. If you want to use this cache to load and cache freetype face object (FT_Face) you can use `get_cached_face()`. This resides in a separate header (`systemfonts-ft.h`) because it requires FreeType to be linked in your package, which the rest of the C api does not. It will look in the cache for a face and size that matches your request and return that if found. If not, it will load it for you and add it to the cache, before returning it to you. `get_cached_face()` sets the passed in error pointer to 0 if successful. ```C get_cached_face( const char* fontfile, int index, double size, double res, int * error ) ``` Freetype uses reference counting to keep track of objects and the count is increased by a call to `get_cached_face()`. It is the responsiblity of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Font fallback When rendering text it is not given that all the requested characters have a glyph in the given font. While one can elect to render a "missing glyph" glyph (often either an empty square or a questionmark in a tilted square) a better approach is often to find a font substitute that does contain the character and use that for rendering it. This function allows you to find a fallback font for a given string and font. The string should be stripped of characters that you already know how to render. The fallback font is returned as a FontSettings object, though features are always empty. ```C FontSettings get_fallback( const char* string, const char* path, int index ) ``` ## Font Weight When encoding text with CSS it may be necessary to know the exact weight of the font given by a file so that it may be reflected in the style sheet. This function takes a path and an index and returns the weight (100-900 in steps of 100) or 0 if it is undefined by the font. ```C int get_font_weight( const char* path, int index ) ``` ## Family name It may be beneficial to know the family name from a given path and index into a font. This can be obtained with `get_font_family()` which will write the name to the provided `char*` argument. It will return 0 if it was somehow unsuccessful. ```C int get_font_family( const char* path, int index, char* family, int max_length ) ``` ## Emoji location Figuring out which character in a string should be treated as an emoji is non-trivial due to the existence of emojis with text representation default etc. systemfonts allow you to get the embedding of emojis in a string based on the correct rules. ```C void detect_emoji_embedding( const uint32_t* string, int n, int* embedding, const char *path, int index ) ``` systemfonts/inst/unfont.ttf0000644000176200001440000000310414672302530015655 0ustar liggesusers`FFTM(GDEF OS/2c3mh`cmapBcvt DgaspglyfHw^,head?6hhea$$hmtxFlocarX maxpH=H nameBpostu9q?/P_< ڝowڝ"Dbby @.33f PfEdffyb D< D,,,FDdU./<2<2/<2<23!%!!D $hUDb 264&"!!ձ|'P"Ed N  x  D    #  VCopyright (c) 2020, Thomas Lin PedersenCopyright (c) 2020, Thomas Lin PedersenunfontunfontRegularRegularFontForge 2.0 : unfont : 22-3-2020FontForge 2.0 : unfont : 22-3-2020unfontunfontVersion 001.000 Version 001.000 unfontunfontnull ڒڝowڝ"systemfonts/tools/0000755000176200001440000000000014741276717014030 5ustar liggesuserssystemfonts/tools/winlibs.R0000644000176200001440000000162114741276717015622 0ustar liggesusersif(!file.exists("../windows/harfbuzz/include/harfbuzz/hb.h")){ unlink("../windows", recursive = TRUE) url <- if(grepl("aarch", R.version$platform)){ "https://github.com/r-windows/bundles/releases/download/harfbuzz-8.2.1/harfbuzz-8.2.1-clang-aarch64.tar.xz" } else if(grepl("clang", Sys.getenv('R_COMPILED_BY'))){ "https://github.com/r-windows/bundles/releases/download/harfbuzz-8.2.1/harfbuzz-8.2.1-clang-x86_64.tar.xz" } else if(getRversion() >= "4.2") { "https://github.com/r-windows/bundles/releases/download/harfbuzz-8.2.1/harfbuzz-8.2.1-ucrt-x86_64.tar.xz" } else { "https://github.com/rwinlib/harfbuzz/archive/v2.7.4.tar.gz" } download.file(url, basename(url), quiet = TRUE) dir.create("../windows", showWarnings = FALSE) untar(basename(url), exdir = "../windows", tar = 'internal') unlink(basename(url)) setwd("../windows") file.rename(list.files(), 'freetype2') } systemfonts/README.md0000644000176200001440000001105714742272672014150 0ustar liggesusers # systemfonts [![Codecov test coverage](https://codecov.io/gh/r-lib/systemfonts/branch/master/graph/badge.svg)](https://app.codecov.io/gh/r-lib/systemfonts?branch=master) [![CRAN status](https://www.r-pkg.org/badges/version/systemfonts)](https://cran.r-project.org/package=systemfonts) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![R-CMD-check](https://github.com/r-lib/systemfonts/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/systemfonts/actions) systemfonts is a package that locates installed fonts. It uses the system-native libraries on Mac (CoreText) and Linux (FontConfig), and uses Freetype to parse the fonts in the registry on Windows. ## Installation systemfonts is available from CRAN using `install.packages('systemfonts')`. It is however still under development and you can install the development version using devtools. ``` r # install.packages('devtools') devtools::install_github('r-lib/systemfonts') ``` ## Examples The main use of this package is to locate font files based on family and style: ``` r library(systemfonts) match_fonts('Avenir', italic = TRUE) #> path index features #> 1 /System/Library/Fonts/Avenir.ttc 1 ``` This function returns the path to the file holding the font, as well as the 0-based index of the font in the file. It is also possible to get a data.frame of all available fonts: ``` r system_fonts() #> # A tibble: 843 × 9 #> path index name family style weight width italic monospace #> #> 1 /System/Library/Fonts… 2 Rock… Rockw… Bold bold norm… FALSE FALSE #> 2 /Users/thomas/Library… 0 Open… Open … Ligh… normal norm… TRUE FALSE #> 3 /Users/thomas/Library… 0 Open… Open … Semi… semib… semi… TRUE FALSE #> 4 /System/Library/Fonts… 0 Note… Notew… Light normal norm… FALSE FALSE #> 5 /System/Library/Fonts… 1 Deva… Devan… Bold bold norm… FALSE FALSE #> 6 /System/Library/Fonts… 0 Kann… Kanna… Regu… normal norm… FALSE FALSE #> 7 /System/Library/Fonts… 0 Verd… Verda… Bold bold norm… FALSE FALSE #> 8 /System/Library/Fonts… 8 Aria… Arial… Light light norm… FALSE FALSE #> 9 /Users/thomas/Library… 0 Open… Open … Medi… medium norm… TRUE FALSE #> 10 /System/Library/Fonts… 10 Appl… Apple… Thin thin norm… FALSE FALSE #> # ℹ 833 more rows ``` Further, you can query additional information about fonts and specific glyphs, if that is of interest using the `font_info()` and `glyph_info()` functions. ## C API While getting this information in R is nice, the intended use is mostly through compiled code so that graphic devices can easily locate relevant font files etc. In order to use functions from systemfonts in C(++) code your package should list systemfonts in the `LinkingTo` field in the `DESCRIPTION` file. Once this is done you can now `#include ` in your code and use the provided functions. Look into the [`inst/include/systemfonts.h`](https://github.com/r-lib/systemfonts/blob/master/inst/include/systemfonts.h) file to familiarise yourself with the C API. ## System Defaults systemfonts will always try to find a font for you, even if none exist with the given family name or style. How it resolves this is system specific and should not be relied on, but it can be expected that a valid font file is always returned no matter the input. A few special aliases exist that behaves predictably but system dependent: - `""` and `"sans"` return *Helvetica* on Mac, *Arial* on Windows, and the default sans-serif font on Linux (*DejaVu Sans* on Ubuntu) - `"serif"` return *Times* on Mac, *Times New Roman* on Windows, and the default serif font on Linux (*DejaVu Serif* on Ubuntu) - `"mono"` return *Courier* on Mac, *Courier New* on Windows, and the default mono font on Linux (*DejaVu Mono* on Ubuntu) - `"emoji"` return *Apple Color Emoji* on Mac, *Segoe UI Emoji* on Windows, and the default emoji font on Linux (*Noto Color* on Ubuntu) ## Code of Conduct Please note that the ‘systemfonts’ project is released with a [Contributor Code of Conduct](https://github.com/r-lib/systemfonts/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms. systemfonts/build/0000755000176200001440000000000014743404477013765 5ustar liggesuserssystemfonts/build/vignette.rds0000644000176200001440000000032614743404476016324 0ustar liggesusersb```b`aad`b2 1# 'O+I-JKLN MA/,.IM+)VpV+ES&lJFIn" PKG_LIBS="-lfontconfig -lfreetype" PKG_OBJCXXFLAGS="" # Alternative config on MacOS for native APIs if [ `uname` = "Darwin" ]; then SYS="DARWIN" PKG_CONFIG_NAME="--static freetype2" PKG_TEST_HEADER="" PKG_LIBS="-lfreetype" PKG_OBJCXXFLAGS="-fobjc-arc" RBIN="${R_HOME}/bin${R_ARCH_BIN}/R" OBJC=`"$RBIN" CMD config OBJC` if [ -z "$OBJC" ]; then echo "--------------------------- [SYSTEMFONTS] -----------------------------" echo "Configuration failed to find an Objective-C compiler." echo " systemfonts require the use of Objective-C code on macOS to access" echo " the system-native font matching API." echo " Please ensure that your build system is setup with an Objective-C" echo " compiler to install systemfonts on macOS" echo "-----------------------------------------------------------------------" exit 1 fi fi # Use pkg-config if available if [ "`command -v pkg-config`" ]; then PKGCONFIG_CFLAGS=`pkg-config --cflags --silence-errors ${PKG_CONFIG_NAME}` PKGCONFIG_LIBS=`pkg-config --libs ${PKG_CONFIG_NAME}` fi # Note that cflags may be empty in case of success if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then echo "Found INCLUDE_DIR and/or LIB_DIR!" PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS" PKG_LIBS="-L$LIB_DIR $PKG_LIBS" elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then echo "Found pkg-config cflags and libs!" PKG_CFLAGS=${PKGCONFIG_CFLAGS} PKG_LIBS=${PKGCONFIG_LIBS} elif [ `uname` = "Darwin" ]; then test ! "$CI" && brew --version 2>/dev/null if [ $? -eq 0 ]; then BREWDIR=`brew --prefix` PKG_CFLAGS="-I$BREWDIR/include -I$BREWDIR/include/freetype2" else curl -sfL "https://autobrew.github.io/scripts/freetype" > autobrew . ./autobrew fi fi # For debugging echo "Using PKG_CFLAGS=$PKG_CFLAGS" echo "Using PKG_LIBS=$PKG_LIBS" # Find compiler CC=`${R_HOME}/bin/R CMD config CC` CFLAGS=`${R_HOME}/bin/R CMD config CFLAGS` CPPFLAGS=`${R_HOME}/bin/R CMD config CPPFLAGS` # Test configuration echo "#include $PKG_TEST_HEADER" | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - >/dev/null 2>configure.log # Customize the error if [ $? -ne 0 ]; then echo "--------------------------- [ANTICONF] --------------------------------" echo "Configuration failed to find the $PKG_CONFIG_NAME library. Try installing:" echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)" echo " * rpm: $PKG_RPM_NAME (Fedora, EPEL)" echo " * csw: $PKG_CSW_NAME (Solaris)" echo " * brew: $PKG_BREW_NAME (OSX)" echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your" echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config" echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:" echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'" echo "-------------------------- [ERROR MESSAGE] ---------------------------" cat configure.log echo "--------------------------------------------------------------------" exit 1 fi # Write to Makevars sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" -e "s|@SYS@|$SYS|g" -e "s|@objcflags@|$PKG_OBJCXXFLAGS|" src/Makevars.in > src/Makevars # Success exit 0 systemfonts/man/0000755000176200001440000000000014742152301013422 5ustar liggesuserssystemfonts/man/system_fonts.Rd0000644000176200001440000000066714672302530016462 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system_fonts.R \name{system_fonts} \alias{system_fonts} \title{List all fonts installed on your system} \usage{ system_fonts() } \value{ A data frame with a row for each font and various information in each column } \description{ List all fonts installed on your system } \examples{ # See all monospace fonts fonts <- system_fonts() fonts[fonts$monospace, ] } systemfonts/man/string_widths_dev.Rd0000644000176200001440000000272414672302530017447 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/dev_strings.R \name{string_widths_dev} \alias{string_widths_dev} \title{Get string widths as measured by the current device} \usage{ string_widths_dev( strings, family = "", face = 1, size = 12, cex = 1, unit = "cm" ) } \arguments{ \item{strings}{A character vector of strings to measure} \item{family}{The font families to use. Will get recycled} \item{face}{The font faces to use. Will get recycled} \item{size}{The font size to use. Will get recycled} \item{cex}{The cex multiplier to use. Will get recycled} \item{unit}{The unit to return the width in. Either \code{"cm"}, \code{"inches"}, \code{"device"}, or \code{"relative"}} } \value{ A numeric vector with the width of each of the strings given in \code{strings} in the unit given in \code{unit} } \description{ For certain composition tasks it is beneficial to get the width of a string as interpreted by the device that is going to plot it. grid provides this through construction of a \code{textGrob} and then converting the corresponding grob width to e.g. cm, but this comes with a huge overhead. \code{string_widths_dev()} provides direct, vectorised, access to the graphic device for as high performance as possible. } \examples{ # Get the widths as measured in cm (default) string_widths_dev(c('a string', 'an even longer string')) } \seealso{ Other device metrics: \code{\link{string_metrics_dev}()} } \concept{device metrics} systemfonts/man/require_font.Rd0000644000176200001440000000311514675474120016426 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/web_fonts.R \name{require_font} \alias{require_font} \title{Ensure font availability in a script} \usage{ require_font( family, fallback = NULL, dir = tempdir(), repositories = c("Google Fonts", "Font Squirrel"), error = TRUE ) } \arguments{ \item{family}{The font family to require} \item{fallback}{An available font to fall back to if \code{family} cannot be found or downloaded} \item{dir}{The location to put the font file downloaded from repositories} \item{repositories}{The repositories to search for the font in case it is not available on the system. They will be tried in the order given. Currently only \code{"Google Fonts"} and \code{"Font Squirrel"} is available.} \item{error}{Should the function throw an error if unsuccessful?} } \value{ Invisibly \code{TRUE} if the font is available or \code{FALSE} if not (this can only be returned if \code{error = FALSE}) } \description{ When running a script on a different machine you are not always in control of which fonts are installed on the system and thus how graphics created by the script ends up looking. \code{require_font()} is a way to specify your font requirements for a script. It will look at the available fonts and if the required font family is not present it will attempt to fetch it from one of the given repositories (in the order given). If that fails, it will either throw an error or, if \code{fallback} is given, provide an alias for the fallback so it maps to the required font. } \examples{ # Should always work require_font("sans") } systemfonts/man/font_feature.Rd0000644000176200001440000000775014672302530016406 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_feature.R \name{font_feature} \alias{font_feature} \title{Define OpenType font feature settings} \usage{ font_feature(ligatures = NULL, letters = NULL, numbers = NULL, ...) } \arguments{ \item{ligatures}{Settings related to ligatures. One or more types of ligatures to turn on (see details).} \item{letters}{Settings related to the appearance of single letters (as opposed to ligatures that substitutes multiple letters). See details for supported values.} \item{numbers}{Settings related to the appearance of numbers. See details for supported values.} \item{...}{key-value pairs with the key being the 4-letter tag and the value being the setting (usually \code{TRUE} to turn it on).} } \value{ A \code{font_feature} object } \description{ This function encapsulates the specification of OpenType font features. Some specific features have named arguments, but all available features can be set by using its specific 4-letter tag For a list of the 4-letter tags available see e.g. the overview on \href{https://en.wikipedia.org/wiki/List_of_typographic_features}{Wikipedia}. } \details{ OpenType features are defined by a 4-letter tag along with an integer value. Often that value is a simple \code{0} (off) or \code{1} (on), but some features support additional values, e.g. stylistic alternates (\code{salt}) where a font may provide multiple variants of a letter and the value will be used to chose which one to use. Common features related to appearance may be given with a long form name to either the \code{ligatures}, \code{letters}, or \code{numbers} argument to avoid remembering the often arbitrary 4-letter tag. Providing a long form name is the same as setting the tag to \code{1} and can thus not be used to set tags to other values. The possible long form names are given below with the tag in parenthesis: \strong{Ligatures} \itemize{ \item \code{standard} (\emph{liga}): Turns on standard multiple letter substitution \item \code{historical} (\emph{hlig}): Use obsolete historical ligatures \item \code{contextual} (\emph{clig}): Apply secondary ligatures based on the character patterns surrounding the potential ligature \item \code{discretionary} (\emph{dlig}): Use ornamental ligatures } \strong{Letters} \itemize{ \item \code{swash} (\emph{cswh}): Use contextual swashes (ornamental decorations) \item \code{alternates} (\emph{calt}): Use alternate letter forms based on the sourrounding pattern \item \code{historical} (\emph{hist}): Use obsolete historical forms of the letters \item \code{localized} (\emph{locl}): Use alternate forms preferred by the script language \item \code{randomize} (\emph{rand}): Use random variants of the letters (e.g. to mimick handwriting) \item \code{alt_annotation} (\emph{nalt}): Use alternate annotations (e.g. circled digits) \item \code{stylistic} (\emph{salt}): Use a stylistic alternative form of the letter \item \code{subscript} (\emph{subs}): Set letter in subscript \item \code{superscript} (\emph{sups}): Set letter in superscript \item \code{titling} (\emph{titl}): Use letter forms well suited for large text and titles \item \code{small_caps} (\emph{smcp}): Use small caps variants of the letters } \strong{Numbers} \itemize{ \item \code{lining} (\emph{lnum}): Use number variants that rest on the baseline \item \code{oldstyle} (\emph{onum}): Use old style numbers that use descender and ascender for various numbers \item \code{proportional} (\emph{pnum}): Let numbers take up width based on the visual width of the glyph \item \code{tabular} (\emph{tnum}): Enforce all numbers to take up the same width \item \code{fractions} (\emph{frac}): Convert numbers separated by \code{/} into a fraction glyph \item \code{fractions_alt} (\emph{afrc}): Use alternate fraction form with a horizontal divider } } \examples{ font_feature(letters = "stylistic", numbers = c("lining", "tabular")) # Use the tag directly to access additional stylistic variants font_feature(numbers = c("lining", "tabular"), salt = 2) } systemfonts/man/string_width.Rd0000644000176200001440000000270314672302530016423 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_string.R \name{string_width} \alias{string_width} \title{Calculate the width of a string, ignoring new-lines} \usage{ string_width( strings, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0 ) } \arguments{ \item{strings}{A character vector of strings} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{include_bearing}{Logical, should left and right bearing be included in the string width?} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A numeric vector giving the width of the strings in pixels. Use the provided \code{res} value to convert it into absolute values. } \description{ This is a very simple alternative to \code{\link[=shape_string]{shape_string()}} that simply calculates the width of strings without taking any newline into account. As such it is suitable to calculate the width of words or lines that has already been splitted by \verb{\\n}. Input is recycled to the length of \code{strings}. } \examples{ strings <- c('A short string', 'A very very looong string') string_width(strings) } systemfonts/man/shape_string.Rd0000644000176200001440000001006614672302530016405 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_string.R \name{shape_string} \alias{shape_string} \title{Calculate glyph positions for strings} \usage{ shape_string( strings, id = NULL, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, lineheight = 1, align = "left", hjust = 0, vjust = 0, width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, index = 0 ) } \arguments{ \item{strings}{A character vector of strings to shape} \item{id}{A vector grouping the strings together. If strings share an id the shaping will continue between strings} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{lineheight}{A multiplier for the lineheight} \item{align}{Within text box alignment, either \code{'left'}, \code{'center'}, or \code{'right'}} \item{hjust, vjust}{The justification of the textbox surrounding the text} \item{width}{The requested with of the string in inches. Setting this to something other than \code{NA} will turn on word wrapping.} \item{tracking}{Tracking of the glyphs (space adjustment) measured in 1/1000 em.} \item{indent}{The indent of the first line in a paragraph measured in inches.} \item{hanging}{The indent of the remaining lines in a paragraph measured in inches.} \item{space_before, space_after}{The spacing above and below a paragraph, measured in points} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A list with two element: \code{shape} contains the position of each glyph, relative to the origin in the enclosing textbox. \code{metrics} contain metrics about the full strings. \code{shape} is a data.frame with the following columns: \describe{ \item{glyph}{The glyph as a character} \item{index}{The index of the glyph in the font file} \item{metric_id}{The index of the string the glyph is part of (referencing a row in the \code{metrics} data.frame)} \item{string_id}{The index of the string the glyph came from (referencing an element in the \code{strings} input)} \item{x_offset}{The x offset in pixels from the origin of the textbox} \item{y_offset}{The y offset in pixels from the origin of the textbox} \item{x_mid}{The x offset in pixels to the middle of the glyph, measured from the origin of the glyph} } \code{metrics} is a data.frame with the following columns: \describe{ \item{string}{The text the string consist of} \item{width}{The width of the string} \item{height}{The height of the string} \item{left_bearing}{The distance from the left edge of the textbox and the leftmost glyph} \item{right_bearing}{The distance from the right edge of the textbox and the rightmost glyph} \item{top_bearing}{The distance from the top edge of the textbox and the topmost glyph} \item{bottom_bearing}{The distance from the bottom edge of the textbox and the bottommost glyph} \item{left_border}{The position of the leftmost edge of the textbox related to the origin} \item{top_border}{The position of the topmost edge of the textbox related to the origin} \item{pen_x}{The horizontal position of the next glyph after the string} \item{pen_y}{The vertical position of the next glyph after the string} } } \description{ Do basic text shaping of strings. This function will use freetype to calculate advances, doing kerning if possible. It will not perform any font substitution or ligature resolving and will thus be much in line with how the standard graphic devices does text shaping. Inputs are recycled to the length of \code{strings}. } \examples{ string <- "This is a long string\nLook; It spans multiple lines\nand all" # Shape with default settings shape_string(string) # Mix styles within the same string string <- c( "This string will have\na ", "very large", " text style\nin the middle" ) shape_string(string, id = c(1, 1, 1), size = c(12, 24, 12)) } systemfonts/man/match_fonts.Rd0000644000176200001440000000667114741276724016247 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/match_fonts.R \name{match_fonts} \alias{match_fonts} \alias{match_font} \title{Find a system font by name and style} \usage{ match_fonts(family, italic = FALSE, weight = "normal", width = "undefined") match_font(family, italic = FALSE, bold = FALSE) } \arguments{ \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{weight}{The weight to query for, either in numbers (\code{0}, \code{100}, \code{200}, \code{300}, \code{400}, \code{500}, \code{600}, \code{700}, \code{800}, or \code{900}) or strings (\code{"undefined"}, \code{"thin"}, \code{"ultralight"}, \code{"light"}, \code{"normal"}, \code{"medium"}, \code{"semibold"}, \code{"bold"}, \code{"ultrabold"}, or \code{"heavy"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{width}{The width to query for either in numbers (\code{0}, \code{1}, \code{2}, \code{3}, \code{4}, \code{5}, \code{6}, \code{7}, \code{8}, or \code{9}) or strings (\code{"undefined"}, \code{"ultracondensed"}, \code{"extracondensed"}, \code{"condensed"}, \code{"semicondensed"}, \code{"normal"}, \code{"semiexpanded"}, \code{"expanded"}, \code{"extraexpanded"}, or \code{"ultraexpanded"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{bold}{logical indicating whether the font weight} } \value{ A list containing the paths locating the font files, the 0-based index of the font in the files and the features for the font in case a registered font was located. } \description{ This function locates the font file (and index) best matching a name and optional style. A font file will be returned even if a perfect match isn't found, but it is not necessarily similar to the requested family and it should not be relied on for font substitution. The aliases \code{"sans"}, \code{"serif"}, \code{"mono"}, \code{"symbol"}, and \code{"emoji"} match to their respective system defaults (\code{""} is equivalent to \code{"sans"}). \code{match_font()} has been deprecated in favour of \code{match_fonts()} which provides vectorisation, as well as querying for different weights (rather than just "normal" and "bold") as well as different widths. } \section{Font matching}{ During font matching, systemfonts has to look in three different locations. The font registry (populated by \code{\link[=register_font]{register_font()}}/\code{\link[=register_variant]{register_variant()}}), the local fonts (populated with \code{\link[=add_fonts]{add_fonts()}}/\code{\link[=scan_local_fonts]{scan_local_fonts()}}), and the fonts installed on the system. It does so in that order: registry > local > installed. The matching performed at each step also differs. The fonts in the registry is only matched by family name. The local fonts are matched based on all the provided parameters (family, weight, italic, etc) in a way that is local to systemfonts, but try to emulate the system native matching. The installed fonts are matched using the system native matching functionality on macOS and Linux. On Windows the installed fonts are read from the system registry and matched using the same approach as for local fonts. Matching will always find a font no matter what you throw at it, defaulting to "sans" if nothing else is found. } \examples{ # Get the system default sans-serif font in italic match_fonts('sans', italic = TRUE) # Try to match it to a thin variant match_fonts(c('sans', 'serif'), weight = "thin") } systemfonts/man/search_web_fonts.Rd0000644000176200001440000000510414675462727017251 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/web_fonts.R \name{search_web_fonts} \alias{search_web_fonts} \title{Search font repositories for a font based on family name} \usage{ search_web_fonts(family, n_max = 10, ...) } \arguments{ \item{family}{The font family name to look for} \item{n_max}{The maximum number of matches to return} \item{...}{ Arguments passed on to \code{\link[utils:adist]{utils::adist}} \describe{ \item{\code{costs}}{a numeric vector or list with names partially matching \samp{insertions}, \samp{deletions} and \samp{substitutions} giving the respective costs for computing the Levenshtein distance, or \code{NULL} (default) indicating using unit cost for all three possible transformations.} \item{\code{counts}}{a logical indicating whether to optionally return the transformation counts (numbers of insertions, deletions and substitutions) as the \code{"counts"} attribute of the return value.} \item{\code{fixed}}{a logical. If \code{TRUE} (default), the \code{x} elements are used as string literals. Otherwise, they are taken as regular expressions and \code{partial = TRUE} is implied (corresponding to the approximate string distance used by \code{\link{agrep}} with \code{fixed = FALSE}).} \item{\code{partial}}{a logical indicating whether the transformed \code{x} elements must exactly match the complete \code{y} elements, or only substrings of these. The latter corresponds to the approximate string distance used by \code{\link{agrep}} (by default).} \item{\code{ignore.case}}{a logical. If \code{TRUE}, case is ignored for computing the distances.} \item{\code{useBytes}}{a logical. If \code{TRUE} distance computations are done byte-by-byte rather than character-by-character.} }} } \value{ A data.frame with the columns \code{family}, giving the family name of the matched font, and \code{repository} giving the repository it was found in. } \description{ While it is often advisable to visit the webpage for a font repository when looking for a font, in order to see examples etc, \code{search_web_fonts()} provide a quick lookup based on family name in the repositories supported by systemfonts (currently \href{https://fonts.google.com}{Google Fonts} and \href{https://www.fontsquirrel.com}{Font Squirrel}). The lookup is based on fuzzy matching provided by \code{\link[utils:adist]{utils::adist()}} and the matching parameters can be controlled through \code{...} } \examples{ # Requires an internet connection # search_web_fonts("Spectral") } systemfonts/man/as_font_weight.Rd0000644000176200001440000000177414742152301016722 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/match_fonts.R \name{as_font_weight} \alias{as_font_weight} \alias{as_font_width} \title{Convert weight and width to numerics} \usage{ as_font_weight(weight) as_font_width(width) } \arguments{ \item{weight, width}{character vectors with valid names for weight or width} } \value{ An integer vector matching the length of the input } \description{ It is often more natural to describe font weight and width with names rather than numbers (e.g. "bold" or "condensed"), but underneath these names are matched to numeric values. These two functions are used to retrieve the numeric counterparts to names } \examples{ as_font_weight( c("undefined", "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy") ) as_font_width( c("undefined", "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded") ) } \keyword{internal} systemfonts/man/add_fonts.Rd0000644000176200001440000000472314742302430015660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/register_font.R \name{add_fonts} \alias{add_fonts} \alias{scan_local_fonts} \alias{clear_local_fonts} \title{Add local font files to the search path} \usage{ add_fonts(files) scan_local_fonts() clear_local_fonts() } \arguments{ \item{files}{A character vector of font file paths to add} } \value{ This function is called for its sideeffects } \description{ systemfonts is mainly about getting system native access to the fonts installed on the OS you are executing the code on. However, you may want to access fonts without doing a full installation, either because you want your project to be reproducible on all systems, because you don't have administrator priviliges on the system, or for a different reason entirely. \code{add_fonts()} provide a way to side load font files so that they are found during font matching. The function differs from \code{\link[=register_font]{register_font()}} and \code{\link[=register_variant]{register_variant()}} in that they add the font file as-is using the family name etc that are provided by the font. \code{scan_local_fonts()} is run when systemfonts is loaded and will automatically add font files stored in \code{./fonts} (project local) and \verb{~/fonts} (user local). } \section{Font matching}{ During font matching, systemfonts has to look in three different locations. The font registry (populated by \code{\link[=register_font]{register_font()}}/\code{\link[=register_variant]{register_variant()}}), the local fonts (populated with \code{\link[=add_fonts]{add_fonts()}}/\code{\link[=scan_local_fonts]{scan_local_fonts()}}), and the fonts installed on the system. It does so in that order: registry > local > installed. The matching performed at each step also differs. The fonts in the registry is only matched by family name. The local fonts are matched based on all the provided parameters (family, weight, italic, etc) in a way that is local to systemfonts, but try to emulate the system native matching. The installed fonts are matched using the system native matching functionality on macOS and Linux. On Windows the installed fonts are read from the system registry and matched using the same approach as for local fonts. Matching will always find a font no matter what you throw at it, defaulting to "sans" if nothing else is found. } \examples{ # example code empty_font <- system.file("unfont.ttf", package = "systemfonts") add_fonts(empty_font) clear_local_fonts() } systemfonts/man/glyph_raster_grob.Rd0000644000176200001440000000545614742200345017441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_outline.R \name{glyph_raster_grob} \alias{glyph_raster_grob} \title{Convert an extracted glyph raster to a grob} \usage{ glyph_raster_grob(glyph, x, y, ..., default.units = "bigpts") } \arguments{ \item{glyph}{The nativeRaster object returned as one of the elements by \code{\link[=glyph_raster]{glyph_raster()}}} \item{x, y}{The baseline location of the glyph} \item{...}{ Arguments passed on to \code{\link[grid:grid.raster]{grid::rasterGrob}} \describe{ \item{\code{image}}{ Any R object that can be coerced to a raster object. } \item{\code{width}}{A numeric vector or unit object specifying width.} \item{\code{height}}{A numeric vector or unit object specifying height.} \item{\code{just}}{The justification of the rectangle relative to its (x, y) location. If there are two values, the first value specifies horizontal justification and the second value specifies vertical justification. Possible string values are: \code{"left"}, \code{"right"}, \code{"centre"}, \code{"center"}, \code{"bottom"}, and \code{"top"}. For numeric values, 0 means left alignment and 1 means right alignment. } \item{\code{hjust}}{A numeric vector specifying horizontal justification. If specified, overrides the \code{just} setting.} \item{\code{vjust}}{A numeric vector specifying vertical justification. If specified, overrides the \code{just} setting.} \item{\code{name}}{ A character identifier. } \item{\code{gp}}{An object of class \code{"gpar"}, typically the output from a call to the function \code{\link[grid]{gpar}}. This is basically a list of graphical parameter settings.} \item{\code{vp}}{A Grid viewport object (or NULL).} \item{\code{interpolate}}{ A logical value indicating whether to linearly interpolate the image (the alternative is to use nearest-neighbour interpolation, which gives a more blocky result). } }} \item{default.units}{A string indicating the default units to use if \code{x}, \code{y}, \code{width}, or \code{height} are only given as numeric vectors.} } \value{ A rasterGrob object } \description{ This is a convenience function that helps in creating \link{rasterGrob} with the correct settings for the glyph. It takes inot account the sizing and offset returned by \code{\link[=glyph_raster]{glyph_raster()}} and allows you to only consider the baseline position of the glyph. } \examples{ font <- font_info() glyph <- glyph_info("R", path = font$path, index = font$index) R <- glyph_raster(glyph$index, font$path, font$index, size = 150) grob <- glyph_raster_grob(R[[1]], 50, 50) grid::grid.newpage() # Mark the baseline location grid::grid.points(50, 50, default.units = "bigpts") # Draw the glyph grid::grid.draw(grob) } systemfonts/man/systemfonts-package.Rd0000644000176200001440000000245414672302530017710 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/systemfonts-package.R \docType{package} \name{systemfonts-package} \alias{systemfonts} \alias{systemfonts-package} \title{systemfonts: System Native Font Finding} \description{ Provides system native access to the font catalogue. As font handling varies between systems it is difficult to correctly locate installed fonts across different operating systems. The 'systemfonts' package provides bindings to the native libraries on Windows, macOS and Linux for finding font files that can then be used further by e.g. graphic devices. The main use is intended to be from compiled code but 'systemfonts' also provides access from R. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/r-lib/systemfonts} \item \url{https://systemfonts.r-lib.org} \item Report bugs at \url{https://github.com/r-lib/systemfonts/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomas.pedersen@posit.co} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) Authors: \itemize{ \item Jeroen Ooms \email{jeroen@berkeley.edu} (\href{https://orcid.org/0000-0002-4035-0289}{ORCID}) \item Devon Govett (Author of font-manager) } Other contributors: \itemize{ \item Posit, PBC [copyright holder, funder] } } \keyword{internal} systemfonts/man/figures/0000755000176200001440000000000014672302505015073 5ustar liggesuserssystemfonts/man/figures/lifecycle-questioning.svg0000644000176200001440000000244414672302530022120 0ustar liggesusers lifecycle: questioning lifecycle questioning systemfonts/man/figures/lifecycle-stable.svg0000644000176200001440000000247214672302530021026 0ustar liggesusers lifecycle: stable lifecycle stable systemfonts/man/figures/lifecycle-experimental.svg0000644000176200001440000000245014672302530022245 0ustar liggesusers lifecycle: experimental lifecycle experimental systemfonts/man/figures/lifecycle-deprecated.svg0000644000176200001440000000244014672302530021647 0ustar liggesusers lifecycle: deprecated lifecycle deprecated systemfonts/man/figures/lifecycle-superseded.svg0000644000176200001440000000244014672302530021712 0ustar liggesusers lifecycle: superseded lifecycle superseded systemfonts/man/figures/lifecycle-archived.svg0000644000176200001440000000243014672302530021333 0ustar liggesusers lifecycle: archived lifecycle archived systemfonts/man/figures/lifecycle-defunct.svg0000644000176200001440000000242414672302530021201 0ustar liggesusers lifecycle: defunct lifecycle defunct systemfonts/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000246614672302530022630 0ustar liggesusers lifecycle: soft-deprecated lifecycle soft-deprecated systemfonts/man/figures/lifecycle-maturing.svg0000644000176200001440000000243014672302530021374 0ustar liggesusers lifecycle: maturing lifecycle maturing systemfonts/man/glyph_raster.Rd0000644000176200001440000000354114742153271016426 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_outline.R \name{glyph_raster} \alias{glyph_raster} \title{Render glyphs to raster image} \usage{ glyph_raster( glyph, path, index = 0, size = 12, res = 300, col = "black", verbose = FALSE ) } \arguments{ \item{glyph}{The index of the glyph in the font file} \item{path}{The path to the font file encoding the glyph} \item{index}{The index of the font in the font file} \item{size}{The size of the font in big points (1/72 inch)} \item{res}{The resolution to render the glyphs to} \item{col}{The color of the glyph assuming the glyph doesn't have a native coloring} \item{verbose}{Should font and glyph loading errors be reported as warnings} } \value{ A list of nativeRaster objects (or \code{NULL} if it failed to render a given glyph). The nativeRasters have additional attributes attached. \code{"size"} will give the size of the glyph in big points and \code{"offset"} will give the location of the top-left corner of the raster with respect to where it should be rendered. } \description{ Not all glyphs are encoded as vector outlines (emojis often not). Even for fonts that provide an outline you might be interested in a raster version. This function gives you just that. It converts a glyph into an optimized raster object that can be plotted with e.g. \code{\link[graphics:rasterImage]{graphics::rasterImage()}} or \code{\link[grid:grid.raster]{grid::grid.raster()}}. For convenience, you can also use \code{\link[=glyph_raster_grob]{glyph_raster_grob()}} for plotting the result. } \examples{ font <- font_info() glyph <- glyph_info("R", path = font$path, index = font$index) R <- glyph_raster(glyph$index, font$path, font$index, size = 150) plot.new() plot.window(c(0,150), c(0, 150), asp = 1) rasterImage(R[[1]], 0, 0, attr(R[[1]], "size")[2], attr(R[[1]], "size")[1]) } systemfonts/man/reset_font_cache.Rd0000644000176200001440000000151114672302530017205 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system_fonts.R \name{reset_font_cache} \alias{reset_font_cache} \title{Reset the system font cache} \usage{ reset_font_cache() } \description{ Building the list of system fonts is time consuming and is therefore cached. This, in turn, means that changes to the system fonts (i.e. installing new fonts), will not propagate to systemfonts. The solution is to reset the cache, which will result in the next call to e.g. \code{\link[=match_fonts]{match_fonts()}} will trigger a rebuild of the cache. } \examples{ all_fonts <- system_fonts() ##-- Install a new font on the system --## all_fonts_new <- system_fonts() ## all_fonts_new will be equal to all_fonts reset_font_cache() all_fonts_new <- system_fonts() ## all_fonts_new will now contain the new font } systemfonts/man/glyph_outline.Rd0000644000176200001440000000307314742152301016576 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_outline.R \name{glyph_outline} \alias{glyph_outline} \title{Get the outline of glyphs} \usage{ glyph_outline( glyph, path, index = 0, size = 12, tolerance = 0.2, verbose = FALSE ) } \arguments{ \item{glyph}{The index of the glyph in the font file} \item{path}{The path to the font file encoding the glyph} \item{index}{The index of the font in the font file} \item{size}{The size of the font in big points (1/72 inch)} \item{tolerance}{The deviation tolerance for decomposing bezier curves of the glyph. Given in the same unit as size. Smaller values give more detailed polygons} \item{verbose}{Should font and glyph loading errors be reported as warnings} } \value{ A data frame giving the outlines of the glyphs provide in \code{glyph}. It contains the columns \code{glyph} pointing to the element in the input it relates to, \code{contour} enumerating the contours the glyph consists of, and \code{x} and \code{y} giving the coordinates in big points } \description{ This function allows you to retrieve the outline of glyphs as polygon coordinates. The glyphs are given as indexes into a font file and not as characters allowing you to retrieve outlines for glyphs that doesn't have a character counterpoint. Glyphs that are given as bitmaps are ignored. } \examples{ # Get the shape of s in the default font font <- font_info() glyph <- glyph_info("s", path = font$path, index = font$index) s <- glyph_outline(glyph$index, font$path, font$index, size = 150) plot(s$x, s$y, type = 'l') } systemfonts/man/web-fonts.Rd0000644000176200001440000000205514742475465015642 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/web_fonts.R \name{web-fonts} \alias{web-fonts} \alias{get_from_google_fonts} \alias{get_from_font_squirrel} \title{Download and add web font} \usage{ get_from_google_fonts(family, dir = "~/fonts", woff2 = FALSE) get_from_font_squirrel(family, dir = "~/fonts") } \arguments{ \item{family}{The font family to download (case insensitive)} \item{dir}{Where to download the font to. The default places it in your user local font folder so that the font will be available automatically in new R sessions. Set to \code{tempdir()} to only keep the font for the session.} \item{woff2}{Should the font be downloaded in the woff2 format (smaller and more optimized)? Defaults to FALSE as the format is not supported on all systems} } \value{ A logical invisibly indicating whether a font was found and downloaded or not } \description{ In order to use a font in R it must first be made available locally. These functions facilitate the download and registration of fonts from online repositories. } systemfonts/man/font_info.Rd0000644000176200001440000000507514672302530015704 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_info.R \name{font_info} \alias{font_info} \title{Query font-specific information} \usage{ font_info( family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0 ) } \arguments{ \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data.frame giving info on the requested font + size combinations. The data.frame will contain the following columns: \describe{ \item{path}{The path to the font file} \item{index}{The 0-based index of the font in the fontfile} \item{family}{The family name of the font} \item{style}{The style name of the font} \item{italic}{A logical giving if the font is italic} \item{bold}{A logical giving if the font is bold} \item{monospace}{A logical giving if the font is monospace} \item{weight}{A factor giving the weight of the font} \item{width}{A factor giving the width of the font} \item{kerning}{A logical giving if the font supports kerning} \item{color}{A logical giving if the font has color glyphs} \item{scalable}{A logical giving if the font is scalable} \item{vertical}{A logical giving if the font is vertical} \item{n_glyphs}{The number of glyphs in the font} \item{n_sizes}{The number of predefined sizes in the font} \item{n_charmaps}{The number of character mappings in the font file} \item{bbox}{A bounding box large enough to contain any of the glyphs in the font} \item{max_ascend}{The maximum ascend of the tallest glyph in the font} \item{max_descent}{The maximum descend of the most descending glyph in the font} \item{max_advance_width}{The maximum horizontal advance a glyph can make} \item{max_advance_height}{The maximum vertical advance a glyph can make} \item{lineheight}{The height of a single line of text in the font} \item{underline_pos}{The position of a potential underlining segment} \item{underline_size}{The width the the underline} } } \description{ Get general information about a font, relative to a given size. Size specific measures will be returned in pixel units. The function is vectorised to the length of the longest argument. } \examples{ font_info('serif') # Avoid lookup if font file is already known sans <- match_fonts('sans') font_info(path = sans$path, index = sans$index) } systemfonts/man/get_fallback.Rd0000644000176200001440000000040514672302530016311 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/zzz.R \name{get_fallback} \alias{get_fallback} \title{Get location of the fallback font} \usage{ get_fallback() } \description{ Get location of the fallback font } \keyword{internal} systemfonts/man/register_font.Rd0000644000176200001440000001007714741276724016607 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/register_font.R \name{register_font} \alias{register_font} \alias{registry_fonts} \alias{clear_registry} \title{Register font collections as families} \usage{ register_font( name, plain, bold = plain, italic = plain, bolditalic = plain, features = font_feature() ) registry_fonts() clear_registry() } \arguments{ \item{name}{The name the collection will be known under (i.e. \emph{family})} \item{plain, bold, italic, bolditalic}{Fontfiles for the different faces of the collection. can either be a filepath or a list containing a filepath and an index (only for font files containing multiple fonts). If not given it will default to the \code{plain} specification.} \item{features}{A \code{\link{font_feature}} object describing the specific OpenType font features to turn on for the registered font.} } \value{ \code{register_font()} and \code{clear_registry()} returns \code{NULL} invisibly. \code{registry_fonts()} returns a data table in the same style as \code{\link[=system_fonts]{system_fonts()}} though less detailed and not based on information in the font file. } \description{ By design, systemfonts searches the fonts installed natively on the system. It is possible, however, to register other fonts from e.g. font packages or local font files, that will get searched before searching any installed fonts. You can always get an overview over all registered fonts with the \code{registry_fonts()} function that works as a registry focused analogue to \code{\link[=system_fonts]{system_fonts()}}. If you wish to clear out the registry, you can either restart the R session or call \code{clear_registry()}. } \details{ \code{register_font} also makes it possible to use system fonts with traits that is not covered by the graphic engine in R. In plotting operations it is only possible to specify a family name and whether or not the font should be bold and/or italic. There are numerous fonts that will never get matched to this, especially because bold is only one of many weights. Apart from granting a way to use new varieties of fonts, font registration also allows you to override the default \code{sans}, \code{serif}, and \code{mono} mappings, simply by registering a collection to the relevant default name. As registered fonts are searched first it will take precedence over the default. } \section{Font matching}{ During font matching, systemfonts has to look in three different locations. The font registry (populated by \code{\link[=register_font]{register_font()}}/\code{\link[=register_variant]{register_variant()}}), the local fonts (populated with \code{\link[=add_fonts]{add_fonts()}}/\code{\link[=scan_local_fonts]{scan_local_fonts()}}), and the fonts installed on the system. It does so in that order: registry > local > installed. The matching performed at each step also differs. The fonts in the registry is only matched by family name. The local fonts are matched based on all the provided parameters (family, weight, italic, etc) in a way that is local to systemfonts, but try to emulate the system native matching. The installed fonts are matched using the system native matching functionality on macOS and Linux. On Windows the installed fonts are read from the system registry and matched using the same approach as for local fonts. Matching will always find a font no matter what you throw at it, defaulting to "sans" if nothing else is found. } \examples{ # Create a random font collection fonts <- system_fonts() plain <- sample(which(!fonts$italic & fonts$weight <= 'normal'), 1) bold <- sample(which(!fonts$italic & fonts$weight > 'normal'), 1) italic <- sample(which(fonts$italic & fonts$weight <= 'normal'), 1) bolditalic <- sample(which(fonts$italic & fonts$weight > 'normal'), 1) register_font( 'random', plain = list(fonts$path[plain], fonts$index[plain]), bold = list(fonts$path[bold], fonts$index[bold]), italic = list(fonts$path[italic], fonts$index[italic]), bolditalic = list(fonts$path[bolditalic], fonts$index[bolditalic]) ) # Look at your creation registry_fonts() # Reset clear_registry() } systemfonts/man/register_variant.Rd0000644000176200001440000000574114741276724017307 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/register_font.R \name{register_variant} \alias{register_variant} \title{Register a font as a variant as an existing one} \usage{ register_variant( name, family, weight = NULL, width = NULL, features = font_feature() ) } \arguments{ \item{name}{The new family name the variant should respond to} \item{family}{The name of an existing font family that this is a variant of} \item{weight}{One or two of \code{"thin"}, \code{"ultralight"}, \code{"light"}, \code{"normal"}, \code{"medium"}, \code{"semibold"}, \code{"bold"}, \code{"ultrabold"}, or \code{"heavy"}. If one is given it sets the weight for the whole variant. If two is given the first one defines the plain weight and the second the bold weight. If \code{NULL} then the variants of the given family closest to \code{"normal"} and \code{"bold"} will be chosen.} \item{width}{One of \code{"ultracondensed"}, \code{"extracondensed"}, \code{"condensed"}, \code{"semicondensed"}, \code{"normal"}, \code{"semiexpanded"}, \code{"expanded"}, \code{"extraexpanded"}, or \code{"ultraexpanded"} giving the width of the variant. If \code{NULL} then the width closest to \code{"normal"} will be chosen.} \item{features}{A \code{\link{font_feature}} object describing the specific OpenType font features to turn on for the registered font variant.} } \description{ This function is a wrapper around \code{\link[=register_font]{register_font()}} that allows you to easily create variants of existing system fonts, e.g. to target different weights and/or widths, or for attaching OpenType features to a font. } \section{Font matching}{ During font matching, systemfonts has to look in three different locations. The font registry (populated by \code{\link[=register_font]{register_font()}}/\code{\link[=register_variant]{register_variant()}}), the local fonts (populated with \code{\link[=add_fonts]{add_fonts()}}/\code{\link[=scan_local_fonts]{scan_local_fonts()}}), and the fonts installed on the system. It does so in that order: registry > local > installed. The matching performed at each step also differs. The fonts in the registry is only matched by family name. The local fonts are matched based on all the provided parameters (family, weight, italic, etc) in a way that is local to systemfonts, but try to emulate the system native matching. The installed fonts are matched using the system native matching functionality on macOS and Linux. On Windows the installed fonts are read from the system registry and matched using the same approach as for local fonts. Matching will always find a font no matter what you throw at it, defaulting to "sans" if nothing else is found. } \examples{ # Get the default "sans" family sans <- match_fonts("sans")$path sans <- system_fonts()$family[system_fonts()$path == sans][1] # Register a variant of it: register_variant( "sans_ligature", sans, features = font_feature(ligatures = "discretionary") ) registry_fonts() # clean up clear_registry() } systemfonts/man/glyph_info.Rd0000644000176200001440000000327314672302530016057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_info.R \name{glyph_info} \alias{glyph_info} \title{Query glyph-specific information from fonts} \usage{ glyph_info( glyphs, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0 ) } \arguments{ \item{glyphs}{A vector of glyphs. Strings will be split into separate glyphs automatically} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data.frame with information about each glyph, containing the following columns: \describe{ \item{glyph}{The glyph as a character} \item{index}{The index of the glyph in the font file} \item{width}{The width of the glyph} \item{height}{The height of the glyph} \item{x_bearing}{The horizontal distance from the origin to the leftmost part of the glyph} \item{y_bearing}{The vertical distance from the origin to the top part of the glyph} \item{x_advance}{The horizontal distance to move the cursor after adding the glyph} \item{y_advance}{The vertical distance to move the cursor after adding the glyph} \item{bbox}{The tight bounding box surrounding the glyph} } } \description{ This function allows you to extract information about the individual glyphs in a font, based on a specified size. All size related measures are in pixel-units. The function is vectorised to the length of the \code{glyphs} vector. } systemfonts/man/str_split_emoji.Rd0000644000176200001440000000272614672302530017131 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/emoji.R \name{str_split_emoji} \alias{str_split_emoji} \title{Split a string into emoji and non-emoji glyph runs} \usage{ str_split_emoji( string, family = "", italic = FALSE, bold = FALSE, path = NULL, index = 0 ) } \arguments{ \item{string}{A character vector of strings that should be splitted.} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data.frame containing the following columns: \describe{ \item{string}{The substring containing a consecutive run of glyphs} \item{id}{The index into the original \code{string} vector that the substring is part of} \item{emoji}{A logical vector giving if the substring is a run of emojis or not} } } \description{ In order to do correct text rendering, the font needed must be figured out. A common case is rendering of emojis within a string where the system emoji font is used rather than the requested font. This function will inspect the provided strings and split them up in runs that must be rendered with the emoji font, and the rest. Arguments are recycled to the length of the \code{string} vector. } \examples{ emoji_string <- "This is a joke\U0001f642. It should be obvious from the smiley" str_split_emoji(emoji_string) } systemfonts/man/string_metrics_dev.Rd0000644000176200001440000000242414672302530017610 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/dev_strings.R \name{string_metrics_dev} \alias{string_metrics_dev} \title{Get string metrics as measured by the current device} \usage{ string_metrics_dev( strings, family = "", face = 1, size = 12, cex = 1, unit = "cm" ) } \arguments{ \item{strings}{A character vector of strings to measure} \item{family}{The font families to use. Will get recycled} \item{face}{The font faces to use. Will get recycled} \item{size}{The font size to use. Will get recycled} \item{cex}{The cex multiplier to use. Will get recycled} \item{unit}{The unit to return the width in. Either \code{"cm"}, \code{"inches"}, \code{"device"}, or \code{"relative"}} } \value{ A data.frame with \code{width}, \code{ascent}, and \code{descent} columns giving the metrics in the requested unit. } \description{ This function is much like \code{\link[=string_widths_dev]{string_widths_dev()}} but also returns the ascent and descent of the string making it possible to construct a tight bounding box around the string. } \examples{ # Get the metrics as measured in cm (default) string_metrics_dev(c('some text', 'a string with descenders')) } \seealso{ Other device metrics: \code{\link{string_widths_dev}()} } \concept{device metrics} systemfonts/man/font_fallback.Rd0000644000176200001440000000261614672302530016506 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_fallback.R \name{font_fallback} \alias{font_fallback} \title{Get the fallback font for a given string} \usage{ font_fallback( string, family = "", italic = FALSE, bold = FALSE, path = NULL, index = 0 ) } \arguments{ \item{string}{The strings to find fallbacks for} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{bold}{logical indicating whether the font weight} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data frame with a \code{path} and \code{index} column giving fallback for the specified string and font combinations } \description{ A fallback font is a font to use as a substitute if the chosen font does not contain the requested characters. Using font fallbacks means that the user doesn't have to worry about mixing characters from different scripts or mixing text and emojies. Fallback is calculated for the full string and the result is platform specific. If no font covers all the characters in the string an undefined "best match" is returned. The best approach is to figure out which characters are not covered by your chosen font and figure out fallbacks for these, rather than just request a fallback for the full string. } \examples{ font_fallback("\U0001f604") # Smile emoji } systemfonts/DESCRIPTION0000644000176200001440000000360614743422652014374 0ustar liggesusersType: Package Package: systemfonts Title: System Native Font Finding Version: 1.2.1 Authors@R: c( person("Thomas Lin", "Pedersen", , "thomas.pedersen@posit.co", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-5147-4711")), person("Jeroen", "Ooms", , "jeroen@berkeley.edu", role = "aut", comment = c(ORCID = "0000-0002-4035-0289")), person("Devon", "Govett", role = "aut", comment = "Author of font-manager"), person("Posit, PBC", role = c("cph", "fnd")) ) Description: Provides system native access to the font catalogue. As font handling varies between systems it is difficult to correctly locate installed fonts across different operating systems. The 'systemfonts' package provides bindings to the native libraries on Windows, macOS and Linux for finding font files that can then be used further by e.g. graphic devices. The main use is intended to be from compiled code but 'systemfonts' also provides access from R. License: MIT + file LICENSE URL: https://github.com/r-lib/systemfonts, https://systemfonts.r-lib.org BugReports: https://github.com/r-lib/systemfonts/issues Depends: R (>= 3.2.0) Suggests: covr, farver, graphics, knitr, rmarkdown, testthat (>= 2.1.0) LinkingTo: cpp11 (>= 0.2.1) VignetteBuilder: knitr Encoding: UTF-8 RoxygenNote: 7.3.2 SystemRequirements: fontconfig, freetype2 Config/Needs/website: tidyverse/tidytemplate Imports: grid, jsonlite, lifecycle, tools, utils Config/build/compilation-database: true NeedsCompilation: yes Packaged: 2025-01-20 08:28:46 UTC; thomas Author: Thomas Lin Pedersen [aut, cre] (), Jeroen Ooms [aut] (), Devon Govett [aut] (Author of font-manager), Posit, PBC [cph, fnd] Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2025-01-20 10:30:02 UTC