systemfonts/0000755000176200001440000000000015067231742012657 5ustar liggesuserssystemfonts/tests/0000755000176200001440000000000014672302505014016 5ustar liggesuserssystemfonts/tests/testthat/0000755000176200001440000000000015067231742015661 5ustar liggesuserssystemfonts/tests/testthat/test-match_font.R0000644000176200001440000000132715066740155021110 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_fonts("sans")$path expect_true(file.exists(font_path)) skip_if_not(sysname %in% c("mac", "windows")) # Not deterministic if not expect_equal(tools::file_path_sans_ext(basename(font_path)), font) }) test_that("Default font is correct", { font_path <- match_fonts("sans")$path expect_true(file.exists(font_path)) skip_if_not(sysname %in% c("mac", "windows")) # Not deterministic if not expect_equal(tools::file_path_sans_ext(basename(font_path)), font) }) systemfonts/tests/testthat/test-system_fonts.R0000644000176200001440000000054615017310404021507 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", "variable" ) ) }) systemfonts/tests/testthat.R0000644000176200001440000000010214672302530015770 0ustar liggesuserslibrary(testthat) library(systemfonts) test_check("systemfonts") systemfonts/MD50000644000176200001440000001501615067231742013172 0ustar liggesusers0d041569c1600ce7aa0c65b4565a0c29 *DESCRIPTION 23a3718c6a01d1fb6750f38c780d4392 *LICENSE 81d29530873396451ccb04ddcc9bd174 *NAMESPACE f3d313dc2ab66dceffd6af5d95d89425 *NEWS.md 90b2de409e2be11318813df291175b64 *R/cpp11.R 3d751ceb3d0ccb2c4bf4b595647ca3d1 *R/dev_strings.R 86a1b5487f167368176c09ca0f387866 *R/emoji.R 5d97352d2704425963218867fc968efc *R/font_fallback.R 6d63f44a05bc1e3390f951f2214de4dc *R/font_feature.R a9655b46a18326b5bb4f9835dc155662 *R/font_info.R 7706931a0b4ba4df27b80c9a7dd88128 *R/font_outline.R ddd1d5a8dba7ddf57dd5d60b56ea5d33 *R/font_variation.R da2e0eb0a7122ab5a066057580e6bf32 *R/match_fonts.R 8c242b729a732f0d12874fd73c88649c *R/register_font.R 2508e69b1e701c0113914fa1e781711a *R/shape_string.R 450a8e74d61467c86628109be3551119 *R/sysdata.rda 7e0372949007f70646c13aae6a9256e2 *R/system_fonts.R 4006c34d424b9058b0e445202a5c0574 *R/systemfonts-package.R 14c1c7b17d8a9d122a4a366b86923b63 *R/web_fonts.R f5e360aad3aeb03afd3e7b943503747b *R/zzz.R afdcec8eaaa0cad4bc5a0f259ea6e426 *README.md 6aa51dd8c0be046a3100097957a74200 *build/vignette.rds 0d45fe9e64d559e4275a84df252448fd *cleanup 7c5b3c7c335263478b8d247dca3b7e10 *configure 0d7b913557fd5fb032c037d7bd6261ca *inst/doc/c_interface.R abb78422cd255d6b1848a6e8d408dffe *inst/doc/c_interface.Rmd 9fb1095245fc5c194e22343c4435430c *inst/doc/c_interface.html 1635454cac6ee9540af4321b085c79b4 *inst/doc/fonts_basics.R 7c252522c509fd19b4c24e8763327f05 *inst/doc/fonts_basics.Rmd 38eba25b89988ab30d65d928361a0629 *inst/doc/fonts_basics.html 96c2431902d0e2dacd2715868d84f3ad *inst/doc/systemfonts.R 20bd907260f72d905c71e2c6a3967849 *inst/doc/systemfonts.Rmd 7e6577954dce435793ac8ea7ef639ff4 *inst/doc/systemfonts.html 80363776c5ae11fe7d6a4e4fa4d7d644 *inst/include/systemfonts-ft.h de08d3112a9dda86aa234ff89ce7ac03 *inst/include/systemfonts.h a11ff11ac7db5df57660f50d65378bf2 *inst/unfont.ttf 43745ee0a533c80c6eb2c921a0d5c5b7 *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 c7f5a698f1425a5cc9ed03d6588d98b0 *man/figures/logo.png 0101d7245e608aa8339875385aa195e5 *man/font_fallback.Rd 97c15feb01289da674f78500b9552895 *man/font_feature.Rd 862174cd434eedf3abfbce969d3bd33c *man/font_info.Rd 0e20eaca50df780a8b8d4df8e1f5a5a2 *man/font_variation.Rd 92b8c834dc07a741dc9ccd4aee8bbf7d *man/fonts_as_import.Rd c4332e3048542cbeda8a4a7f440fc124 *man/get_fallback.Rd c6479196411422bbfea6830b49ce0a6b *man/glyph_info.Rd 6e3db3d45f2e2cb6f6c12a146cee73eb *man/glyph_outline.Rd 8fd5d0c2e0143fb4839fd90b469d61a5 *man/glyph_raster.Rd 2c7d4769d94f80d5a5a83839ecce4c84 *man/glyph_raster_grob.Rd 74a37354f73dd54adbdf806db5b67df4 *man/match_fonts.Rd 21947119d04623d09bf9b0bcb7825276 *man/plot_glyph_stats.Rd dd810c906dc7c6b29adbdf3f0320aaa3 *man/register_font.Rd 4abcf3f2e11548211e3481819704da30 *man/register_variant.Rd 5617f7de52e8df4d76a34a7b845fb665 *man/require_font.Rd 410487ad9c7fce4f2351ebf1569f6e2b *man/reset_font_cache.Rd e3d67355d5637ee1ab73dedb73a5bb16 *man/search_web_fonts.Rd e8281e036262c11e7509925eebb3ff2b *man/shape_string.Rd 90c6a72cba2eb04583949c6efa2340ba *man/str_split_emoji.Rd 74916bb9316fb3b3ef6f9f9b6358dba4 *man/string_metrics_dev.Rd b451f4dc1249c386af310e4c86fdd9b1 *man/string_width.Rd c72ad2a644aa183945b66164aee58ec6 *man/string_widths_dev.Rd 8c3dc7bc1e63c3195611e49364bea5da *man/system_fonts.Rd 1cd1b6a495a0ad641f3d6c489654bf9a *man/systemfonts-package.Rd 78f2132e8d8317f8ece0660e8983f845 *man/web-fonts.Rd 78ce33ad58e39d2729830083e2a98cea *src/FontDescriptor.h d0e1c9348c98db9814169bba4527837b *src/Makevars.in 2e95ad6eb4f0e7261fd10b1a1f23d641 *src/Makevars.win f21f62b8a7e300c356c8ac326552f0c2 *src/cache_lru.h f43a2abadf956953d9b5f8d4bb0b4196 *src/cache_store.cpp 72d8c4b16ff1478d5a8b50db0c8c01a9 *src/cache_store.h 3cd47d1c9279034d7789382993c5c062 *src/caches.cpp 1a997562bc4c7564e8b1fdc12c449d07 *src/caches.h bf051b58da111c29138954f47e734803 *src/cpp11.cpp 8e340c25049f84658747dc1ec0f0fd18 *src/dev_metrics.cpp 689e955be1d6b4baf0c9a0811ae3b9aa *src/dev_metrics.h 04b170936547e799a6875fda13577cb5 *src/emoji.cpp a82f523519ac1dd5809e8e5bdd6505d2 *src/emoji.h b4807fa18976a4d3ddbdd56ae393aef7 *src/font_fallback.cpp 784e382bb407b32f84fff00767a6c760 *src/font_fallback.h 1198c88431af3b51a5283635fa6e0a7d *src/font_local.cpp 9b94ae92d464a14a982428d925de2328 *src/font_local.h f781c55ebe328100c02745c23d4a483e *src/font_matching.cpp 06f4e160ebc99bdb456f34171fa923b0 *src/font_matching.h fc98aa27f56e8da0c888d1c9e2556f9d *src/font_metrics.cpp b423c45ecc7d443840f3c7f802adf89d *src/font_metrics.h 9fb2310fb9798b6353f653ea209c75d8 *src/font_outlines.cpp f090ad50f988f413bcfd2ab087417593 *src/font_outlines.h 0a71a931f0874bab02a2f4f2eff020a4 *src/font_registry.cpp 8b56ef67884fa3b41f6205d30e8afcdd *src/font_registry.h 035c48ebc3b79818e98920a747f12d92 *src/font_variation.cpp 7a638fc081cc538f703af5c8be05f209 *src/font_variation.h b38350fa2e6fb2b79060bcd539ba2b5d *src/ft_cache.cpp 80d1ac62bfc5faebf5a68e365d7ed6b2 *src/ft_cache.h 6c1d002cfbd08c190f4119331fd407f1 *src/init.cpp 4e4e05775106d005c23ec7863904661a *src/mac/FontManagerMac.mm b3bedef5c2657632a2d7c4c0e6be898c *src/string_metrics.cpp e2a636534334f3ce1bf42187f2055476 *src/string_metrics.h 06432554ddaac25eb3bff7a5fd9db68e *src/string_shape.cpp 8feb4ff7fa00b41bdb430718e9cddaf3 *src/string_shape.h 4a7d508826eeb5f6f29cc58dbdd720be *src/types.h 49e2ef4d914afaa955f1935bb4d6112f *src/unix/FontManagerLinux.cpp dcf3f72467c9e1a71b86ff9384e0397f *src/utils.h 882d23bb7c881e87eb82a0de7952d0da *src/win/DirectWriteFontManagerWindows.cpp 5611ed971c89b566695be7fb3e774fb2 *src/win/FontManagerWindows.cpp 2b7eaaf1f6572e72dde8052c8c14412a *tests/testthat.R 7dc3767775c752fb2d751006fa6891e3 *tests/testthat/test-match_font.R 15141ee2fce5fde26f9038407a3bd0bc *tests/testthat/test-system_fonts.R 50216300566a3d9d1468aa834640cd58 *tools/winlibs.R abb78422cd255d6b1848a6e8d408dffe *vignettes/c_interface.Rmd 7c252522c509fd19b4c24e8763327f05 *vignettes/fonts_basics.Rmd 20bd907260f72d905c71e2c6a3967849 *vignettes/systemfonts.Rmd 7177bbbcbc637696e8ec14c9e4f5dcb0 *vignettes/text_call_flow.svg systemfonts/R/0000755000176200001440000000000015067177201013057 5ustar liggesuserssystemfonts/R/system_fonts.R0000644000176200001440000000204015002125620015716 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.R0000644000176200001440000000604115002125620015515 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.R0000644000176200001440000000623315067177201014132 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, variations) { .Call(`_systemfonts_get_fallback_c`, path, index, string, variations) } 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, variations) { .Call(`_systemfonts_get_font_info_c`, path, index, size, res, variations) } get_glyph_info_c <- function(glyphs, path, index, size, res, variations) { .Call(`_systemfonts_get_glyph_info_c`, glyphs, path, index, size, res, variations) } get_glyph_outlines <- function(glyph, path, index, size, variations, tolerance, verbose) { .Call(`_systemfonts_get_glyph_outlines`, glyph, path, index, size, variations, tolerance, verbose) } get_glyph_bitmap <- function(glyph, path, index, size, res, variations, color, verbose) { .Call(`_systemfonts_get_glyph_bitmap`, glyph, path, index, size, res, variations, 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`) } axes_to_tags <- function(axes) { .Call(`_systemfonts_axes_to_tags`, axes) } tags_to_axes <- function(tags) { .Call(`_systemfonts_tags_to_axes`, tags) } values_to_fixed <- function(values) { .Call(`_systemfonts_values_to_fixed`, values) } fixed_to_values <- function(fixed) { .Call(`_systemfonts_fixed_to_values`, fixed) } 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.R0000644000176200001440000001455315066760575015532 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.numeric(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 - 1) * 100 } else if (is.numeric(weight) || is.integer(weight)) { weight[is.na(weight)] <- 0 } else { stop("Can't convert this object to a font weight", call. = FALSE) } as.numeric(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 - 1 } else if (is.numeric(width) || is.integer(width)) { width[is.na(width)] <- 0 } else { stop("Can't convert this object to a font width", call. = FALSE) } as.numeric(width) } systemfonts/R/register_font.R0000644000176200001440000002372215067155073016065 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 #' #' @examplesIf nrow(system_fonts()) >= 4 #' # Create a random font collection #' fonts <- system_fonts() #' random_fonts <- sample(nrow(fonts), 4) #' register_font( #' 'random', #' plain = list(fonts$path[random_fonts[1]], fonts$index[random_fonts[1]]), #' bold = list(fonts$path[random_fonts[2]], fonts$index[random_fonts[2]]), #' italic = list(fonts$path[random_fonts[3]], fonts$index[random_fonts[3]]), #' bolditalic = list(fonts$path[random_fonts[4]], fonts$index[random_fonts[4]]) #' ) #' #' # 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 called `", 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(name, " refers to non-existing font file(s)", 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 privileges 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 or urls 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") } urls <- grepl("^https?://", files) if (any(urls)) { dest <- vapply(which(urls), function(i) tempfile(), character(1)) success <- utils::download.file(files[urls], dest, method = "libcurl") if (success != 0) { stop( "Download of font files failed with the libcurl error ", success, call. = FALSE ) } files[urls] <- dest } do_exist <- file.exists(files) if (!all(do_exist)) { warning( "all elements in `files` must be paths to existing files. Ignoring ", paste0(files[!do_exist], collapse = ", ") ) files <- files[do_exist] } if (length(files) > 0) { add_local_fonts(vapply(files, normalizePath, 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.R0000644000176200001440000000225615017310614014034 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() windows_workaround() } 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 ) } # See https://github.com/r-lib/textshaping/issues/36 windows_workaround <- function(){ # This dll needs to be loaded before the textshaping pkg so we do it here. # It is harmless otherwise (it is also loaded by e.g. RGui in Windows) if(.Platform$OS.type == 'windows'){ if(file.exists("C:\\Windows\\System32\\TextShaping.dll")){ dyn.load("C:\\Windows\\System32\\TextShaping.dll") } } } `%||%` <- function(a, b) if (is.null(a)) b else a systemfonts/R/font_feature.R0000644000176200001440000001545515017511630015666 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 surrounding #' 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 mimic #' 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.R0000644000176200001440000000427215017310404015761 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, weight = "normal", width = "undefined", path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) { if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "1.2.4", "font_fallback(bold)", "font_fallback(weight)" ) weight <- ifelse(bold, "bold", "normal") } if (is_font_variation(variation)) variation <- list(variation) full_length <- length(string) if (is.null(path)) { fonts <- match_fonts( family = rep_len(family, full_length), italic = rep_len(italic, full_length), weight = rep_len(weight, full_length), width = rep_len(width, full_length) ) 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) variation <- rep_len(variation, 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), variation) } systemfonts/R/font_info.R0000644000176200001440000001701715017511630015162 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 measures #' @param variation A `font_variation` object or a list of them to control #' variable fonts #' @param path,index path and index of a font file to circumvent lookup based on #' family and style #' @param bold `r lifecycle::badge("deprecated")` Use `weight = "bold"` instead #' #' @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{name}{The name of the font, if present, otherwise the family name} #' \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, weight = "normal", width = "undefined", size = 12, res = 72, path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) { if (is_font_variation(variation)) variation <- list(variation) full_length <- max( length(size), length(res), length(italic), length(weight), length(width), length(variation) ) if (is.null(path)) { if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft("1.2.4", "font_info(bold)", "font_info(weight)") weight <- ifelse(bold, "bold", "normal") } full_length <- max( length(family), full_length ) italic <- rep_len(italic, full_length) weight <- rep_len(weight, full_length) width <- rep_len(width, full_length) fonts <- match_fonts( family = rep_len(family, full_length), italic = italic, weight = weight, width = width ) path <- fonts$path index <- fonts$index } else { full_length <- max(length(path), length(index), full_length) italic <- rep_len(italic, full_length) weight <- rep_len(weight, full_length) width <- rep_len(width, 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) } variation <- add_standard_to_variations( rep_len(variation, full_length), italic = italic, weight = weight, width = width ) get_font_info_c( path, as.integer(index), as.numeric(size), as.numeric(res), variation ) } #' 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, weight = "normal", width = "undefined", size = 12, res = 72, path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) { if (is_font_variation(variation)) variation <- list(variation) n_strings <- length(glyphs) glyphs <- strsplit(glyphs, '') n_glyphs <- lengths(glyphs) glyphs <- unlist(glyphs) if (is.null(path)) { if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "1.2.4", "glyph_info(bold)", "glyph_info(weight)" ) weight <- ifelse(bold, "bold", "normal") } italic <- rep_len(italic, n_strings) weight <- rep_len(weight, n_strings) width <- rep_len(width, n_strings) fonts <- match_fonts( family = rep_len(family, n_strings), italic = italic, weight = weight, width = width ) path <- rep(fonts$path, n_glyphs) index <- rep(fonts$index, n_glyphs) italic <- rep(italic, n_glyphs) weight <- rep(weight, n_glyphs) width <- rep(width, 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) } italic <- rep(rep_len(italic, n_strings), n_glyphs) weight <- rep(rep_len(weight, n_strings), n_glyphs) width <- rep(rep_len(width, 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) } variation <- add_standard_to_variations( rep(rep_len(variation, n_strings), n_glyphs), italic = italic, weight = weight, width = width ) get_glyph_info_c( glyphs, path, as.integer(index), as.numeric(size), as.numeric(res), variation ) } systemfonts/R/font_outline.R0000644000176200001440000002345515017310404015705 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 variation A `font_variation` object or a list of them to control #' variable fonts #' @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, variation = font_variation(), verbose = FALSE ) { if (is_font_variation(variation)) variation <- list(variation) 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) variation <- rep_len(variation, n_glyphs) tolerance <- as.numeric(tolerance) get_glyph_outlines( glyph, path, index, size, variation, tolerance, as.logical(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, variation = font_variation(), col = "black", verbose = FALSE ) { if (is_font_variation(variation)) variation <- list(variation) 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) variation <- rep_len(variation, n_glyphs) get_glyph_bitmap(glyph, path, index, size, res, variation, 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"), ... ) } #' Create a visual representation of what the various glyph stats mean #' #' This function helps you understand the concepts of width, height, bearing, #' and advance by annotating a glyph with the various measures #' #' @param glyph The character to plot #' @inheritParams glyph_info #' #' @return This function is called for its side effects #' #' @export #' #' @examples #' plot_glyph_stats("g") #' plot_glyph_stats <- function( glyph, family = '', italic = FALSE, weight = "normal", width = "undefined", size = 12, res = 72, variation = font_variation(), path = NULL, index = 0 ) { font <- font_info( family = family, italic = italic, weight = weight, width = width, path = path, index = index ) info <- glyph_info( glyphs = glyph, family = family, italic = italic, weight = weight, width = width, size = size, res = res, variation = variation, path = path, index = index ) outline <- glyph_outline( glyph = info$index[1], path = font$path[1], index = font$index[1], size = size, variation = variation, tolerance = 0.1 ) x_scale <- c(min(0, info$x_bearing), info$x_advance) y_scale <- c(min(0, info$bbox[[1]][3]), info$bbox[[1]][4]) max_dim <- max(diff(x_scale), diff(y_scale)) gutter <- max_dim * 0.1 * c(-1, 1) x_scale <- x_scale + gutter y_scale <- y_scale + gutter max_dim <- max_dim * 1.2 vp <- grid::viewport( width = grid::unit(diff(x_scale) / max_dim, "snpc"), height = grid::unit(diff(y_scale) / max_dim, "snpc"), xscale = x_scale, yscale = y_scale, clip = "off" ) bbox <- grid::rectGrob( x = info$bbox[[1]][1], y = info$bbox[[1]][3], width = info$bbox[[1]][2] - info$bbox[[1]][1], height = info$bbox[[1]][4] - info$bbox[[1]][3], hjust = 0, vjust = 0, gp = grid::gpar(col = NA, fill = "grey90"), default.units = "native", vp = vp ) glyph <- grid::pathGrob( outline$x, outline$y, id = outline$contour, gp = grid::gpar(col = "black", fill = "white"), default.units = "native", vp = vp ) coord <- grid::segmentsGrob( x0 = grid::unit(c(0, 0), c("npc", "native")), y0 = grid::unit(c(0, 0), c("native", "npc")), x1 = grid::unit(c(1, 0), c("npc", "native")), y1 = grid::unit(c(0, 1), c("native", "npc")), vp = vp, gp = grid::gpar(lty = 3) ) origin <- grid::pointsGrob( 0, 0, vp = vp, pch = 19, size = grid::unit(0.03, "snpc") ) arrows <- grid::segmentsGrob( x0 = c( info$x_bearing, info$bbox[[1]][2] + max_dim * 0.02, info$bbox[[1]][1] - max_dim * 0.02, 0, 0 ), y0 = c( info$y_bearing + max_dim * 0.02, info$y_bearing, 0, info$y_bearing + max_dim * 0.05, info$bbox[[1]][3] - max_dim * 0.02 ), x1 = c( info$x_bearing + info$width, info$bbox[[1]][2] + max_dim * 0.02, info$bbox[[1]][1] - max_dim * 0.02, info$x_bearing, info$x_advance ), y1 = c( info$y_bearing + max_dim * 0.02, info$y_bearing - info$height, info$y_bearing, info$y_bearing + max_dim * 0.05, info$bbox[[1]][3] - max_dim * 0.02 ), default.units = "native", arrow = grid::arrow( length = grid::unit(0.015, "snpc"), ends = c("both", "both", "last", "last", "last") ), vp = vp ) labels <- grid::textGrob( label = c( "width", "height", "x bearing", "y bearing", "x advance", "(0,0)" ), x = c( mean(info$bbox[[1]][1:2]), info$bbox[[1]][2] + max_dim * 0.03, min(0, info$x_bearing) - max_dim * 0.01, info$bbox[[1]][1] - max_dim * 0.03, info$x_advance / 2, -max_dim * 0.01 ), y = c( info$y_bearing + max_dim * 0.03, mean(info$bbox[[1]][3:4]), info$y_bearing + max_dim * 0.05, info$y_bearing / 2, info$bbox[[1]][3] - max_dim * 0.03, -max_dim * 0.01 ), hjust = c(0.5, 0, 1, 1, 0.5, 1), vjust = c(0, 0.5, 0.5, 0.5, 1, 1), default.units = "native", vp = vp ) grid::grid.newpage() grid::grid.draw(grid::gList(bbox, coord, origin, glyph, arrows, labels)) } 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_") } paste(axes(x), ': ', coords(x), collapse = '; ', sep = '') } combine_variations <- function(a, b) { a <- unclass(a) b <- unclass(b) new <- setdiff(b$axis, a$axis) current <- intersect(b$axis, a$axis) a$axis <- c(a$axis, new) a$value[match(b$axis, a$axis)] <- b$value `class<-`(a, "font_variation") } add_standard_to_variations <- function(variations, italic, weight, width) { mapply( function(orig_var, italic, weight, width) { combine_variations( font_variation(italic = italic, weight = weight, width = width), orig_var ) }, orig_var = variations, italic = italic, weight = weight, width = width, SIMPLIFY = FALSE ) } axes <- function(x) { tags_to_axes(.subset2(x, "axis")) } coords <- function(x) { fixed_to_values(.subset2(x, "value")) } #' @export length.font_variation <- function(x) { length(.subset2(x, "axis")) } #' @export names.font_variation <- function(x) { axes(x) } #' @export `[[.font_variation` <- function(x, i, ...) { if (is.character(i)) { coords(x)[[match(i, axes(x))]] } else { coords(x)[[i]] } } #' @export `[[<-.font_variation` <- function(x, i, value) { axes <- .subset2(x, "axis") values <- .subset2(x, "value") if (is.character(i)) { if (i == "width") { i <- "wdth" value <- 2^((value - 5) / 4) } else if (i == "weight") { i <- "wght" } else if (i == "italic") { i <- "ital" } else if (i == "slant") { i <- "slnt" } else if (i == "optical_sizing") { i <- "optz" } i <- axes_to_tags(i) ind <- match(i, axes) } else if (i > length(axes)) { stop("subscript out of bounds", call. = FALSE) } else { ind <- i } value <- values_to_fixed(value) if (is.na(ind)) { axes <- c(axes, i) values <- c(values, value) } else { values[ind] <- value } structure( list(axis = axes, value = values), class = "font_variation" ) } #' @export `$.font_variation` <- function(x, name) { x[[name]] } #' @export `$<-.font_variation` <- function(x, name, value) { `[[<-`(x, name, value) } #' @export `[.font_variation` <- function(x, i, ...) { if (is.character(i)) { coords(x)[match(i, axes(x))] } else { coords(x)[i] } } #' @export `[<-.font_variation` <- function(x, i, value) { if (length(value) != length(i)) { stop("`i` and `value` must be the same length") } for (j in seq_along(i)) { x[[i[[j]]]] <- value[[j]] } x } systemfonts/R/shape_string.R0000644000176200001440000002175615005133444015674 0ustar liggesusers#' Calculate glyph positions for strings #' #' 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 `strings`. #' #' @param strings A character vector of strings to shape #' @param id A vector grouping the strings together. If strings share an id the #' shaping will continue between strings #' @inheritParams font_info #' @param lineheight A multiplier for the lineheight #' @param align Within text box alignment, either `'left'`, `'center'`, or #' `'right'` #' @param hjust,vjust The justification of the textbox surrounding the text #' @param max_width The requested with of the string in inches. Setting this to #' something other than `NA` will turn on word wrapping. #' @param tracking Tracking of the glyphs (space adjustment) measured in 1/1000 #' em. #' @param indent The indent of the first line in a paragraph measured in inches. #' @param hanging The indent of the remaining lines in a paragraph measured in #' inches. #' @param space_before,space_after The spacing above and below a paragraph, #' measured in points #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return #' A list with two element: `shape` contains the position of each glyph, #' relative to the origin in the enclosing textbox. `metrics` contain metrics #' about the full strings. #' #' `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 `metrics` data.frame)} #' \item{string_id}{The index of the string the glyph came from (referencing an element in the `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} #' } #' #' `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} #' } #' #' @export #' #' @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)) #' shape_string <- function( strings, id = NULL, family = '', italic = FALSE, weight = "normal", width = "undefined", size = 12, res = 72, lineheight = 1, align = 'left', hjust = 0, vjust = 0, max_width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, index = 0, bold = deprecated() ) { n_strings = length(strings) if (is.null(id)) id <- seq_len(n_strings) id <- rep_len(id, n_strings) id <- match(id, unique(id)) if (anyNA(id)) { stop('id must be a vector of valid integers', call. = FALSE) } ido <- order(id) id <- id[ido] strings <- as.character(strings)[ido] if (is.null(path)) { if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "1.2.4", "shape_string(bold)", "shape_string(weight)" ) weight <- ifelse(bold, "bold", "normal") } fonts <- match_fonts( family = rep_len(family, n_strings), italic = rep_len(italic, n_strings), weight = rep_len(weight, n_strings), width = rep_len(width, n_strings) ) path <- fonts$path[ido] index <- fonts$index[ido] } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, n_strings)[ido] index <- rep_len(index, n_strings)[ido] } } if (length(size) != 1) size <- rep_len(size, n_strings)[ido] if (length(res) != 1) res <- rep_len(res, n_strings)[ido] if (length(lineheight) != 1) lineheight <- rep_len(lineheight, n_strings)[ido] align <- match.arg(align, c('left', 'center', 'right'), TRUE) align <- match(align, c('left', 'center', 'right')) if (length(align) != 1) align <- rep_len(align, n_strings)[ido] if (length(hjust) != 1) hjust <- rep_len(hjust, n_strings)[ido] if (length(vjust) != 1) vjust <- rep_len(vjust, n_strings)[ido] if (length(max_width) != 1) max_width <- rep_len(max_width, n_strings)[ido] max_width[is.na(max_width)] <- -1 if (length(tracking) != 1) tracking <- rep_len(tracking, n_strings)[ido] if (length(indent) != 1) indent <- rep_len(indent, n_strings)[ido] if (length(hanging) != 1) hanging <- rep_len(hanging, n_strings)[ido] if (length(space_before) != 1) { space_before <- rep_len(space_before, n_strings)[ido] } if (length(space_after) != 1) { space_after <- rep_len(space_after, n_strings)[ido] } max_width <- max_width * res indent <- indent * res hanging <- hanging * res if (!all(file.exists(path))) { stop("path must point to a valid file", call. = FALSE) } shape <- get_string_shape_c( strings, id, path, as.integer(index), as.numeric(size), as.numeric(res), as.numeric(lineheight), as.integer(align) - 1L, as.numeric(hjust), as.numeric(vjust), as.numeric(max_width), as.numeric(tracking), as.numeric(indent), as.numeric(hanging), as.numeric(space_before), as.numeric(space_after) ) shape$metrics$string <- vapply( split(strings, id), paste, character(1), collapse = '' ) shape$shape$string_id <- ido[shape$shape$string_id] shape$shape <- shape$shape[order(shape$shape$string_id), , drop = FALSE] shape$shape$glyph <- intToUtf8(shape$shape$glyph, multiple = TRUE) shape$shape$x_offset <- shape$shape$x_offset * (72 / res) shape$shape$y_offset <- shape$shape$y_offset * (72 / res) shape$shape$x_midpoint <- shape$shape$x_midpoint * (72 / res) shape } #' Calculate the width of a string, ignoring new-lines #' #' This is a very simple alternative to [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 `\n`. Input is recycled to the length of `strings`. #' #' @inheritParams font_info #' @param strings A character vector of strings #' @param include_bearing Logical, should left and right bearing be included in #' the string width? #' #' @return A numeric vector giving the width of the strings in pixels. Use the #' provided `res` value to convert it into absolute values. #' #' @export #' #' @examples #' strings <- c('A short string', 'A very very looong string') #' string_width(strings) #' string_width <- function( strings, family = '', italic = FALSE, weight = "normal", width = "undefined", size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0, bold = deprecated() ) { n_strings <- length(strings) if (is.null(path)) { if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "1.2.4", "string_width(bold)", "string_width(weight)" ) weight <- ifelse(bold, "bold", "normal") } fonts <- match_fonts( family = rep_len(family, n_strings), italic = rep_len(italic, n_strings), weight = rep_len(weight, n_strings), width = rep_len(width, n_strings) ) path <- fonts$path index <- fonts$index } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, n_strings) index <- rep_len(index, n_strings) } } if (length(size) != 1) size <- rep_len(size, n_strings) if (length(res) != 1) res <- rep_len(res, n_strings) if (length(include_bearing) != 1) { include_bearing <- rep_len(include_bearing, n_strings) } if (!all(file.exists(path))) { stop("path must point to a valid file", call. = FALSE) } get_line_width_c( as.character(strings), path, as.integer(index), as.numeric(size), as.numeric(res), as.logical(include_bearing) ) } systemfonts/R/web_fonts.R0000644000176200001440000004743115017310402015164 0ustar liggesusers#' Search font repositories for a font based on family name #' #' While it is often advisable to visit the webpage for a font repository when #' looking for a font, in order to see examples etc, `search_web_fonts()` #' provide a quick lookup based on family name in the repositories supported by #' systemfonts (currently [Google Fonts](https://fonts.google.com) and #' [Font Squirrel](https://www.fontsquirrel.com) - [Bunny Fonts](https://fonts.bunny.net/) #' provide the same fonts as Google Fonts but doesn't have a search API). The #' lookup is based on fuzzy matching provided by [utils::adist()] and the #' matching parameters can be controlled through `...` #' #' @param family The font family name to look for #' @param n_max The maximum number of matches to return #' @inheritDotParams utils::adist -x -y #' #' @return A data.frame with the columns `family`, giving the family name of the #' matched font, and `repository` giving the repository it was found in. #' #' @export #' #' @examples #' # Requires an internet connection #' #' # search_web_fonts("Spectral") #' search_web_fonts <- function(family, n_max = 10, ...) { gf <- unique(get_google_fonts_registry()$family) fs <- unique(get_font_squirrel_registry()$family) all <- data.frame( family = c(gf, fs), repository = c(rep( c("Google Fonts/Bunny Fonts", "Font Squirrel"), c(length(gf), length(fs)) )) ) all[ order(utils::adist( x = tolower(family), y = tolower(all$family), ... ))[seq_len(min(n_max, nrow(all)))], ] } #' Download and add web font #' #' 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. #' #' @param family The font family to download (case insensitive) #' @param 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 `tempdir()` to only keep the font for the session. #' @param 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 #' #' @return A logical invisibly indicating whether a font was found and #' downloaded or not #' #' @name web-fonts #' @rdname web-fonts #' NULL #' @rdname web-fonts #' @export #' get_from_google_fonts <- function(family, dir = "~/fonts", woff2 = FALSE) { fonts <- get_google_fonts_registry(woff2) match <- which(tolower(fonts$family) == tolower(family)) if (length(match) == 0) { return(invisible(FALSE)) } if (!dir.exists(dir)) dir.create(dir, recursive = TRUE) files <- fonts$url[match] download_name <- file.path(dir, fonts$file[match]) success <- try( { if (capabilities("libcurl")) { utils::download.file( files, download_name, method = "libcurl", quiet = TRUE, mode = "wb" ) } else { mapply( utils::download.file, url = files, destfile = download_name, quiet = TRUE, mode = "wb" ) } }, silent = TRUE ) if (inherits(success, "try-error")) { return(invisible(FALSE)) } add_fonts(download_name) invisible(TRUE) } #' @rdname web-fonts #' @export #' get_from_font_squirrel <- function(family, dir = "~/fonts") { fonts <- get_font_squirrel_registry() match <- which(tolower(fonts$family) == tolower(family)) if (length(match) == 0) { return(invisible(FALSE)) } if (!dir.exists(dir)) dir.create(dir, recursive = TRUE) files <- fonts$url[match] download_name <- file.path( tempdir(check = TRUE), paste0(basename(fonts$url[match]), ".zip") ) success <- try( { if (capabilities("libcurl")) { utils::download.file( files, download_name, method = "libcurl", quiet = TRUE ) } else { mapply( utils::download.file, url = files, destfile = download_name, quiet = TRUE ) } }, silent = TRUE ) if (inherits(success, "try-error")) { return(invisible(FALSE)) } new_fonts <- unlist(mapply( utils::unzip, zipfile = download_name, MoreArgs = list(exdir = dir) )) is_font <- grepl("\\.(?:ttf|ttc|otf|otc|woff|woff2)$", tolower(new_fonts)) unlink(new_fonts[!is_font]) add_fonts(new_fonts[is_font]) return(invisible(TRUE)) } get_from_font_library <- function(family, dir = "~/fonts") { url <- import_from_font_library(family) if (length(url) == 0) { return(invisible(FALSE)) } url <- readLines(url) urls <- grep("url\\(.*?\\)", url, value = TRUE) urls <- sub(".*url\\((.*?)\\).*", "\\1", urls) urls <- paste0("https://fontlibrary.org", gsub("'|\"", "", urls)) if (!dir.exists(dir)) dir.create(dir, recursive = TRUE) download_name <- file.path(dir, basename(urls)) success <- try( { if (capabilities("libcurl")) { utils::download.file( urls, download_name, method = "libcurl", quiet = TRUE ) } else { mapply( utils::download.file, url = urls, destfile = download_name, quiet = TRUE ) } }, silent = TRUE ) if (inherits(success, "try-error")) { return(invisible(FALSE)) } add_fonts(download_name) return(invisible(TRUE)) } #' Ensure font availability in a script #' #' 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. `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 `fallback` is given, provide an alias for the fallback #' so it maps to the required font. #' #' @param family The font family to require #' @param fallback An available font to fall back to if `family` cannot be found #' or downloaded #' @param dir The location to put the font file downloaded from repositories #' @param 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 #' `"Google Fonts"`, `"Font Squirrel"`, and `"Font Library"` is available. #' @param error Should the function throw an error if unsuccessful? #' @param verbose Should status messages be emitted? #' #' @return Invisibly `TRUE` if the font is available or `FALSE` if not (this can #' only be returned if `error = FALSE`) #' #' @export #' #' @examples #' # Should always work #' require_font("sans") #' require_font <- function( family, fallback = NULL, dir = tempdir(), repositories = c("Google Fonts", "Font Squirrel", "Font Library"), error = TRUE, verbose = TRUE ) { if (tolower(family) %in% c("sans", "serif", "mono", "symbol")) return(invisible(TRUE)) if (!is.character(family) || length(family) != 1) { stop("`family` must be a string") } if ( !is.null(fallback) && (!is.character(fallback) || length(fallback) != 1) ) { stop("`family` must be a string") } fonts <- system_fonts() available <- which(tolower(fonts$family) == tolower(family)) if (length(available) != 0) { if (verbose) { message( "`", family, "` available at ", paste0(unique(dirname(fonts$path[available])), collapse = ", ") ) } return(invisible(TRUE)) } success <- FALSE has_internet <- !inherits( suppressWarnings(try( readLines("https://8.8.8.8", n = 1L), silent = TRUE )), "try-error" ) if (!has_internet) { if (verbose) { message("No internet connection. Can't search online repositories") } } else { for (repo in repositories) { if (verbose) message("Trying ", repo, "...", appendLF = FALSE) success <- switch( tolower(repo), "google fonts" = get_from_google_fonts(family, dir), "font squirrel" = get_from_font_squirrel(family, dir), "font library" = get_from_font_library(family, dir), FALSE ) if (verbose) { if (success) { message(" Found! Downloading font to ", dir) } else { message("Not found.") } } if (success) break } } if (!success) { if (is.null(fallback)) { if (error) stop(paste0( "Required font: ", family, ", is not available on the system" )) } else { message( "Required font: `", family, "`, is not available on the system. Adding alias to `", fallback, "`" ) register_variant(family, fallback) success <- TRUE } } invisible(success) } #' Create import specifications for web content #' #' If you create content in a text-based format such as HTML or SVG you need to #' make sure that the font is available on the computer where it is viewed. This #' can be achieved through the use of stylesheets that can either be added with #' a `` tag or inserted with an `@import` statement. This function #' facilitates the creation of either of these (or the bare URL to the #' stylesheet). It can rely on the Bunny Fonts, Google Fonts and/or Font Library #' repositories for serving the fonts. If the requested font is not found it can #' optionally hard code the data into the stylesheet. #' #' @inheritParams match_fonts #' @param ... Additional arguments passed on to the specific functions for the #' repositories. Currently: #' * **Google Fonts and Bunny Fonts:** #' - `text` A piece of text containing the glyphs required. Using this can #' severely cut down on the size of the required download #' - `display` One of `"auto"`, `"block"`, `"swap"`, `"fallback"`, or #' `"optional"`. Controls how the text is displayed while the font is #' downloading. #' @param type The type of return value. `"url"` returns the bare url pointing #' to the style sheet. `"import"` returns the stylesheet as an import statement #' (`@import url()`). `"link"` returns the stylesheet as a link tag #' (``) #' @param may_embed Logical. Should fonts that can't be found in the provided #' repositories be embedded as data-URLs. This is only possible if the font is #' available locally and in a `woff2`, `woff`, `otf`, or `ttf` file. #' @param repositories The repositories to try looking for the font. Currently #' `"Bunny Fonts"`, `"Google Fonts"`, and `"Font Library"` are supported. Set #' this to `NULL` together with `may_embed = TRUE` to force embedding of the #' font data. #' #' @return A character vector with stylesheet specifications according to `type` #' @export #' fonts_as_import <- function( family, italic = NULL, weight = NULL, width = NULL, ..., type = c("url", "import", "link"), may_embed = TRUE, repositories = c("Bunny Fonts", "Google Fonts", "Font Library") ) { import <- character(0) type <- match.arg(type) if (may_embed) repositories <- c(repositories, "local") all_families <- family for (repo in repositories) { fonts <- switch( tolower(repo), "bunny fonts" = import_from_bunny_fonts( family, italic = italic, weight = weight, width = width, ... ), "google fonts" = import_from_google_fonts( family, italic = italic, weight = weight, width = width, ... ), "font library" = import_from_font_library(family, ...), "local" = import_embedded( family, italic = italic, weight = weight, width = width, ... ), NULL ) if (!is.null(fonts)) { import <- c(import, fonts) missing <- attr(fonts, "no_match") family <- family[missing] if (!is.null(italic)) italic <- italic[missing] if (!is.null(width)) width <- width[missing] if (!is.null(weight)) weight <- weight[missing] } if (length(family) == 0) break } if (length(family) != 0) { warning("No import found for ", paste(family, collapse = ", ")) } imported <- unique(setdiff(all_families, family)) for (f in imported) { success <- require_font(imported, error = FALSE, verbose = FALSE) if (!success) { warning( "An import URL for ", f, " could be made but the font could not be made avialable on the system" ) } } switch( type, import = paste0("@import url('", import, "');"), link = paste0(''), url = import ) } import_from_google_fonts <- function( family, italic = NULL, weight = NULL, width = NULL, ..., text = NULL, display = "swap" ) { n_fonts <- max(length(family), length(italic), length(weight), length(width)) family <- rep_len(family, n_fonts) if (!is.null(italic)) italic <- rep_len(as.integer(italic), n_fonts) if (!is.null(weight)) weight <- rep_len(systemfonts::as_font_weight(weight), n_fonts) if (!is.null(width)) width <- rep_len(systemfonts::as_font_width(width), n_fonts) clusters <- split(seq_along(family), family) possible_fonts <- unique(get_google_fonts_registry()$family) fonts <- lapply(clusters, function(i) { fam <- family[i[1]] fam <- match(tolower(fam), tolower(possible_fonts)) if (is.na(fam)) return() fam <- possible_fonts[fam] fam <- paste0("family=", gsub(" ", "+", family[i[1]])) spec <- list() spec$ital <- if (!is.null(italic)) as.character(unique(range(italic[i]))) if (isTRUE(spec$ital == 0L)) spec$ital <- NULL spec$wdth <- if (!is.null(width)) paste0(unique(range(width[i])), collapse = "..") spec$wght <- if (!is.null(weight)) paste0(unique(range(weight[i])), collapse = "..") spec <- spec[lengths(spec) != 0] if (length(spec) != 0) { n_specs <- max(lengths(spec)) spec <- lapply(spec, rep_len, n_specs) val <- paste0( vapply( seq_len(n_specs), function(i) { paste0(vapply(spec, `[[`, character(1), i), collapse = ",") }, character(1) ), collapse = ";" ) spec <- paste0(names(spec), collapse = ",") fam <- paste0(fam, ":", spec, "@", val) } fam }) missing <- unlist(clusters[lengths(fonts) == 0]) %||% integer() fonts <- paste0(unlist(fonts), collapse = "&") display <- match.arg( display, c("auto", "block", "swap", "fallback", "optional") ) url <- paste0("https://fonts.googleapis.com/css2?", fonts) if (display != "auto") { url <- paste0(url, "&display=", display) } if (!is.null(text)) { url <- paste0(url, "&text=", utils::URLencode(text)) } success <- try(suppressWarnings(readLines(url, n = 1)), silent = TRUE) if (inherits(success, "try-error")) { url <- character(0) missing <- seq_along(family) } structure(url %||% character(), no_match = missing) } import_from_bunny_fonts <- function( family, italic = NULL, weight = NULL, width = NULL, ..., text = NULL, display = "swap" ) { url <- import_from_google_fonts( family = family, italic = italic, weight = weight, width = width, ..., text = text, display = display ) success <- try(suppressWarnings(readLines(url, n = 1)), silent = TRUE) if ( inherits(success, "try-error") || any(grepl("Error: API Error", success)) ) { return(structure(character(), no_match = seq_along(family))) } structure( sub( "https://fonts.googleapis.com/css2?", "https://fonts.bunny.net/css2?", url, fixed = TRUE ), no_match = attr(url, "no_match") ) } import_from_font_library <- function(family, ...) { family <- gsub(" ", "-", tolower(family)) u_fam <- unique(family) names(u_fam) <- u_fam fonts <- lapply(u_fam, function(name) { url <- paste0("https://fontlibrary.org/face/", name) if (length(readLines(url, n = 1)) != 0) { url } else { NULL } }) missing <- which(family %in% names(fonts[lengths(fonts) == 0])) fonts <- unlist(fonts) structure(fonts %||% character(), no_match = missing, names = NULL) } import_embedded <- function( family, italic = NULL, weight = NULL, width = NULL, ... ) { fonts <- match_fonts( family, italic = italic %||% FALSE, weight = weight %||% "normal", width = width %||% "undefined" ) fonts <- lapply(seq_along(family), function(i) { found <- font_info(path = fonts$path[i], index = fonts$index[i]) if (tolower(family[i]) != tolower(found$family)) return() ext <- tools::file_ext(fonts$path[i]) if (!ext %in% c("woff2", "woff", "otf", "ttf")) { warning(ext, "-files cannot be embedded (", family[i], ")") return() } format <- switch( ext, woff2 = 'format("woff2")', woff = 'format("woff")', otf = 'format("opentype")', ttf = 'format("truetype")' ) src <- paste0( "url(data:font/", ext, ";charset=utf-8;base64,", base64enc::base64encode(fonts$path[i]), ") ", format ) features <- paste0( '"', fonts$features[[i]][[1]], '" ', fonts$features[[i]][[2]], collapse = ", " ) # fmt: skip x <- paste0( '@font-face {\n', ' font-family: "', found$family, '";\n', ' src: ', src, ';\n', if (length(fonts$features[[i]]) != 0) paste0( ' font-feature-settings: ', features, ';\n'), if (!is.na(found$width)) paste0( ' font-stretch: ', sub("(ra|mi)", "\\1-", found$width), ';\n'), if (!is.na(found$weight)) paste0( ' font-weight: ', as_font_weight(found$weight), ';\n'), ' font-style: ', if (found$italic) "italic" else "normal", ';\n', '}' ) paste0("data:text/css,", utils::URLencode(x)) }) missing <- which(lengths(fonts) == 0) fonts <- unlist(fonts) structure(fonts %||% character(), no_match = missing) } # 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/cleanup0000755000176200001440000000007015067213520014223 0ustar liggesusers#!/bin/sh rm -f src/Makevars configure.log rm -Rf .deps systemfonts/vignettes/0000755000176200001440000000000015067213517014667 5ustar liggesuserssystemfonts/vignettes/text_call_flow.svg0000644000176200001440000002644515010574476020433 0ustar liggesusers ggplot2 Font information family fontface size Function call geom_text() grid Font information fontfamily fontface fontsize cex Function call textGrob() graphics system Font information fontfamily fontface ps cex Function call GEText() graphics device Font information fontfamily fontface ps cex Function call pGEDevDesctext() systemfonts/vignettes/systemfonts.Rmd0000644000176200001440000002221415067177637017746 0ustar liggesusers--- title: "Using systemfonts to handle fonts in R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using systemfonts to handle fonts in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} google <- suppressWarnings(try(readLines("https://8.8.8.8", n = 1L), silent = TRUE)) is_online <- !inherits(google, "try-error") knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = is_online ) systemfonts::require_font("Spectral", fallback = "serif") ``` ```{asis, echo = !is_online} > This vignette was compiled with no internet access. Since many of the code chunks require access to online font repositories evaluation has been turned off. ``` > This text expects a basic understanding of fonts and typography. If you feel like you could use a brush-up you can consult [this light introduction to the subject](fonts_basics.html). systemfonts is designed to give R a modern text rendering stack. That's unfortunately impossible without coordination with the graphics device, which means that to use all these features you need a supported graphics device. There are currently two options: - The [ragg](https://ragg.r-lib.org) package provides graphics devices for rendering raster graphics in a variety of formats (PNG, JPEG, TIFF) and uses systemfonts and textshaping extensively. - The [svglite](https://svglite.r-lib.org) package provides a graphic device for rendering vector graphics to SVG using systemfonts and textshaping for text. You might notice there's currently a big hole in this workflow: PDFs. This is something we plan to work on in the future. ## A systemfonts based workflow With all that said, how do you actually use systemfonts to use custom fonts in your plots? First, you'll need to use ragg or svglite. ### Using ragg While there is no way to unilaterally make `ragg::agg_png()` the default everywhere, it's possible to get close: - Positron: recent versions automatically use ragg for the plot pane if it's installed. - RStudio IDE: set "AGG" as the backend under Global Options \> General \> Graphics. - `ggplot2::ggsave()`: ragg will be automatically used for raster output if installed. - R Markdown and Quarto: you need to set the `dev` option to `"ragg_png"`. You can either do this with code: ``` r #| include: false knitr::opts_chunk$set(dev = "ragg_png") ``` Or in Quarto, you can set it in the yaml metadata: ``` yaml --- title: "My Document" format: html knitr: opts_chunk: dev: "ragg_png" --- ``` If you want to use a font installed on your computer, you're done! ```{r} #| fig.height: 1 #| out.extra: class='fig' grid::grid.text( "Spectral 🎉", gp = grid::gpar(fontfamily = "Spectral", fontface = 2, fontsize = 30) ) ``` Or, if using ggplot2 ```{r} #| out.extra: class='fig' #| eval: !expr exists("penguins") library(ggplot2) ggplot(na.omit(penguins)) + geom_point(aes(x = bill_len, y = body_mass, colour = species)) + labs(x = "Bill Length", y = "Body Mass", colour = "Species") + theme_minimal(base_family = "Spectral") ``` If the results don't look as you expect, you can use various systemfonts helpers to diagnose the problem: ```{r} systemfonts::match_fonts("Spectral", weight = "bold") systemfonts::font_fallback("🎉", family = "Spectral", weight = "bold") ``` If you want to see all the fonts that are available for use, you can use `systemfonts::system_fonts()` ```{r} #| eval: false systemfonts::system_fonts() ``` ```{r} #| echo: false all_fonts <- systemfonts::system_fonts() all_fonts <- all_fonts[!grepl("^/Users", all_fonts$path),] rmarkdown::paged_table(all_fonts) ``` ### Extra font styles As we discussed above, the R interface only allows you to select between four styles: plain, italic, bold, and bold-italic. If you want to use a thin font, you have no way of communicating this wish to the device. To overcome this, systemfonts provides `register_variant()` which allows you to register a font with a new typeface name. For example, to use the light font from the Spectral typeface you can register it as follows: ```{r} systemfonts::register_variant( name = "Spectral Light", family = "Spectral", weight = "light" ) ``` Now you can use Spectral Light where you would otherwise specify the typeface: ```{r} #| fig.height: 1 #| out.extra: class='fig' grid::grid.text( "Light weight is soo classy", gp = grid::gpar(fontfamily = "Spectral Light", fontsize = 30) ) ``` `register_variant()` also allows you to turn on font features otherwise hidden away: ```{r} #| fig.height: 1 #| out.extra: class='fig' systemfonts::register_variant( name = "Spectral Small Caps", family = "Spectral", features = systemfonts::font_feature( letters = "small_caps" ) ) grid::grid.text( "All caps — Small caps", gp = grid::gpar(fontfamily = "Spectral Small Caps", fontsize = 30) ) ``` ### Fonts from other places Historically, systemfonts primary role was to access the font installed on your computer, the **system fonts**. But what if you're using a computer where you don't have the rights to install new fonts, or you don't want the hassle of installing a font just to use it for a single plot? That's the problem solved by `systemfonts::add_font()` which makes it easy to use a font based on a path. But in many cases you don't even need that as systemfont now scans `./fonts` and `~/fonts` and adds any font files it find. This means that you can put personal fonts in a fonts folder in your home directory, and project fonts in a fonts directory at the root of the project. This is a great way to ensure that specific fonts are available when you deploy some code to a server. And you don't even need to leave R to populate these folders. `systemfonts::get_from_google_fonts()` will download and install a google font in `~/fonts`: ```{r} #| eval: false systemfonts::get_from_google_fonts("Barrio") grid::grid.text( "A new font a day keeps Tufte away", gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) ) ``` ```{r} #| echo: false #| message: false #| fig.height: 1 #| out.extra: class='fig' systemfonts::require_font("Barrio") grid::grid.text( "A new font a day keeps Tufte away", gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) ) ``` And if you want to make sure this code works for anyone using your code (regardless of whether or not they already have the font installed), you can use `systemfonts::require_font()`. If the font isn't already installed, this function download it from one of the repositories it knows about. If it can't find it it will either throw an error (the default) or remap the name to another font so that plotting will still succeed. ```{r} #| fig.height: 1.5 #| out.extra: class='fig' systemfonts::require_font("Rubik Distressed") grid::grid.text( "There are no bad fonts\nonly bad text", gp = grid::gpar(fontfamily = "Rubik Distressed", fontsize = 30) ) ``` By default, `require_font()` places new fonts in a temporary folder so it doesn't pollute your carefully curated collection of fonts. ### Font embedding in SVG Fonts work a little differently in vector formats like SVG. These formats include the raw text and only render the font when you open the file. This makes for small, accessible files with crisp text at every level of zoom. But it comes with a price: since the text is rendered when it's opened, it relies on the font in use being available on the viewer's computer. This obviously puts you at the mercy of their font selection, so if you want consistent outputs you'll need to **embed** the font. In SVG, you can embed fonts using an `@import` statement in the stylesheet, and can point to a web resource so the SVG doesn't need to contain the entire font. systemfonts provides facilities to generate URLs for import statements and can provide them in a variety of formats: ```{r} systemfonts::fonts_as_import("Barrio") systemfonts::fonts_as_import("Rubik Distressed", type = "link") ``` Further, if the font is not available from a given online repository, it can embed the font data directly into the URL: ```{r} substr(systemfonts::fonts_as_import("Arial", repositories = NULL), 1, 200) ``` svglite uses this feature to allow seamless font embedding with the `web_fonts` argument. It can take a URL as returned by `fonts_as_import()` or just the name of the typeface and the URL will automatically be resolved. Look at line 6 in the SVG generated below ```{r} svg <- svglite::svgstring(web_fonts = "Barrio") grid::grid.text("Example", gp = grid::gpar(fontfamily = "Barrio")) invisible(dev.off()) svg() ``` ## Want more? This text has mainly focused on how to use the fonts you desire from within R. R has other limitations when it comes to text rendering specifically how to render text that consists of a mix of fonts. This has been solved by [marquee](https://marquee.r-lib.org) and the curious soul can continue there in order to up their skills in rendering text with R. systemfonts/vignettes/fonts_basics.Rmd0000644000176200001440000035715615066741566020042 0ustar liggesusers--- title: "Typography and R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Typography and R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r} #| include: false knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = FALSE ) ``` This text is meant as an introduction to the subject of typography, both in general but more importantly as it relates to R. If you are more interested in how to use systemfonts to use fonts installed on your computer during plotting then please see [the package introduction vignette](systemfonts.html). > The code examples in this vignette is based on fonts that may not be available on the users machine. As such, you should not expect the provided examples to execute locally out of the box. ## Digital typography Many books could be, and have been, written about the subject of typography. This blog post is not meant to be an exhaustive deep dive into all areas of this vast subject. Rather, it is meant to give you just enough understanding of core concepts and terminology to appreciate how it all plays into using fonts in R. ### Typeface or font? There is a good chance that you, like 99% of world, use "font" as the term describing "the look" of the letters you type. You may, perhaps, have heard the term "typeface" as well and thought it synonymous. This is in fact slightly wrong, and a great deal of typography snobbery has been dealt out on that account (much like the distinction between packages and libraries in R). It is a rather inconsequential mix-up for the most part, especially because 99% of the population wouldn't bat an eye if you use them interchangeably. However, the distinction between the two serves as a good starting point to talk about other terms in digital typography as well as the nature of font files, so let's dive in. When most people use the word "font" or "font family", what they are actually describing is a typeface. A **typeface** is a style of lettering that forms a cohesive whole. As an example, consider the well-known "Helvetica" typeface. This name embraces many different weights (bold, normal, light) as well as slanted (italic) and upright. However, all of these variations are all as much Helvetica as the others - they are all part of the same typeface. A **font** is a subset of a typeface, describing a particular variation of the typeface, i.e. the combination of weight, width, and slant that comes together to describe the specific subset of a typeface that is used. We typically give a specific combination of these features a name, like "bold" or "medium" or "italic", which we call the **font style**[^1]. In other words, a font is a particularly style within a typeface. [^1]: Be aware that the style name is at the discretion of the developer of the typeface. It is very common to see discrepancies between the style name and e.g. the weight reported by the font (e.g. Avenir Next Ultra Light is a *thin* weight font). ```{r} #| echo: false #| fig.cap: "Different fonts from the Avenir Next typeface" #| fig.height: 3.5 st <- marquee::classic_style(30, body_font = "Avenir Next", lineheight = 1, margin = marquee::trbl(4)) |> marquee::modify_style("anul", weight = "thin") |> marquee::modify_style("anub", weight = "ultrabold") text <- paste( "{.anul Avenir Next Ultra Light} ", "{.anul *Avenir Next Ultra Light Italic*} ", "Avenir Next ", "*Avenir Next Italic* ", "{.anub Avenir Next Ultra Bold} ", "{.anub *Avenir Next Ultra Bold Italic*} ", sep = "\n" ) grid::grid.draw( marquee::marquee_grob(text, st) ) ``` ![Different fonts from the Avenir Next typeface](data:image/png;base64,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){.fig} In the rest of this document we will use the terms typeface and font with the meaning described above. ### Font files Next, we need to talk about how typefaces are represented for use by computers. Font files record information on how to draw the individual glyphs (characters), but also instructions about how to draw sequences of glyphs like distance adjustments (kerning) and substitution rules (ligatures). Font files typically encode a single font but can encode a full typeface: ``` r typefaces <- systemfonts::system_fonts()[, c("path", "index", "family", "style")] # Full typeface in one file typefaces[typefaces$family == "Helvetica", ] #> # A tibble: 6 × 4 #> path index family style #> #> 1 /System/Library/Fonts/Helvetica.ttc 2 Helvetica Oblique #> 2 /System/Library/Fonts/Helvetica.ttc 4 Helvetica Light #> 3 /System/Library/Fonts/Helvetica.ttc 5 Helvetica Light Oblique #> 4 /System/Library/Fonts/Helvetica.ttc 1 Helvetica Bold #> 5 /System/Library/Fonts/Helvetica.ttc 3 Helvetica Bold Oblique #> 6 /System/Library/Fonts/Helvetica.ttc 0 Helvetica Regular # One font per font file typefaces[typefaces$family == "Arial", ] #> # A tibble: 4 × 4 #> path index family style #> #> 1 /System/Library/Fonts/Supplemental/Arial.ttf 0 Arial Regular #> 2 /System/Library/Fonts/Supplemental/Arial Bold.ttf 0 Arial Bold #> 3 /System/Library/Fonts/Supplemental/Arial Bold Italic.ttf 0 Arial Bold Italic #> 4 /System/Library/Fonts/Supplemental/Arial Italic.ttf 0 Arial Italic ``` Here, each row is a font, with **family** giving the name of the typeface, and **style** the font style. It took a considerable number of tries before the world managed to nail the digital representation of fonts, leading to a proliferation of file types. As an R user, there are three formats that are particularly important: - **TrueType** (ttf/ttc). Truetype is the baseline format that all modern formats stand on top of. It was developed by Apple in the '80s and became popular due to its great balance between size and quality. Fonts can be encoded, either as scalable paths, or as bitmaps of various sizes, the former generally being preferred as it allows for seamless scaling and small file size at the same time. - **OpenType** (otf/otc). OpenType was created by Microsoft and Adobe to improve upon TrueType. While TrueType was a great success, the number of glyphs it could contain was limited and so was its support for selecting different features during [shaping](#text-shaping). OpenType resolved these issues, so if you want access to advanced typography features you'll need a font in OpenType format. - **Web Open Font Format** (woff/woff2). TrueType and OpenType tend to create large files. Since a large percentage of the text consumed today is delivered over the internet this creates a problem. WOFF resolves this problem by acting as a compression wrapper around TrueType/OpenType to reduce file sizes while also limiting the number of advanced features provided to those relevant to web fonts. The woff2 format is basically identical to woff except it uses the more efficient [brotli](https://en.wikipedia.org/wiki/Brotli) compression algorithm. WOFF was designed specifically to be delivered over the internet and support is still a bit limited outside of browsers. While we have mainly talked about font files as containers for the shape of glyphs, they also carries a lot of other information needed for rendering text in a way pleasant for reading. Font level information records a lot of stylistic information about typeface/font, statistics on the number of glyphs and how many different mappings between character encodings and glyphs it contains, and overall sizing information such as the maximum descend of the font, the position of an underline relative to the baseline etc. systemfonts provides a convenient way to access this data from R: ``` r systemfonts::font_info(family = "Helvetica") #> # A tibble: 1 × 24 #> path index family style italic bold monospace weight width kerning color scalable vertical n_glyphs n_sizes #> #> 1 /Sys… 0 Helve… Regu… FALSE FALSE FALSE normal norm… FALSE FALSE TRUE FALSE 2252 0 #> # ℹ 9 more variables: n_charmaps , bbox , max_ascend , max_descend , #> # max_advance_width , max_advance_height , lineheight , underline_pos , #> # underline_size ``` Further, for each glyph there is a range of information in addition to its shape: ``` r systemfonts::glyph_info("j", family = "Helvetica", size = 30) #> # A tibble: 1 × 9 #> glyph index width height x_bearing y_bearing x_advance y_advance bbox #> #> 1 j 77 6 27 -1 21 7 0 ``` These terms are more easily understood with a diagram: ```{r} #| echo: false systemfonts::plot_glyph_stats("j", family = "Helvetica", size = 30) ``` ![](data:image/png;base64,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ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0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){.fig} The `x_advance` in particular is important when rendering text because it tells you how far to move to the right before rendering the next glyph (ignoring for a bit the concept of kerning) ### Text shaping {#text-shaping} The next important concept to understand is **text shaping**, which, in the simplest of terms, is to convert a succession of characters into a sequence of glyphs along with their locations. Important here is the distinction between **characters**, the things you think of as letters, and **glyphs**, which is what the font will draw. For example, think of the character "f", which is often tricky to draw because the "hook" of the f can interfere with other characters. To solve this problem, many typefaces include **ligatures**, like "fi", which are used for specific pairs of characters. Ligatures are extremely important for languages like Arabic. A few of the challenges of text shaping include kerning, bidirectional text, and font substitution. **Kerning** is the adjustment of distance between specific pairs of characters. For example, you can put "VM" a little closer together but "OO" needs to be a little further apart. Kerning is an integral part of all modern text rendering and you will almost solemnly notice it when it is absent (or worse, [wrongly applied](https://www.creativebloq.com/design/fonts-typography/the-popes-tomb-was-not-on-my-design-debate-bingo-card-for-2025)). Not every language writes text in the same direction, but regardless of your native script, you are likely to use arabic numerals which are always written left-to-right. This gives rise to the challenge of **bidirectional** (or bidi) text, which mixes text flowing in different directions. This imposes a whole new range of challenges! Finally, you might request a character that a font doesn't contain. One way to deal with this is to render a glyph representing a missing glyph, usually an empty box or a question mark. But it's typically more useful to use the correct glyph from a different font. This is called **font fallback** and happens all the time for emojis, but can also happen when you suddenly change script without bothering to pick a new font. Font fallback is an imprecise science, typically relying on an operating system font that has a very large number of characters, but might look very different from your existing font. Once you have determined the order and location of glyphs, you are still not done. Text often needs to be wrapped to fit into a specific width, it may need a specific justification, perhaps, indentation or tracking must be applied, etc. Thankfully, all of this is generally a matter of (often gnarly) math that you just have to get right. That is, all except text wrapping which should happen at the right boundaries, and may need to break up a word and inserting a hyphen etc. Like I said, the pit of despair is bottomless... ## Font handling in R {#font-handling-in-r} You hopefully arrive at this section with an appreciation of the horrors that goes into rendering text. If not, maybe this [blog post](https://faultlore.com/blah/text-hates-you/) will convince you. Are you still here? Good. Now that you understand the basics of what goes into handling fonts and text, we can now discuss the details of fonts in R specifically. ### Fonts and text from a user perspective {#fonts-and-text-from-a-user-perspective} The users perception of working with fonts in R is largely shaped by plots. This means using either base or grid graphics or one of the packages that have been build on top of it, like [ggplot2](https://ggplot2.tidyverse.org). While the choice of tool will affect *where* you specify the font to use, they generally agree on how to specify it. +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | Graphic system | Argument | | | +=============================================================================================================+==============+=======================================================+===============================================================================================================+ | | *Typeface* | *Font* | *Size* | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **Base** | `family` | `font` | `cra` (pixels) or `cin` (inches) multiplied by `cex` | | | | | | | *Arguments are passed to `par()` to set globally or directly to the call that renders text (e.g. `text()`)* | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **Grid** | `fontfamily` | `fontface` | `fontsize` (points) multiplied by `cex` | | | | | | | Arguments are passed to the `gp` argument of relevant grobs using the `gpar()` constructor | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **ggplot2** | `family` | `face` (in `element_text()`) or `fontface` (in geoms) | `size` (points when used in `element_text()`, depends on the value of `size.unit` argument when used in geom) | | | | | | | Arguments are set in `element_text()` to alter theme fonts or directly in the geom call to alter geom fonts | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ From the table it is clear that in R `fontfamily`/`family` is used to describe the typeface and `font`/`fontface`/`face` is used to select a font from the typeface. Size settings is just a plain mess. The major limitation in `fontface` (and friends) is that it takes a number, not a string, and you can only select from four options: `1`: plain, `2`: bold, `3`: italic, and `4`: bold-italic. This means, for example, that there's no way to select Futura Condensed Extra Bold. Another limitation is that it's not possible to specify any font variations such as using tabular numbers or stylistic ligatures. ### Fonts and text from a graphics device perspective In R, a graphics device is the part responsible for doing the rendering you request and put it on your screen or in a file. When you call `png()` or `ragg::agg_png()` you open up a graphics device that will receive all the plotting instructions from R. Both graphics devices will ultimately produce the same file type (PNG), but how they choose to handle and respond to the plotting instructions may differ (greatly). Nowhere is this difference more true than when it comes to text rendering. After a user has made a call that renders some text, it is funneled through the graphic system (base or grid), handed off to the graphics engine, which ultimately asks the graphics device to render the text. From the perspective of the graphics device it is much the same information that the user provided which are presented to it. The `text()` method of the device are given an array of characters, the typeface, the size in points, and an integer denoting if the style is regular, bold, italic, or bold-italic. ![Flow of font information through the R rendering stack](text_call_flow.svg){.fig fig-alt="A diagram showing the flow of text rendering instructions from ggplot2, grid, the graphics engine, and down to the graphics device. Very little changes in the available information about the font during the flow"} This means that it is up to the graphics device to find the appropriate font file (using the provided typeface and font style) and shape the text with all that that entails. This is a lot of work, which is why text is handled so inconsistently between graphics devices. Issues can range from not being able to find fonts installed on the computer, to not providing font fallback mechanisms, or even handling right-to-left text. It may also be that certain font file formats are not well supported so that e.g. color emojis are not rendered correctly. There have been a number of efforts to resolve these problems over the years: - **extrafont**: Developed by Winston Chang, [extrafont](https://github.com/wch/extrafont) sought to mainly improve the situation for the `pdf()` device which generally only had access to the postscript fonts that comes with R. The package allows the `pdf()` device to get access to TrueType fonts installed on the computer, as well as provide means for embedding the font into the PDF so that it can be opened on systems where the font is not installed. (It also provides the capabilities to the Windows `png()` device). - **sysfonts** and **showtext**. These packages are developed by Yixuan Qiu and provide support for system fonts to all graphics devices, by hijacking the `text()` method of the graphics device to treat text as polygons or raster images. This guarantees your plots will look the same on every device, but it doesn't do advanced text shaping, so there's no support for ligatures or font substitution. Additionally, it produces large files with inaccessible text when used to produce pdf and svg outputs. - **systemfonts** and **textshaping**. These packages are developed by me to provide a soup-to-nuts solution to text rendering for graphics devices. [systemfonts](https://systemfonts.r-lib.org) provides access to fonts installed on the system along with font fallback mechanisms, registration of non-system fonts, reading of font files etc. [textshaping](https://github.com/r-lib/textshaping) builds on top of systemfonts and provides a fully modern engine for shaping text. The functionality is exposed both at the R level and at the C level, so that graphics devices can directly access to font lookup and shaping. systemfonts/vignettes/c_interface.Rmd0000644000176200001440000001505215066503755017605 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} #| label: 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_fonts()` imported into your package namespace. All functions are provided in the `systemfonts::ver2` namespace. Legacy API is not namespaced. The different functionality will be discussed below: ## Font matching The C equivalent of the `match_fonts()` R function is `locate_font()` with the following signature: ```C FontSettings2 locate_font( const char *family, double italic, double weight, double width, const int* axes, const int* coords, int n_axes ) ``` It takes a UTF-8 encoded string with the font family name, a double giving italic (usually 0.0 == "upright" and 1.0 == "italic"), a double giving weight (usually ranging between 100.0 and 1000.0 — 0.0 means "undefined") and a double giving "width" (usually ranging from 1.0 to 10.0 — 0.0 means undefined). Lastly you can provide variable axis coords with the `axes` and `coords` array pointers with `n_axes` giving the number in the arrays (which are assumed to be of the same length). The values of each array are not immediately understandable to the human eye and will usually come from a user through a call to `font_variation()`. If the axes array contain "ital", "wght", and/or "wdth" *and* the font has these variable axes then the values for these axes will overwrite the values provide in `italic`, `weight`, and `width`. The returned `FontSettings2` struct will contain both the font location and index along with any OpenType feature settings and the axes settings in the case of a variable font. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings2` struct as the `features`, `axes`, and `coords` arrays 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; const int* axes; const int* coords; int n_axes; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const FontSettings2& font, 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, with a `FontSettings2` object describing the font. 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. ## 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 int error pointer to 0 if successful. ```C get_cached_face( const FontSettings2& font, 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 responsibility of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Check for Freetype compatibility If you are using a cached face from systemfonts you should ensure that your code has been compiled with the same version of Freetype as systemfonts has. You can do this with the `check_ft_version()` from the `systemfonts-ft.h` header. It takes no arguments and return `true` if the Freetype version from systemfonts corresponds with the one your library is compiled with. ## 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 `FontSettings2` object, though features are always empty. ```C FontSettings2 get_fallback( const char* string, const FontSettings2& font ) ``` ## 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 `FontSettings2` object and returns the weight (100-900 or 0 if it is undefined by the font) respecting the variable axes settings if given. ```C int get_font_weight( const FontSettings2& font ) ``` ## Family name It may be beneficial to know the family name from a given 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 FontSettings2& font, 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 FontSettings2& font ) ``` systemfonts/src/0000755000176200001440000000000015067213517013446 5ustar liggesuserssystemfonts/src/win/0000755000176200001440000000000015017511630014233 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.cpp0000644000176200001440000003221115017511630020512 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; } FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); font_list.push_back(new FontDescriptor(face, font_path.c_str(), 0, error == 0 && variations->num_axis != 0)); FT_Done_MM_Var(library, variations); 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 (!result->var_wght && desc->weight && desc->weight != result->weight) return false; if (!result->var_wdth && desc->width && desc->width != result->width) return false; if (!result->var_ital && 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 "Javanese 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.h0000644000176200001440000000510215017310404014745 0ustar liggesusers#pragma once #include #include #include #include #include #include // The exact location of a single file struct FontLoc { std::string file; unsigned int index; std::vector axes; std::vector coords; }; // 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; FontSettings() : index(0), features(nullptr), n_features(0) { file[0] = '\0'; } }; // A structure to pass around a single font with features and variable axes (used by the C interface) struct FontSettings2 : public FontSettings { const int* axes; const int* coords; int n_axes; FontSettings2() : axes(nullptr), coords(nullptr), n_axes(0) { } FontSettings2(FontSettings x) : axes(nullptr), coords(nullptr), n_axes(0) { strncpy(file, x.file, PATH_MAX + 1); index = x.index; features = x.features; n_features = x.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.cpp0000644000176200001440000000643515017310404016733 0ustar liggesusers#include #include #include #include "font_fallback.h" #include "FontDescriptor.h" #include "cpp11/list.hpp" #include "cpp11/list_of.hpp" #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, const int* axes = nullptr, const int* coords = nullptr, int n_axes = 0) { FreetypeCache& cache = get_font_cache(); if (!cache.load_font(file, index)) { return NULL; } cache.set_axes(axes, coords, n_axes); 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, cpp11::list_of variations) { 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]), INTEGER(variations[i]["axis"]), INTEGER(variations[i]["value"]), Rf_xlength(variations[i]["axis"]) ); 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; } FontSettings2 request_fallback2(const char *string, const FontSettings2& font) { FontDescriptor *fallback = fallback_font(font.file, font.index, string, font.axes, font.coords, font.n_axes); FontSettings2 result = {}; if (fallback == NULL) { return font; } 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); R_RegisterCCallable("systemfonts", "get_fallback2", (DL_FUNC)request_fallback2); } systemfonts/src/caches.cpp0000644000176200001440000000167215017310404015372 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 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(); 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 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.cpp0000644000176200001440000000742615017310404017045 0ustar liggesusers#include "font_registry.h" #include "caches.h" #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; 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.win0000644000176200001440000000154415017310404015726 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_variation.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.cpp0000644000176200001440000000022215017310404015075 0ustar liggesusers#include #include "caches.h" extern "C" void R_unload_systemfonts(DllInfo *dll) { unload_caches(dll); unload_ft_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.h0000644000176200001440000000315215017310404016424 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); FontSettings2 locate_font_with_features2(const char *family, double italic, double weight, double width, const int* axes, const int* coords, int n_axes); [[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::doubles italic, cpp11::doubles weight, cpp11::doubles 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.h0000755000176200001440000002174315017310404016562 0ustar liggesusers#ifndef FONT_DESCRIPTOR_H #define FONT_DESCRIPTOR_H #include #include #include #include #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H #include FT_MULTIPLE_MASTERS_H #include "utils.h" #include "ft_cache.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; } inline FontWeight fixed_to_weight(int weight) { return (FontWeight) int(weight / FIXED_MOD); } inline int weight_to_fixed(double weight) { return weight * FIXED_MOD; } inline bool fixed_to_italic(int italic) { return italic / FIXED_MOD > 0.5; } inline int italic_to_fixed(double italic) { return italic < 0.0 ? 0 : (italic > 1.0 ? FIXED_MOD : italic * FIXED_MOD); } inline FontWidth fixed_to_width(int width) { return width <= 0 ? FontWidthUndefined : (FontWidth) int(std::log2(width / FIXED_MOD) * 4.0 + 5.0); } inline int width_to_fixed(double width) { return width <= 0.0 ? 0 : std::pow(2.0, (width - 5.0) / 4.0) * FIXED_MOD; } 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; bool variable; bool var_wght; bool var_wdth; bool var_ital; FontDescriptor() { path = NULL; index = -1; postscriptName = NULL; family = NULL; style = NULL; weight = FontWeightUndefined; width = FontWidthUndefined; italic = false; monospace = false; variable = false; add_var_flags(); } // 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; this->variable = false; add_var_flags(); } // 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; this->variable = false; add_var_flags(); } // Constructor added by Thomas Lin Pedersen FontDescriptor(FT_Face face, const char* path, int index, bool variable = false) { 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); this->variable = variable; add_var_flags(); } FontDescriptor(const char *path, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace, bool variable = false) { 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; this->variable = variable; add_var_flags(); } FontDescriptor(const char *path, int index, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace, bool variable = false) { 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; this->variable = variable; add_var_flags(); } 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; variable = desc->variable; var_wght = desc->var_wght; var_wdth = desc->var_wdth; var_ital = desc->var_ital; } 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 && !var_wght && !other.var_wght && weight != other.weight) return false; if (width && !var_wdth && !other.var_wdth && width != other.width) return false; if (!var_ital && !other.var_ital && 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; } void add_var_flags() { if (variable && path != NULL) { FreetypeCache& cache = get_font_cache(); if (cache.load_font(path, 0)) { cache.has_axes(var_wght, var_wdth, var_ital); return; } } var_wght = false; var_wdth = false; var_ital = false; } }; 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.h0000644000176200001440000000223515017310404016301 0ustar liggesusers#pragma once #include "cpp11/list_of.hpp" #include "types.h" #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::list_of variations); [[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, cpp11::list_of variations); int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width); int glyph_metrics2(uint32_t code, const FontSettings2& font, double size, double res, double* ascent, double* descent, double* width); int font_weight(const char* fontfile, int index); int font_weight2(const FontSettings2& font); 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.cpp0000644000176200001440000003715015067176641015722 0ustar liggesusers#include "ft_cache.h" #include "FontDescriptor.h" #include "R_ext/Print.h" #include "utils.h" #include #include #include #include #include #include #include #include #include FreetypeCache::FreetypeCache() : error_code(0), glyphstore(), face_cache(16), size_cache(32), cur_id(), cur_var(0), cur_size(-1), cur_res(-1), cur_can_kern(false), cur_glyph(0), cur_has_variations(false) { 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_var = 0; cur_size = size; cur_res = res; glyphstore.clear(); cur_can_kern = FT_HAS_KERNING(face); cur_has_variations = is_variable(); 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_var = 0; 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; err = FT_New_Face(this->library, face.file.c_str(), 0, &new_face); if (err != 0) { 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; } std::string enc_to_string(FT_Encoding_ enc) { switch(enc) { case FT_ENCODING_NONE: return "none"; case FT_ENCODING_UNICODE: return "unicode"; case FT_ENCODING_MS_SYMBOL: return "microsoft symbol"; case FT_ENCODING_SJIS: return "shift jis"; case FT_ENCODING_PRC: return "prc"; case FT_ENCODING_BIG5: return "big5"; case FT_ENCODING_WANSUNG: return "extended wansung"; case FT_ENCODING_JOHAB: return "johab"; case FT_ENCODING_ADOBE_LATIN_1: return "abobe latin-1"; case FT_ENCODING_ADOBE_STANDARD: return "adobe standard"; case FT_ENCODING_ADOBE_EXPERT: return "adobe expert"; case FT_ENCODING_ADOBE_CUSTOM: return "adobe custom"; default: return ""; } } FontFaceInfo FreetypeCache::font_info() { FontFaceInfo res = {}; res.family = std::string(face->family_name); res.style = std::string(face->style_name); res.name = cur_name(); res.is_italic = face->style_flags & FT_STYLE_FLAG_ITALIC; res.is_bold = face->style_flags & FT_STYLE_FLAG_BOLD; res.weight = get_weight(); res.width = get_width(); 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; for (size_t i = 0; i < face->num_charmaps; ++i) { res.charmaps.push_back(enc_to_string(face->charmaps[i]->encoding)); } 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); if (cur_has_variations) { FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error == 0) { std::vector set_val(variations->num_axis); error = FT_Get_Var_Design_Coordinates(face, variations->num_axis, set_val.data()); if (error == 0) { for (FT_UInt i = 0; i < variations->num_axis; ++i) { if (variations->axis[i].tag == ITAL_TAG) { res.is_italic = fixed_to_italic(set_val[i]); } else if (variations->axis[i].tag == WGHT_TAG) { res.weight = fixed_to_weight(set_val[i]); res.is_bold = res.weight >= FontWeightBold; } else if (variations->axis[i].tag == WDTH_TAG) { res.width = fixed_to_width(set_val[i]); } } } FT_Done_MM_Var(library, variations); } } return res; } GlyphInfo FreetypeCache::glyph_info() { static char name_buffer[50]; 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; } if (FT_HAS_GLYPH_NAMES(face)) { FT_Get_Glyph_Name(face, cur_glyph, name_buffer, 50); res.name = std::string(name_buffer); } else { res.name = ""; } 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::cur_is_variable() { return cur_has_variations; } 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() { return face; } FT_Face FreetypeCache::get_referenced_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}; } std::vector FreetypeCache::cur_axes() { std::vector axes; if (!cur_has_variations) return axes; FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error != 0) { return axes; } std::vector set_var(variations->num_axis); FT_Get_Var_Design_Coordinates(face, set_var.size(), set_var.data()); for (FT_UInt i = 0; i < variations->num_axis; ++i) { axes.push_back({ tag_to_axis(variations->axis[i].tag), variations->axis[i].minimum / FIXED_MOD, variations->axis[i].maximum / FIXED_MOD, variations->axis[i].def / FIXED_MOD, set_var[i] / FIXED_MOD }); } FT_Done_MM_Var(library, variations); return axes; } void FreetypeCache::has_axes(bool& weight, bool& width, bool& italic) { width = false; weight = false; italic = false; if (!cur_has_variations) return; FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error == 0) { for (FT_UInt i = 0; i < variations->num_axis; ++i) { long tag = variations->axis[i].tag; if (tag == WGHT_TAG) weight = true; else if (tag == WDTH_TAG) width = true; else if (tag == ITAL_TAG) italic = true; } FT_Done_MM_Var(library, variations); } } int FreetypeCache::n_axes() { int n = 0; if (!cur_has_variations) return n; FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error == 0) { n = variations->num_axis; FT_Done_MM_Var(library, variations); } return n; } bool FreetypeCache::is_variable() { bool variable = false; FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error == 0) { variable = variations->num_axis != 0; FT_Done_MM_Var(library, variations); } return variable; } inline int var_hash(const int* axes, const int* vals, size_t n) { int hash = 0; for (size_t i = 0; i < n; ++i) { hash ^= std::hash()(axes[i]); hash ^= std::hash()(vals[i]); } return hash; } void FreetypeCache::set_axes(const int* axes, const int* vals, size_t n) { if (!cur_has_variations) { cur_var = 0; return; } int this_var = var_hash(axes, vals, n); if (this_var == cur_var) return; std::vector fvals; if (n == 0) { FT_Set_Var_Design_Coordinates(face, 0, fvals.data()); glyphstore.clear(); cur_var = this_var; return; } FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error != 0) { return; } for (FT_UInt i = 0; i < variations->num_axis; ++i) { auto it = std::find(axes, axes + n, (int) variations->axis[i].tag); if (it == axes + n) { fvals.push_back(variations->axis[i].def); } else { fvals.push_back(std::min(variations->axis[i].maximum, std::max(variations->axis[i].minimum, FT_Fixed(vals[it - axes])))); } } FT_Done_MM_Var(library, variations); FT_Set_Var_Design_Coordinates(face, fvals.size(), fvals.data()); glyphstore.clear(); cur_var = this_var; } int FreetypeCache::get_weight() { // Support for variations if (cur_has_variations) { FT_MM_Var* variations = nullptr; int error = FT_Get_MM_Var(face, &variations); if (error == 0) { unsigned wght_index = 0; for (; wght_index < variations->num_axis; ++wght_index) { if (variations->axis[wght_index].tag == WGHT_TAG) { break; } } if (wght_index != variations->num_axis) { std::vector set_var(variations->num_axis); FT_Get_Var_Design_Coordinates(face, set_var.size(), set_var.data()); return fixed_to_weight(set_var[wght_index]); } } } // Classic reading from OS table 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 compatibility (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); } static FreetypeCache* font_cache; FreetypeCache& get_font_cache() { return *font_cache; } void init_ft_caches(DllInfo* dll) { font_cache = new FreetypeCache(); } void unload_ft_caches(DllInfo* dll) { delete font_cache; } systemfonts/src/cache_store.cpp0000644000176200001440000000243415066733440016435 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_referenced_face(); END_CPP *error = 0; return face; } FT_Face get_cached_face2(const FontSettings2& font, double size, double res, int* error) { FT_Face face = nullptr; BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(font.file, font.index, size, res)) { *error = cache.error_code; return face; } cache.set_axes(font.axes, font.coords, font.n_axes); face = cache.get_referenced_face(); END_CPP *error = 0; return face; } bool check_ft_version(int major, int minor, int patch) { return major == FREETYPE_MAJOR && minor == FREETYPE_MINOR && patch == FREETYPE_PATCH; } void export_cache_store(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_cached_face", (DL_FUNC)get_cached_face); R_RegisterCCallable("systemfonts", "get_cached_face2", (DL_FUNC)get_cached_face2); R_RegisterCCallable("systemfonts", "check_ft_version", (DL_FUNC)check_ft_version); } systemfonts/src/utils.h0000644000176200001440000001265515067174320014766 0ustar liggesusers#pragma once #include #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); \ } const static long WGHT_TAG = 2003265652; const static long WDTH_TAG = 2003072104; const static long ITAL_TAG = 1769234796; const static double FIXED_MOD = 65536.0; 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(); } }; inline uint32_t axis_to_tag(std::string axis) { std::reverse(axis.begin(), axis.end()); if (axis.size() < 4) { // Should not happen for properly named axes axis += " "; } return ((uint32_t)axis[3] << 24) | ((uint32_t)axis[2] << 16) | ((uint32_t)axis[1] << 8) | (uint32_t)axis[0]; } inline std::string tag_to_axis(int tag) { const char *axis = reinterpret_cast(&tag); std::string axis2(axis, 4); std::reverse(axis2.begin(), axis2.end()); return axis2; } systemfonts/src/font_variation.h0000644000176200001440000000076415017310404016634 0ustar liggesusers#pragma once #include #include #include #include #include [[cpp11::register]] cpp11::writable::integers axes_to_tags(cpp11::strings axes); [[cpp11::register]] cpp11::writable::strings tags_to_axes(cpp11::integers tags); [[cpp11::register]] cpp11::writable::integers values_to_fixed(cpp11::doubles values); [[cpp11::register]] cpp11::writable::doubles fixed_to_values(cpp11::integers fixed); systemfonts/src/cpp11.cpp0000644000176200001440000003607415067177201015107 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, cpp11::list_of variations); extern "C" SEXP _systemfonts_get_fallback_c(SEXP path, SEXP index, SEXP string, SEXP variations) { BEGIN_CPP11 return cpp11::as_sexp(get_fallback_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(string), cpp11::as_cpp>>(variations))); 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::doubles italic, cpp11::doubles weight, cpp11::doubles 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, cpp11::list_of variations); extern "C" SEXP _systemfonts_get_font_info_c(SEXP path, SEXP index, SEXP size, SEXP res, SEXP variations) { 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), cpp11::as_cpp>>(variations))); 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, cpp11::list_of variations); extern "C" SEXP _systemfonts_get_glyph_info_c(SEXP glyphs, SEXP path, SEXP index, SEXP size, SEXP res, SEXP variations) { 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), cpp11::as_cpp>>(variations))); END_CPP11 } // font_outlines.h cpp11::writable::data_frame get_glyph_outlines(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::list_of variations, double tolerance, bool verbose); extern "C" SEXP _systemfonts_get_glyph_outlines(SEXP glyph, SEXP path, SEXP index, SEXP size, SEXP variations, 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>>(variations), 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::list_of variations, cpp11::integers color, bool verbose); extern "C" SEXP _systemfonts_get_glyph_bitmap(SEXP glyph, SEXP path, SEXP index, SEXP size, SEXP res, SEXP variations, 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>>(variations), 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 } // font_variation.h cpp11::writable::integers axes_to_tags(cpp11::strings axes); extern "C" SEXP _systemfonts_axes_to_tags(SEXP axes) { BEGIN_CPP11 return cpp11::as_sexp(axes_to_tags(cpp11::as_cpp>(axes))); END_CPP11 } // font_variation.h cpp11::writable::strings tags_to_axes(cpp11::integers tags); extern "C" SEXP _systemfonts_tags_to_axes(SEXP tags) { BEGIN_CPP11 return cpp11::as_sexp(tags_to_axes(cpp11::as_cpp>(tags))); END_CPP11 } // font_variation.h cpp11::writable::integers values_to_fixed(cpp11::doubles values); extern "C" SEXP _systemfonts_values_to_fixed(SEXP values) { BEGIN_CPP11 return cpp11::as_sexp(values_to_fixed(cpp11::as_cpp>(values))); END_CPP11 } // font_variation.h cpp11::writable::doubles fixed_to_values(cpp11::integers fixed); extern "C" SEXP _systemfonts_fixed_to_values(SEXP fixed) { BEGIN_CPP11 return cpp11::as_sexp(fixed_to_values(cpp11::as_cpp>(fixed))); 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_axes_to_tags", (DL_FUNC) &_systemfonts_axes_to_tags, 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_fixed_to_values", (DL_FUNC) &_systemfonts_fixed_to_values, 1}, {"_systemfonts_get_fallback_c", (DL_FUNC) &_systemfonts_get_fallback_c, 4}, {"_systemfonts_get_font_info_c", (DL_FUNC) &_systemfonts_get_font_info_c, 5}, {"_systemfonts_get_glyph_bitmap", (DL_FUNC) &_systemfonts_get_glyph_bitmap, 8}, {"_systemfonts_get_glyph_info_c", (DL_FUNC) &_systemfonts_get_glyph_info_c, 6}, {"_systemfonts_get_glyph_outlines", (DL_FUNC) &_systemfonts_get_glyph_outlines, 7}, {"_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}, {"_systemfonts_tags_to_axes", (DL_FUNC) &_systemfonts_tags_to_axes, 1}, {"_systemfonts_values_to_fixed", (DL_FUNC) &_systemfonts_values_to_fixed, 1}, {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_font_outline(DllInfo* dll); void init_ft_caches(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_font_outline(dll); init_ft_caches(dll); export_string_metrics(dll); R_forceSymbols(dll, TRUE); } systemfonts/src/font_metrics.cpp0000644000176200001440000002571315067176645016670 0ustar liggesusers#include "font_metrics.h" #include "Rinternals.h" #include "cpp11/as.hpp" #include "cpp11/list_of.hpp" #include "ft_cache.h" #include "types.h" #include "caches.h" #include "utils.h" #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 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, cpp11::list_of variations) { static strings_w var_names = {"set", "min", "def", "max"}; 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); strings_w name(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); cpp11::writable::list_of charmaps(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); cpp11::writable::list_of axes(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); } cache.set_axes(INTEGER(variations[i]["axis"]), INTEGER(variations[i]["value"]), Rf_xlength(variations[i]["axis"])); FontFaceInfo 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; name[i] = info.name; italic[i] = (Rboolean) info.is_italic; bold[i] = (Rboolean) info.is_bold; monospace[i] = (Rboolean) info.is_monospace; weight[i] = info.weight / 100; if (weight[i] == 0) { weight[i] = NA_INTEGER; } width[i] = info.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; charmaps[i] = strings_w(info.charmaps.begin(), info.charmaps.end()); 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; std::vector ax = cache.cur_axes(); cpp11::writable::list_of ax2(ax.size()); strings_w names; for (size_t j = 0; j < ax.size(); ++j) { doubles_w var_info = {ax[j].set, ax[j].min, ax[j].def, ax[j].max}; var_info.names() = var_names; ax2[(R_xlen_t) j] = var_info; names.push_back(ax[j].name); } ax2.names() = names; axes[i] = ax2; } data_frame_w info({ "path"_nm = path_col, "index"_nm = index_col, "family"_nm = family, "style"_nm = style, "name"_nm = name, "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, "charmaps"_nm = charmaps, "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, "variation_axes"_nm = axes }); 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, cpp11::list_of variations) { 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); strings_w glyph_name(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); } cache.set_axes(INTEGER(variations[i]["axis"]), INTEGER(variations[i]["value"]), Rf_xlength(variations[i]["axis"])); 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; glyph_name[i] = glyph_info.name; 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, "name"_nm = glyph_name, "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 glyph_metrics2(uint32_t code, const FontSettings2& font, double size, double res, double* ascent, double* descent, double* width) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(font.file, font.index, size, res)) { return cache.error_code; } cache.set_axes(font.axes, font.coords, font.n_axes); 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_weight2(const FontSettings2& font) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(font.file, font.index)) { return 0; } cache.set_axes(font.axes, font.coords, font.n_axes); 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", "glyph_metrics2", (DL_FUNC)glyph_metrics2); R_RegisterCCallable("systemfonts", "font_weight", (DL_FUNC)font_weight); R_RegisterCCallable("systemfonts", "font_weight2", (DL_FUNC)font_weight2); R_RegisterCCallable("systemfonts", "font_family", (DL_FUNC)font_family); } systemfonts/src/unix/0000755000176200001440000000000015017310404014415 5ustar liggesuserssystemfonts/src/unix/FontManagerLinux.cpp0000755000176200001440000001726715017575221020374 0ustar liggesusers#include #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; FcBool variable = FcFalse; 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); #ifdef FC_VARIABLE FcPatternGetBool(pattern, FC_VARIABLE, 0, &variable); #endif if (variable == FcTrue) index = 0; return new FontDescriptor( (char *) path, index, (char *) psName, (char *) family, (char *) style, convertWeight(weight), convertWidth(width), slant == FC_SLANT_ITALIC, spacing == FC_MONO, variable == FcTrue ); } 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(); FcObjectSet *os = FcObjectSetBuild( FC_FILE, #ifdef FC_POSTSCRIPT_NAME FC_POSTSCRIPT_NAME, #endif FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, #ifdef FC_VARIABLE FC_VARIABLE, #endif NULL ); 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); FcObjectSet *os = FcObjectSetBuild( FC_FILE, #ifdef FC_POSTSCRIPT_NAME FC_POSTSCRIPT_NAME, #endif FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, #ifdef FC_VARIABLE FC_VARIABLE, #endif NULL ); 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.h0000644000176200001440000000055715017310404016071 0ustar liggesusers#pragma once #include #include FT_FREETYPE_H #include #include #include "types.h" FT_Face get_cached_face(const char* file, int index, double size, double res, int* error); FT_Face get_cached_face2(const FontSettings2& font, double size, double res, int* error); [[cpp11::init]] void export_cache_store(DllInfo* dll); systemfonts/src/Makevars.in0000644000176200001440000000101315017310404015526 0ustar liggesusersPKG_CPPFLAGS=@cflags@ PKG_OBJCXXFLAGS=$(CXX17STD) @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_variation.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.cpp0000644000176200001440000000421315017310404016256 0ustar liggesusers#include "font_local.h" #include "FontDescriptor.h" #include "Rinternals.h" #include "caches.h" #include "ft_cache.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())); } FreetypeCache& cache = get_font_cache(); for (R_xlen_t i = 0; i < paths.size(); ++i) { std::string path(paths[i]); if (current_files.find(path) != current_files.end()) { continue; } bool success = cache.load_font(path.c_str(), 0); if (!success) { continue; } font_list.push_back(new FontDescriptor(cache.get_face(), path.c_str(), 0, cache.n_axes() != 0)); int n_fonts = cache.get_face()->num_faces; for (int i = 1; i < n_fonts; ++i) { success = cache.load_font(path.c_str(), i); if (!success) { continue; } font_list.push_back(new FontDescriptor(cache.get_face(), path.c_str(), i, cache.n_axes() != 0)); } } 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.cpp0000644000176200001440000004200015063327235017035 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 "types.h" #include #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, cpp11::list_of variations, 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", std::string(path[i]).c_str(), index[i]); } unscallable.push_back(i+1); continue; } cache.set_axes(INTEGER(variations[i]["axis"]), INTEGER(variations[i]["value"]), Rf_xlength(variations[i]["axis"])); 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); } SEXP one_glyph_bitmap(int glyph, const char* path, int index, double size, double res, const int* axes, const int* coords, int n_axes, int color, FreetypeCache& cache, bool verbose) { if (!cache.load_font(path, index, size, res)) { if (verbose) { cpp11::warning("Failed to load %s:%i with freetype error %i", path, index, cache.error_code); } return R_NilValue; } cache.set_axes(axes, coords, n_axes); double scaling = 72.0 / res; if (!FT_IS_SCALABLE(cache.get_face())) { scaling *= set_font_size(cache.get_face(), size * res * 64.0 / 72.0); } if (!cache.load_glyph(glyph, 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, path, index, cache.error_code); } return R_NilValue; } 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, path, index, error); } return R_NilValue; } int red = R_RED(color); int green = R_GREEN(color); int blue = R_BLUE(color); int alpha = R_ALPHA(color); 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, path, index); } return R_NilValue; } const unsigned char* buffer = bitmap.buffer; SEXP raster = PROTECT(Rf_allocMatrix(INTSXP, bitmap.width, bitmap.rows)); int* raster_p = INTEGER(raster); size_t offset = 0; for (size_t j = 0; j < bitmap.rows; ++j) { for (int k = 0; k < bitmap.pitch; ++k) { size_t index = k + j*bitmap.width; switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_GRAY: { if (buffer[offset + k] == 0) raster_p[index] = R_RGBA(0, 0, 0, 0); else raster_p[index] = R_RGBA(red, green, blue, multiply(alpha, buffer[offset + k])); break; }; case FT_PIXEL_MODE_BGRA: { size_t index = offset + k * 4; raster_p[index] = 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, R_DimSymbol, dims); SEXP channels = PROTECT(Rf_ScalarInteger(4)); Rf_setAttrib(raster, Rf_mkString("channels"), channels); Rf_classgets(raster, 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, 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, Rf_mkString("size"), raster_size); UNPROTECT(5); return raster; } cpp11::writable::list get_glyph_bitmap(cpp11::integers glyph, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::list_of variations, cpp11::integers color, bool verbose) { cpp11::writable::list bitmaps; FreetypeCache& cache = get_font_cache(); for (R_xlen_t i = 0; i < glyph.size(); ++i) { SEXP bitmap = PROTECT( one_glyph_bitmap( glyph[i], std::string(path[i]).c_str(), index[i], size[i], res[i], INTEGER(variations[i]["axis"]), INTEGER(variations[i]["value"]), Rf_xlength(variations[i]["axis"]), color[i], cache, verbose ) ); bitmaps.push_back(bitmap); } UNPROTECT(glyph.size()); return bitmaps; } struct Path { std::string path; double* transformation; Path(double* t) : path(""), transformation(t) {} void add_point(double _x, double _y) { _x *= 0.015625; _y *= 0.015625; double x = transformation[0] * _x + transformation[2] * _y + transformation[4]; double y = transformation[1] * _x + transformation[3] * _y + transformation[5]; path += std::to_string(x) + " "; path += std::to_string(y) + " "; } }; static int move_func_a(const FT_Vector *to, void *user) { Path *outline = static_cast(user); if (!outline->path.empty()) { outline->path += "Z M "; } else { outline->path += "M "; } outline->add_point(to->x, to->y); return 0; } static int line_func_a(const FT_Vector *to, void *user) { Path *outline = static_cast(user); outline->path += "L "; outline->add_point(to->x, to->y); return 0; } static int conic_func_a(const FT_Vector *control, const FT_Vector *to, void *user) { Path *outline = static_cast(user); outline->path += "Q "; outline->add_point(control->x, control->y); outline->add_point(to->x, to->y); return 0; } static int cubic_func_a(const FT_Vector *controlOne, const FT_Vector *controlTwo, const FT_Vector *to, void *user) { Path *outline = static_cast(user); outline->path += "C "; outline->add_point(controlOne->x, controlOne->y); outline->add_point(controlTwo->x, controlTwo->y); outline->add_point(to->x, to->y); return 0; } std::string get_glyph_path_impl(int glyph, double* t, bool* no_outline, const char* path, int index) { Path path_outline(t); *no_outline = false; FreetypeCache& cache = get_font_cache(); FT_Outline_Funcs callbacks; callbacks.move_to = move_func_a; callbacks.line_to = line_func_a; callbacks.conic_to = conic_func_a; callbacks.cubic_to = cubic_func_a; callbacks.delta = 0; callbacks.shift = 0; if (!FT_IS_SCALABLE(cache.get_face())) { *no_outline = true; return ""; } if (!cache.load_glyph(glyph)) { cpp11::warning("Failed to load glyph %i in %s:%i with freetype error %i", glyph, path, index, cache.error_code); return ""; } FT_GlyphSlot& slot = cache.get_face()->glyph; FT_Outline& outline = slot->outline; if (slot->format != FT_GLYPH_FORMAT_OUTLINE) { *no_outline = true; return ""; } if (outline.n_contours <= 0 || outline.n_points <= 0) { return ""; } FT_Error error = FT_Outline_Decompose(&outline, &callbacks, &path_outline); if (error) { cpp11::warning("Couldn't extract outline from glyph %i in %s:%i with freetype error %i", glyph, path, index, error); return ""; } return path_outline.path; } std::string get_glyph_path(int glyph, double* t, const char* path, int index, double size, bool* no_outline) { FreetypeCache& cache = get_font_cache(); if (!cache.load_font(path, index, size, 72.0)) { cpp11::warning("Failed to load %s:%i with freetype error %i", path, index, cache.error_code); return ""; } return get_glyph_path_impl(glyph, t, no_outline, path, index); } std::string get_glyph_path2(int glyph, double* t, const FontSettings2& font, double size, bool* no_outline) { FreetypeCache& cache = get_font_cache(); if (!cache.load_font(font.file, font.index, size, 72.0)) { cpp11::warning("Failed to load %s:%i with freetype error %i", font.file, font.index, cache.error_code); return ""; } cache.set_axes(font.axes, font.coords, font.n_axes); return get_glyph_path_impl(glyph, t, no_outline, font.file, font.index); } SEXP get_glyph_raster(int glyph, const char* path, int index, double size, double res, int color) { FreetypeCache& cache = get_font_cache(); return one_glyph_bitmap( glyph, path, index, size, res, nullptr, nullptr, 0, color, cache, true ); } SEXP get_glyph_raster2(int glyph, const FontSettings2& font, double size, double res, int color) { FreetypeCache& cache = get_font_cache(); return one_glyph_bitmap( glyph, font.file, font.index, size, res, font.axes, font.coords, font.n_axes, color, cache, true ); } void export_font_outline(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_glyph_path", (DL_FUNC)get_glyph_path); R_RegisterCCallable("systemfonts", "get_glyph_path2", (DL_FUNC)get_glyph_path2); R_RegisterCCallable("systemfonts", "get_glyph_raster", (DL_FUNC)get_glyph_raster); R_RegisterCCallable("systemfonts", "get_glyph_raster2", (DL_FUNC)get_glyph_raster2); } 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.cpp0000755000176200001440000002415015017310404016763 0ustar liggesusers#include "font_matching.h" #include "Rinternals.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 #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 doubles_t = cpp11::doubles; using doubles_w = cpp11::writable::doubles; 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(); void locate_systemfont(const char *family, int italic, int weight, int width, FontSettings2& res) { 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(res.file, font_it->second.file.c_str(), PATH_MAX); res.file[PATH_MAX] = '\0'; res.index = font_it->second.index; res.axes = font_it->second.axes.data(); res.coords = font_it->second.coords.data(); res.n_axes = font_it->second.axes.size(); return; } FontDescriptor font_desc(resolved_family, fixed_to_italic(italic), fixed_to_weight(weight), fixed_to_width(width)); std::unique_ptr font_loc(match_local_fonts(&font_desc)); if (!font_loc) { font_loc = std::unique_ptr(findFont(&font_desc)); } FontLoc cached_loc; if (!font_loc) { list_t fallback = cpp11::as_cpp(cpp11::package("systemfonts")["get_fallback"]()); cached_loc.file = std::string(Rf_translateCharUTF8(STRING_ELT(fallback[0], 0))); cached_loc.index = INTEGER(fallback[1])[0]; } else { cached_loc.file = std::string(font_loc->path); cached_loc.index = font_loc->index; if (font_loc->var_ital) { cached_loc.axes.push_back(ITAL_TAG); cached_loc.coords.push_back(italic); } if (font_loc->var_wght) { cached_loc.axes.push_back(WGHT_TAG); cached_loc.coords.push_back(weight); } if (font_loc->var_wdth) { cached_loc.axes.push_back(WDTH_TAG); cached_loc.coords.push_back(width); } } font_map[key] = cached_loc; strncpy(res.file, cached_loc.file.c_str(), PATH_MAX); res.file[PATH_MAX] = '\0'; res.index = cached_loc.index; res.axes = cached_loc.axes.data(); res.coords = cached_loc.coords.data(); res.n_axes = cached_loc.axes.size(); } int locate_font(const char *family, int italic, int bold, char *path, int max_path_length) { FontSettings2 match; BEGIN_CPP if (!locate_in_registry(family, italic, bold, match)) { locate_systemfont( family, italic_to_fixed(italic), weight_to_fixed(bold ? FontWeightBold : FontWeightNormal), 0, match ); } END_CPP strncpy(path, match.file, max_path_length); return match.index; } FontSettings locate_font_with_features(const char *family, int italic, int bold) { FontSettings2 match = {}; BEGIN_CPP if (!locate_in_registry(family, italic, bold, match)) { locate_systemfont( family, italic_to_fixed(italic), weight_to_fixed(bold ? FontWeightBold : FontWeightNormal), 0, match ); } END_CPP return match; } FontSettings2 locate_font_with_features2(const char *family, double italic, double weight, double width, const int* axes, const int* coords, int n_axes) { FontSettings2 match = {}; BEGIN_CPP int italic_int = italic_to_fixed(italic); int weight_int = weight_to_fixed(FontWeight(weight)); int width_int = width_to_fixed(FontWidth(width)); for (int i = 0; i < n_axes; ++i) { if (axes[i] == ITAL_TAG) { italic_int = coords[i]; } else if (axes[i] == WGHT_TAG) { weight_int = coords[i]; } else if (axes[i] == WDTH_TAG) { width_int = coords[i]; } } int weight2 = fixed_to_weight(weight_int); if (!locate_in_registry(family, fixed_to_italic(italic_int), weight2 >=650 && weight2 < 750, match)) { locate_systemfont( family, italic_int, weight_int, width_int, match ); } END_CPP return 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, doubles_t italic, doubles_t weight, doubles_t width) { strings_w paths; integers_w indices; list_w features; list_w variations; FontSettings2 match = {}; for (R_xlen_t i = 0; i < family.size(); ++i) { const char* fam = Rf_translateCharUTF8(family[i]); bool standard_width = (width[i] == FontWidthUndefined || width[i] == FontWidthNormal); bool standard_weight = (weight[i] == FontWeightNormal || weight[i] == FontWeightBold || weight[i] == FontWeightUndefined); if (!(standard_width && standard_weight && locate_in_registry(fam, italic[i], weight[i] != FontWeightNormal, match))) { locate_systemfont( fam, italic_to_fixed(italic[i]), weight_to_fixed(weight[i]), width_to_fixed(width[i]), match ); } paths.push_back(match.file); indices.push_back(match.index); strings_w tags(match.n_features); integers_w vals(match.n_features); for (int j = 0; j < match.n_features; ++j) { tags[j] = cpp11::r_string({ match.features[j].feature[0], match.features[j].feature[1], match.features[j].feature[2], match.features[j].feature[3] }); vals[j] = match.features[j].setting; } list_w f_feat({tags, vals}); f_feat.attr("class") = {"font_feature"}; features.push_back(f_feat); list_w f_vars({ "axis"_nm = integers_w(match.axes, match.axes + match.n_axes), "value"_nm = integers_w(match.coords, match.coords + match.n_axes) }); f_vars.attr("class") = {"font_variation"}; variations.push_back(f_vars); } data_frame_w res({ "path"_nm = paths, "index"_nm = indices, "features"_nm = features, "variations"_nm = variations }); 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); logicals_w variable(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; variable[i] = (Rboolean) (*it)->variable; ++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, "variable"_nm = variable }); 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); R_RegisterCCallable("systemfonts", "locate_font_with_features2", (DL_FUNC)locate_font_with_features2); } systemfonts/src/ft_cache.h0000644000176200001440000001261015067176634015363 0ustar liggesusers#pragma once #include #include #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 #include FT_MULTIPLE_MASTERS_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 FontFaceInfo { std::string family; std::string style; std::string name; bool is_italic; bool is_bold; bool is_monospace; int weight; int width; bool is_vertical; bool has_kerning; bool has_color; bool is_scalable; int n_glyphs; int n_sizes; int n_charmaps; std::vector 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; std::string name; long x_bearing; long y_bearing; long width; long height; long x_advance; long y_advance; std::vector bbox; }; struct VariationInfo { std::string name; double min; double max; double def; double set; }; 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); FontFaceInfo 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 cur_is_variable(); 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(); FT_Face get_referenced_face(); int get_weight(); int get_width(); void get_family_name(char* family, int max_length); std::string cur_name(); std::vector cur_axes(); void has_axes(bool& weight, bool& width, bool& italic); int n_axes(); void set_axes(const int* axes, const int* vals, size_t n); int error_code; private: FT_Library library; std::map glyphstore; FaceCache face_cache; SizeCache size_cache; FaceID cur_id; int cur_var; double cur_size; double cur_res; bool cur_can_kern; unsigned int cur_glyph; bool cur_is_scalable; bool cur_has_variations; 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; }; bool is_variable(); }; FreetypeCache& get_font_cache(); [[cpp11::init]] void init_ft_caches(DllInfo* dll); void unload_ft_caches(DllInfo* dll); 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.h0000644000176200001440000000107515017310404016373 0ustar liggesusers#pragma once #include #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, cpp11::list_of variations); FontSettings request_fallback(const char *string, const char *path, int index); FontSettings2 request_fallback2(const char *string, const FontSettings2& font); [[cpp11::init]] void export_font_fallback(DllInfo* dll); systemfonts/src/font_variation.cpp0000644000176200001440000000206015017310404017156 0ustar liggesusers#include "font_variation.h" #include "Rinternals.h" #include "cpp11/integers.hpp" #include "utils.h" cpp11::writable::integers axes_to_tags(cpp11::strings axes) { cpp11::writable::integers tags(axes.size()); int* tags_data = INTEGER(tags.data()); for (R_xlen_t i = 0; i < axes.size(); ++i) { tags_data[i] = axis_to_tag(axes[i]); } return tags; } cpp11::writable::strings tags_to_axes(cpp11::integers tags) { cpp11::writable::strings axes(tags.size()); int* tags_data = INTEGER(tags.data()); for (R_xlen_t i = 0; i < axes.size(); ++i) { axes[i] = tag_to_axis(tags_data[i]); } return axes; } cpp11::writable::integers values_to_fixed(cpp11::doubles values) { cpp11::writable::integers fixed(values.size()); for (R_xlen_t i = 0; i < values.size(); ++i) { fixed[i] = values[i] * FIXED_MOD; } return fixed; } cpp11::writable::doubles fixed_to_values(cpp11::integers fixed) { cpp11::writable::doubles values(fixed.size()); for (R_xlen_t i = 0; i < fixed.size(); ++i) { values[i] = fixed[i] / FIXED_MOD; } return values; } 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/0000755000176200001440000000000015067213517014206 5ustar liggesuserssystemfonts/src/mac/FontManagerMac.mm0000755000176200001440000002646115017310404017363 0ustar liggesusers#include #include #include #include #include "../FontDescriptor.h" // converts a CoreText weight (-1 to +1) to a standard weight (100 to 900) // See https://chromium.googlesource.com/chromium/src/+/master/ui/gfx/platform_font_mac.mm // for conversion static int convertWeight(float weight) { if (weight <= -0.7f) return 100; else if (weight <= -0.45f) return 200; else if (weight <= -0.1f) return 300; else if (weight <= 0.1f) return 400; else if (weight <= 0.27f) return 500; else if (weight <= 0.35f) return 600; else if (weight <= 0.5f) 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; if (font->variable) { font->index = 0; return; } 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]; CFArrayRef variations = CTFontCopyVariationAxes(CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL)); FontDescriptor *res = new FontDescriptor( [[url path] UTF8String], [psName UTF8String], [family UTF8String], [style UTF8String], weight, width, (symbolicTraits & kCTFontItalicTrait) != 0, (symbolicTraits & kCTFontMonoSpaceTrait) != 0, variations != nullptr && CFArrayGetCount(variations) != 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); // See if font has variations for certain traits. If so, ignore these in the metric bool has_var_weight = false; bool has_var_width = false; bool has_var_italic = false; CFArrayRef variations = CTFontCopyVariationAxes(CTFontCreateWithFontDescriptor(match, 0.0, NULL)); if (variations != nullptr) { long weight_tag = 2003265652; long width_tag = 2003072104; long italic_tag = 1769234796; long tag_val = 0; for (CFIndex i = 0; i < CFArrayGetCount(variations); ++i) { CFDictionaryRef axis = (CFDictionaryRef)CFArrayGetValueAtIndex(variations, i); CFNumberRef tag = (CFNumberRef) CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey); CFNumberGetValue(tag, kCFNumberLongType, &tag_val); if (tag_val == weight_tag) has_var_weight = true; else if (tag_val == width_tag) has_var_width = true; else if (tag_val == italic_tag) has_var_italic = true; } } // normalize everything to base-900 int metric = 0; if (!has_var_weight && desc->weight) metric += sqr(convertWeight([dict[(id)kCTFontWeightTrait] floatValue]) - desc->weight); if (!has_var_width && desc->width) metric += sqr((convertWidth([dict[(id)kCTFontWidthTrait] floatValue]) - desc->width) * 100); if (!has_var_italic) 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.h0000644000176200001440000000126715017310404016501 0ustar liggesusers#pragma once #include #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, cpp11::list_of variations, 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::list_of variations, cpp11::integers color, bool verbose); [[cpp11::init]] void export_font_outline(DllInfo* dll); systemfonts/NAMESPACE0000644000176200001440000000243215017310404014063 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("$",font_variation) S3method("$<-",font_variation) S3method("[",font_variation) S3method("[<-",font_variation) S3method("[[",font_variation) S3method("[[<-",font_variation) S3method(c,font_feature) S3method(format,font_feature) S3method(format,font_variation) S3method(length,font_feature) S3method(length,font_variation) S3method(names,font_variation) S3method(print,font_feature) S3method(print,font_variation) 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(font_variation) export(fonts_as_import) 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(plot_glyph_stats) 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/LICENSE0000644000176200001440000000006115002125620013643 0ustar liggesusersYEAR: 2025 COPYRIGHT HOLDER: systemfonts authors systemfonts/NEWS.md0000644000176200001440000001572715067213313013763 0ustar liggesusers# systemfonts 1.3.1 * Fixed a sanitizer issue with converting variation axis names to tags * Avoid spurious build issues on old macOS systems * Avoid writing font files to the user directory during test # systemfonts 1.3.0 * Fixed a bug in the URL generation for Google Font imports * Added support for Bunny Fonts imports (#132) * Begin deprecation of `bold` argument in favour of `weight` throughout package * Improve messaging in `require_font()` * Fonts are automatically added to the session when an import is created (#131) * Fixed a bug in converting font weights as reported by macOS into ISO-style weight used by systemfonts and FreeType * `require_font()` now better handles lack of internet access * Added `plot_glyph_stats()` to provide visual explanation for various glyph measures * `font_info()` now returns the PostScript name of the font in the `name` column * Added support for variable fonts throughout the package. Other packages will need to be upgraded to take advantage of this. A new function `font_variation()` can be used to define coords for the variation axes * Fixed a bug in webfont download on Windows where fontfiles would become corrupted (#134) * Fixed an issue in textshaping where conflicting DLL names resulted in the R process inability to render text if textshaping was loaded first (textshaping#36) * Add a way to test if the freetype version matches between systemfonts and another package match * Added a `name` column to the output of `glyph_info()` to report the name of the glyph is provided by the font * Added a `charmaps` column to the output of `font_info()` to report the name of the character maps provided by the font * Cached faces are now reference counted when they are handed off to another package and it is the other packages' responsibility to decrement the reference by calling `FT_Done_Face()` when finished with it. # systemfonts 1.2.3 * Added `fonts_as_import()` to create stylesheet urls for embedding of fonts in HTML and SVG * Added two C-level functions for getting glyph outline and bitmap information # systemfonts 1.2.2 * Fix compilation on macOS when the obj-c++ compiler fails to pick up the right obj-c++ version (#122) * `add_fonts()` now supports urls as well as file paths (#124) # 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/0000755000176200001440000000000015067213517013634 5ustar liggesuserssystemfonts/inst/include/0000755000176200001440000000000015017310404015243 5ustar liggesuserssystemfonts/inst/include/systemfonts-ft.h0000644000176200001440000000377215066503564020451 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 #include "systemfonts.h" // 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); } namespace systemfonts { namespace ver2 { // 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 FontSettings2& font, double size, double res, int* error) { static FT_Face (*p_get_cached_face)(const FontSettings2&, double, double, int*) = NULL; if (p_get_cached_face == NULL) { p_get_cached_face = (FT_Face (*)(const FontSettings2&, double, double, int*)) R_GetCCallable("systemfonts", "get_cached_face2"); } return p_get_cached_face(font, size, res, error); } // Check if this header is compiled with the same version of freetype as // systemfonts has been compiled with static inline bool check_ft_version() { static bool (*p_check_ft_version)(int, int, int) = NULL; if (p_check_ft_version == NULL) { p_check_ft_version = (bool (*)(int, int, int)) R_GetCCallable("systemfonts", "check_ft_version"); } return p_check_ft_version(FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); } } } #endif systemfonts/inst/include/systemfonts.h0000644000176200001440000003106715066505477020045 0ustar liggesusers#pragma once #ifndef R_NO_REMAP #define R_NO_REMAP #endif #include #include #include #include #include #ifdef __cplusplus #include #include #else #include #endif 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) // 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; FontSettings() : index(0), features(nullptr), n_features(0) { file[0] = '\0'; } FontSettings(const char* p, unsigned int i, const FontFeature* f, int n) : index(i), features(f), n_features(n) { strncpy(file, p, PATH_MAX + 1); file[PATH_MAX] = '\0'; } }; typedef struct FontSettings FontSettings; // A structure to pass around a single font with features and variable axes (used by the C interface) struct FontSettings2 : public FontSettings { const int* axes; const int* coords; int n_axes; FontSettings2() : axes(nullptr), coords(nullptr), n_axes(0) { } FontSettings2(FontSettings x) : axes(nullptr), coords(nullptr), n_axes(0) { strncpy(file, x.file, PATH_MAX + 1); index = x.index; features = x.features; n_features = x.n_features; } }; typedef struct FontSettings2 FontSettings2; // 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); } #ifdef __cplusplus // Get the outline of a glyph as a string static inline std::string get_glyph_path(int glyph, double* t, const char* path, int index, double size, bool* no_outline) { static std::string (*p_get_glyph_path)(int, double*, const char*, int, double, bool*) = NULL; if (p_get_glyph_path == NULL) { p_get_glyph_path = (std::string (*)(int, double*, const char*, int, double, bool*)) R_GetCCallable("systemfonts", "get_glyph_path"); } return p_get_glyph_path(glyph, t, path, index, size, no_outline); } #endif // Get a raster of a glyph as a nativeRaster static inline SEXP get_glyph_raster(int glyph, const char* path, int index, double size, double res, int color) { static SEXP (*p_get_glyph_raster)(int, const char*, int, double, double, int) = NULL; if (p_get_glyph_raster == NULL) { p_get_glyph_raster = (SEXP (*)(int, const char*, int, double, double, int)) R_GetCCallable("systemfonts", "get_glyph_raster"); } return p_get_glyph_raster(glyph, path, index, size, res, color); } namespace systemfonts { namespace ver2 { // This API version uses FontSettings2 to pass around information of a font // with all it's settings etc. // // Support for font features and variable axes // // string_width and string_shape has been deprecated as textshaping provides // a better solution to this // Get the file and index of a font along with possible registered OpenType // features, returned as a FontSettings2 object. Support variable fonts static inline FontSettings2 locate_font(const char *family, double italic, double weight, double width, const int* axes, const int* coords, int n_axes) { static FontSettings2 (*p_locate_font_with_features)(const char*, double, double, double, const int*, const int*, int) = NULL; if (p_locate_font_with_features == NULL) { p_locate_font_with_features = (FontSettings2 (*)(const char*, double, double, double, const int*, const int*, int)) R_GetCCallable("systemfonts", "locate_font_with_features2"); } return p_locate_font_with_features(family, italic, weight, width, axes, coords, n_axes); } // Get the file and index of a fallback font for the given string based on the // given font and index. Supports variable fonts static inline FontSettings2 get_fallback(const char *string, const FontSettings2& font) { static FontSettings2 (*p_get_fallback)(const char*, const FontSettings2&) = NULL; if (p_get_fallback == NULL) { p_get_fallback = (FontSettings2 (*)(const char*, const FontSettings2&)) R_GetCCallable("systemfonts", "get_fallback2"); } return p_get_fallback(string, font); } // 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 FontSettings2& font, double size, double res, double* ascent, double* descent, double* width) { static int (*p_glyph_metrics)(uint32_t, const FontSettings2&, double, double, double*, double*, double*) = NULL; if (p_glyph_metrics == NULL) { p_glyph_metrics = (int (*)(uint32_t, const FontSettings2&, double, double, double*, double*, double*)) R_GetCCallable("systemfonts", "glyph_metrics2"); } return p_glyph_metrics(code, font, size, res, ascent, descent, width); } // Get the weight of the font as encoded in the OTT/2 table static inline int get_font_weight(const FontSettings2& font) { static int (*p_get_weight)(const FontSettings2&) = NULL; if (p_get_weight == NULL) { p_get_weight = (int (*)(const FontSettings2&)) R_GetCCallable("systemfonts", "font_weight2"); } return p_get_weight(font); } // 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 FontSettings2& font, 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(font.file, font.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 FontSettings2& font) { 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, font.file, font.index); } #ifdef __cplusplus // Get the outline of a glyph as a string static inline std::string get_glyph_path(int glyph, double* t, const FontSettings2& font, double size, bool* no_outline) { static std::string (*p_get_glyph_path)(int, double*, const FontSettings2&, double, bool*) = NULL; if (p_get_glyph_path == NULL) { p_get_glyph_path = (std::string (*)(int, double*, const FontSettings2&, double, bool*)) R_GetCCallable("systemfonts", "get_glyph_path2"); } return p_get_glyph_path(glyph, t, font, size, no_outline); } #endif // Get a raster of a glyph as a nativeRaster static inline SEXP get_glyph_raster(int glyph, const FontSettings2& font, double size, double res, int color) { static SEXP (*p_get_glyph_raster)(int, const FontSettings2&, double, double, int) = NULL; if (p_get_glyph_raster == NULL) { p_get_glyph_raster = (SEXP (*)(int, const FontSettings2&, double, double, int)) R_GetCCallable("systemfonts", "get_glyph_raster2"); } return p_get_glyph_raster(glyph, font, size, res, color); } } } systemfonts/inst/doc/0000755000176200001440000000000015067213517014401 5ustar liggesuserssystemfonts/inst/doc/systemfonts.R0000644000176200001440000000670015067213517017125 0ustar liggesusers## ----include = FALSE---------------------------------------------------------- google <- suppressWarnings(try(readLines("https://8.8.8.8", n = 1L), silent = TRUE)) is_online <- !inherits(google, "try-error") knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = is_online ) systemfonts::require_font("Spectral", fallback = "serif") ## ----------------------------------------------------------------------------- grid::grid.text( "Spectral 🎉", gp = grid::gpar(fontfamily = "Spectral", fontface = 2, fontsize = 30) ) ## ----------------------------------------------------------------------------- library(ggplot2) ggplot(na.omit(penguins)) + geom_point(aes(x = bill_len, y = body_mass, colour = species)) + labs(x = "Bill Length", y = "Body Mass", colour = "Species") + theme_minimal(base_family = "Spectral") ## ----------------------------------------------------------------------------- systemfonts::match_fonts("Spectral", weight = "bold") systemfonts::font_fallback("🎉", family = "Spectral", weight = "bold") ## ----------------------------------------------------------------------------- # systemfonts::system_fonts() ## ----------------------------------------------------------------------------- all_fonts <- systemfonts::system_fonts() all_fonts <- all_fonts[!grepl("^/Users", all_fonts$path),] rmarkdown::paged_table(all_fonts) ## ----------------------------------------------------------------------------- systemfonts::register_variant( name = "Spectral Light", family = "Spectral", weight = "light" ) ## ----------------------------------------------------------------------------- grid::grid.text( "Light weight is soo classy", gp = grid::gpar(fontfamily = "Spectral Light", fontsize = 30) ) ## ----------------------------------------------------------------------------- systemfonts::register_variant( name = "Spectral Small Caps", family = "Spectral", features = systemfonts::font_feature( letters = "small_caps" ) ) grid::grid.text( "All caps — Small caps", gp = grid::gpar(fontfamily = "Spectral Small Caps", fontsize = 30) ) ## ----------------------------------------------------------------------------- # systemfonts::get_from_google_fonts("Barrio") # # grid::grid.text( # "A new font a day keeps Tufte away", # gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) # ) ## ----------------------------------------------------------------------------- systemfonts::require_font("Barrio") grid::grid.text( "A new font a day keeps Tufte away", gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) ) ## ----------------------------------------------------------------------------- systemfonts::require_font("Rubik Distressed") grid::grid.text( "There are no bad fonts\nonly bad text", gp = grid::gpar(fontfamily = "Rubik Distressed", fontsize = 30) ) ## ----------------------------------------------------------------------------- systemfonts::fonts_as_import("Barrio") systemfonts::fonts_as_import("Rubik Distressed", type = "link") ## ----------------------------------------------------------------------------- substr(systemfonts::fonts_as_import("Arial", repositories = NULL), 1, 200) ## ----------------------------------------------------------------------------- svg <- svglite::svgstring(web_fonts = "Barrio") grid::grid.text("Example", gp = grid::gpar(fontfamily = "Barrio")) invisible(dev.off()) svg() systemfonts/inst/doc/c_interface.R0000644000176200001440000000036615067213506016771 0ustar liggesusers## ----------------------------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----------------------------------------------------------------------------- library(systemfonts) systemfonts/inst/doc/c_interface.html0000644000176200001440000005474615067213507017550 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_fonts() imported into your package namespace. All functions are provided in the systemfonts::ver2 namespace. Legacy API is not namespaced. The different functionality will be discussed below:

Font matching

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

FontSettings2 locate_font(
  const char *family,
  double italic,
  double weight,
  double width,
  const int* axes,
  const int* coords,
  int n_axes
)

It takes a UTF-8 encoded string with the font family name, a double giving italic (usually 0.0 == “upright” and 1.0 == “italic”), a double giving weight (usually ranging between 100.0 and 1000.0 — 0.0 means “undefined”) and a double giving “width” (usually ranging from 1.0 to 10.0 — 0.0 means undefined). Lastly you can provide variable axis coords with the axes and coords array pointers with n_axes giving the number in the arrays (which are assumed to be of the same length). The values of each array are not immediately understandable to the human eye and will usually come from a user through a call to font_variation(). If the axes array contain “ital”, “wght”, and/or “wdth” and the font has these variable axes then the values for these axes will overwrite the values provide in italic, weight, and width.

The returned FontSettings2 struct will contain both the font location and index along with any OpenType feature settings and the axes settings in the case of a variable font. The struct (along with its FontFeature struct dependency) is shown below and is pretty self-documenting.

Do not cache the FontSettings2 struct as the features, axes, and coords arrays 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;
  const int* axes;
  const int* coords;
  int n_axes;
};

Glyph metrics

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

int glyph_metrics(
  uint32_t code,
  const FontSettings2& font,
  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, with a FontSettings2 object describing the font. 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.

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 int error pointer to 0 if successful.

get_cached_face(
  const FontSettings2& font,
  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 responsibility of the caller to decrease it once the face is no longer needed using FT_Done_Face().

Check for Freetype compatibility

If you are using a cached face from systemfonts you should ensure that your code has been compiled with the same version of Freetype as systemfonts has. You can do this with the check_ft_version() from the systemfonts-ft.h header. It takes no arguments and return true if the Freetype version from systemfonts corresponds with the one your library is compiled with.

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 FontSettings2 object, though features are always empty.

FontSettings2 get_fallback(
  const char* string,
  const FontSettings2& font
)

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 FontSettings2 object and returns the weight (100-900 or 0 if it is undefined by the font) respecting the variable axes settings if given.

int get_font_weight(
  const FontSettings2& font
)

Family name

It may be beneficial to know the family name from a given 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 FontSettings2& font,
  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 FontSettings2& font
)
systemfonts/inst/doc/fonts_basics.html0000644000176200001440000044053615067213510017751 0ustar liggesusers Typography and R

Typography and R

This text is meant as an introduction to the subject of typography, both in general but more importantly as it relates to R. If you are more interested in how to use systemfonts to use fonts installed on your computer during plotting then please see the package introduction vignette.

The code examples in this vignette is based on fonts that may not be available on the users machine. As such, you should not expect the provided examples to execute locally out of the box.

Digital typography

Many books could be, and have been, written about the subject of typography. This blog post is not meant to be an exhaustive deep dive into all areas of this vast subject. Rather, it is meant to give you just enough understanding of core concepts and terminology to appreciate how it all plays into using fonts in R.

Typeface or font?

There is a good chance that you, like 99% of world, use “font” as the term describing “the look” of the letters you type. You may, perhaps, have heard the term “typeface” as well and thought it synonymous. This is in fact slightly wrong, and a great deal of typography snobbery has been dealt out on that account (much like the distinction between packages and libraries in R). It is a rather inconsequential mix-up for the most part, especially because 99% of the population wouldn’t bat an eye if you use them interchangeably. However, the distinction between the two serves as a good starting point to talk about other terms in digital typography as well as the nature of font files, so let’s dive in.

When most people use the word “font” or “font family”, what they are actually describing is a typeface. A typeface is a style of lettering that forms a cohesive whole. As an example, consider the well-known “Helvetica” typeface. This name embraces many different weights (bold, normal, light) as well as slanted (italic) and upright. However, all of these variations are all as much Helvetica as the others - they are all part of the same typeface.

A font is a subset of a typeface, describing a particular variation of the typeface, i.e. the combination of weight, width, and slant that comes together to describe the specific subset of a typeface that is used. We typically give a specific combination of these features a name, like “bold” or “medium” or “italic”, which we call the font style1. In other words, a font is a particularly style within a typeface.

Different fonts from the Avenir Next typeface
Different fonts from the Avenir Next typeface

In the rest of this document we will use the terms typeface and font with the meaning described above.

Font files

Next, we need to talk about how typefaces are represented for use by computers. Font files record information on how to draw the individual glyphs (characters), but also instructions about how to draw sequences of glyphs like distance adjustments (kerning) and substitution rules (ligatures). Font files typically encode a single font but can encode a full typeface:

typefaces <- systemfonts::system_fonts()[, c("path", "index", "family", "style")]

# Full typeface in one file
typefaces[typefaces$family == "Helvetica", ]
#> # A tibble: 6 × 4
#>   path                                index family    style
#>   <chr>                               <int> <chr>     <chr>
#> 1 /System/Library/Fonts/Helvetica.ttc     2 Helvetica Oblique
#> 2 /System/Library/Fonts/Helvetica.ttc     4 Helvetica Light
#> 3 /System/Library/Fonts/Helvetica.ttc     5 Helvetica Light Oblique
#> 4 /System/Library/Fonts/Helvetica.ttc     1 Helvetica Bold
#> 5 /System/Library/Fonts/Helvetica.ttc     3 Helvetica Bold Oblique
#> 6 /System/Library/Fonts/Helvetica.ttc     0 Helvetica Regular

# One font per font file
typefaces[typefaces$family == "Arial", ]
#> # A tibble: 4 × 4
#>   path                                                     index family style
#>   <chr>                                                    <int> <chr>  <chr>
#> 1 /System/Library/Fonts/Supplemental/Arial.ttf                 0 Arial  Regular
#> 2 /System/Library/Fonts/Supplemental/Arial Bold.ttf            0 Arial  Bold
#> 3 /System/Library/Fonts/Supplemental/Arial Bold Italic.ttf     0 Arial  Bold Italic
#> 4 /System/Library/Fonts/Supplemental/Arial Italic.ttf          0 Arial  Italic

Here, each row is a font, with family giving the name of the typeface, and style the font style.

It took a considerable number of tries before the world managed to nail the digital representation of fonts, leading to a proliferation of file types. As an R user, there are three formats that are particularly important:

  • TrueType (ttf/ttc). Truetype is the baseline format that all modern formats stand on top of. It was developed by Apple in the ’80s and became popular due to its great balance between size and quality. Fonts can be encoded, either as scalable paths, or as bitmaps of various sizes, the former generally being preferred as it allows for seamless scaling and small file size at the same time.

  • OpenType (otf/otc). OpenType was created by Microsoft and Adobe to improve upon TrueType. While TrueType was a great success, the number of glyphs it could contain was limited and so was its support for selecting different features during shaping. OpenType resolved these issues, so if you want access to advanced typography features you’ll need a font in OpenType format.

  • Web Open Font Format (woff/woff2). TrueType and OpenType tend to create large files. Since a large percentage of the text consumed today is delivered over the internet this creates a problem. WOFF resolves this problem by acting as a compression wrapper around TrueType/OpenType to reduce file sizes while also limiting the number of advanced features provided to those relevant to web fonts. The woff2 format is basically identical to woff except it uses the more efficient brotli compression algorithm. WOFF was designed specifically to be delivered over the internet and support is still a bit limited outside of browsers.

While we have mainly talked about font files as containers for the shape of glyphs, they also carries a lot of other information needed for rendering text in a way pleasant for reading. Font level information records a lot of stylistic information about typeface/font, statistics on the number of glyphs and how many different mappings between character encodings and glyphs it contains, and overall sizing information such as the maximum descend of the font, the position of an underline relative to the baseline etc. systemfonts provides a convenient way to access this data from R:

systemfonts::font_info(family = "Helvetica")
#> # A tibble: 1 × 24
#>   path  index family style italic bold  monospace weight width kerning color scalable vertical n_glyphs n_sizes
#>   <chr> <int> <chr>  <chr> <lgl>  <lgl> <lgl>     <ord>  <ord> <lgl>   <lgl> <lgl>    <lgl>       <int>   <int>
#> 1 /Sys…     0 Helve… Regu… FALSE  FALSE FALSE     normal norm… FALSE   FALSE TRUE     FALSE        2252       0
#> # ℹ 9 more variables: n_charmaps <int>, bbox <list>, max_ascend <dbl>, max_descend <dbl>,
#> #   max_advance_width <dbl>, max_advance_height <dbl>, lineheight <dbl>, underline_pos <dbl>,
#> #   underline_size <dbl>

Further, for each glyph there is a range of information in addition to its shape:

systemfonts::glyph_info("j", family = "Helvetica", size = 30)
#> # A tibble: 1 × 9
#>   glyph index width height x_bearing y_bearing x_advance y_advance bbox
#>   <chr> <int> <dbl>  <dbl>     <dbl>     <dbl>     <dbl>     <dbl> <list>
#> 1 j        77     6     27        -1        21         7         0 <dbl [4]>

These terms are more easily understood with a diagram:

The x_advance in particular is important when rendering text because it tells you how far to move to the right before rendering the next glyph (ignoring for a bit the concept of kerning)

Text shaping

The next important concept to understand is text shaping, which, in the simplest of terms, is to convert a succession of characters into a sequence of glyphs along with their locations. Important here is the distinction between characters, the things you think of as letters, and glyphs, which is what the font will draw. For example, think of the character “f”, which is often tricky to draw because the “hook” of the f can interfere with other characters. To solve this problem, many typefaces include ligatures, like “fi”, which are used for specific pairs of characters. Ligatures are extremely important for languages like Arabic.

A few of the challenges of text shaping include kerning, bidirectional text, and font substitution. Kerning is the adjustment of distance between specific pairs of characters. For example, you can put “VM” a little closer together but “OO” needs to be a little further apart. Kerning is an integral part of all modern text rendering and you will almost solemnly notice it when it is absent (or worse, wrongly applied).

Not every language writes text in the same direction, but regardless of your native script, you are likely to use arabic numerals which are always written left-to-right. This gives rise to the challenge of bidirectional (or bidi) text, which mixes text flowing in different directions. This imposes a whole new range of challenges!

Finally, you might request a character that a font doesn’t contain. One way to deal with this is to render a glyph representing a missing glyph, usually an empty box or a question mark. But it’s typically more useful to use the correct glyph from a different font. This is called font fallback and happens all the time for emojis, but can also happen when you suddenly change script without bothering to pick a new font. Font fallback is an imprecise science, typically relying on an operating system font that has a very large number of characters, but might look very different from your existing font.

Once you have determined the order and location of glyphs, you are still not done. Text often needs to be wrapped to fit into a specific width, it may need a specific justification, perhaps, indentation or tracking must be applied, etc. Thankfully, all of this is generally a matter of (often gnarly) math that you just have to get right. That is, all except text wrapping which should happen at the right boundaries, and may need to break up a word and inserting a hyphen etc.

Like I said, the pit of despair is bottomless…

Font handling in R

You hopefully arrive at this section with an appreciation of the horrors that goes into rendering text. If not, maybe this blog post will convince you.

Are you still here? Good.

Now that you understand the basics of what goes into handling fonts and text, we can now discuss the details of fonts in R specifically.

Fonts and text from a user perspective

The users perception of working with fonts in R is largely shaped by plots. This means using either base or grid graphics or one of the packages that have been build on top of it, like ggplot2. While the choice of tool will affect where you specify the font to use, they generally agree on how to specify it.

Graphic system Argument
Typeface Font Size

Base

Arguments are passed to par() to set globally or directly to the call that renders text (e.g. text())

family font cra (pixels) or cin (inches) multiplied by cex

Grid

Arguments are passed to the gp argument of relevant grobs using the gpar() constructor

fontfamily fontface fontsize (points) multiplied by cex

ggplot2

Arguments are set in element_text() to alter theme fonts or directly in the geom call to alter geom fonts

family face (in element_text()) or fontface (in geoms) size (points when used in element_text(), depends on the value of size.unit argument when used in geom)

From the table it is clear that in R fontfamily/family is used to describe the typeface and font/fontface/face is used to select a font from the typeface. Size settings is just a plain mess.

The major limitation in fontface (and friends) is that it takes a number, not a string, and you can only select from four options: 1: plain, 2: bold, 3: italic, and 4: bold-italic. This means, for example, that there’s no way to select Futura Condensed Extra Bold. Another limitation is that it’s not possible to specify any font variations such as using tabular numbers or stylistic ligatures.

Fonts and text from a graphics device perspective

In R, a graphics device is the part responsible for doing the rendering you request and put it on your screen or in a file. When you call png() or ragg::agg_png() you open up a graphics device that will receive all the plotting instructions from R. Both graphics devices will ultimately produce the same file type (PNG), but how they choose to handle and respond to the plotting instructions may differ (greatly). Nowhere is this difference more true than when it comes to text rendering.

After a user has made a call that renders some text, it is funneled through the graphic system (base or grid), handed off to the graphics engine, which ultimately asks the graphics device to render the text. From the perspective of the graphics device it is much the same information that the user provided which are presented to it. The text() method of the device are given an array of characters, the typeface, the size in points, and an integer denoting if the style is regular, bold, italic, or bold-italic.

Flow of font information through the R rendering stack
Flow of font information through the R rendering stack

This means that it is up to the graphics device to find the appropriate font file (using the provided typeface and font style) and shape the text with all that that entails. This is a lot of work, which is why text is handled so inconsistently between graphics devices. Issues can range from not being able to find fonts installed on the computer, to not providing font fallback mechanisms, or even handling right-to-left text. It may also be that certain font file formats are not well supported so that e.g. color emojis are not rendered correctly.

There have been a number of efforts to resolve these problems over the years:

  • extrafont: Developed by Winston Chang, extrafont sought to mainly improve the situation for the pdf() device which generally only had access to the postscript fonts that comes with R. The package allows the pdf() device to get access to TrueType fonts installed on the computer, as well as provide means for embedding the font into the PDF so that it can be opened on systems where the font is not installed. (It also provides the capabilities to the Windows png() device).

  • sysfonts and showtext. These packages are developed by Yixuan Qiu and provide support for system fonts to all graphics devices, by hijacking the text() method of the graphics device to treat text as polygons or raster images. This guarantees your plots will look the same on every device, but it doesn’t do advanced text shaping, so there’s no support for ligatures or font substitution. Additionally, it produces large files with inaccessible text when used to produce pdf and svg outputs.

  • systemfonts and textshaping. These packages are developed by me to provide a soup-to-nuts solution to text rendering for graphics devices. systemfonts provides access to fonts installed on the system along with font fallback mechanisms, registration of non-system fonts, reading of font files etc. textshaping builds on top of systemfonts and provides a fully modern engine for shaping text. The functionality is exposed both at the R level and at the C level, so that graphics devices can directly access to font lookup and shaping.


  1. Be aware that the style name is at the discretion of the developer of the typeface. It is very common to see discrepancies between the style name and e.g. the weight reported by the font (e.g. Avenir Next Ultra Light is a thin weight font).↩︎

systemfonts/inst/doc/systemfonts.Rmd0000644000176200001440000002221415067177637017460 0ustar liggesusers--- title: "Using systemfonts to handle fonts in R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using systemfonts to handle fonts in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} google <- suppressWarnings(try(readLines("https://8.8.8.8", n = 1L), silent = TRUE)) is_online <- !inherits(google, "try-error") knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = is_online ) systemfonts::require_font("Spectral", fallback = "serif") ``` ```{asis, echo = !is_online} > This vignette was compiled with no internet access. Since many of the code chunks require access to online font repositories evaluation has been turned off. ``` > This text expects a basic understanding of fonts and typography. If you feel like you could use a brush-up you can consult [this light introduction to the subject](fonts_basics.html). systemfonts is designed to give R a modern text rendering stack. That's unfortunately impossible without coordination with the graphics device, which means that to use all these features you need a supported graphics device. There are currently two options: - The [ragg](https://ragg.r-lib.org) package provides graphics devices for rendering raster graphics in a variety of formats (PNG, JPEG, TIFF) and uses systemfonts and textshaping extensively. - The [svglite](https://svglite.r-lib.org) package provides a graphic device for rendering vector graphics to SVG using systemfonts and textshaping for text. You might notice there's currently a big hole in this workflow: PDFs. This is something we plan to work on in the future. ## A systemfonts based workflow With all that said, how do you actually use systemfonts to use custom fonts in your plots? First, you'll need to use ragg or svglite. ### Using ragg While there is no way to unilaterally make `ragg::agg_png()` the default everywhere, it's possible to get close: - Positron: recent versions automatically use ragg for the plot pane if it's installed. - RStudio IDE: set "AGG" as the backend under Global Options \> General \> Graphics. - `ggplot2::ggsave()`: ragg will be automatically used for raster output if installed. - R Markdown and Quarto: you need to set the `dev` option to `"ragg_png"`. You can either do this with code: ``` r #| include: false knitr::opts_chunk$set(dev = "ragg_png") ``` Or in Quarto, you can set it in the yaml metadata: ``` yaml --- title: "My Document" format: html knitr: opts_chunk: dev: "ragg_png" --- ``` If you want to use a font installed on your computer, you're done! ```{r} #| fig.height: 1 #| out.extra: class='fig' grid::grid.text( "Spectral 🎉", gp = grid::gpar(fontfamily = "Spectral", fontface = 2, fontsize = 30) ) ``` Or, if using ggplot2 ```{r} #| out.extra: class='fig' #| eval: !expr exists("penguins") library(ggplot2) ggplot(na.omit(penguins)) + geom_point(aes(x = bill_len, y = body_mass, colour = species)) + labs(x = "Bill Length", y = "Body Mass", colour = "Species") + theme_minimal(base_family = "Spectral") ``` If the results don't look as you expect, you can use various systemfonts helpers to diagnose the problem: ```{r} systemfonts::match_fonts("Spectral", weight = "bold") systemfonts::font_fallback("🎉", family = "Spectral", weight = "bold") ``` If you want to see all the fonts that are available for use, you can use `systemfonts::system_fonts()` ```{r} #| eval: false systemfonts::system_fonts() ``` ```{r} #| echo: false all_fonts <- systemfonts::system_fonts() all_fonts <- all_fonts[!grepl("^/Users", all_fonts$path),] rmarkdown::paged_table(all_fonts) ``` ### Extra font styles As we discussed above, the R interface only allows you to select between four styles: plain, italic, bold, and bold-italic. If you want to use a thin font, you have no way of communicating this wish to the device. To overcome this, systemfonts provides `register_variant()` which allows you to register a font with a new typeface name. For example, to use the light font from the Spectral typeface you can register it as follows: ```{r} systemfonts::register_variant( name = "Spectral Light", family = "Spectral", weight = "light" ) ``` Now you can use Spectral Light where you would otherwise specify the typeface: ```{r} #| fig.height: 1 #| out.extra: class='fig' grid::grid.text( "Light weight is soo classy", gp = grid::gpar(fontfamily = "Spectral Light", fontsize = 30) ) ``` `register_variant()` also allows you to turn on font features otherwise hidden away: ```{r} #| fig.height: 1 #| out.extra: class='fig' systemfonts::register_variant( name = "Spectral Small Caps", family = "Spectral", features = systemfonts::font_feature( letters = "small_caps" ) ) grid::grid.text( "All caps — Small caps", gp = grid::gpar(fontfamily = "Spectral Small Caps", fontsize = 30) ) ``` ### Fonts from other places Historically, systemfonts primary role was to access the font installed on your computer, the **system fonts**. But what if you're using a computer where you don't have the rights to install new fonts, or you don't want the hassle of installing a font just to use it for a single plot? That's the problem solved by `systemfonts::add_font()` which makes it easy to use a font based on a path. But in many cases you don't even need that as systemfont now scans `./fonts` and `~/fonts` and adds any font files it find. This means that you can put personal fonts in a fonts folder in your home directory, and project fonts in a fonts directory at the root of the project. This is a great way to ensure that specific fonts are available when you deploy some code to a server. And you don't even need to leave R to populate these folders. `systemfonts::get_from_google_fonts()` will download and install a google font in `~/fonts`: ```{r} #| eval: false systemfonts::get_from_google_fonts("Barrio") grid::grid.text( "A new font a day keeps Tufte away", gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) ) ``` ```{r} #| echo: false #| message: false #| fig.height: 1 #| out.extra: class='fig' systemfonts::require_font("Barrio") grid::grid.text( "A new font a day keeps Tufte away", gp = grid::gpar(fontfamily = "Barrio", fontsize = 30) ) ``` And if you want to make sure this code works for anyone using your code (regardless of whether or not they already have the font installed), you can use `systemfonts::require_font()`. If the font isn't already installed, this function download it from one of the repositories it knows about. If it can't find it it will either throw an error (the default) or remap the name to another font so that plotting will still succeed. ```{r} #| fig.height: 1.5 #| out.extra: class='fig' systemfonts::require_font("Rubik Distressed") grid::grid.text( "There are no bad fonts\nonly bad text", gp = grid::gpar(fontfamily = "Rubik Distressed", fontsize = 30) ) ``` By default, `require_font()` places new fonts in a temporary folder so it doesn't pollute your carefully curated collection of fonts. ### Font embedding in SVG Fonts work a little differently in vector formats like SVG. These formats include the raw text and only render the font when you open the file. This makes for small, accessible files with crisp text at every level of zoom. But it comes with a price: since the text is rendered when it's opened, it relies on the font in use being available on the viewer's computer. This obviously puts you at the mercy of their font selection, so if you want consistent outputs you'll need to **embed** the font. In SVG, you can embed fonts using an `@import` statement in the stylesheet, and can point to a web resource so the SVG doesn't need to contain the entire font. systemfonts provides facilities to generate URLs for import statements and can provide them in a variety of formats: ```{r} systemfonts::fonts_as_import("Barrio") systemfonts::fonts_as_import("Rubik Distressed", type = "link") ``` Further, if the font is not available from a given online repository, it can embed the font data directly into the URL: ```{r} substr(systemfonts::fonts_as_import("Arial", repositories = NULL), 1, 200) ``` svglite uses this feature to allow seamless font embedding with the `web_fonts` argument. It can take a URL as returned by `fonts_as_import()` or just the name of the typeface and the URL will automatically be resolved. Look at line 6 in the SVG generated below ```{r} svg <- svglite::svgstring(web_fonts = "Barrio") grid::grid.text("Example", gp = grid::gpar(fontfamily = "Barrio")) invisible(dev.off()) svg() ``` ## Want more? This text has mainly focused on how to use the fonts you desire from within R. R has other limitations when it comes to text rendering specifically how to render text that consists of a mix of fonts. This has been solved by [marquee](https://marquee.r-lib.org) and the curious soul can continue there in order to up their skills in rendering text with R. systemfonts/inst/doc/fonts_basics.Rmd0000644000176200001440000035715615066741566017554 0ustar liggesusers--- title: "Typography and R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Typography and R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r} #| include: false knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = FALSE ) ``` This text is meant as an introduction to the subject of typography, both in general but more importantly as it relates to R. If you are more interested in how to use systemfonts to use fonts installed on your computer during plotting then please see [the package introduction vignette](systemfonts.html). > The code examples in this vignette is based on fonts that may not be available on the users machine. As such, you should not expect the provided examples to execute locally out of the box. ## Digital typography Many books could be, and have been, written about the subject of typography. This blog post is not meant to be an exhaustive deep dive into all areas of this vast subject. Rather, it is meant to give you just enough understanding of core concepts and terminology to appreciate how it all plays into using fonts in R. ### Typeface or font? There is a good chance that you, like 99% of world, use "font" as the term describing "the look" of the letters you type. You may, perhaps, have heard the term "typeface" as well and thought it synonymous. This is in fact slightly wrong, and a great deal of typography snobbery has been dealt out on that account (much like the distinction between packages and libraries in R). It is a rather inconsequential mix-up for the most part, especially because 99% of the population wouldn't bat an eye if you use them interchangeably. However, the distinction between the two serves as a good starting point to talk about other terms in digital typography as well as the nature of font files, so let's dive in. When most people use the word "font" or "font family", what they are actually describing is a typeface. A **typeface** is a style of lettering that forms a cohesive whole. As an example, consider the well-known "Helvetica" typeface. This name embraces many different weights (bold, normal, light) as well as slanted (italic) and upright. However, all of these variations are all as much Helvetica as the others - they are all part of the same typeface. A **font** is a subset of a typeface, describing a particular variation of the typeface, i.e. the combination of weight, width, and slant that comes together to describe the specific subset of a typeface that is used. We typically give a specific combination of these features a name, like "bold" or "medium" or "italic", which we call the **font style**[^1]. In other words, a font is a particularly style within a typeface. [^1]: Be aware that the style name is at the discretion of the developer of the typeface. It is very common to see discrepancies between the style name and e.g. the weight reported by the font (e.g. Avenir Next Ultra Light is a *thin* weight font). ```{r} #| echo: false #| fig.cap: "Different fonts from the Avenir Next typeface" #| fig.height: 3.5 st <- marquee::classic_style(30, body_font = "Avenir Next", lineheight = 1, margin = marquee::trbl(4)) |> marquee::modify_style("anul", weight = "thin") |> marquee::modify_style("anub", weight = "ultrabold") text <- paste( "{.anul Avenir Next Ultra Light} ", "{.anul *Avenir Next Ultra Light Italic*} ", "Avenir Next ", "*Avenir Next Italic* ", "{.anub Avenir Next Ultra Bold} ", "{.anub *Avenir Next Ultra Bold Italic*} ", sep = "\n" ) grid::grid.draw( marquee::marquee_grob(text, st) ) ``` ![Different fonts from the Avenir Next typeface](data:image/png;base64,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){.fig} In the rest of this document we will use the terms typeface and font with the meaning described above. ### Font files Next, we need to talk about how typefaces are represented for use by computers. Font files record information on how to draw the individual glyphs (characters), but also instructions about how to draw sequences of glyphs like distance adjustments (kerning) and substitution rules (ligatures). Font files typically encode a single font but can encode a full typeface: ``` r typefaces <- systemfonts::system_fonts()[, c("path", "index", "family", "style")] # Full typeface in one file typefaces[typefaces$family == "Helvetica", ] #> # A tibble: 6 × 4 #> path index family style #> #> 1 /System/Library/Fonts/Helvetica.ttc 2 Helvetica Oblique #> 2 /System/Library/Fonts/Helvetica.ttc 4 Helvetica Light #> 3 /System/Library/Fonts/Helvetica.ttc 5 Helvetica Light Oblique #> 4 /System/Library/Fonts/Helvetica.ttc 1 Helvetica Bold #> 5 /System/Library/Fonts/Helvetica.ttc 3 Helvetica Bold Oblique #> 6 /System/Library/Fonts/Helvetica.ttc 0 Helvetica Regular # One font per font file typefaces[typefaces$family == "Arial", ] #> # A tibble: 4 × 4 #> path index family style #> #> 1 /System/Library/Fonts/Supplemental/Arial.ttf 0 Arial Regular #> 2 /System/Library/Fonts/Supplemental/Arial Bold.ttf 0 Arial Bold #> 3 /System/Library/Fonts/Supplemental/Arial Bold Italic.ttf 0 Arial Bold Italic #> 4 /System/Library/Fonts/Supplemental/Arial Italic.ttf 0 Arial Italic ``` Here, each row is a font, with **family** giving the name of the typeface, and **style** the font style. It took a considerable number of tries before the world managed to nail the digital representation of fonts, leading to a proliferation of file types. As an R user, there are three formats that are particularly important: - **TrueType** (ttf/ttc). Truetype is the baseline format that all modern formats stand on top of. It was developed by Apple in the '80s and became popular due to its great balance between size and quality. Fonts can be encoded, either as scalable paths, or as bitmaps of various sizes, the former generally being preferred as it allows for seamless scaling and small file size at the same time. - **OpenType** (otf/otc). OpenType was created by Microsoft and Adobe to improve upon TrueType. While TrueType was a great success, the number of glyphs it could contain was limited and so was its support for selecting different features during [shaping](#text-shaping). OpenType resolved these issues, so if you want access to advanced typography features you'll need a font in OpenType format. - **Web Open Font Format** (woff/woff2). TrueType and OpenType tend to create large files. Since a large percentage of the text consumed today is delivered over the internet this creates a problem. WOFF resolves this problem by acting as a compression wrapper around TrueType/OpenType to reduce file sizes while also limiting the number of advanced features provided to those relevant to web fonts. The woff2 format is basically identical to woff except it uses the more efficient [brotli](https://en.wikipedia.org/wiki/Brotli) compression algorithm. WOFF was designed specifically to be delivered over the internet and support is still a bit limited outside of browsers. While we have mainly talked about font files as containers for the shape of glyphs, they also carries a lot of other information needed for rendering text in a way pleasant for reading. Font level information records a lot of stylistic information about typeface/font, statistics on the number of glyphs and how many different mappings between character encodings and glyphs it contains, and overall sizing information such as the maximum descend of the font, the position of an underline relative to the baseline etc. systemfonts provides a convenient way to access this data from R: ``` r systemfonts::font_info(family = "Helvetica") #> # A tibble: 1 × 24 #> path index family style italic bold monospace weight width kerning color scalable vertical n_glyphs n_sizes #> #> 1 /Sys… 0 Helve… Regu… FALSE FALSE FALSE normal norm… FALSE FALSE TRUE FALSE 2252 0 #> # ℹ 9 more variables: n_charmaps , bbox , max_ascend , max_descend , #> # max_advance_width , max_advance_height , lineheight , underline_pos , #> # underline_size ``` Further, for each glyph there is a range of information in addition to its shape: ``` r systemfonts::glyph_info("j", family = "Helvetica", size = 30) #> # A tibble: 1 × 9 #> glyph index width height x_bearing y_bearing x_advance y_advance bbox #> #> 1 j 77 6 27 -1 21 7 0 ``` These terms are more easily understood with a diagram: ```{r} #| echo: false systemfonts::plot_glyph_stats("j", family = "Helvetica", size = 30) ``` ![](data:image/png;base64,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ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFJouQEAObTcAACUwgo9AAAA4MUI9AAgh5YbAIBSCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSaLkBADm03AAAlMIKPQAAAODFCPQAIIeWGwCAUgj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUmi5AQA5tNwAAJTCCj0AAADgxQj0ACCHlhsAgFII9AAgh5YbAIBSCPQAIIeWGwCAUgj0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){.fig} The `x_advance` in particular is important when rendering text because it tells you how far to move to the right before rendering the next glyph (ignoring for a bit the concept of kerning) ### Text shaping {#text-shaping} The next important concept to understand is **text shaping**, which, in the simplest of terms, is to convert a succession of characters into a sequence of glyphs along with their locations. Important here is the distinction between **characters**, the things you think of as letters, and **glyphs**, which is what the font will draw. For example, think of the character "f", which is often tricky to draw because the "hook" of the f can interfere with other characters. To solve this problem, many typefaces include **ligatures**, like "fi", which are used for specific pairs of characters. Ligatures are extremely important for languages like Arabic. A few of the challenges of text shaping include kerning, bidirectional text, and font substitution. **Kerning** is the adjustment of distance between specific pairs of characters. For example, you can put "VM" a little closer together but "OO" needs to be a little further apart. Kerning is an integral part of all modern text rendering and you will almost solemnly notice it when it is absent (or worse, [wrongly applied](https://www.creativebloq.com/design/fonts-typography/the-popes-tomb-was-not-on-my-design-debate-bingo-card-for-2025)). Not every language writes text in the same direction, but regardless of your native script, you are likely to use arabic numerals which are always written left-to-right. This gives rise to the challenge of **bidirectional** (or bidi) text, which mixes text flowing in different directions. This imposes a whole new range of challenges! Finally, you might request a character that a font doesn't contain. One way to deal with this is to render a glyph representing a missing glyph, usually an empty box or a question mark. But it's typically more useful to use the correct glyph from a different font. This is called **font fallback** and happens all the time for emojis, but can also happen when you suddenly change script without bothering to pick a new font. Font fallback is an imprecise science, typically relying on an operating system font that has a very large number of characters, but might look very different from your existing font. Once you have determined the order and location of glyphs, you are still not done. Text often needs to be wrapped to fit into a specific width, it may need a specific justification, perhaps, indentation or tracking must be applied, etc. Thankfully, all of this is generally a matter of (often gnarly) math that you just have to get right. That is, all except text wrapping which should happen at the right boundaries, and may need to break up a word and inserting a hyphen etc. Like I said, the pit of despair is bottomless... ## Font handling in R {#font-handling-in-r} You hopefully arrive at this section with an appreciation of the horrors that goes into rendering text. If not, maybe this [blog post](https://faultlore.com/blah/text-hates-you/) will convince you. Are you still here? Good. Now that you understand the basics of what goes into handling fonts and text, we can now discuss the details of fonts in R specifically. ### Fonts and text from a user perspective {#fonts-and-text-from-a-user-perspective} The users perception of working with fonts in R is largely shaped by plots. This means using either base or grid graphics or one of the packages that have been build on top of it, like [ggplot2](https://ggplot2.tidyverse.org). While the choice of tool will affect *where* you specify the font to use, they generally agree on how to specify it. +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | Graphic system | Argument | | | +=============================================================================================================+==============+=======================================================+===============================================================================================================+ | | *Typeface* | *Font* | *Size* | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **Base** | `family` | `font` | `cra` (pixels) or `cin` (inches) multiplied by `cex` | | | | | | | *Arguments are passed to `par()` to set globally or directly to the call that renders text (e.g. `text()`)* | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **Grid** | `fontfamily` | `fontface` | `fontsize` (points) multiplied by `cex` | | | | | | | Arguments are passed to the `gp` argument of relevant grobs using the `gpar()` constructor | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ | **ggplot2** | `family` | `face` (in `element_text()`) or `fontface` (in geoms) | `size` (points when used in `element_text()`, depends on the value of `size.unit` argument when used in geom) | | | | | | | Arguments are set in `element_text()` to alter theme fonts or directly in the geom call to alter geom fonts | | | | +-------------------------------------------------------------------------------------------------------------+--------------+-------------------------------------------------------+---------------------------------------------------------------------------------------------------------------+ From the table it is clear that in R `fontfamily`/`family` is used to describe the typeface and `font`/`fontface`/`face` is used to select a font from the typeface. Size settings is just a plain mess. The major limitation in `fontface` (and friends) is that it takes a number, not a string, and you can only select from four options: `1`: plain, `2`: bold, `3`: italic, and `4`: bold-italic. This means, for example, that there's no way to select Futura Condensed Extra Bold. Another limitation is that it's not possible to specify any font variations such as using tabular numbers or stylistic ligatures. ### Fonts and text from a graphics device perspective In R, a graphics device is the part responsible for doing the rendering you request and put it on your screen or in a file. When you call `png()` or `ragg::agg_png()` you open up a graphics device that will receive all the plotting instructions from R. Both graphics devices will ultimately produce the same file type (PNG), but how they choose to handle and respond to the plotting instructions may differ (greatly). Nowhere is this difference more true than when it comes to text rendering. After a user has made a call that renders some text, it is funneled through the graphic system (base or grid), handed off to the graphics engine, which ultimately asks the graphics device to render the text. From the perspective of the graphics device it is much the same information that the user provided which are presented to it. The `text()` method of the device are given an array of characters, the typeface, the size in points, and an integer denoting if the style is regular, bold, italic, or bold-italic. ![Flow of font information through the R rendering stack](text_call_flow.svg){.fig fig-alt="A diagram showing the flow of text rendering instructions from ggplot2, grid, the graphics engine, and down to the graphics device. Very little changes in the available information about the font during the flow"} This means that it is up to the graphics device to find the appropriate font file (using the provided typeface and font style) and shape the text with all that that entails. This is a lot of work, which is why text is handled so inconsistently between graphics devices. Issues can range from not being able to find fonts installed on the computer, to not providing font fallback mechanisms, or even handling right-to-left text. It may also be that certain font file formats are not well supported so that e.g. color emojis are not rendered correctly. There have been a number of efforts to resolve these problems over the years: - **extrafont**: Developed by Winston Chang, [extrafont](https://github.com/wch/extrafont) sought to mainly improve the situation for the `pdf()` device which generally only had access to the postscript fonts that comes with R. The package allows the `pdf()` device to get access to TrueType fonts installed on the computer, as well as provide means for embedding the font into the PDF so that it can be opened on systems where the font is not installed. (It also provides the capabilities to the Windows `png()` device). - **sysfonts** and **showtext**. These packages are developed by Yixuan Qiu and provide support for system fonts to all graphics devices, by hijacking the `text()` method of the graphics device to treat text as polygons or raster images. This guarantees your plots will look the same on every device, but it doesn't do advanced text shaping, so there's no support for ligatures or font substitution. Additionally, it produces large files with inaccessible text when used to produce pdf and svg outputs. - **systemfonts** and **textshaping**. These packages are developed by me to provide a soup-to-nuts solution to text rendering for graphics devices. [systemfonts](https://systemfonts.r-lib.org) provides access to fonts installed on the system along with font fallback mechanisms, registration of non-system fonts, reading of font files etc. [textshaping](https://github.com/r-lib/textshaping) builds on top of systemfonts and provides a fully modern engine for shaping text. The functionality is exposed both at the R level and at the C level, so that graphics devices can directly access to font lookup and shaping. systemfonts/inst/doc/systemfonts.html0000644000176200001440000135670015067213517017701 0ustar liggesusers Using systemfonts to handle fonts in R

Using systemfonts to handle fonts in R

This text expects a basic understanding of fonts and typography. If you feel like you could use a brush-up you can consult this light introduction to the subject.

systemfonts is designed to give R a modern text rendering stack. That’s unfortunately impossible without coordination with the graphics device, which means that to use all these features you need a supported graphics device. There are currently two options:

  • The ragg package provides graphics devices for rendering raster graphics in a variety of formats (PNG, JPEG, TIFF) and uses systemfonts and textshaping extensively.
  • The svglite package provides a graphic device for rendering vector graphics to SVG using systemfonts and textshaping for text.

You might notice there’s currently a big hole in this workflow: PDFs. This is something we plan to work on in the future.

A systemfonts based workflow

With all that said, how do you actually use systemfonts to use custom fonts in your plots? First, you’ll need to use ragg or svglite.

Using ragg

While there is no way to unilaterally make ragg::agg_png() the default everywhere, it’s possible to get close:

  • Positron: recent versions automatically use ragg for the plot pane if it’s installed.

  • RStudio IDE: set “AGG” as the backend under Global Options > General > Graphics.

  • ggplot2::ggsave(): ragg will be automatically used for raster output if installed.

  • R Markdown and Quarto: you need to set the dev option to "ragg_png". You can either do this with code:

    #| include: false
    knitr::opts_chunk$set(dev = "ragg_png")

    Or in Quarto, you can set it in the yaml metadata:

    ---
    title: "My Document"
    format: html
    knitr:
      opts_chunk:
        dev: "ragg_png"
    ---

If you want to use a font installed on your computer, you’re done!

grid::grid.text(
  "Spectral 🎉",
  gp = grid::gpar(fontfamily = "Spectral", fontface = 2, fontsize = 30)
)

Or, if using ggplot2

library(ggplot2)
ggplot(na.omit(penguins)) +
  geom_point(aes(x = bill_len, y = body_mass, colour = species)) +
  labs(x = "Bill Length", y = "Body Mass", colour = "Species") +
  theme_minimal(base_family = "Spectral")

If the results don’t look as you expect, you can use various systemfonts helpers to diagnose the problem:

systemfonts::match_fonts("Spectral", weight = "bold")
#> # A tibble: 1 × 4
#>   path                                         index features   variations
#>   <chr>                                        <int> <list>     <list>    
#> 1 /Users/thomas/Library/Fonts/Spectral 700.ttf     0 <font_ftr> <fnt_vrtn>
systemfonts::font_fallback("🎉", family = "Spectral", weight = "bold")
#>                                          path index
#> 1 /System/Library/Fonts/Apple Color Emoji.ttc     0

If you want to see all the fonts that are available for use, you can use systemfonts::system_fonts()

systemfonts::system_fonts()

Extra font styles

As we discussed above, the R interface only allows you to select between four styles: plain, italic, bold, and bold-italic. If you want to use a thin font, you have no way of communicating this wish to the device. To overcome this, systemfonts provides register_variant() which allows you to register a font with a new typeface name. For example, to use the light font from the Spectral typeface you can register it as follows:

systemfonts::register_variant(
  name = "Spectral Light",
  family = "Spectral",
  weight = "light"
)

Now you can use Spectral Light where you would otherwise specify the typeface:

grid::grid.text(
  "Light weight is soo classy",
  gp = grid::gpar(fontfamily = "Spectral Light", fontsize = 30)
)

register_variant() also allows you to turn on font features otherwise hidden away:

systemfonts::register_variant(
  name = "Spectral Small Caps",
  family = "Spectral",
  features = systemfonts::font_feature(
    letters = "small_caps"
  )
)
grid::grid.text(
  "All caps — Small caps",
  gp = grid::gpar(fontfamily = "Spectral Small Caps", fontsize = 30)
)

Fonts from other places

Historically, systemfonts primary role was to access the font installed on your computer, the system fonts. But what if you’re using a computer where you don’t have the rights to install new fonts, or you don’t want the hassle of installing a font just to use it for a single plot? That’s the problem solved by systemfonts::add_font() which makes it easy to use a font based on a path. But in many cases you don’t even need that as systemfont now scans ./fonts and ~/fonts and adds any font files it find. This means that you can put personal fonts in a fonts folder in your home directory, and project fonts in a fonts directory at the root of the project. This is a great way to ensure that specific fonts are available when you deploy some code to a server.

And you don’t even need to leave R to populate these folders. systemfonts::get_from_google_fonts() will download and install a google font in ~/fonts:

systemfonts::get_from_google_fonts("Barrio")

grid::grid.text(
  "A new font a day keeps Tufte away",
  gp = grid::gpar(fontfamily = "Barrio", fontsize = 30)
)

And if you want to make sure this code works for anyone using your code (regardless of whether or not they already have the font installed), you can use systemfonts::require_font(). If the font isn’t already installed, this function download it from one of the repositories it knows about. If it can’t find it it will either throw an error (the default) or remap the name to another font so that plotting will still succeed.

systemfonts::require_font("Rubik Distressed")
#> Trying Google Fonts... Found! Downloading font to /var/folders/l4/tvfrd0ps4dqdr2z7kvnl9xh40000gn/T//Rtmp4X1MIR

grid::grid.text(
  "There are no bad fonts\nonly bad text",
  gp = grid::gpar(fontfamily = "Rubik Distressed", fontsize = 30)
)

By default, require_font() places new fonts in a temporary folder so it doesn’t pollute your carefully curated collection of fonts.

Font embedding in SVG

Fonts work a little differently in vector formats like SVG. These formats include the raw text and only render the font when you open the file. This makes for small, accessible files with crisp text at every level of zoom. But it comes with a price: since the text is rendered when it’s opened, it relies on the font in use being available on the viewer’s computer. This obviously puts you at the mercy of their font selection, so if you want consistent outputs you’ll need to embed the font.

In SVG, you can embed fonts using an @import statement in the stylesheet, and can point to a web resource so the SVG doesn’t need to contain the entire font. systemfonts provides facilities to generate URLs for import statements and can provide them in a variety of formats:

systemfonts::fonts_as_import("Barrio")
#> [1] "https://fonts.bunny.net/css2?family=Barrio&display=swap"
systemfonts::fonts_as_import("Rubik Distressed", type = "link")
#> [1] "<link rel=\"stylesheet\" href=\"https://fonts.bunny.net/css2?family=Rubik+Distressed&display=swap\"/>"

Further, if the font is not available from a given online repository, it can embed the font data directly into the URL:

substr(systemfonts::fonts_as_import("Arial", repositories = NULL), 1, 200)
#> [1] "data:text/css,@font-face%20%7B%0A%20%20font-family:%20%22Arial%22;%0A%20%20src:%20url(data:font/ttf;charset=utf-8;base64,AAEAAAAYAQAABACARFNJR3IyojEAC6hEAAAkMEdERUaJ1Y1JAAq3BAAAAsJHUE9TDxNq3wAK1YAAALE"

svglite uses this feature to allow seamless font embedding with the web_fonts argument. It can take a URL as returned by fonts_as_import() or just the name of the typeface and the URL will automatically be resolved. Look at line 6 in the SVG generated below

svg <- svglite::svgstring(web_fonts = "Barrio")
grid::grid.text("Example", gp = grid::gpar(fontfamily = "Barrio"))
invisible(dev.off())
svg()
#> <?xml version='1.0' encoding='UTF-8' ?>
#> <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='720.00pt' height='576.00pt' viewBox='0 0 720.00 576.00'>
#> <g class='svglite'>
#> <defs>
#>   <style type='text/css'><![CDATA[
#>     @import url('https://fonts.bunny.net/css2?family=Barrio&display=swap');
#>     .svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle {
#>       fill: none;
#>       stroke: #000000;
#>       stroke-linecap: round;
#>       stroke-linejoin: round;
#>       stroke-miterlimit: 10.00;
#>     }
#>     .svglite text {
#>       white-space: pre;
#>     }
#>     .svglite g.glyphgroup path {
#>       fill: inherit;
#>       stroke: none;
#>     }
#>   ]]></style>
#> </defs>
#> <rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/>
#> <defs>
#>   <clipPath id='cpMC4wMHw3MjAuMDB8MC4wMHw1NzYuMDA='>
#>     <rect x='0.00' y='0.00' width='720.00' height='576.00' />
#>   </clipPath>
#> </defs>
#> <g clip-path='url(#cpMC4wMHw3MjAuMDB8MC4wMHw1NzYuMDA=)'>
#> <text x='360.00' y='292.32' text-anchor='middle' style='font-size: 12.00px; font-family: "Barrio";' textLength='48.12px' lengthAdjust='spacingAndGlyphs'>Example</text>
#> </g>
#> </g>
#> </svg>

Want more?

This text has mainly focused on how to use the fonts you desire from within R. R has other limitations when it comes to text rendering specifically how to render text that consists of a mix of fonts. This has been solved by marquee and the curious soul can continue there in order to up their skills in rendering text with R.

systemfonts/inst/doc/fonts_basics.R0000644000176200001440000000200315067213507017173 0ustar liggesusers## ----------------------------------------------------------------------------- knitr::opts_chunk$set( dev = "ragg_png", dpi = 144, collapse = TRUE, comment = "#>", fig.asp = NULL, fig.height = 4.326, fig.width = 7, eval = FALSE ) ## ----------------------------------------------------------------------------- # st <- marquee::classic_style(30, body_font = "Avenir Next", lineheight = 1, margin = marquee::trbl(4)) |> # marquee::modify_style("anul", weight = "thin") |> # marquee::modify_style("anub", weight = "ultrabold") # text <- paste( # "{.anul Avenir Next Ultra Light} ", # "{.anul *Avenir Next Ultra Light Italic*} ", # "Avenir Next ", # "*Avenir Next Italic* ", # "{.anub Avenir Next Ultra Bold} ", # "{.anub *Avenir Next Ultra Bold Italic*} ", # sep = "\n" # ) # grid::grid.draw( # marquee::marquee_grob(text, st) # ) ## ----------------------------------------------------------------------------- # systemfonts::plot_glyph_stats("j", family = "Helvetica", size = 30) systemfonts/inst/doc/c_interface.Rmd0000644000176200001440000001505215066503755017317 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} #| label: 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_fonts()` imported into your package namespace. All functions are provided in the `systemfonts::ver2` namespace. Legacy API is not namespaced. The different functionality will be discussed below: ## Font matching The C equivalent of the `match_fonts()` R function is `locate_font()` with the following signature: ```C FontSettings2 locate_font( const char *family, double italic, double weight, double width, const int* axes, const int* coords, int n_axes ) ``` It takes a UTF-8 encoded string with the font family name, a double giving italic (usually 0.0 == "upright" and 1.0 == "italic"), a double giving weight (usually ranging between 100.0 and 1000.0 — 0.0 means "undefined") and a double giving "width" (usually ranging from 1.0 to 10.0 — 0.0 means undefined). Lastly you can provide variable axis coords with the `axes` and `coords` array pointers with `n_axes` giving the number in the arrays (which are assumed to be of the same length). The values of each array are not immediately understandable to the human eye and will usually come from a user through a call to `font_variation()`. If the axes array contain "ital", "wght", and/or "wdth" *and* the font has these variable axes then the values for these axes will overwrite the values provide in `italic`, `weight`, and `width`. The returned `FontSettings2` struct will contain both the font location and index along with any OpenType feature settings and the axes settings in the case of a variable font. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings2` struct as the `features`, `axes`, and `coords` arrays 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; const int* axes; const int* coords; int n_axes; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const FontSettings2& font, 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, with a `FontSettings2` object describing the font. 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. ## 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 int error pointer to 0 if successful. ```C get_cached_face( const FontSettings2& font, 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 responsibility of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Check for Freetype compatibility If you are using a cached face from systemfonts you should ensure that your code has been compiled with the same version of Freetype as systemfonts has. You can do this with the `check_ft_version()` from the `systemfonts-ft.h` header. It takes no arguments and return `true` if the Freetype version from systemfonts corresponds with the one your library is compiled with. ## 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 `FontSettings2` object, though features are always empty. ```C FontSettings2 get_fallback( const char* string, const FontSettings2& font ) ``` ## 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 `FontSettings2` object and returns the weight (100-900 or 0 if it is undefined by the font) respecting the variable axes settings if given. ```C int get_font_weight( const FontSettings2& font ) ``` ## Family name It may be beneficial to know the family name from a given 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 FontSettings2& font, 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 FontSettings2& font ) ``` 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/0000755000176200001440000000000015002125620014001 5ustar liggesuserssystemfonts/tools/winlibs.R0000644000176200001440000000162715002125620015601 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.md0000644000176200001440000001403315066740236014141 0ustar liggesusers # systemfonts systemfonts website [![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/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/systemfonts/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/r-lib/systemfonts/graph/badge.svg)](https://app.codecov.io/gh/r-lib/systemfonts) 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('pak') pak::pak('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 variations #> 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: 1,189 × 10 #> path index name family style weight width italic monospace variable #> #> 1 /Users/thoma… 0 Exo2… Exo 2 Black heavy norm… FALSE FALSE FALSE #> 2 /Users/thoma… 0 Exo2… Exo 2 Blac… heavy norm… TRUE FALSE FALSE #> 3 /Users/thoma… 0 Exo2… Exo 2 Bold bold norm… FALSE FALSE FALSE #> 4 /Users/thoma… 0 Exo2… Exo 2 Bold… bold norm… TRUE FALSE FALSE #> 5 /Users/thoma… 0 Exo2… Exo 2 Extr… ultra… norm… FALSE FALSE FALSE #> 6 /Users/thoma… 0 Exo2… Exo 2 Extr… ultra… norm… TRUE FALSE FALSE #> 7 /Users/thoma… 0 Exo2… Exo 2 Extr… light norm… FALSE FALSE FALSE #> 8 /Users/thoma… 0 Exo2… Exo 2 Extr… light norm… TRUE FALSE FALSE #> 9 /Users/thoma… 0 Exo2… Exo 2 Ital… normal norm… TRUE FALSE FALSE #> 10 /Users/thoma… 0 Exo2… Exo 2 Light light norm… FALSE FALSE FALSE #> # ℹ 1,179 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. ### Custom fonts While the package was created to provide transparent access to fonts installed on the system, it has grown to also provide ways to work with font files not part of the system installation. This is especially beneficial if you are running code on a system where you don’t have administrator rights and need to use a custom font. systemfonts provide the `add_fonts()` function which takes a vector of file paths and add these to the lookup database without installing them. Further, systemfonts automatically looks in the `./fonts` and `~/fonts` folders and adds any font files located there during startup. This means that you can distribute a script along with a fonts directory and have those fonts automatically available during execution of the script. In addition to the above, systemfonts also provides access to online font repositories such as [Google Fonts](https://fonts.google.com) and can search and download from these, automatically adding the downloaded fonts to the lookup database. All these functionalities are condensed into a single function, `require_font()`, which allows you to state a font dependency inside a script. The function will first look for the font on the system, and failing that, will try to fetch it from an online repository. If that fails it will either throw an error or remap the font to another of the developers choosing. ## 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/0000755000176200001440000000000015067213520013750 5ustar liggesuserssystemfonts/build/vignette.rds0000644000176200001440000000043315067213517016315 0ustar liggesusersRn05$". C,%v䳄KzC~=5#I`-LK/d"?i.YγmSX:-4pf rt`x3y27gZuԬ:dA߃G?6VRszBbw8,W-d?<؅%_M`H,ypc,#kHpĒZnN6\o,OޝFjuܰo}f9rV0òR{D?osystemfonts/configure0000755000176200001440000000722215067213520014563 0ustar liggesusers# Anticonf (tm) script by Jeroen Ooms (2020) # This script will query 'pkg-config' for the required cflags and ldflags. # If pkg-config is unavailable or does not find the library, try setting # INCLUDE_DIR and LIB_DIR manually via e.g: # R CMD INSTALL --configure-vars='INCLUDE_DIR=/.../include LIB_DIR=/.../lib' # Library settings SYS="UNIX" PKG_CONFIG_NAME="fontconfig freetype2" PKG_DEB_NAME="libfontconfig1-dev" PKG_RPM_NAME="fontconfig-devel" PKG_CSW_NAME="fontconfig_dev" PKG_BREW_NAME="freetype" PKG_TEST_HEADER="" 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/0000755000176200001440000000000015017511630013422 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/fonts_as_import.Rd0000644000176200001440000000647615005123425017132 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/web_fonts.R \name{fonts_as_import} \alias{fonts_as_import} \title{Create import specifications for web content} \usage{ fonts_as_import( family, italic = NULL, weight = NULL, width = NULL, ..., type = c("url", "import", "link"), may_embed = TRUE, repositories = c("Bunny Fonts", "Google Fonts", "Font Library") ) } \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{...}{Additional arguments passed on to the specific functions for the repositories. Currently: \itemize{ \item \strong{Google Fonts and Bunny Fonts:} \itemize{ \item \code{text} A piece of text containing the glyphs required. Using this can severely cut down on the size of the required download \item \code{display} One of \code{"auto"}, \code{"block"}, \code{"swap"}, \code{"fallback"}, or \code{"optional"}. Controls how the text is displayed while the font is downloading. } }} \item{type}{The type of return value. \code{"url"} returns the bare url pointing to the style sheet. \code{"import"} returns the stylesheet as an import statement (\verb{@import url()}). \code{"link"} returns the stylesheet as a link tag (\verb{})} \item{may_embed}{Logical. Should fonts that can't be found in the provided repositories be embedded as data-URLs. This is only possible if the font is available locally and in a \code{woff2}, \code{woff}, \code{otf}, or \code{ttf} file.} \item{repositories}{The repositories to try looking for the font. Currently \code{"Bunny Fonts"}, \code{"Google Fonts"}, and \code{"Font Library"} are supported. Set this to \code{NULL} together with \code{may_embed = TRUE} to force embedding of the font data.} } \value{ A character vector with stylesheet specifications according to \code{type} } \description{ If you create content in a text-based format such as HTML or SVG you need to make sure that the font is available on the computer where it is viewed. This can be achieved through the use of stylesheets that can either be added with a \verb{} tag or inserted with an \verb{@import} statement. This function facilitates the creation of either of these (or the bare URL to the stylesheet). It can rely on the Bunny Fonts, Google Fonts and/or Font Library repositories for serving the fonts. If the requested font is not found it can optionally hard code the data into the stylesheet. } 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.Rd0000644000176200001440000000326615005133503016416 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", "Font Library"), error = TRUE, verbose = 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 \code{"Google Fonts"}, \code{"Font Squirrel"}, and \code{"Font Library"} is available.} \item{error}{Should the function throw an error if unsuccessful?} \item{verbose}{Should status messages be emitted?} } \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/plot_glyph_stats.Rd0000644000176200001440000000374515017511630017321 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_outline.R \name{plot_glyph_stats} \alias{plot_glyph_stats} \title{Create a visual representation of what the various glyph stats mean} \usage{ plot_glyph_stats( glyph, family = "", italic = FALSE, weight = "normal", width = "undefined", size = 12, res = 72, variation = font_variation(), path = NULL, index = 0 ) } \arguments{ \item{glyph}{The character to plot} \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{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related measures} \item{variation}{A \code{font_variation} object or a list of them to control variable fonts} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ This function is called for its side effects } \description{ This function helps you understand the concepts of width, height, bearing, and advance by annotating a glyph with the various measures } \examples{ plot_glyph_stats("g") } systemfonts/man/font_variation.Rd0000644000176200001440000000560315017310404016733 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_variation.R \name{font_variation} \alias{font_variation} \title{Define axis coordinates for variable fonts} \usage{ font_variation( italic = NULL, weight = NULL, width = NULL, slant = NULL, optical_sizing = NULL, ... ) } \arguments{ \item{italic}{Value between 0 and 1. Usually treated as a boolean rather than a continuous axis} \item{weight}{Usually a value between 100 and 900 though fonts can limit or expand the supported range. Weight names are also allowed (see \code{\link[=as_font_weight]{as_font_weight()}})} \item{width}{Usually a value between 1 and 9 though fonts can limit or expand the supported range. Width names are also allowed (see \code{\link[=as_font_width]{as_font_width()}})} \item{slant}{The angular slant of the font, usually between -90 and 90 (negative values tilt in the "standard" italic way)} \item{optical_sizing}{Stroke thickness compensation for the glyphs. During rendering the thickness of the stroke is usually increased when the font is set at small sizes to increase readability. Set this to the size of the font to get the "expected" look at that size.} \item{...}{Further axes and coordinate settings} } \value{ A \code{font_variation} object with coordinates for the provided axes } \description{ Variable fonts is a technology that allows font designers to encode the full, continuous font space of a typeface into a single font file and then have the user set the coordinates of the variable axes to define the font. So, rather than having a font file for bold, bold + italic, thin, and thin + italic, etc. it is all encoded in a single file with a continuous range of all axes (e.g. weight doesn't have to be one of the 9 standard weights but can be anything in between). There are 5 standard axes that fonts can use, but font designers are free to define their own completely arbitrary axes as well. You can use \code{\link[=font_info]{font_info()}} to see which axes a font defines along with their value range and default setting. Values given as \code{font_variation()} will always win over the conventional setting \emph{if} the axis is present in the font. For example, setting \code{weight = "bold"} along with \code{variation = font_variation(weight = 650)} will eventually request a weight of \code{650} (helfway between semibold and bold), assuming the weight-axis is present in the font. For clarity however, it is advised that \code{font_variation()} is only used for axes that can otherwise not be accessed by "top-level" arguments. } \note{ systemfonts uses a scale of width values ranging from 1-9 while the width axis uses a different scale (0.5 - 2.0) going from half as wide to twice as wide as "normal". When using the \code{width} argument the coordinate values is automatically converted. If you set the value based on the width tag (\code{wdth}) then no conversion will happen. } systemfonts/man/font_feature.Rd0000644000176200001440000000774615017511630016410 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 surrounding 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 mimic 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.Rd0000644000176200001440000000502515017511630016420 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, weight = "normal", width = "undefined", size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0, bold = deprecated() ) } \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{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{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related measures} \item{include_bearing}{Logical, should left and right bearing be included in the string width?} \item{path, index}{path and index of a font file to circumvent lookup based on family and style} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} } \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.Rd0000644000176200001440000001221715017511630016402 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, weight = "normal", width = "undefined", size = 12, res = 72, lineheight = 1, align = "left", hjust = 0, vjust = 0, max_width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, index = 0, bold = deprecated() ) } \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{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{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related measures} \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{max_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} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} } \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.Rd0000644000176200001440000000527015005123736017233 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} - \href{https://fonts.bunny.net/}{Bunny Fonts} provide the same fonts as Google Fonts but doesn't have a search API). 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.Rd0000644000176200001440000000473315017511630015661 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 or urls 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 privileges 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.Rd0000644000176200001440000000262515010615070017701 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{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 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 Software, PBC (03wc8by49) [copyright holder, funder] } } \keyword{internal} systemfonts/man/figures/0000755000176200001440000000000015010615043015062 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/logo.png0000644000176200001440000007362615010615043016546 0ustar liggesusersPNG  IHDR7bKGD̿ pHYs.#.#x?vtIME #<vIDATxڼ}umWq7ֶc{Q%!സKPhq BPR,[P$!!!ޕZ3?>yk^2ߚfܗ7(bH>׿~'n[ _"V;DDz">1 N0?1% ,*.vݒs N"i ) _ڜ!OySV{qFW4x 8lm0}gPXK"hpArc|635_Z{ރxxZpU4}AW@nT?`QEJ0JoNg\ y C+p3}K,+}Boy^#?vy6󝿿xW[_/-kȋEaiU b+aڽ_l",o9J á?NyҎKNɪAJعէ(fI'ܰ? armXҿԃ-b*#g cd.r9w Aȭ__q$.Ϻ޴`ZRҐ32$~37`h {s/ejFF:Ͼ1+))a!Dǟ#c#((~"V>{ĭ GtG0m<,Vfo}űˆ[k`i9'7XYΑ i+!l@_;z{h bE$@ªj@JJu-/?Vk?t g )8Zg4qY4', '_Vߛ xr;Is4]ė14q>+[TB=qv9[Fw|Í'poN>yyy - }0VD`0ZV`O3B?_SZ,bܫOx +pc GRX}o3S{ ڭs^ u Tމ(gU "P֢ĄK7;q6Bk b<憟kl@ˎ.l/e=gS&Vbg,USid+VFcj}pi;bz5kd%$,{9_|~DJ(i )%xė :Иz@B륁#>~y{{pV<˪jϺ'&LeǪj Wz[8XVQO ߨY`%?kĻ| :ܵ GY4@Jʴ%+* 6ތ<5=;o|pwB7awܴyMƴt곢DL u{JL f"V Y/氧DL\CLLJ|YĪ&6v?+hb%*jQQ!@Vc 4U*FAD4x'AE+ Ѵ:烈`yG﵌6?w_;,Gٗ7)#֪F,H(By bbҎ%CBXdX PT#æbd˖ԒwM~q53FǧĢS? J"?^ּG=ɘ= )^7DBI R%b9D!%7c% we|K78O2Icvd X)Si f`2^))Փ^+\.üZC cS=ma=qhL@ GPO,䯞?Bͦ|+ݒ*::f&s:c@DLLʵXI@LDDJQ4BLh%b""ҚIHg|%^y›rpGYw={ajRJJ]G9(Du8,H'[j15dl-VF RbH2w 9JjFk;^Oy-_p#Ͻ˻̇DBV=E_Ďy҇UE"ȘX* tDD fjƒQ`SpnMdm؈(q)l6 bbUj.r3 18@ɘ3=7m0IϚ76&6O|axw`KX"hjOx QQPtHT%X Bh`;bvA}d/r+WE]*fuKK8wmX׿v]ppV5tBK0)bDX#ʱX*. 2.JkIVD>qNtCnIuEXx Q}.N#]CMm5ߗ`ޔIw[a^0k߶/wjĽYDUR4_B,o ()쁴/=X|%"Y %Kh3V?d(_pꦸCk^2Zּ7y-!rc֌j9؂))/>?`2]4A$f_U@!|9__+O{|緸).#pKJ9iXƑgv̬ F6Q,3pCNfښe17}4D(MT̺|hc^zYE]|7V{~t6)QDs:kjdwڥ)~8Uu!pxϾ۝~x_26 JV`wOhvN(☚$PhaH"+V %:o# [+ U(∘fxW/-?=t)| |)[飧m PrrR"'*E_ HjY.qN}T(P`2PҙbPjPuw)iUM.'U ,B끒8 _.ۀ>~,Mj ϾxAe}?5ֺ B8f;`m7lv2zA {f@GV1IiY1P(gHAP!OHF:4\Q:8IT UQd^! O!pI썚堤'\pl@{Dn6[.st薩̯֑42&*^Rs_yeI 9O'zS4@:2S %V5GUC.^4(o IZCR[{ޟ8؀bg>ˋ~þ-H93aHgXh}R" "Jc D`\-7 P6ׂ歴 -.ɧ̶nf`by)gϬC0_/U(Q "V T< x!RU!C UXgy!U!r$414H8("UCK)ע&%ey۠*ZpP6"҆$U0Ga}Ֆ N|s!pq/;L;CT))WUfЫZO:P`hӉL,;hY)VbFq4R#QFw"4rj#&Ɣ8 #mt(J'`A)xSO3'SV M%1zG ViO>P<_#'-$V9o8zAJD: P5Pe0Y\ )͌EǢh!`fDD  Ž@$9$&饃킚hm$z>"@ (0sh4ȊM{ )+=76Lb6Cyn]*AkuQQ_!ʪKPyPFcGU g,WEҫju'#*•vԭԴ*Un :碨t= ](D%UTNEЙ7o/9zlD}_o90 ƎL@V &V`:Y/cSuF 6Jj9)u jX-+:&OQp4NyN,l4*djVJkk8*{p/yfs{<ı34j\[D  fݣ67ZxUv"ZRˆ mՀ6"U >KC"qm[}of|8"iZ|ANӈn7U?CRV 6VinSSP!˹9T'{Qo綕-/˼ڦݹ"{UnWlbY3-yނHfv cnZP&rL<+y~O7;BtoVT<Թج@@J&0woR҂)B S6x^&+ 7.PR#lII;o; BBj54ۙ`X5ӨN~Nݩt :.tKTI Ӥ*҂}\'r.EB<;%|PK'\ `E_@ʥti> =4sA$I\ mL* !u/gl]h:^u'(hSk3:tɝqMRNKݑS׺fQ3-kki)u4#@uj$T(2h;PUdzt֜wLO9 SX)aG O_SF3GR5.h&RY/N-*;ENU0A׾r^7a̖ ˦2I btx3t!bPx%c6&i)b8;XJĚA0nC( e820Th#(EdjY(0`*[V0CMRwhq̫B) 5|DE A;_fCq]E sNUZUII-7EhL u\Ij:[-JPvAAՆ JAמ|*Ij!ۓ~KE&}l)vj8kXidF죴M(w+dcRkL2lH(ƛ,'l GRJ+XL̈8X5l w5X֛ QҸ48K>#&bI}ȘH;74>&<y/MWsL_{Z( 0d$MtȦ`0Bt TE[ @d!v,3x9X2āDEQ7YH">QupD$*jtD3]µa9ny/uM}Z4 t8+?He%"5l~[Uޛ~Ŋ Fws/ZALVO¸%02Vful;/<+S5Sn$ӻ>d-8jG̤*iiMxK@jl_r.:gU;ڢ2>\`B /9㞉|N"B JDV&QI}$L,5<_O³:  ݿ 0>&igu :F]8u6]k•}k;sZ B|D(RL ,#f)Ewlegȫ+lVޭҋ`l۾vJ㺵aB{.M|z?9?ǻֳʥÀ˼Ww<6:]fJϝudsu Ў_Һty1Sc56lD٭v̤Ɔ bZ%Y"w}䜋\f<O4<uԁ]2\)ʾn5N/~Ez6|xܧP(fzٝ`H bX&&5WWHkG<ό'~ay"6õĕE~9'|&W\{Y~ewyQy-;{6~ٿGOcPEo~Pm_ YOGd6(q fc Te_'PrlͭrTJg/CJؗ7Q.+z\wWz1:/xA;9>OٵRXS)gɅ`U %(qV~]f鈓N=~okEt!_qwcKs+(II])&"۳=zD&rLk҆=;C)kSg1/_|as~ٙ7@*>]wxMFRNBiMcB<{fӳyrx˟zXgfu -3IG"NVt Ɗ&>L`VU2P2 e"zʻ\{$C'vQ j/- 5a=o7|rd "(ÈP1dѫuZ^ngMOb3c91Hfe]\ߵP}?n1 r'g>a/ :_5@귗V'~87bn_T2d@޵@|Ǡ5>CoNaQ,nC7Cv1#Y'T+D9JQURE>#'aOh2 ۡ)>ML6]8Ք^|Q|)2*~6ݎxےIw&Mez\|Uݤm70u?Vȥ"6)1vE](, wnW,y=[[#U PGY]&Id-MB*T`^#Wm`Pk\w[qHWeS#(~OJHeθV$j=3@)`VgB,@ZQD+~`-zi6bvǟl\=)Ȭ4P{,Wj'cS=|eOOD=!QJ{__@+ H+{^JԈE[~CCVbH{`uemmueecc3!ű+ҠCh$hiF"P8|AhY;k+VôZ /9\?ǜz[l%2M|?kۧبH7&J`o}B齆2NGUu(+D͠ʬ4Q?Mӥ: TXeKޔ]-Qp=L`k^]٬_^ց+|?o}V*H~ϝ_KjIw{JN}2"~'GDU >Rv(wȞK_n6ⵥ/oZ}-V#{)R Av昐Šhkb-GVM8#r} {}QbUm\@X0Iݓ#Kr߅^~nߥ>D[kjQ̉MdT82QD&}Y +X6;jk8S56"RJ .0TU(ͤNpr,[~[7z7_. Aڌ<女=})wLj? ,'IC\^}G|| MOT!: ZB03n]]~?=}mKX`&bgWqW2(?fHe [jHAH!f;tV ""—*SJECD6~ʣzuR!q|>$%$wp W?~W#+<mpcguU _褹/#p77 L^|VUV94Ikd .,TQ[/J.`Gd[-}IL].SKӾ_RN讎?=4Vm}'o2X"PGkԗv+Zr,Є32-S7p쏺psȯ|޸ x n>)slJ]1iv=]:;!У. U!?E/\}&Wx۞We$ w0u 7Bw(Gu6nfDl~h|1z (J7A8f:O,XǸ`1 "V!"0yƧnc3h'O̍O#0fQqͱv2Κp]Jj$\%MsRc4{իCt{wٲ|PBAHHjB@ w"f#C܋kObY&.V Eз6tFmHD;~Gºov+#NS:'IUbYz֏'WڭotLYf:$mG훿{I׹A=hK5\# ){eܧ6JZEZ9e:hFXQ:ߐ& 9RP1YlKH +d_&,VƬHK)ΏS'PAy3Rjȱ ae̠}]x`m#JP% #Q6}oEjR47aU=pDj%)ڗ= ϛ62 b31tgsUymAuPpᨥ0/ZQPZ/ r*h[H|=n}ѡH)RJGs- 5#.VjMk'2jiލ9OJA-FšVy'2-`L-HjD q݄LNL@8C#>IyK?2>0|1\ʤԩ{DrK((0DK ⬭S0.% 0 F(֊G@q 8aRb&uW$x)OBvq;ȏFnTS#9GFȃ#=XCG>[~D~ۗLƵ)s7f-1"/dTndu l˸jiSus MUX7yƅ툩F*cdOt%{ư )U;gBZEv] EquW"f2fƉ&IT]W׈wQD`Gy)*A)(qFw=ڬA:ZEr-FVAi7rq{+ uẼV(ؕc?yr# a2*.< X$2 QgXTW`<@UU"BM 4J@AQ8&"JV3g#>i jzK-B ް6%F=p~<]f.c'U.w',&xCQ1L:0ۑҹ:Vf"QN6%ʡiRDVWVZˌfEى#,jhN΄"RsGL+3ra4g/4O4xOq"UdK6ݞ iO.l{NX%zE h Ԇ6c PA*(hO۔'KX9/??`;f@N7&D1Ksލ*3ұSJb {m (G4.'ii/#4r /C(Bq^iYQVf-i3bD.=ggjW8jMJ A6E٪ FIjH'X$ sB1.o^ҀJcU bT*́YH90zHUhR g*j:^9rgf@Y}΁ZJaq3^rikfnB<qq'_LYI՝`,pq}5yw8zJAK<{mZzqKN߂BA'>|όHyaU2ZD$ثdצŘ&?b;q=>zV_LkYRw__|cwsUϦ3f'5kopV+{1fcITy9qQ)VHHmkm\/֒u\hX1TO Nyh`{+zn H0!>mǫ gp`~6QPq_[w="R┶$v 5\Uy%nj%B{}}Sa >6>v4sO?񾗜#"z%y,sN2FUs\Eȫ@P-IRH;ιC: 3]|sh^: Y]\Yh [Ve9ZҔ N;^ɹZ]S9?>̷hg@1\Phb5Mf#Ma#jifVhݯ۸26J*+|(׍zjdžup)_4wU2Cdoз?/L¯. :՚j`NY jJ+ozYWX}X-rD.>CzcPYN3) @i2R$"0tH#f >Pt=t[]p#6R%hHџ\[ЅEUutVI,;;o2h"o>>JB)]9,OZdD9,KShVPSJ` =3gS>x]WI =W<[}OziAƒxt1oHŊQ "M@'i H|;Ri=(H`dtݶ Vh2L $kvB .:<+t tZ k~1N{?*wx<5߽o|O#={SA |,|w P|z/K *&q]WvYS>6:ue}Ɲ'學KʙWyid͒2wrb=}iGO9W:QO?_C_}_o_<()5 &5#tRƖ=P{n5z'KUVQfz)`zJ|x5Cin46^2F4ɻM9Q3/NAwzpKQ➯×OϏݮy٭_ݏwKߘyg+Pt>۠uOG)~ 3εn~GoNֻȖW-GqQgޗkh=UѱR@ )"ӷu`sbsgU _͐$\~z8靟N wEv[gwł@>|7Ʋ ]֝TpЎC5c8S1hԹ9&LCK"h2C}2^+(nRLMQG=FJ(*!D[-L9no½}8 Xܱ關? :v+q;OWN 1oY)]T)Kҫ*Ť"}E'ysf("UHG14#lt@ *V4 HHHP_;"Шk~5nUpP#xr uܗo]w]D=.Df tܨH( ԆHbDd$Y#HR|\GBluWtY/c !eN}}J9=Nea] ʫ:6 cMm@ġOk ~pg{qr l:@s0h74Sf SiMP'%l^P#T H8ӎfPڔ٬ܦm!e]PQ6JA֐؎}YT_n?}7{?q[pzl]w w00nmmEX`66ەA5@SWlOl6XoTκ'Mdn%b1sPo.c,NWJ8h9*GQGo8+7C{gG?eGsxW6yUP͢7};ǹpWO4Œg44CJ͎ S{}5umL놄'Av9 4l.T)X7/LBX$QlS('2axČYo#M~6L;,V,??9_]#;zzO>?)Oï RSg>l﬌ׇtl&[pu {Eg;;κwhWc${6}m_]/~Ew{Ǿz? -IYeLbSeJ;H\ϼK`ZTR)쁓3>G?GZ.{UdAAHl/ "N5Uw^M %TgT(0OHn=q³tece z^luM^gkC;ٝ=UYD#ǔ?֟7T+"3+ow~y{r ĂI!nB01hw&/8#;>rM-Jv^yO?i&'r #ʏ>wYI0۽KkI%AQI2$Y-I w"M;4o, հ[ݑn{63m.45$ؙyAK'D4DjUԺiVwZX*:S**\Tmw;fb+FR[k"I* AKVg뽱Z': 덳WXM-٠"DQ5] "9zBjP%*fg !8Q2jDb|2J>/Dq !hvxdiVS(IʖKL)QH%rV#h*骂Sjhޢ1iQ@4(SȚ FU1]EV*U#x2E_[%HT)i,"1B ƇOIU[K_ S?,6punGP`7>aiYHT 84yכm$G,1zCpdC+HlLvb[r"ʆm*Ť&d4V5Y6=S0"NHi̶̘4EQeM x*EYQG:HM/6e`'aH) Jʎd%jdU3ш s^s7롭5! v/'EtMhMZDZIyǵ3F5:t88ZsJ6f֚V35Im]qI,*cak"SYMzl& 12dȢ@);LN`"Ӓvd2u# qT;==Lh-j+25„!3!0 C|"D hCHF!lw:IIX@4L>DyEh^rל\%s! x|PO]}<̂ZgKuQԩ#{-8"CYJi(zC0vlsoIm}(@Qi\"6$ ̺ jk!d|$MPU2. %LRą.<;zdToW+}ymU{˔xkҨyE.\,K iLv{)>9 iœ̤+:,& we[v z`>>_,N?{bc>gu//znAڄP%"AHt>=UZ k"ij+4*rP5.@Z46 ZM_,/γzcH40Ȝ](уzA,ƲtH2tpXƙϳ |އDM.|QmIyDzB>$ \OA:n..q^t&nA>.eo˱ZViWicmSiΉ0<XB[D\3<[ ɋˤly”%1 F{3"Ϫu~M \ﻒ)+oɴA\|WOfIgGA>0_ W{!G7I Jm.BؐFI<A!d yJEH B88C8KLD΅|a+r+_w堶mo5&#xi)!8KؚIQآm_YQ؆LJ|*(x:Ɩh6H ˜Q1KۼƳ܍+4Mx-7+#;n/~6uXc)Qw4ŎlN=u#7 [妛6ѨTuC?}^ZH l*bt8~߻i?}s`3I#dưV+ 2Y$j )N7eTZ;\Z]72p R׬/EϹ˧ג rN>V\ ((TmU\$r6Ib4O;7V|EWjC&@,VϨ׍Fy'ʅRĠ`U0IB TʋL`ԟF+_/VbX%aHK-=qDyX7 5 ,jWJ_X2#QC ԍ0XI %MNu&xfVS5c,:>JIkctzіCG>爠tD}h?3Z1sKK{5ޑ5GJM([Gr@>\R֣yNC6jT7@e{]ѼQI 1t~6?v >m` l[|=2)(ԑY@]F$KBRE^ƑW{=50+ *(soQ˝搕ڐeɡTɤ 3fngkVq fp~oZR8GN~}y6<sYq/WRY Ϟ~fN"=)BwMPy( l d䶣xf & <iIR"ߒAB\ \᲋}^ =nߓ_gݭEngf3;l1OLBvJ.Ť<c.P%1ie8t ۉNuf*͝sc|әx;**MaSH%i|qfܹb\,ZysUGݢ ࣸ,%15aZu&< HqccIR`B v+[05tHXRx#@^߳|ݑ3tE8eD] nC@yO\#KM(3`h/IJ%8;Q$/Lh M 簫i┍آPh񬝌5a[\=M |5CjN8s*jyyx7@h@0;51*L(-fQ!#B *Y1 !2Z@$LчsU $Mom*MGIb]6!LpJ,DqRr.D2m<, ; JJdF7f[IGwQs090@6_{/(sI7N1(7N-W3Lt&KQ;i5 MZNLu:\ emDi1|)y-z Xʳ~> %xJiDG *A@Zi?H鴣 !+qSܢ۶.Kń9\l?r9;FqSHu)E"`E|6dvւczw$rf-gЕqߟnnܸ#cFrhQlɯsx=VC)d=DKXr.ܽ~Giy)ÝV>2bˊEuĊJSQ'yJ1Fu@ 5%<'W48*mqK\0;n} i{n:U_-dҀGW_< p2ųkg>`ҨIR.\ ]Ezdl\m@ Ip5:Sqp{aw-:Lqr 8nv+{lf='PD Wm[.LǟC+D-`aI {S8ZJ ,߶*t_V 85eG})N [b @?߂o[gKHb 8c@'ל2HTvLbD+ww=߻e _lO)ּh~ap7 X+ v[ X]߷v?O6^6#]bMpwz{``g̟V X{q w 8ò(G/l5ᨍ-j`VWW{R(MVxz8_;ϥ.z2;X',BF/’},⬸;*w"X|W/",lL\r˗M}Y}g+3pj,xmL]Qo3im nNGg,Pt@ %[ πێ] ?]w0Za/&tEXticc:copyrightNo copyright, use freely!tEXticc:descriptionsRGB IEC61966-2.1WGIENDB`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.Rd0000644000176200001440000000373715017310404016422 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, variation = font_variation(), 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{variation}{A \code{font_variation} object or a list of them to control variable fonts} \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.Rd0000644000176200001440000000327115017310404016572 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, variation = font_variation(), 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{variation}{A \code{font_variation} object or a list of them to control variable fonts} \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.Rd0000644000176200001440000000752615017511630015704 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, weight = "normal", width = "undefined", size = 12, res = 72, path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) } \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{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related measures} \item{path, index}{path and index of a font file to circumvent lookup based on family and style} \item{variation}{A \code{font_variation} object or a list of them to control variable fonts} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} } \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{name}{The name of the font, if present, otherwise the family name} \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.Rd0000644000176200001440000001004715067155103016571 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{ \dontshow{if (nrow(system_fonts()) >= 4) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Create a random font collection fonts <- system_fonts() random_fonts <- sample(nrow(fonts), 4) register_font( 'random', plain = list(fonts$path[random_fonts[1]], fonts$index[random_fonts[1]]), bold = list(fonts$path[random_fonts[2]], fonts$index[random_fonts[2]]), italic = list(fonts$path[random_fonts[3]], fonts$index[random_fonts[3]]), bolditalic = list(fonts$path[random_fonts[4]], fonts$index[random_fonts[4]]) ) # Look at your creation registry_fonts() # Reset clear_registry() \dontshow{\}) # examplesIf} } 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.Rd0000644000176200001440000000561215017511630016053 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, weight = "normal", width = "undefined", size = 12, res = 72, path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) } \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{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{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related measures} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} \item{variation}{A \code{font_variation} object or a list of them to control variable fonts} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} } \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.Rd0000644000176200001440000000513515017310404016476 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, weight = "normal", width = "undefined", path = NULL, index = 0, variation = font_variation(), bold = deprecated() ) } \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{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{path, index}{path and index of a font file to circumvent lookup based on family and style} \item{variation}{A \code{font_variation} object or a list of them to control variable fonts} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} } \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/DESCRIPTION0000644000176200001440000000410615067231742014366 0ustar liggesusersType: Package Package: systemfonts Title: System Native Font Finding Version: 1.3.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 Software, PBC", role = c("cph", "fnd"), comment = c(ROR = "03wc8by49")) ) 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) Imports: base64enc, grid, jsonlite, lifecycle, tools, utils Suggests: covr, farver, ggplot2, graphics, knitr, ragg, rmarkdown, svglite, testthat (>= 2.1.0) LinkingTo: cpp11 (>= 0.2.1) VignetteBuilder: knitr Config/build/compilation-database: true Config/Needs/website: tidyverse/tidytemplate Config/usethis/last-upkeep: 2025-04-23 Encoding: UTF-8 RoxygenNote: 7.3.2 SystemRequirements: fontconfig, freetype2 NeedsCompilation: yes Packaged: 2025-10-01 11:58:08 UTC; thomas Author: Thomas Lin Pedersen [aut, cre] (ORCID: ), Jeroen Ooms [aut] (ORCID: ), Devon Govett [aut] (Author of font-manager), Posit Software, PBC [cph, fnd] (ROR: ) Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2025-10-01 14:00:02 UTC