systemfonts/0000755000176200001440000000000014511003551012643 5ustar liggesuserssystemfonts/NAMESPACE0000644000176200001440000000076214507500576014105 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(print,font_feature) export(clear_registry) export(font_fallback) export(font_feature) export(font_info) export(get_fallback) export(glyph_info) export(match_font) export(register_font) export(register_variant) export(registry_fonts) export(reset_font_cache) export(shape_string) export(str_split_emoji) export(string_metrics_dev) export(string_width) export(string_widths_dev) export(system_fonts) useDynLib(systemfonts, .registration = TRUE) systemfonts/LICENSE0000644000176200001440000000006114507500576013663 0ustar liggesusersYEAR: 2019 COPYRIGHT HOLDER: Thomas Lin Pedersen systemfonts/tools/0000755000176200001440000000000014510732006014006 5ustar liggesuserssystemfonts/tools/winlibs.R0000644000176200001440000000162014510732006015577 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(), 'harfbuzz') } systemfonts/README.md0000644000176200001440000001111114507500576014133 0ustar liggesusers # systemfonts [![Codecov test coverage](https://codecov.io/gh/r-lib/systemfonts/branch/master/graph/badge.svg)](https://app.codecov.io/gh/r-lib/systemfonts?branch=master) [![CRAN status](https://www.r-pkg.org/badges/version/systemfonts)](https://cran.r-project.org/package=systemfonts) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![R-CMD-check](https://github.com/r-lib/systemfonts/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/systemfonts/actions) systemfonts is a package that locates installed fonts. It uses the system-native libraries on Mac (CoreText) and Linux (FontConfig), and uses Freetype to parse the fonts in the registry on Windows. ## Installation systemfonts is available from CRAN using `install.packages('systemfonts')`. It is however still under development and you can install the development version using devtools. ``` r # install.packages('devtools') devtools::install_github('r-lib/systemfonts') ``` ## Examples The main use of this package is to locate font files based on family and style: ``` r library(systemfonts) match_font('Avenir', italic = TRUE) #> $path #> [1] "/System/Library/Fonts/Avenir.ttc" #> #> $index #> [1] 1 #> #> $features #> NULL ``` 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: 819 × 9 #> path index name family style weight width italic monospace #> #> 1 /System/Library/Fonts… 2 Rock… Rockw… Bold bold norm… FALSE FALSE #> 2 /Users/thomas/Library… 0 Open… Open … Ligh… normal norm… TRUE FALSE #> 3 /Users/thomas/Library… 0 Open… Open … Semi… semib… semi… TRUE FALSE #> 4 /System/Library/Fonts… 0 Note… Notew… Light normal norm… FALSE FALSE #> 5 /System/Library/Fonts… 1 Deva… Devan… Bold bold norm… FALSE FALSE #> 6 /System/Library/Fonts… 0 Kann… Kanna… Regu… normal norm… FALSE FALSE #> 7 /System/Library/Fonts… 0 Verd… Verda… Bold bold norm… FALSE FALSE #> 8 /System/Library/Fonts… 8 Aria… Arial… Light light norm… FALSE FALSE #> 9 /Users/thomas/Library… 0 Open… Open … Medi… medium norm… TRUE FALSE #> 10 /System/Library/Fonts… 10 Appl… Apple… Thin thin norm… FALSE FALSE #> # … with 809 more rows ``` Further, you can query additional information about fonts and specific glyphs, if that is of interest using the `font_info()` and `glyph_info()` functions. ## C API While getting this information in R is nice, the intended use is mostly through compiled code so that graphic devices can easily locate relevant font files etc. In order to use functions from systemfonts in C(++) code your package should list systemfonts in the `LinkingTo` field in the `DESCRIPTION` file. Once this is done you can now `#include ` in your code and use the provided functions. Look into the [`inst/include/systemfonts.h`](https://github.com/r-lib/systemfonts/blob/master/inst/include/systemfonts.h) file to familiarise yourself with the C API. ## System Defaults systemfonts will always try to find a font for you, even if none exist with the given family name or style. How it resolves this is system specific and should not be relied on, but it can be expected that a valid font file is always returned no matter the input. A few special aliases exist that behaves predictably but system dependent: - `""` and `"sans"` return *Helvetica* on Mac, *Arial* on Windows, and the default sans-serif font on Linux (*DejaVu Sans* on Ubuntu) - `"serif"` return *Times* on Mac, *Times New Roman* on Windows, and the default serif font on Linux (*DejaVu Serif* on Ubuntu) - `"mono"` return *Courier* on Mac, *Courier New* on Windows, and the default mono font on Linux (*DejaVu Mono* on Ubuntu) - `"emoji"` return *Apple Color Emoji* on Mac, *Segoe UI Emoji* on Windows, and the default emoji font on Linux (*Noto Color* on Ubuntu) ## Code of Conduct Please note that the ‘systemfonts’ project is released with a [Contributor Code of Conduct](https://github.com/r-lib/systemfonts/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms. systemfonts/man/0000755000176200001440000000000014507500576013434 5ustar liggesuserssystemfonts/man/str_split_emoji.Rd0000644000176200001440000000270414507500576017134 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 family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \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_width.Rd0000644000176200001440000000300514507500576016426 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_string.R \name{string_width} \alias{string_width} \title{Calculate the width of a string, ignoring new-lines} \usage{ string_width( strings, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0 ) } \arguments{ \item{strings}{A character vector of strings} \item{family}{The name of the font family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{include_bearing}{Logical, should left and right bearing be included in the string width?} \item{path}{path an index of a font file to circumvent lookup based on family and style} \item{index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A numeric vector giving the width of the strings in pixels. Use the provided \code{res} value to convert it into absolute values. } \description{ This is a very simple alternative to \code{\link[=shape_string]{shape_string()}} that simply calculates the width of strings without taking any newline into account. As such it is suitable to calculate the width of words or lines that has already been splitted by \verb{\\n}. Input is recycled to the length of \code{strings}. } \examples{ strings <- c('A short string', 'A very very looong string') string_width(strings) } systemfonts/man/systemfonts-package.Rd0000644000176200001440000000237014507500576017714 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/systemfonts-package.R \docType{package} \name{systemfonts-package} \alias{systemfonts} \alias{systemfonts-package} \title{systemfonts: System Native Font Finding} \description{ Provides system native access to the font catalogue. As font handling varies between systems it is difficult to correctly locate installed fonts across different operating systems. The 'systemfonts' package provides bindings to the native libraries on Windows, macOS and Linux for finding font files that can then be used further by e.g. graphic devices. The main use is intended to be from compiled code but 'systemfonts' also provides access from R. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/r-lib/systemfonts} \item Report bugs at \url{https://github.com/r-lib/systemfonts/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomas.pedersen@rstudio.com} (\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 RStudio [copyright holder] } } \keyword{internal} systemfonts/man/register_variant.Rd0000644000176200001440000000365414507500576017303 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. } \examples{ # Get the default "sans" family sans <- match_font("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/system_fonts.Rd0000644000176200001440000000066714507500576016471 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/get_fallback.Rd0000644000176200001440000000040514507500576016320 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/match_font.Rd0000644000176200001440000000177514507500576016057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/match_font.R \name{match_font} \alias{match_font} \title{Find a system font by name and style} \usage{ match_font(family, italic = FALSE, bold = FALSE) } \arguments{ \item{family}{The name of the font family} \item{italic, bold}{logicals indicating the font style} } \value{ A list containing the path locating the font file and the 0-based index of the font in the file. } \description{ This function locates the font file (and index) best matching a name and optional style (italic/bold). A font file will be returned even if a 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"}, and \code{"mono"} match to the system default sans-serif, serif, and mono fonts respectively (\code{""} is equivalent to \code{"sans"}). } \examples{ # Get the system default sans-serif font in italic match_font('sans', italic = TRUE) } systemfonts/man/font_fallback.Rd0000644000176200001440000000272014507500576016511 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_fallback.R \name{font_fallback} \alias{font_fallback} \title{Get the fallback font for a given string} \usage{ font_fallback( string, family = "", italic = FALSE, bold = FALSE, path = NULL, index = 0 ) } \arguments{ \item{string}{The strings to find fallbacks for} \item{family}{The name of the font family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \item{path}{path an index of a font file to circumvent lookup based on family and style} \item{index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data frame with a \code{path} and \code{index} column giving fallback for the specified string and font combinations } \description{ A fallback font is a font to use as a substitute if the chosen font does not contain the requested characters. Using font fallbacks means that the user doesn't have to worry about mixing characters from different scripts or mixing text and emojies. Fallback is calculated for the full string and the result is platform specific. If no font covers all the characters in the string an undefined "best match" is returned. The best approach is to figure out which characters are not covered by your chosen font and figure out fallbacks for these, rather than just request a fallback for the full string. } \examples{ font_fallback("\U0001f604") # Smile emoji } systemfonts/man/register_font.Rd0000644000176200001440000000601514507500576016577 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. } \examples{ # Create a random font collection fonts <- system_fonts() plain <- sample(which(!fonts$italic & fonts$weight <= 'normal'), 1) bold <- sample(which(!fonts$italic & fonts$weight > 'normal'), 1) italic <- sample(which(fonts$italic & fonts$weight <= 'normal'), 1) bolditalic <- sample(which(fonts$italic & fonts$weight > 'normal'), 1) register_font( 'random', plain = list(fonts$path[plain], fonts$index[plain]), bold = list(fonts$path[bold], fonts$index[bold]), italic = list(fonts$path[italic], fonts$index[italic]), bolditalic = list(fonts$path[bolditalic], fonts$index[bolditalic]) ) # Look at your creation registry_fonts() # Reset clear_registry() } systemfonts/man/font_feature.Rd0000644000176200001440000000775014507500576016415 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_feature.R \name{font_feature} \alias{font_feature} \title{Define OpenType font feature settings} \usage{ font_feature(ligatures = NULL, letters = NULL, numbers = NULL, ...) } \arguments{ \item{ligatures}{Settings related to ligatures. One or more types of ligatures to turn on (see details).} \item{letters}{Settings related to the appearance of single letters (as opposed to ligatures that substitutes multiple letters). See details for supported values.} \item{numbers}{Settings related to the appearance of numbers. See details for supported values.} \item{...}{key-value pairs with the key being the 4-letter tag and the value being the setting (usually \code{TRUE} to turn it on).} } \value{ A \code{font_feature} object } \description{ This function encapsulates the specification of OpenType font features. Some specific features have named arguments, but all available features can be set by using its specific 4-letter tag For a list of the 4-letter tags available see e.g. the overview on \href{https://en.wikipedia.org/wiki/List_of_typographic_features}{Wikipedia}. } \details{ OpenType features are defined by a 4-letter tag along with an integer value. Often that value is a simple \code{0} (off) or \code{1} (on), but some features support additional values, e.g. stylistic alternates (\code{salt}) where a font may provide multiple variants of a letter and the value will be used to chose which one to use. Common features related to appearance may be given with a long form name to either the \code{ligatures}, \code{letters}, or \code{numbers} argument to avoid remembering the often arbitrary 4-letter tag. Providing a long form name is the same as setting the tag to \code{1} and can thus not be used to set tags to other values. The possible long form names are given below with the tag in parenthesis: \strong{Ligatures} \itemize{ \item \code{standard} (\emph{liga}): Turns on standard multiple letter substitution \item \code{historical} (\emph{hlig}): Use obsolete historical ligatures \item \code{contextual} (\emph{clig}): Apply secondary ligatures based on the character patterns surrounding the potential ligature \item \code{discretionary} (\emph{dlig}): Use ornamental ligatures } \strong{Letters} \itemize{ \item \code{swash} (\emph{cswh}): Use contextual swashes (ornamental decorations) \item \code{alternates} (\emph{calt}): Use alternate letter forms based on the sourrounding pattern \item \code{historical} (\emph{hist}): Use obsolete historical forms of the letters \item \code{localized} (\emph{locl}): Use alternate forms preferred by the script language \item \code{randomize} (\emph{rand}): Use random variants of the letters (e.g. to mimick handwriting) \item \code{alt_annotation} (\emph{nalt}): Use alternate annotations (e.g. circled digits) \item \code{stylistic} (\emph{salt}): Use a stylistic alternative form of the letter \item \code{subscript} (\emph{subs}): Set letter in subscript \item \code{superscript} (\emph{sups}): Set letter in superscript \item \code{titling} (\emph{titl}): Use letter forms well suited for large text and titles \item \code{small_caps} (\emph{smcp}): Use small caps variants of the letters } \strong{Numbers} \itemize{ \item \code{lining} (\emph{lnum}): Use number variants that rest on the baseline \item \code{oldstyle} (\emph{onum}): Use old style numbers that use descender and ascender for various numbers \item \code{proportional} (\emph{pnum}): Let numbers take up width based on the visual width of the glyph \item \code{tabular} (\emph{tnum}): Enforce all numbers to take up the same width \item \code{fractions} (\emph{frac}): Convert numbers separated by \code{/} into a fraction glyph \item \code{fractions_alt} (\emph{afrc}): Use alternate fraction form with a horizontal divider } } \examples{ font_feature(letters = "stylistic", numbers = c("lining", "tabular")) # Use the tag directly to access additional stylistic variants font_feature(numbers = c("lining", "tabular"), salt = 2) } systemfonts/man/reset_font_cache.Rd0000644000176200001440000000150714507500576017221 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_font]{match_font()}} 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/font_info.Rd0000644000176200001440000000505214507500576015706 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_info.R \name{font_info} \alias{font_info} \title{Query font-specific information} \usage{ font_info( family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0 ) } \arguments{ \item{family}{The name of the font family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data.frame giving info on the requested font + size combinations. The data.frame will contain the following columns: \describe{ \item{path}{The path to the font file} \item{index}{The 0-based index of the font in the fontfile} \item{family}{The family name of the font} \item{style}{The style name of the font} \item{italic}{A logical giving if the font is italic} \item{bold}{A logical giving if the font is bold} \item{monospace}{A logical giving if the font is monospace} \item{weight}{A factor giving the weight of the font} \item{width}{A factor giving the width of the font} \item{kerning}{A logical giving if the font supports kerning} \item{color}{A logical giving if the font has color glyphs} \item{scalable}{A logical giving if the font is scalable} \item{vertical}{A logical giving if the font is vertical} \item{n_glyphs}{The number of glyphs in the font} \item{n_sizes}{The number of predefined sizes in the font} \item{n_charmaps}{The number of character mappings in the font file} \item{bbox}{A bounding box large enough to contain any of the glyphs in the font} \item{max_ascend}{The maximum ascend of the tallest glyph in the font} \item{max_descent}{The maximum descend of the most descending glyph in the font} \item{max_advance_width}{The maximum horizontal advance a glyph can make} \item{max_advance_height}{The maximum vertical advance a glyph can make} \item{lineheight}{The height of a single line of text in the font} \item{underline_pos}{The position of a potential underlining segment} \item{underline_size}{The width the the underline} } } \description{ Get general information about a font, relative to a given size. Size specific measures will be returned in pixel units. The function is vectorised to the length of the longest argument. } \examples{ font_info('serif') # Avoid lookup if font file is already known sans <- match_font('sans') font_info(path = sans$path, index = sans$index) } systemfonts/man/string_widths_dev.Rd0000644000176200001440000000272414507500576017456 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/glyph_info.Rd0000644000176200001440000000325114507500576016062 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_info.R \name{glyph_info} \alias{glyph_info} \title{Query glyph-specific information from fonts} \usage{ glyph_info( glyphs, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0 ) } \arguments{ \item{glyphs}{A vector of glyphs. Strings will be split into separate glyphs automatically} \item{family}{The name of the font family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A data.frame with information about each glyph, containing the following columns: \describe{ \item{glyph}{The glyph as a character} \item{index}{The index of the glyph in the font file} \item{width}{The width of the glyph} \item{height}{The height of the glyph} \item{x_bearing}{The horizontal distance from the origin to the leftmost part of the glyph} \item{y_bearing}{The vertical distance from the origin to the top part of the glyph} \item{x_advance}{The horizontal distance to move the cursor after adding the glyph} \item{y_advance}{The vertical distance to move the cursor after adding the glyph} \item{bbox}{The tight bounding box surrounding the glyph} } } \description{ This function allows you to extract information about the individual glyphs in a font, based on a specified size. All size related measures are in pixel-units. The function is vectorised to the length of the \code{glyphs} vector. } systemfonts/man/shape_string.Rd0000644000176200001440000001004414507500576016410 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_string.R \name{shape_string} \alias{shape_string} \title{Calculate glyph positions for strings} \usage{ shape_string( strings, id = NULL, family = "", italic = FALSE, bold = FALSE, size = 12, res = 72, lineheight = 1, align = "left", hjust = 0, vjust = 0, width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, index = 0 ) } \arguments{ \item{strings}{A character vector of strings to shape} \item{id}{A vector grouping the strings together. If strings share an id the shaping will continue between strings} \item{family}{The name of the font family} \item{italic}{logicals indicating the font style} \item{bold}{logicals indicating the font style} \item{size}{The pointsize of the font to use for size related measures} \item{res}{The ppi of the size related mesures} \item{lineheight}{A multiplier for the lineheight} \item{align}{Within text box alignment, either \code{'left'}, \code{'center'}, or \code{'right'}} \item{hjust, vjust}{The justification of the textbox surrounding the text} \item{width}{The requested with of the string in inches. Setting this to something other than \code{NA} will turn on word wrapping.} \item{tracking}{Tracking of the glyphs (space adjustment) measured in 1/1000 em.} \item{indent}{The indent of the first line in a paragraph measured in inches.} \item{hanging}{The indent of the remaining lines in a paragraph measured in inches.} \item{space_before, space_after}{The spacing above and below a paragraph, measured in points} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A list with two element: \code{shape} contains the position of each glyph, relative to the origin in the enclosing textbox. \code{metrics} contain metrics about the full strings. \code{shape} is a data.frame with the following columns: \describe{ \item{glyph}{The glyph as a character} \item{index}{The index of the glyph in the font file} \item{metric_id}{The index of the string the glyph is part of (referencing a row in the \code{metrics} data.frame)} \item{string_id}{The index of the string the glyph came from (referencing an element in the \code{strings} input)} \item{x_offset}{The x offset in pixels from the origin of the textbox} \item{y_offset}{The y offset in pixels from the origin of the textbox} \item{x_mid}{The x offset in pixels to the middle of the glyph, measured from the origin of the glyph} } \code{metrics} is a data.frame with the following columns: \describe{ \item{string}{The text the string consist of} \item{width}{The width of the string} \item{height}{The height of the string} \item{left_bearing}{The distance from the left edge of the textbox and the leftmost glyph} \item{right_bearing}{The distance from the right edge of the textbox and the rightmost glyph} \item{top_bearing}{The distance from the top edge of the textbox and the topmost glyph} \item{bottom_bearing}{The distance from the bottom edge of the textbox and the bottommost glyph} \item{left_border}{The position of the leftmost edge of the textbox related to the origin} \item{top_border}{The position of the topmost edge of the textbox related to the origin} \item{pen_x}{The horizontal position of the next glyph after the string} \item{pen_y}{The vertical position of the next glyph after the string} } } \description{ Do basic text shaping of strings. This function will use freetype to calculate advances, doing kerning if possible. It will not perform any font substitution or ligature resolving and will thus be much in line with how the standard graphic devices does text shaping. Inputs are recycled to the length of \code{strings}. } \examples{ string <- "This is a long string\nLook; It spans multiple lines\nand all" # Shape with default settings shape_string(string) # Mix styles within the same string string <- c( "This string will have\na ", "very large", " text style\nin the middle" ) shape_string(string, id = c(1, 1, 1), size = c(12, 24, 12)) } systemfonts/man/string_metrics_dev.Rd0000644000176200001440000000242414507500576017617 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/DESCRIPTION0000644000176200001440000000344214511003551014354 0ustar liggesusersType: Package Package: systemfonts Title: System Native Font Finding Version: 1.0.5 Authors@R: c( person("Thomas Lin", "Pedersen", , "thomas.pedersen@posit.co", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-5147-4711")), person("Jeroen", "Ooms", , "jeroen@berkeley.edu", role = "aut", comment = c(ORCID = "0000-0002-4035-0289")), person("Devon", "Govett", role = "aut", comment = "Author of font-manager"), person("Posit, PBC", role = c("cph", "fnd")) ) Description: Provides system native access to the font catalogue. As font handling varies between systems it is difficult to correctly locate installed fonts across different operating systems. The 'systemfonts' package provides bindings to the native libraries on Windows, macOS and Linux for finding font files that can then be used further by e.g. graphic devices. The main use is intended to be from compiled code but 'systemfonts' also provides access from R. License: MIT + file LICENSE URL: https://github.com/r-lib/systemfonts, https://systemfonts.r-lib.org BugReports: https://github.com/r-lib/systemfonts/issues Depends: R (>= 3.2.0) Suggests: covr, knitr, rmarkdown, testthat (>= 2.1.0), tools LinkingTo: cpp11 (>= 0.2.1) VignetteBuilder: knitr Encoding: UTF-8 RoxygenNote: 7.1.2 SystemRequirements: fontconfig, freetype2 Config/Needs/website: tidyverse/tidytemplate NeedsCompilation: yes Packaged: 2023-10-09 08:28:00 UTC; thomas Author: Thomas Lin Pedersen [aut, cre] (), Jeroen Ooms [aut] (), Devon Govett [aut] (Author of font-manager), Posit, PBC [cph, fnd] Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2023-10-09 14:00:09 UTC systemfonts/build/0000755000176200001440000000000014510734620013751 5ustar liggesuserssystemfonts/build/vignette.rds0000644000176200001440000000032614510734620016311 0ustar liggesusersb```b`abb`b2 1# 'O+I-JKLN MA/,.IM+)VpV+ES&lJFIn #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/font_matching.cpp0000755000176200001440000001364414507500576017007 0ustar liggesusers#include "font_matching.h" #include "types.h" #include "caches.h" #include "utils.h" #include "FontDescriptor.h" #include "font_registry.h" #include #include #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using namespace cpp11::literals; // these functions are implemented by the platform ResultSet *getAvailableFonts(); ResultSet *findFonts(FontDescriptor *); FontDescriptor *findFont(FontDescriptor *); void resetFontCache(); int locate_systemfont(const char *family, int italic, int bold, char *path, int max_path_length) { const char* resolved_family = family; if (strcmp_no_case(family, "") || strcmp_no_case(family, "sans")) { resolved_family = SANS; } else if (strcmp_no_case(family, "serif")) { resolved_family = SERIF; } else if (strcmp_no_case(family, "mono")) { resolved_family = MONO; } else if (strcmp_no_case(family, "emoji")) { resolved_family = EMOJI; } else if (strcmp_no_case(family, "symbol")) { resolved_family = SYMBOL; } FontMap& font_map = get_font_map(); static FontKey key; key.family.assign(resolved_family); key.bold = bold; key.italic = italic; FontMap::iterator font_it = font_map.find(key); if (font_it != font_map.end()) { strncpy(path, font_it->second.file.c_str(), max_path_length); return font_it->second.index; } FontDescriptor font_desc(resolved_family, italic, bold); std::unique_ptr font_loc(findFont(&font_desc)); int index = 0; if (!font_loc) { list_t fallback = cpp11::as_cpp(cpp11::package("systemfonts")["get_fallback"]()); strncpy(path, CHAR(STRING_ELT(fallback[0], 0)), max_path_length); index = INTEGER(fallback[1])[0]; } else { strncpy(path, font_loc->path, max_path_length); index = font_loc->index; } font_map[key] = {std::string(path), (unsigned int) index}; return index; } int locate_font(const char *family, int italic, int bold, char *path, int max_path_length) { BEGIN_CPP FontSettings registry_match; if (locate_in_registry(family, italic, bold, registry_match)) { strncpy(path, registry_match.file, max_path_length); return registry_match.index; } return locate_systemfont(family, italic, bold, path, max_path_length); END_CPP return 0; } FontSettings locate_font_with_features(const char *family, int italic, int bold) { FontSettings registry_match = {}; registry_match.features = NULL; registry_match.n_features = 0; BEGIN_CPP if (locate_in_registry(family, italic, bold, registry_match)) { return registry_match; } registry_match.index = locate_systemfont(family, italic, bold, registry_match.file, PATH_MAX); END_CPP registry_match.file[PATH_MAX] = '\0'; return registry_match; } list_t match_font_c(strings_t family, logicals_t italic, logicals_t bold) { FontSettings loc = locate_font_with_features( Rf_translateCharUTF8(family[0]), italic[0], bold[0] ); if (loc.n_features == 0) { return list_w({ "path"_nm = cpp11::r_string(loc.file), "index"_nm = loc.index, "features"_nm = cpp11::integers() }); } integers_w feat(loc.n_features); 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 system_fonts_c() { int n = 0; std::unique_ptr all_fonts(getAvailableFonts()); n = all_fonts->n_fonts(); strings_w path(n); integers_w index(n); strings_w name(n); strings_w family(n); strings_w style(n); integers_w weight(n); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = { "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy" }; integers_w width(n); width.attr("class") = {"ordered", "factor"}; width.attr("levels") = { "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded" }; logicals_w italic(n); logicals_w monospace(n); int i = 0; for (ResultSet::iterator it = all_fonts->begin(); it != all_fonts->end(); it++) { path[i] = (*it)->get_path(); index[i] = (*it)->index; name[i] = (*it)->get_psname(); family[i] = (*it)->get_family(); style[i] = (*it)->get_style(); weight[i] = (*it)->get_weight(); if (weight[i] == 0) { weight[i] = NA_INTEGER; } width[i] = (*it)->get_width(); if (width[i] == 0) { width[i] = NA_INTEGER; } italic[i] = (Rboolean) (*it)->italic; monospace[i] = (Rboolean) (*it)->monospace; ++i; } data_frame_w res({ "path"_nm = path, "index"_nm = index, "name"_nm = name, "family"_nm = family, "style"_nm = style, "weight"_nm = weight, "width"_nm = width, "italic"_nm = italic, "monospace"_nm = monospace }); res.attr("class") = {"tbl_df", "tbl", "data.frame"}; return res; } void reset_font_cache_c() { resetFontCache(); FontMap& font_map = get_font_map(); font_map.clear(); } void export_font_matching(DllInfo* dll) { R_RegisterCCallable("systemfonts", "locate_font", (DL_FUNC)locate_font); R_RegisterCCallable("systemfonts", "locate_font_with_features", (DL_FUNC)locate_font_with_features); } systemfonts/src/dev_metrics.h0000644000176200001440000000151614507500576016130 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/win/0000755000176200001440000000000014507500576014245 5ustar liggesuserssystemfonts/src/win/FontManagerWindows.cpp0000644000176200001440000003221014507500576020523 0ustar liggesusers#include #include #include #include #include #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_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; } 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 (FontWeight) os2_table->usWeightClass; } 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; } FontDescriptor* descriptor_from_face(FT_Face &face, const char* path, int index) { FontDescriptor* res = NULL; res = new FontDescriptor( path, index, FT_Get_Postscript_Name(face) == NULL ? "" : FT_Get_Postscript_Name(face), face->family_name, face->style_name, get_font_weight(face), get_font_width(face), face->style_flags & FT_STYLE_FLAG_ITALIC, FT_IS_FIXED_WIDTH(face) ); return res; } int scan_font_dir(HKEY which, bool data_is_path, bool last_chance = false) { char win_dir[MAX_PATH]; GetWindowsDirectoryA(win_dir, MAX_PATH); std::string font_dir; if (last_chance) { font_dir = "C:\\WINDOWS"; } else { font_dir += win_dir; } font_dir += "\\Fonts\\"; static const LPCSTR font_registry_path = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; HKEY h_key; LONG result; result = RegOpenKeyExA(which, font_registry_path, 0, KEY_READ, &h_key); if (result != ERROR_SUCCESS) { return 1; } DWORD max_value_name_size, max_value_data_size; result = RegQueryInfoKey(h_key, 0, 0, 0, 0, 0, 0, 0, &max_value_name_size, &max_value_data_size, 0, 0); if (result != ERROR_SUCCESS) { return 1; } DWORD value_index = 0; LPSTR value_name = new CHAR[max_value_name_size]; LPBYTE value_data = new BYTE[max_value_data_size]; DWORD value_name_size, value_data_size, value_type; std::string font_path; ResultSet& font_list = get_font_list(); FT_Library library; FT_Face face; FT_Error error; error = FT_Init_FreeType(&library); if (error) { return 1; } do { // Loop over font registry, construct file path and parse with freetype value_data_size = max_value_data_size; value_name_size = max_value_name_size; result = RegEnumValueA(h_key, value_index, value_name, &value_name_size, 0, &value_type, value_data, &value_data_size); value_index++; if (!(result == ERROR_SUCCESS || result == ERROR_MORE_DATA) || value_type != REG_SZ) { continue; } font_path.clear(); if (!data_is_path) { font_path += font_dir; } font_path.append((LPSTR) value_data, value_data_size); error = FT_New_Face(library, font_path.c_str(), 0, &face); if (error) { continue; } font_list.push_back(descriptor_from_face(face, font_path.c_str(), 0)); int n_fonts = face->num_faces; FT_Done_Face(face); for (int i = 1; i < n_fonts; ++i) { error = FT_New_Face(library, font_path.c_str(), i, &face); if (error) { continue; } font_list.push_back(descriptor_from_face(face, font_path.c_str(), i)); FT_Done_Face(face); } } while (result != ERROR_NO_MORE_ITEMS); // Cleanup delete[] value_name; delete[] value_data; FT_Done_FreeType(library); return 0; } int scan_font_reg() { scan_font_dir(HKEY_LOCAL_MACHINE, false); scan_font_dir(HKEY_CURRENT_USER, true); // Move Arial Regular to front ResultSet& font_list = get_font_list(); if (font_list.n_fonts() == 0) { scan_font_dir(HKEY_LOCAL_MACHINE, false, true); } for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { if (strcmp((*it)->family, "Arial") == 0 && strcmp((*it)->style, "Regular") == 0) { FontDescriptor* arial = *it; font_list.erase(it); font_list.insert(font_list.begin(), arial); break; } } return 0; } void resetFontCache() { ResultSet& font_list = get_font_list(); font_list.clear(); WinLinkMap& font_links = get_win_link_map(); font_links.clear(); } ResultSet *getAvailableFonts() { ResultSet *res = new ResultSet(); ResultSet& font_list = get_font_list(); if (font_list.size() == 0) scan_font_reg(); for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { FontDescriptor* font = new FontDescriptor(*it); res->push_back(font); } return res; } bool resultMatches(FontDescriptor *result, FontDescriptor *desc) { if (desc->postscriptName && !strcmp_no_case(desc->postscriptName, result->postscriptName)) return false; if (desc->family && !strcmp_no_case(desc->family, result->family)) return false; if (desc->style && !strcmp_no_case(desc->style, result->style)) return false; if (desc->weight && desc->weight != result->weight) return false; if (desc->width && desc->width != result->width) return false; if (desc->italic != result->italic) return false; return true; } ResultSet *findFonts(FontDescriptor *desc) { ResultSet *res = new ResultSet(); ResultSet& font_list = get_font_list(); if (font_list.size() == 0) scan_font_reg(); for (ResultSet::iterator it = font_list.begin(); it != font_list.end(); it++) { if (!resultMatches(*it, desc)) { continue; } FontDescriptor* font = new FontDescriptor(*it); res->push_back(font); } return res; } FontDescriptor *findFont(FontDescriptor *desc) { ResultSet *fonts = findFonts(desc); // if we didn't find anything, try again with 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) "Malgun Gothic", // CJK (Korean) "PMingLiU", // CJK (Traditional Chinese) "SimSun", // CJK (Simplified Chinese) "Gulim", // CJK (Korean) "Yu Gothic", // CJK (Japanese) "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/win/DirectWriteFontManagerWindows.cpp0000755000176200001440000003043414507500576022702 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/unix/0000755000176200001440000000000014507500576014433 5ustar liggesuserssystemfonts/src/unix/FontManagerLinux.cpp0000755000176200001440000001623414507500576020371 0ustar liggesusers#include #include "../FontDescriptor.h" int convertWeight(FontWeight weight) { switch (weight) { case FontWeightThin: return FC_WEIGHT_THIN; case FontWeightUltraLight: return FC_WEIGHT_ULTRALIGHT; case FontWeightLight: return FC_WEIGHT_LIGHT; case FontWeightNormal: return FC_WEIGHT_REGULAR; case FontWeightMedium: return FC_WEIGHT_MEDIUM; case FontWeightSemiBold: return FC_WEIGHT_SEMIBOLD; case FontWeightBold: return FC_WEIGHT_BOLD; case FontWeightUltraBold: return FC_WEIGHT_EXTRABOLD; case FontWeightHeavy: return FC_WEIGHT_ULTRABLACK; default: return FC_WEIGHT_REGULAR; } } FontWeight convertWeight(int weight) { switch (weight) { case FC_WEIGHT_THIN: return FontWeightThin; case FC_WEIGHT_ULTRALIGHT: return FontWeightUltraLight; case FC_WEIGHT_LIGHT: return FontWeightLight; case FC_WEIGHT_REGULAR: return FontWeightNormal; case FC_WEIGHT_MEDIUM: return FontWeightMedium; case FC_WEIGHT_SEMIBOLD: return FontWeightSemiBold; case FC_WEIGHT_BOLD: return FontWeightBold; case FC_WEIGHT_EXTRABOLD: return FontWeightUltraBold; case FC_WEIGHT_ULTRABLACK: return FontWeightHeavy; default: return FontWeightNormal; } } int convertWidth(FontWidth width) { switch (width) { case FontWidthUltraCondensed: return FC_WIDTH_ULTRACONDENSED; case FontWidthExtraCondensed: return FC_WIDTH_EXTRACONDENSED; case FontWidthCondensed: return FC_WIDTH_CONDENSED; case FontWidthSemiCondensed: return FC_WIDTH_SEMICONDENSED; case FontWidthNormal: return FC_WIDTH_NORMAL; case FontWidthSemiExpanded: return FC_WIDTH_SEMIEXPANDED; case FontWidthExpanded: return FC_WIDTH_EXPANDED; case FontWidthExtraExpanded: return FC_WIDTH_EXTRAEXPANDED; case FontWidthUltraExpanded: return FC_WIDTH_ULTRAEXPANDED; default: return FC_WIDTH_NORMAL; } } FontWidth convertWidth(int width) { switch (width) { case FC_WIDTH_ULTRACONDENSED: return FontWidthUltraCondensed; case FC_WIDTH_EXTRACONDENSED: return FontWidthExtraCondensed; case FC_WIDTH_CONDENSED: return FontWidthCondensed; case FC_WIDTH_SEMICONDENSED: return FontWidthSemiCondensed; case FC_WIDTH_NORMAL: return FontWidthNormal; case FC_WIDTH_SEMIEXPANDED: return FontWidthSemiExpanded; case FC_WIDTH_EXPANDED: return FontWidthExpanded; case FC_WIDTH_EXTRAEXPANDED: return FontWidthExtraExpanded; case FC_WIDTH_ULTRAEXPANDED: return FontWidthUltraExpanded; default: return FontWidthNormal; } } FontDescriptor *createFontDescriptor(FcPattern *pattern) { FcChar8 *path = NULL, *psName = NULL, *family = NULL, *style = NULL; int index = 0, weight = 0, width = 0, slant = 0, spacing = 0; FcPatternGetString(pattern, FC_FILE, 0, &path); #ifdef FC_POSTSCRIPT_NAME FcPatternGetString(pattern, FC_POSTSCRIPT_NAME, 0, &psName); #else psName = (FcChar8*) ""; #endif FcPatternGetString(pattern, FC_FAMILY, 0, &family); FcPatternGetString(pattern, FC_STYLE, 0, &style); FcPatternGetInteger(pattern, FC_INDEX, 0, &index); FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight); FcPatternGetInteger(pattern, FC_WIDTH, 0, &width); FcPatternGetInteger(pattern, FC_SLANT, 0, &slant); FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing); return new FontDescriptor( (char *) path, index, (char *) psName, (char *) family, (char *) style, convertWeight(weight), convertWidth(width), slant == FC_SLANT_ITALIC, spacing == FC_MONO ); } ResultSet *getResultSet(FcFontSet *fs) { ResultSet *res = new ResultSet(); if (!fs) return res; for (int i = 0; i < fs->nfont; i++) { res->push_back(createFontDescriptor(fs->fonts[i])); } return res; } void resetFontCache() { FcInitReinitialize(); } ResultSet *getAvailableFonts() { FcInit(); FcPattern *pattern = FcPatternCreate(); #ifdef FC_POSTSCRIPT_NAME FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #else FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #endif FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); FcFontSetDestroy(fs); return res; } FcPattern *createPattern(FontDescriptor *desc) { FcInit(); FcPattern *pattern = FcPatternCreate(); #ifdef FC_POSTSCRIPT_NAME if (desc->postscriptName) FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) desc->postscriptName); #endif if (desc->family) FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *) desc->family); if (desc->style) FcPatternAddString(pattern, FC_STYLE, (FcChar8 *) desc->style); if (desc->italic) FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); if (desc->weight) FcPatternAddInteger(pattern, FC_WEIGHT, convertWeight(desc->weight)); if (desc->width) FcPatternAddInteger(pattern, FC_WIDTH, convertWidth(desc->width)); if (desc->monospace) FcPatternAddInteger(pattern, FC_SPACING, FC_MONO); return pattern; } ResultSet *findFonts(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); #ifdef FC_POSTSCRIPT_NAME FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #else FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); #endif FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcFontSetDestroy(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); return res; } FontDescriptor *findFont(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); FcConfigSubstitute(NULL, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *font = FcFontMatch(NULL, pattern, &result); FontDescriptor *res = font ? createFontDescriptor(font) : NULL; FcPatternDestroy(pattern); FcPatternDestroy(font); 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/font_fallback.h0000644000176200001440000000065014507500576016407 0ustar liggesusers#pragma once #include #include #include #include #include "types.h" [[cpp11::register]] cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string); FontSettings request_fallback(const char *string, const char *path, int index); [[cpp11::init]] void export_font_fallback(DllInfo* dll); systemfonts/src/ft_cache.cpp0000644000176200001440000002241614507500576015715 0ustar liggesusers#include "ft_cache.h" #include #include FreetypeCache::FreetypeCache() : error_code(0), glyphstore(), face_cache(16), size_cache(32), cur_id(), cur_size(-1), cur_res(-1), cur_can_kern(false), cur_glyph(0) { FT_Error err = FT_Init_FreeType(&library); if (err != 0) { cpp11::stop("systemfonts failed to initialise the freetype font cache"); } } FreetypeCache::~FreetypeCache() { FT_Done_FreeType(library); } bool FreetypeCache::load_font(const char* file, int index, double size, double res) { FaceID id(std::string(file), index); if (current_face(id, size, res)) { return true; } if (!load_face(id)) { return false; } if (!load_size(id, size, res)) { return false; } cur_id = id; cur_size = size; cur_res = res; glyphstore.clear(); cur_can_kern = FT_HAS_KERNING(face); return true; } bool FreetypeCache::load_font(const char* file, int index) { std::string file_str(file); FaceID id(file_str, index); if (id == cur_id) { return true; } if (!load_face(id)) { return false; } cur_id = id; cur_size = -1; cur_res = -1; glyphstore.clear(); cur_can_kern = FT_HAS_KERNING(face); return true; } bool FreetypeCache::load_face(FaceID face) { if (face == cur_id) { return true; } FaceStore cached_face; if (face_cache.get(face, cached_face)) { this->face = cached_face.face; cur_is_scalable = FT_IS_SCALABLE(this->face); return true; } FT_Face new_face; FT_Error err = FT_New_Face(this->library, face.file.c_str(), face.index, &new_face); if (err != 0) { error_code = err; return false; } this->face = new_face; cur_is_scalable = FT_IS_SCALABLE(new_face); if (face_cache.add(face, FaceStore(new_face), cached_face)) { for(std::unordered_set::iterator it = cached_face.sizes.begin(); it != cached_face.sizes.end(); ++it) { size_cache.remove(*it); } FT_Done_Face(cached_face.face); } return true; } bool FreetypeCache::load_size(FaceID face, double size, double res) { SizeID id(face, size, res); FT_Size cached_size; SizeID cached_id; FaceStore cached_face; if (size_cache.get(id, cached_size)) { FT_Activate_Size(cached_size); this->size = cached_size; return true; } FT_Size new_size; FT_Error err = FT_New_Size(this->face, &new_size); if (err != 0) { error_code = err; return false; } FT_Size old_size = this->face->size; FT_Activate_Size(new_size); if (cur_is_scalable) { err = FT_Set_Char_Size(this->face, 0, size * 64, res, res); if (err != 0) { error_code = err; FT_Activate_Size(old_size); return false; } } else { if (this->face->num_fixed_sizes == 0) { error_code = 23; FT_Activate_Size(old_size); return false; } int best_match = 0; int diff = 1e6; int scaled_size = 64 * size * res / 72; int largest_size = 0; int largest_ind = -1; bool found_match = false; for (int i = 0; i < this->face->num_fixed_sizes; ++i) { if (this->face->available_sizes[i].size > largest_size) { largest_ind = i; } int ndiff = this->face->available_sizes[i].size - scaled_size; if (ndiff >= 0 && ndiff < diff) { best_match = i; diff = ndiff; found_match = true; } } if (!found_match && scaled_size >= largest_size) { best_match = largest_ind; } err = FT_Select_Size(this->face, best_match); if (err != 0) { error_code = err; FT_Activate_Size(old_size); return false; } unscaled_scaling = 1; } if (size_cache.add(id, new_size, cached_id)) { if (face_cache.get(cached_id.face, cached_face)) { cached_face.sizes.erase(cached_id); } } face_cache.add_size_id(face, id); this->size = new_size; return true; } bool FreetypeCache::has_glyph(uint32_t index) { FT_UInt glyph_id = FT_Get_Char_Index(face, index); return glyph_id != 0; } bool FreetypeCache::load_glyph(uint32_t index) { FT_UInt glyph_id = FT_Get_Char_Index(face, index); FT_Error err = 0; err = FT_Load_Glyph(face, glyph_id, FT_LOAD_DEFAULT); error_code = err; if (err == 0) { cur_glyph = glyph_id; } return err == 0; } FontInfo FreetypeCache::font_info() { FontInfo res = {}; res.family = std::string(face->family_name); res.style = std::string(face->style_name); res.is_italic = face->style_flags & FT_STYLE_FLAG_ITALIC; res.is_bold = face->style_flags & FT_STYLE_FLAG_BOLD; res.is_monospace = FT_IS_FIXED_WIDTH(face); res.is_vertical = FT_HAS_VERTICAL(face); res.has_kerning = cur_can_kern; #ifdef FT_HAS_COLOR res.has_color = FT_HAS_COLOR(face); #else res.has_color = false; #endif res.is_scalable = cur_is_scalable; res.n_glyphs = face->num_glyphs; res.n_sizes = face->num_fixed_sizes; res.n_charmaps = face->num_charmaps; res.bbox = { FT_MulFix(face->bbox.xMin, size->metrics.x_scale), FT_MulFix(face->bbox.xMax, size->metrics.x_scale), FT_MulFix(face->bbox.yMin, size->metrics.y_scale), FT_MulFix(face->bbox.yMax, size->metrics.y_scale) }; res.max_ascend = FT_MulFix(face->ascender, size->metrics.y_scale); res.max_descend = FT_MulFix(face->descender, size->metrics.y_scale); res.max_advance_h = FT_MulFix(face->max_advance_height, size->metrics.y_scale); res.max_advance_w = FT_MulFix(face->max_advance_width, size->metrics.x_scale); res.lineheight = FT_MulFix(face->height, size->metrics.y_scale); res.underline_pos = FT_MulFix(face->underline_position, size->metrics.y_scale); res.underline_size = FT_MulFix(face->underline_thickness, size->metrics.y_scale); return res; } GlyphInfo FreetypeCache::glyph_info() { GlyphInfo res = {}; res.index = cur_glyph; res.width = face->glyph->metrics.width; res.height = face->glyph->metrics.height; res.x_advance = face->glyph->advance.x; res.y_advance = face->glyph->advance.y; if (res.y_advance != 0) { // Vertical res.x_bearing = face->glyph->metrics.vertBearingX; res.y_bearing = face->glyph->metrics.vertBearingY; } else { res.x_bearing = face->glyph->metrics.horiBearingX; res.y_bearing = face->glyph->metrics.horiBearingY; } res.bbox = {res.x_bearing, res.x_bearing + res.width, res.y_bearing - res.height, res.y_bearing}; if (!cur_is_scalable) { res.width *= unscaled_scaling; res.height *= unscaled_scaling; res.x_advance *= unscaled_scaling; res.y_advance *= unscaled_scaling; res.x_bearing *= unscaled_scaling; res.y_bearing *= unscaled_scaling; res.bbox[0] *= unscaled_scaling; res.bbox[1] *= unscaled_scaling; res.bbox[2] *= unscaled_scaling; res.bbox[3] *= unscaled_scaling; } return res; } GlyphInfo FreetypeCache::cached_glyph_info(uint32_t index, int& error) { std::map::iterator cached_gi = glyphstore.find(index); GlyphInfo info = {}; error = 0; if (cached_gi == glyphstore.end()) { if (load_glyph(index)) { info = glyph_info(); glyphstore[index] = info; } else { error = error_code; } } else { info = cached_gi->second; } return info; } long FreetypeCache::cur_lineheight() { return FT_MulFix(face->height, size->metrics.y_scale); } long FreetypeCache::cur_ascender() { return FT_MulFix(face->ascender, size->metrics.y_scale); } long FreetypeCache::cur_descender() { return FT_MulFix(face->descender, size->metrics.y_scale); } bool FreetypeCache::get_kerning(uint32_t left, uint32_t right, long &x, long &y) { x = 0; y = 0; // Early exit if (!cur_can_kern) return true; FT_UInt left_id = FT_Get_Char_Index(face, left); FT_UInt right_id = FT_Get_Char_Index(face, right); FT_Vector delta = {}; FT_Error error = FT_Get_Kerning(face, left_id, right_id, FT_KERNING_DEFAULT, &delta); if (error != 0) { error_code = error; return false; } x = delta.x; y = delta.y; return true; } bool FreetypeCache::apply_kerning(uint32_t left, uint32_t right, long &x, long &y) { long delta_x = 0, delta_y = 0; if (!get_kerning(left, right, delta_x, delta_y)) { return false; } x += delta_x; y += delta_y; return true; } double FreetypeCache::tracking_diff(double tracking) { return (double) FT_MulFix(face->units_per_EM, size->metrics.x_scale) * tracking / 1000; } FT_Face FreetypeCache::get_face() { //FT_Reference_Face(face); return face; } std::string FreetypeCache::cur_name() { const char* ps_name = FT_Get_Postscript_Name(face); if (ps_name == NULL) { const char* f_name = face->family_name; if (f_name == NULL) f_name = ""; return {f_name}; } return {ps_name}; } int FreetypeCache::get_weight() { void* table = FT_Get_Sfnt_Table(face, ft_sfnt_os2); // [1] ft_sfnt_os2 is deprecated and should be replaced by FT_SFNT_OS2 (only) in the remote future for compatibilty (2021-03-04) if (table == NULL) { return 0; } TT_OS2* os2_table = (TT_OS2*) table; return os2_table->usWeightClass; } int FreetypeCache::get_width() { void* table = FT_Get_Sfnt_Table(face, ft_sfnt_os2); // see comment [1] above if (table == NULL) { return 0; } TT_OS2* os2_table = (TT_OS2*) table; return os2_table->usWidthClass; } void FreetypeCache::get_family_name(char* family, int max_length) { strncpy(family, face->family_name, max_length); } systemfonts/src/font_matching.h0000644000176200001440000000222614507500751016436 0ustar liggesusers#pragma once #include #include #include #include #include #include "types.h" #include "caches.h" // Default fonts based on browser behaviour #if defined _WIN32 #define SANS "Arial" #define SERIF "Times New Roman" #define MONO "Courier New" #define EMOJI "Segoe UI Emoji" #define SYMBOL "Segoe UI Symbol" #elif defined __APPLE__ #define SANS "Helvetica" #define SERIF "Times" #define MONO "Courier New" #define EMOJI "Apple Color Emoji" #define SYMBOL "Symbol" #else #define SANS "sans" #define SERIF "serif" #define MONO "mono" #define EMOJI "emoji" #define SYMBOL "symbol" #endif int locate_font(const char *family, int italic, int bold, char *path, int max_path_length); FontSettings locate_font_with_features(const char *family, int italic, int bold); [[cpp11::register]] cpp11::list match_font_c(cpp11::strings family, cpp11::logicals italic, cpp11::logicals bold); [[cpp11::register]] cpp11::writable::data_frame system_fonts_c(); [[cpp11::register]] void reset_font_cache_c(); [[cpp11::init]] void export_font_matching(DllInfo* dll); systemfonts/src/dev_metrics.cpp0000644000176200001440000000555214507500576016467 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/init.cpp0000644000176200001440000000017114507500576015116 0ustar liggesusers#include #include "caches.h" extern "C" void R_unload_systemfonts(DllInfo *dll) { unload_caches(dll); } systemfonts/src/types.h0000644000176200001440000000350514507500576014770 0ustar liggesusers#pragma once #include #include #include #include #include // The exact location of a single file struct FontLoc { std::string file; unsigned int index; }; // Settings related to a single OpenType feature (all feature ids are 4 char long) struct FontFeature { char feature[4]; int setting; }; // A collection of four fonts (plain, bold, italic, bold-italic) along with optional features struct FontCollection { FontLoc fonts[4]; std::vector features; }; // A structure to pass around a single font with features (used by the C interface) struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; // A collection of registered fonts typedef std::unordered_map FontReg; // A map of Emoji unicode points typedef std::unordered_map EmojiMap; // A map for keeping font linking on Windows typedef std::unordered_map > WinLinkMap; // Key for looking up cached font locations struct FontKey { std::string family; int bold; int italic; FontKey() : family(""), bold(0), italic(0) {} FontKey(std::string _family) : family(_family), bold(0), italic(0) {} FontKey(std::string _family, int _bold, int _italic) : family(_family), bold(_bold), italic(_italic) {} inline bool operator==(const FontKey &other) const { return (bold == other.bold && 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.bold) ^std::hash()(x.italic); } }; } // Map for keeping already resolved font locations typedef std::unordered_map FontMap; systemfonts/src/caches.cpp0000644000176200001440000000167614507500576015414 0ustar liggesusers#include "caches.h" static ResultSet* fonts; ResultSet& get_font_list() { return *fonts; } static FontReg* font_registry; FontReg& get_font_registry() { return *font_registry; } static FreetypeCache* font_cache; FreetypeCache& get_font_cache() { return *font_cache; } static EmojiMap* emoji_map; EmojiMap& get_emoji_map() { return *emoji_map; } static FontMap* font_locations; FontMap& get_font_map() { return *font_locations; } static WinLinkMap* win_font_linking; WinLinkMap& get_win_link_map() { return *win_font_linking; } void init_caches(DllInfo* dll) { fonts = new ResultSet(); font_registry = new FontReg(); font_cache = new FreetypeCache(); emoji_map = new EmojiMap(); font_locations = new FontMap(); win_font_linking = new WinLinkMap(); } void unload_caches(DllInfo* dll) { delete fonts; delete font_registry; delete font_cache; delete emoji_map; delete font_locations; delete win_font_linking; } systemfonts/src/cache_lru.h0000644000176200001440000001010514507500576015543 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/font_metrics.h0000644000176200001440000000152714507500576016322 0ustar liggesusers#pragma once #include #include #include #include #include #include [[cpp11::register]] cpp11::writable::data_frame get_font_info_c(cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); [[cpp11::register]] cpp11::writable::data_frame get_glyph_info_c(cpp11::strings glyphs, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width); int font_weight(const char* fontfile, int index); int font_family(const char* fontfile, int index, char* family, int max_length); [[cpp11::init]] void export_font_metrics(DllInfo* dll); systemfonts/src/utils.h0000644000176200001440000001160214507500576014761 0ustar liggesusers#pragma once #include #include #include #include #include #include #ifdef HAS_UNWIND_PROTECT #define CPP_UNWIND R_ContinueUnwind(err); #else #define CPP_UNWIND \ do { \ } while (false); #endif #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) { \ CPP_UNWIND \ } inline bool strcmp_no_case(const char * A, const char * B) { if (A == NULL && B == NULL) return true; if (A == NULL || B == NULL) return false; unsigned int a_len = strlen(A); if (strlen(B) != a_len) return false; for (unsigned int i = 0; i < a_len; ++i) if (tolower(A[i]) != tolower(B[i])) return false; return true; } /* Basic UTF-8 manipulation routines by Jeff Bezanson placed in the public domain Fall 2005 This code is designed to provide the utilities you need to manipulate UTF-8 as an internal string encoding. These functions do not perform the error checking normally needed when handling UTF-8 data, so if you happen to be from the Unicode Consortium you will want to flay me alive. I do this because error checking can be performed at the boundaries (I/O), with these routines reserved for higher performance on data known to be valid. Source: https://www.cprogramming.com/tutorial/utf8.c Modified 2019 by Thomas Lin Pedersen to work with const char* */ static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; static const char trailingBytesForUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /* conversions without error checking only works for valid UTF-8, i.e. no 5- or 6-byte sequences srcsz = source size in bytes, or -1 if 0-terminated sz = dest size in # of wide characters returns # characters converted dest will always be L'\0'-terminated, even if there isn't enough room for all the characters. if sz = srcsz+1 (i.e. 4*srcsz+4 bytes), there will always be enough space. */ static int u8_toucs(uint32_t *dest, int sz, const char *src, int srcsz) { uint32_t ch; const char *src_end = src + srcsz; int nb; int i=0; while (i < sz-1) { nb = trailingBytesForUTF8[(unsigned char)*src]; if (srcsz == -1) { if (*src == 0) goto done_toucs; } else { if (src + nb >= src_end) goto done_toucs; } ch = 0; switch (nb) { /* these fall through deliberately */ case 5: ch += (unsigned char)*src++; ch <<= 6; case 4: ch += (unsigned char)*src++; ch <<= 6; case 3: ch += (unsigned char)*src++; ch <<= 6; case 2: ch += (unsigned char)*src++; ch <<= 6; case 1: ch += (unsigned char)*src++; ch <<= 6; case 0: ch += (unsigned char)*src++; } ch -= offsetsFromUTF8[nb]; dest[i++] = ch; } done_toucs: dest[i] = 0; return i; } /* End of Basic UTF-8 manipulation routines by Jeff Bezanson */ class UTF_UCS { std::vector buffer; public: UTF_UCS() { // Allocate space in buffer buffer.resize(1024); } ~UTF_UCS() { } uint32_t * convert(const char * string, int &n_conv) { if (string == NULL) { n_conv = 0; return buffer.data(); } int n_bytes = strlen(string) + 1; unsigned int max_size = n_bytes * 4; if (buffer.size() < max_size) { buffer.resize(max_size); } n_conv = u8_toucs(buffer.data(), max_size, string, -1); return buffer.data(); } }; systemfonts/src/cache_store.h0000644000176200001440000000040214507500576016074 0ustar liggesusers#pragma once #include #include FT_FREETYPE_H #include #include FT_Face get_cached_face(const char* file, int index, double size, double res, int* error); [[cpp11::init]] void export_cache_store(DllInfo* dll); systemfonts/src/font_registry.cpp0000644000176200001440000000742414507500576017061 0ustar liggesusers#include "font_registry.h" #include "caches.h" #include #include #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using namespace cpp11::literals; void register_font_c(strings_t family, strings_t paths, integers_t indices, strings_t features, integers_t settings) { FontReg& registry = get_font_registry(); std::string name(family[0]); FontCollection col = {}; for (int i = 0; i < features.size(); ++i) { const char* f = Rf_translateCharUTF8(features[i]); col.features.push_back({{f[0], f[1], f[2], f[3]}, settings[i]}); } for (int i = 0; i < Rf_length(paths); ++i) { if (i > 3) continue; col.fonts[i] = {paths[i], (unsigned int) indices[i]}; } registry[name] = col; FontMap& font_map = get_font_map(); font_map.clear(); } void clear_registry_c() { FontReg& registry = get_font_registry(); registry.clear(); FontMap& font_map = get_font_map(); font_map.clear(); } data_frame_w registry_fonts_c() { FontReg& registry = get_font_registry(); int n_reg = registry.size(); int n = n_reg * 4; strings_w path(n); integers_w index(n); strings_w family(n); strings_w style(n); integers_w weight(n); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = {"normal", "bold"}; logicals_w italic(n); list_w features(n); int i = 0; for (auto it = registry.begin(); it != registry.end(); ++it) { for (int j = 0; j < 4; j++) { path[i] = it->second.fonts[j].file; index[i] = it->second.fonts[j].index; family[i] = it->first; switch (j) { case 0: style[i] = "Regular"; break; case 1: style[i] = "Bold"; break; case 2: style[i] = "Italic"; break; case 3: style[i] = "Bold Italic"; break; } weight[i] = 1 + (int) (j == 1 || j == 3); italic[i] = (Rboolean) (j > 1); if (it->second.features.size() == 0) { 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/caches.h0000644000176200001440000000061714507500576015053 0ustar liggesusers#pragma once #include #include #include "types.h" #include "FontDescriptor.h" #include "ft_cache.h" ResultSet& get_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/mac/0000755000176200001440000000000014510734620014201 5ustar liggesuserssystemfonts/src/mac/FontManagerMac.mm0000755000176200001440000002272014507500576017373 0ustar liggesusers#include #include #include #include #include "../FontDescriptor.h" // converts a CoreText weight (-1 to +1) to a standard weight (100 to 900) static int convertWeight(float weight) { if (weight <= -0.8f) return 100; else if (weight <= -0.6f) return 200; else if (weight <= -0.4f) return 300; else if (weight <= 0.0f) return 400; else if (weight <= 0.25f) return 500; else if (weight <= 0.35f) return 600; else if (weight <= 0.4f) return 700; else if (weight <= 0.6f) return 800; else return 900; } // converts a CoreText width (-1 to +1) to a standard width (1 to 9) static int convertWidth(float unit) { if (unit < 0) { return 1 + (1 + unit) * 4; } else { return 5 + unit * 4; } } void addFontIndex(FontDescriptor* font) { @autoreleasepool { static std::map font_index; std::string font_name(font->postscriptName); int font_no; std::map::iterator it = font_index.find(font_name); if (it == font_index.end()) { NSString *font_path = [NSString stringWithUTF8String:font->path]; NSURL *font_url = [NSURL fileURLWithPath: font_path]; CFArrayRef font_descriptors = CTFontManagerCreateFontDescriptorsFromURL((CFURLRef) font_url); int n_fonts = CFArrayGetCount(font_descriptors); if (n_fonts == 1) { font_no = 0; font_index[font_name] = 0; } else { for (int i = 0; i < n_fonts; i++) { CTFontDescriptorRef font_at_i = (CTFontDescriptorRef) CFArrayGetValueAtIndex(font_descriptors, i); std::string font_name_at_i = [(__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(font_at_i, kCTFontNameAttribute) UTF8String]; font_index[font_name_at_i] = i; if (font_name.compare(font_name_at_i) == 0) { font_no = i; } } } CFRelease(font_descriptors); } else { font_no = (*it).second; } font->index = font_no; }} FontDescriptor *createFontDescriptor(CTFontDescriptorRef descriptor) { @autoreleasepool { NSURL *url = (__bridge_transfer NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); NSString *psName = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); NSString *family = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); NSString *style = (__bridge_transfer NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); NSDictionary *traits = (__bridge_transfer NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); NSNumber *weightVal = traits[(id)kCTFontWeightTrait]; FontWeight weight = (FontWeight) convertWeight([weightVal floatValue]); NSNumber *widthVal = traits[(id)kCTFontWidthTrait]; FontWidth width = (FontWidth) convertWidth([widthVal floatValue]); NSNumber *symbolicTraitsVal = traits[(id)kCTFontSymbolicTrait]; unsigned int symbolicTraits = [symbolicTraitsVal unsignedIntValue]; FontDescriptor *res = new FontDescriptor( [[url path] UTF8String], [psName UTF8String], [family UTF8String], [style UTF8String], weight, width, (symbolicTraits & kCTFontItalicTrait) != 0, (symbolicTraits & kCTFontMonoSpaceTrait) != 0 ); addFontIndex(res); return res; }} static CTFontCollectionRef collection = NULL; void resetFontCache() { if (collection != NULL) { CTFontCollectionRef temp = collection; collection = NULL; CFRelease(temp); } } ResultSet *getAvailableFonts() { @autoreleasepool { // cache font collection for fast use in future calls if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL); NSArray *matches = (__bridge_transfer NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); ResultSet *results = new ResultSet(); for (id m in matches) { CTFontDescriptorRef match = (__bridge CTFontDescriptorRef) m; results->push_back(createFontDescriptor(match)); } return results; }} // helper to square a value static inline int sqr(int value) { return value * value; } CTFontDescriptorRef getFontDescriptor(FontDescriptor *desc) { // build a dictionary of font attributes NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; CTFontSymbolicTraits symbolicTraits = 0; if (desc->postscriptName) { NSString *postscriptName = [NSString stringWithUTF8String:desc->postscriptName]; attrs[(id)kCTFontNameAttribute] = postscriptName; } if (desc->family) { NSString *family = [NSString stringWithUTF8String:desc->family]; attrs[(id)kCTFontFamilyNameAttribute] = family; } if (desc->style) { NSString *style = [NSString stringWithUTF8String:desc->style]; attrs[(id)kCTFontStyleNameAttribute] = style; } // build symbolic traits if (desc->italic) symbolicTraits |= kCTFontItalicTrait; if (desc->weight == FontWeightBold) symbolicTraits |= kCTFontBoldTrait; if (desc->monospace) symbolicTraits |= kCTFontMonoSpaceTrait; if (desc->width == FontWidthCondensed) symbolicTraits |= kCTFontCondensedTrait; if (desc->width == FontWidthExpanded) symbolicTraits |= kCTFontExpandedTrait; if (symbolicTraits) { NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolicTraits]}; attrs[(id)kCTFontTraitsAttribute] = traits; } // create a font descriptor and search for matches return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); } int metricForMatch(CTFontDescriptorRef match, FontDescriptor *desc) { @autoreleasepool { NSDictionary *dict = (__bridge_transfer NSDictionary *)CTFontDescriptorCopyAttribute(match, kCTFontTraitsAttribute); bool italic = ([dict[(id)kCTFontSymbolicTrait] unsignedIntValue] & kCTFontItalicTrait); // normalize everything to base-900 int metric = 0; if (desc->weight) metric += sqr(convertWeight([dict[(id)kCTFontWeightTrait] floatValue]) - desc->weight); if (desc->width) metric += sqr((convertWidth([dict[(id)kCTFontWidthTrait] floatValue]) - desc->width) * 100); metric += sqr((italic != desc->italic) * 900); return metric; }} ResultSet *findFonts(FontDescriptor *desc) { @autoreleasepool { CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); ResultSet *results = new ResultSet(); NSArray *sorted = [matches sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { int ma = metricForMatch((__bridge CTFontDescriptorRef) a, desc); int mb = metricForMatch((__bridge CTFontDescriptorRef) b, desc); return ma < mb ? NSOrderedAscending : ma > mb ? NSOrderedDescending : NSOrderedSame; }]; for (id m in sorted) { CTFontDescriptorRef match = (__bridge CTFontDescriptorRef) m; int mb = metricForMatch((__bridge CTFontDescriptorRef) m, desc); if (mb < 10000) { results->push_back(createFontDescriptor(match)); } } CFRelease(descriptor); return results; }} CTFontDescriptorRef findBest(FontDescriptor *desc, NSArray *matches) { // find the closest match for width and weight attributes CTFontDescriptorRef best = NULL; int bestMetric = INT_MAX; for (id m in matches) { int metric = metricForMatch((__bridge CTFontDescriptorRef) m, desc); if (metric < bestMetric) { bestMetric = metric; best = (__bridge CTFontDescriptorRef) m; } // break if this is an exact match if (metric == 0) break; } return best; } FontDescriptor *findFont(FontDescriptor *desc) { @autoreleasepool { FontDescriptor *res = NULL; CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (__bridge_transfer NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); // if there was no match, try again but 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_metrics.cpp0000644000176200001440000002131114507500576016646 0ustar liggesusers#include "font_metrics.h" #include "types.h" #include "caches.h" #include "utils.h" #include #include #include using list_t = cpp11::list; using list_w = cpp11::writable::list; using data_frame_w = cpp11::writable::data_frame; using strings_t = cpp11::strings; using strings_w = cpp11::writable::strings; using integers_t = cpp11::integers; using integers_w = cpp11::writable::integers; using logicals_t = cpp11::logicals; using logicals_w = cpp11::writable::logicals; using doubles_t = cpp11::doubles; using doubles_w = cpp11::writable::doubles; using namespace cpp11::literals; data_frame_w get_font_info_c(strings_t path, integers_t index, doubles_t size, doubles_t res) { bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; int full_length = 1; if (!one_path) full_length = path.size(); else if (!one_size) full_length = size.size(); else if (!one_res) full_length = res.size(); FreetypeCache& cache = get_font_cache(); strings_w path_col(full_length); integers_w index_col(full_length); strings_w family(full_length); strings_w style(full_length); logicals_w italic(full_length); logicals_w bold(full_length); logicals_w monospace(full_length); integers_w weight(full_length); weight.attr("class") = {"ordered", "factor"}; weight.attr("levels") = { "thin", "ultralight", "light", "normal", "medium", "semibold", "bold", "ultrabold", "heavy" }; integers_w width(full_length); width.attr("class") = {"ordered", "factor"}; width.attr("levels") = { "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded" }; logicals_w kerning(full_length); logicals_w color(full_length); logicals_w scalable(full_length); logicals_w vertical(full_length); integers_w nglyphs(full_length); integers_w nsizes(full_length); integers_w ncharmaps(full_length); list_w bbox(full_length); doubles_w ascend(full_length); doubles_w descend(full_length); doubles_w advance_w(full_length); doubles_w advance_h(full_length); doubles_w lineheight(full_length); doubles_w u_pos(full_length); doubles_w u_size(full_length); for (int i = 0; i < full_length; ++i) { bool success = cache.load_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i] ); if (!success) { cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code); } FontInfo info = cache.font_info(); path_col[i] = one_path ? first_path : path[i]; index_col[i] = one_path ? first_index : index[i]; family[i] = info.family; style[i] = info.style; italic[i] = (Rboolean) info.is_italic; bold[i] = (Rboolean) info.is_bold; monospace[i] = (Rboolean) info.is_monospace; weight[i] = cache.get_weight() / 100; if (weight[i] == 0) { weight[i] = NA_INTEGER; } width[i] = cache.get_width(); if (width[i] == 0) { width[i] = NA_INTEGER; } kerning[i] = (Rboolean) info.has_kerning; color[i] = (Rboolean) info.has_color; scalable[i] = (Rboolean) info.is_scalable; vertical[i] = (Rboolean) info.is_vertical; nglyphs[i] = info.n_glyphs; nsizes[i] = info.n_sizes; ncharmaps[i] = info.n_charmaps; bbox[i] = doubles_w({ "xmin"_nm = double(info.bbox[0]) / 64.0, "xmax"_nm = double(info.bbox[1]) / 64.0, "ymin"_nm = double(info.bbox[2]) / 64.0, "ymax"_nm = double(info.bbox[3]) / 64.0 }); ascend[i] = info.max_ascend / 64.0; descend[i] = info.max_descend / 64.0; advance_w[i] = info.max_advance_w / 64.0; advance_h[i] = info.max_advance_h / 64.0; lineheight[i] = info.lineheight / 64.0; u_pos[i] = info.underline_pos / 64.0; u_size[i] = info.underline_size / 64.0; } data_frame_w info({ "path"_nm = path_col, "index"_nm = index_col, "family"_nm = family, "style"_nm = style, "italic"_nm = italic, "bold"_nm = bold, "monospace"_nm = monospace, "weight"_nm = weight, "width"_nm = width, "kerning"_nm = kerning, "color"_nm = color, "scalable"_nm = scalable, "vertical"_nm = vertical, "n_glyphs"_nm = nglyphs, "n_sizes"_nm = nsizes, "n_charmaps"_nm = ncharmaps, "bbox"_nm = bbox, "max_ascend"_nm = ascend, "max_descend"_nm = descend, "max_advance_width"_nm = advance_w, "max_advance_height"_nm = advance_h, "lineheight"_nm = lineheight, "underline_pos"_nm = u_pos, "underline_size"_nm = u_size }); info.attr("class") = {"tbl_df", "tbl", "data.frame"}; return info; } data_frame_w get_glyph_info_c(strings_t glyphs, strings_t path, integers_t index, doubles_t size, doubles_t res) { int n_glyphs = glyphs.size(); bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_size = size.size() == 1; double first_size = size[0]; bool one_res = res.size() == 1; double first_res = res[0]; FreetypeCache& cache = get_font_cache(); integers_w glyph_ids(n_glyphs); doubles_w widths(n_glyphs); doubles_w heights(n_glyphs); doubles_w x_bearings(n_glyphs); doubles_w y_bearings(n_glyphs); doubles_w x_advances(n_glyphs); doubles_w y_advances(n_glyphs); list_w bboxes(n_glyphs); UTF_UCS utf_converter; int length = 0; int error_c = 0; for (int i = 0; i < n_glyphs; ++i) { bool success = cache.load_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_size ? first_size : size[i], one_res ? first_res : res[i] ); if (!success) { cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code); } const char* glyph = Rf_translateCharUTF8(glyphs[i]); uint32_t* glyph_code = utf_converter.convert(glyph, length); GlyphInfo glyph_info = cache.cached_glyph_info(glyph_code[0], error_c); if (error_c != 0) { cpp11::stop("Failed to load `%s` from font (%s) with freetype error %i", glyph, Rf_translateCharUTF8(path[i]), error_c); } glyph_ids[i] = glyph_info.index; widths[i] = glyph_info.width / 64.0; heights[i] = glyph_info.height / 64.0; x_bearings[i] = glyph_info.x_bearing / 64.0; y_bearings[i] = glyph_info.y_bearing / 64.0; x_advances[i] = glyph_info.x_advance / 64.0; y_advances[i] = glyph_info.y_advance / 64.0; bboxes[i] = doubles_w({ "xmin"_nm = double(glyph_info.bbox[0]) / 64.0, "xmax"_nm = double(glyph_info.bbox[1]) / 64.0, "ymin"_nm = double(glyph_info.bbox[2]) / 64.0, "ymax"_nm = double(glyph_info.bbox[3]) / 64.0 }); } data_frame_w info({ "glyph"_nm = glyphs, "index"_nm = glyph_ids, "width"_nm = widths, "height"_nm = heights, "x_bearing"_nm = x_bearings, "y_bearing"_nm = y_bearings, "x_advance"_nm = x_advances, "y_advance"_nm = y_advances, "bbox"_nm = bboxes }); info.attr("class") = {"tbl_df", "tbl", "data.frame"}; return info; } int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index, size, res)) { return cache.error_code; } int error = 0; GlyphInfo metrics = cache.cached_glyph_info(code, error); if (error != 0) { return error; } *width = metrics.x_advance / 64.0; *ascent = metrics.bbox[3] / 64.0; *descent = -metrics.bbox[2] / 64.0; END_CPP return 0; } int font_weight(const char* fontfile, int index) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index)) { return 0; } return cache.get_weight(); END_CPP return 0; } int font_family(const char* fontfile, int index, char* family, int max_length) { BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(fontfile, index)) { return 0; } cache.get_family_name(family, max_length); END_CPP return 1; } void export_font_metrics(DllInfo* dll) { R_RegisterCCallable("systemfonts", "glyph_metrics", (DL_FUNC)glyph_metrics); R_RegisterCCallable("systemfonts", "font_weight", (DL_FUNC)font_weight); R_RegisterCCallable("systemfonts", "font_family", (DL_FUNC)font_family); } systemfonts/src/Makevars.win0000644000176200001440000000103414510732006015723 0ustar liggesusersRWINLIB = ../windows/harfbuzz PKG_CPPFLAGS = \ -I$(RWINLIB)/include/freetype2 PKG_LIBS = -L$(RWINLIB)/lib$(R_ARCH) \ -L$(RWINLIB)/lib \ -lfreetype -lharfbuzz -lpng -lbz2 -lz -lrpcrt4 -lgdi32 -luuid OBJECTS = caches.o cpp11.o dev_metrics.o font_matching.o font_registry.o \ ft_cache.o string_shape.o font_metrics.o font_fallback.o string_metrics.o \ emoji.o cache_store.o init.o win/FontManagerWindows.o all: clean winlibs winlibs: "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" clean: rm -f $(OBJECTS) $(SHLIB) systemfonts/src/string_shape.cpp0000644000176200001440000002644414507500576016654 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 != 1.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/string_shape.h0000644000176200001440000000715214507500576016314 0ustar liggesusers#pragma once #include #include #include #include #include FT_FREETYPE_H #include FT_TYPES_H #include FT_CACHE_H #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/string_metrics.cpp0000644000176200001440000002203214507500576017207 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/emoji.cpp0000644000176200001440000001307414507500576015264 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/ft_cache.h0000644000176200001440000001114414507500576015356 0ustar liggesusers#pragma once #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 "cache_lru.h" struct FaceID { std::string file; unsigned int index; inline FaceID() : file(""), index(0) {} inline FaceID(std::string f) : file(f), index(0) {} inline FaceID(std::string f, unsigned int i) : file(f), index(i) {} inline FaceID(const FaceID& face) : file(face.file), index(face.index) {} inline bool operator==(const FaceID &other) const { return (index == other.index && file == other.file); } }; struct SizeID { FaceID face; double size; double res; inline SizeID() : face(), size(-1.0), res(-1.0) {} inline SizeID(FaceID f, double s, double r) : face(f), size(s), res(r) {} inline SizeID(const SizeID& s) : face(s.face), size(s.size), res(s.res) {} inline bool operator==(const SizeID &other) const { return (size == other.size && res == other.res && face == other.face); } }; namespace std { template <> struct hash { size_t operator()(const FaceID & x) const { return std::hash()(x.file) ^ std::hash()(x.index); } }; template<> struct hash { size_t operator()(const SizeID & x) const { return std::hash()(x.face) ^ std::hash()(x.size) ^ std::hash()(x.res); } }; } struct FaceStore { FT_Face face; std::unordered_set sizes; FaceStore() : sizes() {}; FaceStore(FT_Face f) : face(f), sizes() {} }; struct FontInfo { std::string family; std::string style; bool is_italic; bool is_bold; bool is_monospace; bool is_vertical; bool has_kerning; bool has_color; bool is_scalable; int n_glyphs; int n_sizes; int n_charmaps; std::vector bbox; long max_ascend; long max_descend; long max_advance_h; long max_advance_w; long lineheight; long underline_pos; long underline_size; }; struct GlyphInfo { unsigned index; long x_bearing; long y_bearing; long width; long height; long x_advance; long y_advance; std::vector bbox; }; class FaceCache : public LRU_Cache { using typename LRU_Cache::key_value_t; using typename LRU_Cache::list_t; using typename LRU_Cache::cache_list_it_t; using typename LRU_Cache::map_t; using typename LRU_Cache::cache_map_it_t; public: FaceCache() : LRU_Cache() { } FaceCache(size_t max_size) : LRU_Cache(max_size) { } void add_size_id(FaceID fid, SizeID sid) { cache_map_it_t it = _cache_map.find(fid); if (it == _cache_map.end()) { return; } it->second->second.sizes.insert(sid); } private: inline virtual void value_dtor(FaceStore& value) { FT_Done_Face(value.face); } }; class SizeCache : public LRU_Cache { public: SizeCache() : LRU_Cache() { } SizeCache(size_t max_size) : LRU_Cache(max_size) { } private: inline virtual void value_dtor(FT_Size& value) { FT_Done_Size(value); } }; class FreetypeCache { public: FreetypeCache(); ~FreetypeCache(); bool load_font(const char* file, int index, double size, double res); bool load_font(const char* file, int index); FontInfo font_info(); bool has_glyph(uint32_t index); bool load_glyph(uint32_t index); GlyphInfo glyph_info(); GlyphInfo cached_glyph_info(uint32_t index, int& error); double string_width(uint32_t* string, int length, bool add_kern); long cur_lineheight(); long cur_ascender(); long cur_descender(); bool get_kerning(uint32_t left, uint32_t right, long &x, long &y); bool apply_kerning(uint32_t left, uint32_t right, long &x, long &y); double tracking_diff(double tracking); FT_Face get_face(); int get_weight(); int get_width(); void get_family_name(char* family, int max_length); std::string cur_name(); int error_code; private: FT_Library library; std::map glyphstore; FaceCache face_cache; SizeCache size_cache; FaceID cur_id; double cur_size; double cur_res; bool cur_can_kern; unsigned int cur_glyph; bool cur_is_scalable; double unscaled_scaling; FT_Face face; FT_Size size; bool load_face(FaceID face); bool load_size(FaceID face, double size, double res); inline bool current_face(FaceID id, double size, double res) { return size == cur_size && res == cur_res && id == cur_id; }; }; systemfonts/src/emoji.h0000644000176200001440000000124614507500576014727 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/string_metrics.h0000644000176200001440000000267514507500576016667 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/FontDescriptor.h0000755000176200001440000001143614507500576016576 0ustar liggesusers#ifndef FONT_DESCRIPTOR_H #define FONT_DESCRIPTOR_H #include #include 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 }; struct FontDescriptor { public: const char *path; int index; const char *postscriptName; const char *family; const char *style; FontWeight weight; FontWidth width; bool italic; bool monospace; FontDescriptor() { path = NULL; index = -1; postscriptName = NULL; family = NULL; style = NULL; weight = FontWeightUndefined; width = FontWidthUndefined; italic = false; monospace = false; } // Constructor added by Thomas Lin Pedersen FontDescriptor(const char *family, bool italic, bool bold) { this->path = NULL; this->index = -1; this->postscriptName = NULL; this->family = copyString(family); this->style = NULL; this->weight = bold ? FontWeightBold : FontWeightNormal; this->width = FontWidthUndefined; this->italic = italic; this->monospace = false; } FontDescriptor(const char *path, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace) { this->path = copyString(path); this->index = 0; this->postscriptName = copyString(postscriptName); this->family = copyString(family); this->style = copyString(style); this->weight = weight; this->width = width; this->italic = italic; this->monospace = monospace; } FontDescriptor(const char *path, int index, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace) { this->path = copyString(path); this->index = index; this->postscriptName = copyString(postscriptName); this->family = copyString(family); this->style = copyString(style); this->weight = weight; this->width = width; this->italic = italic; this->monospace = monospace; } FontDescriptor(FontDescriptor *desc) { path = copyString(desc->path); index = desc->index; postscriptName = copyString(desc->postscriptName); family = copyString(desc->family); style = copyString(desc->style); weight = desc->weight; width = desc->width; italic = desc->italic; monospace = desc->monospace; } const char* get_path() { return path == NULL ? "" : path; } const char* get_psname() { return postscriptName == NULL ? "" : postscriptName; } const char* get_family() { return family == NULL ? "" : family; } const char* get_style() { return style == NULL ? "" : style; } int get_weight() { switch (weight) { case FontWeightThin: return 1; case FontWeightUltraLight: return 2; case FontWeightLight: return 3; case FontWeightNormal: return 4; case FontWeightMedium: return 5; case FontWeightSemiBold: return 6; case FontWeightBold: return 7; case FontWeightUltraBold: return 8; case FontWeightHeavy: return 9; case FontWeightUndefined: return 0; } return 0; } int get_width() { switch (width) { case FontWidthUltraCondensed: return 1; case FontWidthExtraCondensed: return 2; case FontWidthCondensed: return 3; case FontWidthSemiCondensed: return 4; case FontWidthNormal: return 5; case FontWidthSemiExpanded: return 6; case FontWidthExpanded: return 7; case FontWidthExtraExpanded: return 8; case FontWidthUltraExpanded: return 9; case FontWidthUndefined: return 0; } return 0; } ~FontDescriptor() { if (path) delete[] path; if (postscriptName) delete[] postscriptName; if (family) delete[] family; if (style) delete[] style; postscriptName = NULL; family = NULL; style = NULL; } private: char *copyString(const char *input) { if (input == NULL) { return NULL; } char *str = new char[strlen(input) + 1]; strcpy(str, input); return str; } }; class ResultSet : public std::vector { public: ~ResultSet() { for (ResultSet::iterator it = this->begin(); it != this->end(); it++) { delete *it; } } int n_fonts() { return size(); } }; #endif systemfonts/src/cache_store.cpp0000644000176200001440000000106314507500576016433 0ustar liggesusers#include "cache_store.h" #include "types.h" #include "caches.h" #include "utils.h" FT_Face get_cached_face(const char* file, int index, double size, double res, int* error) { FT_Face face = nullptr; BEGIN_CPP FreetypeCache& cache = get_font_cache(); if (!cache.load_font(file, index, size, res)) { *error = cache.error_code; return face; } face = cache.get_face(); END_CPP *error = 0; return face; } void export_cache_store(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_cached_face", (DL_FUNC)get_cached_face); } systemfonts/src/cpp11.cpp0000644000176200001440000002411314507500576015101 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "cpp11/declarations.hpp" #include // dev_metrics.h cpp11::doubles dev_string_widths_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); extern "C" SEXP _systemfonts_dev_string_widths_c(SEXP string, SEXP family, SEXP face, SEXP size, SEXP cex, SEXP unit) { BEGIN_CPP11 return cpp11::as_sexp(dev_string_widths_c(cpp11::as_cpp>(string), cpp11::as_cpp>(family), cpp11::as_cpp>(face), cpp11::as_cpp>(size), cpp11::as_cpp>(cex), cpp11::as_cpp>(unit))); END_CPP11 } // dev_metrics.h cpp11::writable::data_frame dev_string_metrics_c(cpp11::strings string, cpp11::strings family, cpp11::integers face, cpp11::doubles size, cpp11::doubles cex, cpp11::integers unit); extern "C" SEXP _systemfonts_dev_string_metrics_c(SEXP string, SEXP family, SEXP face, SEXP size, SEXP cex, SEXP unit) { BEGIN_CPP11 return cpp11::as_sexp(dev_string_metrics_c(cpp11::as_cpp>(string), cpp11::as_cpp>(family), cpp11::as_cpp>(face), cpp11::as_cpp>(size), cpp11::as_cpp>(cex), cpp11::as_cpp>(unit))); END_CPP11 } // emoji.h void load_emoji_codes_c(cpp11::integers all, cpp11::integers default_text, cpp11::integers base_mod); extern "C" SEXP _systemfonts_load_emoji_codes_c(SEXP all, SEXP default_text, SEXP base_mod) { BEGIN_CPP11 load_emoji_codes_c(cpp11::as_cpp>(all), cpp11::as_cpp>(default_text), cpp11::as_cpp>(base_mod)); return R_NilValue; END_CPP11 } // emoji.h cpp11::list emoji_split_c(cpp11::strings string, cpp11::strings path, cpp11::integers index); extern "C" SEXP _systemfonts_emoji_split_c(SEXP string, SEXP path, SEXP index) { BEGIN_CPP11 return cpp11::as_sexp(emoji_split_c(cpp11::as_cpp>(string), cpp11::as_cpp>(path), cpp11::as_cpp>(index))); END_CPP11 } // font_fallback.h cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string); extern "C" SEXP _systemfonts_get_fallback_c(SEXP path, SEXP index, SEXP string) { BEGIN_CPP11 return cpp11::as_sexp(get_fallback_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(string))); END_CPP11 } // font_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 system_fonts_c(); extern "C" SEXP _systemfonts_system_fonts_c() { BEGIN_CPP11 return cpp11::as_sexp(system_fonts_c()); END_CPP11 } // font_matching.h void reset_font_cache_c(); extern "C" SEXP _systemfonts_reset_font_cache_c() { BEGIN_CPP11 reset_font_cache_c(); return R_NilValue; END_CPP11 } // font_metrics.h cpp11::writable::data_frame get_font_info_c(cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); extern "C" SEXP _systemfonts_get_font_info_c(SEXP path, SEXP index, SEXP size, SEXP res) { BEGIN_CPP11 return cpp11::as_sexp(get_font_info_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res))); END_CPP11 } // font_metrics.h cpp11::writable::data_frame get_glyph_info_c(cpp11::strings glyphs, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res); extern "C" SEXP _systemfonts_get_glyph_info_c(SEXP glyphs, SEXP path, SEXP index, SEXP size, SEXP res) { BEGIN_CPP11 return cpp11::as_sexp(get_glyph_info_c(cpp11::as_cpp>(glyphs), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res))); END_CPP11 } // font_registry.h void register_font_c(cpp11::strings family, cpp11::strings paths, cpp11::integers indices, cpp11::strings features, cpp11::integers settings); extern "C" SEXP _systemfonts_register_font_c(SEXP family, SEXP paths, SEXP indices, SEXP features, SEXP settings) { BEGIN_CPP11 register_font_c(cpp11::as_cpp>(family), cpp11::as_cpp>(paths), cpp11::as_cpp>(indices), cpp11::as_cpp>(features), cpp11::as_cpp>(settings)); return R_NilValue; END_CPP11 } // font_registry.h void clear_registry_c(); extern "C" SEXP _systemfonts_clear_registry_c() { BEGIN_CPP11 clear_registry_c(); return R_NilValue; END_CPP11 } // font_registry.h cpp11::writable::data_frame registry_fonts_c(); extern "C" SEXP _systemfonts_registry_fonts_c() { BEGIN_CPP11 return cpp11::as_sexp(registry_fonts_c()); END_CPP11 } // string_metrics.h cpp11::list get_string_shape_c(cpp11::strings string, cpp11::integers id, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::doubles lineheight, cpp11::integers align, cpp11::doubles hjust, cpp11::doubles vjust, cpp11::doubles width, cpp11::doubles tracking, cpp11::doubles indent, cpp11::doubles hanging, cpp11::doubles space_before, cpp11::doubles space_after); extern "C" SEXP _systemfonts_get_string_shape_c(SEXP string, SEXP id, SEXP path, SEXP index, SEXP size, SEXP res, SEXP lineheight, SEXP align, SEXP hjust, SEXP vjust, SEXP width, SEXP tracking, SEXP indent, SEXP hanging, SEXP space_before, SEXP space_after) { BEGIN_CPP11 return cpp11::as_sexp(get_string_shape_c(cpp11::as_cpp>(string), cpp11::as_cpp>(id), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(lineheight), cpp11::as_cpp>(align), cpp11::as_cpp>(hjust), cpp11::as_cpp>(vjust), cpp11::as_cpp>(width), cpp11::as_cpp>(tracking), cpp11::as_cpp>(indent), cpp11::as_cpp>(hanging), cpp11::as_cpp>(space_before), cpp11::as_cpp>(space_after))); END_CPP11 } // string_metrics.h cpp11::doubles get_line_width_c(cpp11::strings string, cpp11::strings path, cpp11::integers index, cpp11::doubles size, cpp11::doubles res, cpp11::logicals include_bearing); extern "C" SEXP _systemfonts_get_line_width_c(SEXP string, SEXP path, SEXP index, SEXP size, SEXP res, SEXP include_bearing) { BEGIN_CPP11 return cpp11::as_sexp(get_line_width_c(cpp11::as_cpp>(string), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(include_bearing))); END_CPP11 } extern "C" { static const R_CallMethodDef CallEntries[] = { {"_systemfonts_clear_registry_c", (DL_FUNC) &_systemfonts_clear_registry_c, 0}, {"_systemfonts_dev_string_metrics_c", (DL_FUNC) &_systemfonts_dev_string_metrics_c, 6}, {"_systemfonts_dev_string_widths_c", (DL_FUNC) &_systemfonts_dev_string_widths_c, 6}, {"_systemfonts_emoji_split_c", (DL_FUNC) &_systemfonts_emoji_split_c, 3}, {"_systemfonts_get_fallback_c", (DL_FUNC) &_systemfonts_get_fallback_c, 3}, {"_systemfonts_get_font_info_c", (DL_FUNC) &_systemfonts_get_font_info_c, 4}, {"_systemfonts_get_glyph_info_c", (DL_FUNC) &_systemfonts_get_glyph_info_c, 5}, {"_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_match_font_c", (DL_FUNC) &_systemfonts_match_font_c, 3}, {"_systemfonts_register_font_c", (DL_FUNC) &_systemfonts_register_font_c, 5}, {"_systemfonts_registry_fonts_c", (DL_FUNC) &_systemfonts_registry_fonts_c, 0}, {"_systemfonts_reset_font_cache_c", (DL_FUNC) &_systemfonts_reset_font_cache_c, 0}, {"_systemfonts_system_fonts_c", (DL_FUNC) &_systemfonts_system_fonts_c, 0}, {NULL, NULL, 0} }; } void export_cache_store(DllInfo* dll); void init_caches(DllInfo* dll); void export_emoji_detection(DllInfo* dll); void export_font_fallback(DllInfo* dll); void export_font_matching(DllInfo* dll); void export_font_metrics(DllInfo* dll); void export_string_metrics(DllInfo* dll); extern "C" attribute_visible void R_init_systemfonts(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); export_cache_store(dll); init_caches(dll); export_emoji_detection(dll); export_font_fallback(dll); export_font_matching(dll); export_font_metrics(dll); export_string_metrics(dll); R_forceSymbols(dll, TRUE); } systemfonts/src/font_fallback.cpp0000644000176200001440000000473114507500576016746 0ustar liggesusers#include #include #include "font_fallback.h" #include "FontDescriptor.h" #include "ft_cache.h" #include "caches.h" using namespace cpp11::literals; // these functions are implemented by the platform FontDescriptor *substituteFont(char *, char *); FontDescriptor *fallback_font(const char* file, int index, const char* string) { FreetypeCache& cache = get_font_cache(); if (!cache.load_font(file, index)) { return NULL; } std::string font_name = cache.cur_name(); std::vector writable_name(font_name.begin(), font_name.end()); writable_name.push_back('\0'); std::vector writable_string(string, string + std::strlen(string)); writable_string.push_back('\0'); return substituteFont(writable_name.data(), writable_string.data()); } cpp11::writable::data_frame get_fallback_c(cpp11::strings path, cpp11::integers index, cpp11::strings string) { bool one_path = path.size() == 1; const char* first_path = Rf_translateCharUTF8(path[0]); int first_index = index[0]; bool one_string = string.size() == 1; const char* first_string = Rf_translateCharUTF8(string[0]); int full_length = 1; if (!one_path) full_length = path.size(); else if (!one_string) full_length = string.size(); cpp11::writable::strings paths; paths.reserve(full_length); cpp11::writable::integers indices; indices.reserve(full_length); for (int i = 0; i < full_length; ++i) { FontDescriptor* fallback = fallback_font( one_path ? first_path : Rf_translateCharUTF8(path[i]), one_path ? first_index : index[i], one_string ? first_string : Rf_translateCharUTF8(string[i]) ); if (fallback == NULL) { paths.push_back(R_NaString); indices.push_back(R_NaInt); } else { paths.push_back(fallback->path); indices.push_back(fallback->index); } delete fallback; } return cpp11::writable::data_frame({ "path"_nm = paths, "index"_nm = indices }); } FontSettings request_fallback(const char *string, const char *path, int index) { FontDescriptor *fallback = fallback_font(path, index, string); FontSettings result = {}; if (fallback == NULL) { std::strncpy(result.file, path, PATH_MAX); result.index = index; } else { std::strncpy(result.file, fallback->path, PATH_MAX); result.index = fallback->index; } delete fallback; return result; } void export_font_fallback(DllInfo* dll) { R_RegisterCCallable("systemfonts", "get_fallback", (DL_FUNC)request_fallback); } systemfonts/src/Makevars.in0000644000176200001440000000072114510732006015536 0ustar liggesusersPKG_CPPFLAGS=@cflags@ PKG_OBJCXXFLAGS=@objcflags@ DARWIN_LIBS = -framework CoreText -framework Foundation DARWIN_OBJECTS = mac/FontManagerMac.o UNIX_OBJECTS = unix/FontManagerLinux.o PKG_LIBS = @libs@ $(@SYS@_LIBS) OBJECTS = caches.o cpp11.o dev_metrics.o font_matching.o font_registry.o \ ft_cache.o string_shape.o font_metrics.o font_fallback.o string_metrics.o \ emoji.o cache_store.o init.o $(@SYS@_OBJECTS) all: clean clean: rm -f $(SHLIB) $(OBJECTS) systemfonts/vignettes/0000755000176200001440000000000014510734620014662 5ustar liggesuserssystemfonts/vignettes/c_interface.Rmd0000644000176200001440000001720414507500576017603 0ustar liggesusers--- title: "systemfonts C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{systemfonts C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(systemfonts) ``` Most of the functionality in systemfonts is intended to be used from compiled code to help e.g. graphic devices to resolve font specifications to a font file prior to rendering. systemfonts provide key functionality to get called at the C level by putting systemfonts in the `LinkingTo` field in the description and adding `#include ` to your C code. Make sure systemfonts is loaded before using it, e.g. by having `match_font()` imported into your package namespace. The different functionality will be discussed below ## Font matching The C equivalent of the `match_font()` R function is `locate_font()` with the following signature: ```C int locate_font( const char *family, int italic, int bold, char *path, int max_path_length ) ``` It takes a UTF-8 encoded string with the font family name, an int setting both italic and bold styles along with a char pointer to be filled with the located path and the maximum length it can hold. The return value is an int giving the index of the font in the font file. With the advent of systemfonts 0.3.0 fonts can now also have OpenType features attached to them through the use of `register_font()` or `register_variant()`. If you wish to support such features you can use an alternative to the above: ```C FontSettings locate_font_with_features( const char *family, int italic, int bold ) ``` The returned `FontSettings` struct will contain both the font location and index along with any OpenType feature settings. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings` struct as the `features` array may be cleared at any time after the call has ended. systemfonts itself takes care of caching so this is not something you should be concerned with in your code. ```C struct FontFeature { char feature[4]; int setting; }; struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width ) ``` It takes the glyph to measure as an int giving the UTF code of the glyph, along with a fontfile and index to identify the font to measure with. Further it takes a size in pt and a resolution in ppi. It will write the ascent, descent, and width in pts to the pointers passed in, and return `0` if the operation was successful. ## String width The C equivalent of the `string_width()` R function is also called `string_width()` with the following signature: ```C string_width( const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width ) ``` This function calculates the width of a string, ignoring any newlines (these are automatically being handled by the graphic engine). It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the width in pts to the passed in pointer and return 0 if successful. ## String shape A parred down version of `shape_string()` is accessible at the C level with `string_shape()`. It behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. ```C string_shape( const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length ) ``` `string_shape()` behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the x and y location of each glyph in pts to the passed in arrays, stopping before the provided max_length and return 0 if successful. ## Retrieving cached freetype face A heavy part of text layouting is reading and parsing font files. systemfonts contains its own cache to make sure that parsing is kept at a minimum. If you want to use this cache to load and cache freetype face object (FT_Face) you can use `get_cached_face()`. This resides in a separate header (`systemfonts-ft.h`) because it requires FreeType to be linked in your package, which the rest of the C api does not. It will look in the cache for a face and size that matches your request and return that if found. If not, it will load it for you and add it to the cache, before returning it to you. `get_cached_face()` sets the passed in error pointer to 0 if successful. ```C get_cached_face( const char* fontfile, int index, double size, double res, int * error ) ``` Freetype uses reference counting to keep track of objects and the count is increased by a call to `get_cached_face()`. It is the responsiblity of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Font fallback When rendering text it is not given that all the requested characters have a glyph in the given font. While one can elect to render a "missing glyph" glyph (often either an empty square or a questionmark in a tilted square) a better approach is often to find a font substitute that does contain the character and use that for rendering it. This function allows you to find a fallback font for a given string and font. The string should be stripped of characters that you already know how to render. The fallback font is returned as a FontSettings object, though features are always empty. ```C FontSettings get_fallback( const char* string, const char* path, int index ) ``` ## Font Weight When encoding text with CSS it may be necessary to know the exact weight of the font given by a file so that it may be reflected in the style sheet. This function takes a path and an index and returns the weight (100-900 in steps of 100) or 0 if it is undefined by the font. ```C int get_font_weight( const char* path, int index ) ``` ## Family name It may be beneficial to know the family name from a given path and index into a font. This can be obtained with `get_font_family()` which will write the name to the provided `char*` argument. It will return 0 if it was somehow unsuccessful. ```C int get_font_family( const char* path, int index, char* family, int max_length ) ``` ## Emoji location Figuring out which character in a string should be treated as an emoji is non-trivial due to the existence of emojis with text representation default etc. systemfonts allow you to get the embedding of emojis in a string based on the correct rules. ```C void detect_emoji_embedding( const uint32_t* string, int n, int* embedding, const char *path, int index ) ``` systemfonts/R/0000755000176200001440000000000014507500576013062 5ustar liggesuserssystemfonts/R/sysdata.rda0000644000176200001440000001774314507500576015236 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_ 'normal'), 1) #' italic <- sample(which(fonts$italic & fonts$weight <= 'normal'), 1) #' bolditalic <- sample(which(fonts$italic & fonts$weight > 'normal'), 1) #' register_font( #' 'random', #' plain = list(fonts$path[plain], fonts$index[plain]), #' bold = list(fonts$path[bold], fonts$index[bold]), #' italic = list(fonts$path[italic], fonts$index[italic]), #' bolditalic = list(fonts$path[bolditalic], fonts$index[bolditalic]) #' ) #' #' # Look at your creation #' registry_fonts() #' #' # Reset #' clear_registry() #' register_font <- function(name, plain, bold = plain, italic = plain, bolditalic = plain, features = font_feature()) { if (name %in% system_fonts()$family) { stop("A system font with that family name already exists", call. = FALSE) } if (is.character(plain)) plain <- list(plain, 0) if (is.character(bold)) bold <- list(bold, 0) if (is.character(italic)) italic <- list(italic, 0) if (is.character(bolditalic)) bolditalic <- list(bolditalic, 0) files <- c(plain[[1]], bold[[1]], italic[[1]], bolditalic[[1]]) indices <- c(plain[[2]], bold[[2]], italic[[2]], bolditalic[[2]]) if (!all(file.exists(files))) { stop("reference to non-existing font file", call. = FALSE) } register_font_c(as.character(name), as.character(files), as.integer(indices), features[[1]], features[[2]]) } #' @rdname register_font #' @export registry_fonts <- function() { registry_fonts_c() } #' @rdname register_font #' @export clear_registry <- function() { clear_registry_c() } #' Register a font as a variant as an existing one #' #' This function is a wrapper around [register_font()] that allows you to easily #' create variants of existing system fonts, e.g. to target different weights #' and/or widths, or for attaching OpenType features to a font. #' #' @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_font("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 ) } systemfonts/R/dev_strings.R0000644000176200001440000000575514507500576015550 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/zzz.R0000644000176200001440000000124214507500576014041 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() } warn_env <- new.env(parent = emptyenv()) warn_env$warned <- FALSE #' Get location of the fallback font #' #' @export #' @keywords internal get_fallback <- function() { if (!warn_env$warned) { warning("No fonts detected on your system. Using an empty font.", call. = FALSE) warn_env$warned <- TRUE } list( system.file("unfont.ttf", package = "systemfonts"), 0L ) } systemfonts/R/emoji.R0000644000176200001440000000551614507500576014317 0ustar liggesusers#' Split a string into emoji and non-emoji glyph runs #' #' 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 `string` #' vector. #' #' @param string A character vector of strings that should be splitted. #' @inheritParams match_font #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return 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 `string` vector that the substring is part of} #' \item{emoji}{A logical vector giving if the substring is a run of emojis or not} #' } #' #' @export #' #' @examples #' emoji_string <- "This is a joke\U0001f642. It should be obvious from the smiley" #' str_split_emoji(emoji_string) #' str_split_emoji <- function(string, family = '', italic = FALSE, bold = FALSE, path = NULL, index = 0) { n_strings <- length(string) if (is.null(path)) { if (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) bold <- rep_len(bold, n_strings) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) } } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, n_strings) index <- rep_len(index, n_strings) } } if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) emoji_splitted <- emoji_split_c(as.character(string), path, index) groups <- diff(c(0, which(diff(emoji_splitted[[3]]) != 0), length(emoji_splitted[[3]]))) groups <- rep(seq_along(groups), groups) groups <- paste0(emoji_splitted[[2]], '_', groups) string <- vapply(split(emoji_splitted[[1]], groups), intToUtf8, character(1)) id <- vapply(split(emoji_splitted[[2]], groups), `[[`, integer(1), 1) emoji <- vapply(split(emoji_splitted[[3]], groups), `[[`, logical(1), 1) res <- data.frame(string = string, id = id + 1, emoji = emoji, stringsAsFactors = FALSE, row.names = NULL) class(res) <- c("tbl_df", "tbl", "data.frame") res } load_emoji_codes <- function() { load_emoji_codes_c(as.integer(all_emoji), as.integer(text_pres_emoji), as.integer(base_mod_emoji)) } systemfonts/R/system_fonts.R0000644000176200001440000000205414507500576015743 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_font()] 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/systemfonts-package.R0000644000176200001440000000037214507500576017176 0ustar liggesusers#' @keywords internal #' @useDynLib systemfonts, .registration = TRUE "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start ## usethis namespace: end NULL systemfonts/R/shape_string.R0000644000176200001440000002173714507500576015705 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 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, bold = FALSE, size = 12, res = 72, lineheight = 1, align = 'left', hjust = 0, vjust = 0, width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, index = 0) { 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 (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) bold <- rep_len(bold, n_strings) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE)[ido] index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE)[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(width) != 1) width <- rep_len(width, n_strings)[ido] width[is.na(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] width <- 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(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, bold = FALSE, size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0) { n_strings <- length(strings) if (is.null(path)) { if (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) bold <- rep_len(bold, n_strings) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) } } 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/font_feature.R0000644000176200001440000001442414507500576015673 0ustar liggesusers#' Define OpenType font feature settings #' #' This function encapsulates the specification of OpenType font features. Some #' specific features have named arguments, but all available features can be #' set by using its specific 4-letter tag For a list of the 4-letter tags #' available see e.g. the overview on #' [Wikipedia](https://en.wikipedia.org/wiki/List_of_typographic_features). #' #' @param ligatures Settings related to ligatures. One or more types of #' ligatures to turn on (see details). #' @param letters Settings related to the appearance of single #' letters (as opposed to ligatures that substitutes multiple letters). See #' details for supported values. #' @param numbers Settings related to the appearance of numbers. See details for #' supported values. #' @param ... key-value pairs with the key being the 4-letter tag and the value #' being the setting (usually `TRUE` to turn it on). #' #' @details #' OpenType features are defined by a 4-letter tag along with an integer value. #' Often that value is a simple `0` (off) or `1` (on), but some features support #' additional values, e.g. stylistic alternates (`salt`) where a font may #' provide multiple variants of a letter and the value will be used to chose #' which one to use. #' #' Common features related to appearance may be given with a long form name to #' either the `ligatures`, `letters`, or `numbers` argument to avoid remembering #' the often arbitrary 4-letter tag. Providing a long form name is the same as #' setting the tag to `1` and can thus not be used to set tags to other values. #' #' The possible long form names are given below with the tag in parenthesis: #' #' **Ligatures** #' - `standard` (*liga*): Turns on standard multiple letter substitution #' - `historical` (*hlig*): Use obsolete historical ligatures #' - `contextual` (*clig*): Apply secondary ligatures based on the character #' patterns surrounding the potential ligature #' - `discretionary` (*dlig*): Use ornamental ligatures #' #' **Letters** #' - `swash` (*cswh*): Use contextual swashes (ornamental decorations) #' - `alternates` (*calt*): Use alternate letter forms based on the sourrounding #' pattern #' - `historical` (*hist*): Use obsolete historical forms of the letters #' - `localized` (*locl*): Use alternate forms preferred by the script language #' - `randomize` (*rand*): Use random variants of the letters (e.g. to mimick #' handwriting) #' - `alt_annotation` (*nalt*): Use alternate annotations (e.g. circled digits) #' - `stylistic` (*salt*): Use a stylistic alternative form of the letter #' - `subscript` (*subs*): Set letter in subscript #' - `superscript` (*sups*): Set letter in superscript #' - `titling` (*titl*): Use letter forms well suited for large text and titles #' - `small_caps` (*smcp*): Use small caps variants of the letters #' #' **Numbers** #' - `lining` (*lnum*): Use number variants that rest on the baseline #' - `oldstyle` (*onum*): Use old style numbers that use descender and ascender #' for various numbers #' - `proportional` (*pnum*): Let numbers take up width based on the visual #' width of the glyph #' - `tabular` (*tnum*): Enforce all numbers to take up the same width #' - `fractions` (*frac*): Convert numbers separated by `/` into a fraction #' glyph #' - `fractions_alt` (*afrc*): Use alternate fraction form with a horizontal #' divider #' #' @return A `font_feature` object #' #' @export #' #' @examples #' font_feature(letters = "stylistic", numbers = c("lining", "tabular")) #' #' # Use the tag directly to access additional stylistic variants #' font_feature(numbers = c("lining", "tabular"), salt = 2) #' font_feature <- function(ligatures = NULL, letters = NULL, numbers = NULL , ...) { features <- list(...) if (!is.null(ligatures)) { for (lig in ligatures) { features[get_ligature_tag(lig)] <- 1L } } if (!is.null(letters)) { for (let in letters) { features[get_letter_tag(let)] <- 1L } } if (!is.null(numbers)) { for (num in numbers) { features[get_number_tag(num)] <- 1L } } if (length(features) == 0) { features <- list(character(), integer()) } else { feature_names <- names(features) if (any(vapply(features, length, integer(1)) != 1)) { stop("A feature setting can only be of length 1", call. = FALSE) } if (anyDuplicated(feature_names)) { stop("Features can only be given once", call. = FALSE) } if (any(nchar(feature_names) != 4)) { stop("Feature tags must be 4 character long", call. = FALSE) } feature_settings <- vapply(features, as.integer, integer(1)) features <- list(feature_names, feature_settings) } class(features) <- "font_feature" features } is_font_feature <- function(x) inherits(x, "font_feature") 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) } 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/cpp11.R0000644000176200001440000000410714507500576014133 0ustar liggesusers# Generated by cpp11: do not edit by hand dev_string_widths_c <- function(string, family, face, size, cex, unit) { .Call(`_systemfonts_dev_string_widths_c`, string, family, face, size, cex, unit) } dev_string_metrics_c <- function(string, family, face, size, cex, unit) { .Call(`_systemfonts_dev_string_metrics_c`, string, family, face, size, cex, unit) } load_emoji_codes_c <- function(all, default_text, base_mod) { invisible(.Call(`_systemfonts_load_emoji_codes_c`, all, default_text, base_mod)) } emoji_split_c <- function(string, path, index) { .Call(`_systemfonts_emoji_split_c`, string, path, index) } get_fallback_c <- function(path, index, string) { .Call(`_systemfonts_get_fallback_c`, path, index, string) } match_font_c <- function(family, italic, bold) { .Call(`_systemfonts_match_font_c`, family, italic, bold) } system_fonts_c <- function() { .Call(`_systemfonts_system_fonts_c`) } reset_font_cache_c <- function() { invisible(.Call(`_systemfonts_reset_font_cache_c`)) } get_font_info_c <- function(path, index, size, res) { .Call(`_systemfonts_get_font_info_c`, path, index, size, res) } get_glyph_info_c <- function(glyphs, path, index, size, res) { .Call(`_systemfonts_get_glyph_info_c`, glyphs, path, index, size, res) } register_font_c <- function(family, paths, indices, features, settings) { invisible(.Call(`_systemfonts_register_font_c`, family, paths, indices, features, settings)) } clear_registry_c <- function() { invisible(.Call(`_systemfonts_clear_registry_c`)) } registry_fonts_c <- function() { .Call(`_systemfonts_registry_fonts_c`) } get_string_shape_c <- function(string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) { .Call(`_systemfonts_get_string_shape_c`, string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) } get_line_width_c <- function(string, path, index, size, res, include_bearing) { .Call(`_systemfonts_get_line_width_c`, string, path, index, size, res, include_bearing) } systemfonts/R/font_fallback.R0000644000176200001440000000424014507500576015772 0ustar liggesusers#' Get the fallback font for a given string #' #' A fallback font is a font to use as a substitute if the chosen font does not #' contain the requested characters. Using font fallbacks means that the user #' doesn't have to worry about mixing characters from different scripts or #' mixing text and emojies. Fallback is calculated for the full string and the #' result is platform specific. If no font covers all the characters in the #' string an undefined "best match" is returned. The best approach is to figure #' out which characters are not covered by your chosen font and figure out #' fallbacks for these, rather than just request a fallback for the full string. #' #' @param string The strings to find fallbacks for #' @inheritParams font_info #' #' @return A data frame with a `path` and `index` column giving fallback for the #' specified string and font combinations #' #' @export #' #' @examples #' font_fallback("\U0001f604") # Smile emoji #' font_fallback <- function(string, family = '', italic = FALSE, bold = FALSE, path = NULL, index = 0) { full_length <- length(string) if (is.null(path)) { full_length <- max(length(family), length(italic), length(bold), full_length) if (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, full_length) italic <- rep_len(italic, full_length) bold <- rep_len(bold, full_length) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) } } else { full_length <- max(length(path), length(index), full_length) if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, full_length) index <- rep_len(index, full_length) } } if (length(string) != 1) string <- rep_len(string, full_length) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_fallback_c(path, as.integer(index), as.character(string)) } systemfonts/R/font_info.R0000644000176200001440000001432114507500576015167 0ustar liggesusers#' Query font-specific information #' #' Get general information about a font, relative to a given size. Size specific #' measures will be returned in pixel units. The function is vectorised to the #' length of the longest argument. #' #' @inheritParams match_font #' @param size The pointsize of the font to use for size related measures #' @param res The ppi of the size related mesures #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return #' A data.frame giving info on the requested font + size combinations. The #' data.frame will contain the following columns: #' #' \describe{ #' \item{path}{The path to the font file} #' \item{index}{The 0-based index of the font in the fontfile} #' \item{family}{The family name of the font} #' \item{style}{The style name of the font} #' \item{italic}{A logical giving if the font is italic} #' \item{bold}{A logical giving if the font is bold} #' \item{monospace}{A logical giving if the font is monospace} #' \item{weight}{A factor giving the weight of the font} #' \item{width}{A factor giving the width of the font} #' \item{kerning}{A logical giving if the font supports kerning} #' \item{color}{A logical giving if the font has color glyphs} #' \item{scalable}{A logical giving if the font is scalable} #' \item{vertical}{A logical giving if the font is vertical} #' \item{n_glyphs}{The number of glyphs in the font} #' \item{n_sizes}{The number of predefined sizes in the font} #' \item{n_charmaps}{The number of character mappings in the font file} #' \item{bbox}{A bounding box large enough to contain any of the glyphs in the font} #' \item{max_ascend}{The maximum ascend of the tallest glyph in the font} #' \item{max_descent}{The maximum descend of the most descending glyph in the font} #' \item{max_advance_width}{The maximum horizontal advance a glyph can make} #' \item{max_advance_height}{The maximum vertical advance a glyph can make} #' \item{lineheight}{The height of a single line of text in the font} #' \item{underline_pos}{The position of a potential underlining segment} #' \item{underline_size}{The width the the underline} #' } #' #' @export #' #' @examples #' font_info('serif') #' #' # Avoid lookup if font file is already known #' sans <- match_font('sans') #' font_info(path = sans$path, index = sans$index) #' font_info <- function(family = '', italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0) { full_length <- max(length(size), length(res)) if (is.null(path)) { full_length <- max(length(family), length(italic), length(bold), full_length) if (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, full_length) italic <- rep_len(italic, full_length) bold <- rep_len(bold, full_length) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) } } else { full_length <- max(length(path), length(index), full_length) if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, full_length) index <- rep_len(index, full_length) } } if (length(size) != 1) size <- rep_len(size, full_length) if (length(res) != 1) res <- rep_len(res, full_length) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_font_info_c(path, as.integer(index), as.numeric(size), as.numeric(res)) } #' Query glyph-specific information from fonts #' #' This function allows you to extract information about the individual glyphs #' in a font, based on a specified size. All size related measures are in #' pixel-units. The function is vectorised to the length of the `glyphs` vector. #' #' @param glyphs A vector of glyphs. Strings will be split into separate glyphs #' automatically #' @inheritParams font_info #' @param path,index path an index of a font file to circumvent lookup based on #' family and style #' #' @return #' A data.frame with information about each glyph, containing the following #' columns: #' #' \describe{ #' \item{glyph}{The glyph as a character} #' \item{index}{The index of the glyph in the font file} #' \item{width}{The width of the glyph} #' \item{height}{The height of the glyph} #' \item{x_bearing}{The horizontal distance from the origin to the leftmost part of the glyph} #' \item{y_bearing}{The vertical distance from the origin to the top part of the glyph} #' \item{x_advance}{The horizontal distance to move the cursor after adding the glyph} #' \item{y_advance}{The vertical distance to move the cursor after adding the glyph} #' \item{bbox}{The tight bounding box surrounding the glyph} #' } #' #' @export glyph_info <- function(glyphs, family = '', italic = FALSE, bold = FALSE, size = 12, res = 72, path = NULL, index = 0) { n_strings <- length(glyphs) glyphs <- strsplit(glyphs, '') n_glyphs <- lengths(glyphs) glyphs <- unlist(glyphs) if (is.null(path)) { if (all(c(length(family), length(italic), length(bold)) == 1)) { loc <- match_font(family, italic, bold) path <- loc$path index <- loc$index } else { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) bold <- rep_len(bold, n_strings) loc <- Map(match_font, family = family, italic = italic, bold = bold) path <- rep(vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE), n_glyphs) index <- rep(vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE), n_glyphs) } } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep(rep_len(path, n_strings), n_glyphs) index <- rep(rep_len(index, n_strings), n_glyphs) } } if (length(size) != 1) size <- rep(rep_len(size, n_strings), n_glyphs) if (length(res) != 1) res <- rep(rep_len(res, n_strings), n_glyphs) if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) get_glyph_info_c(glyphs, path, as.integer(index), as.numeric(size), as.numeric(res)) } systemfonts/R/match_font.R0000644000176200001440000000201514507500576015325 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 (italic/bold). A font file will be returned even if a 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"`, and `"mono"` match to the system default sans-serif, serif, and #' mono fonts respectively (`""` is equivalent to `"sans"`). #' #' #' @param family The name of the font family #' @param italic,bold logicals indicating the font style #' #' @return A list containing the path locating the font file and the 0-based #' index of the font in the file. #' #' @export #' #' @examples #' # Get the system default sans-serif font in italic #' match_font('sans', italic = TRUE) #' match_font <- function(family, italic = FALSE, bold = FALSE) { if (!is.character(family)) stop("family must be a string", call. = FALSE) match_font_c(family, as.logical(italic), as.logical(bold)) } systemfonts/NEWS.md0000644000176200001440000000667114510732006013756 0ustar liggesusers# 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/MD50000644000176200001440000001037714511003551013163 0ustar liggesusersaf5e554b192257af290e8ddf8bbd82b9 *DESCRIPTION c31ee18d335f7158ed985f5939319c1f *LICENSE a80dfddcb725c83d6fa1d3923d5f04a6 *NAMESPACE b79d5c9a073af8b17aca081cea60ccfe *NEWS.md 7d0092b74385ec0ffe07694f41d7a805 *R/cpp11.R 74786ff5d1f5dde3454d17d8a2de1ccb *R/dev_strings.R 66d9e75c427bf2b9d93f6bb0d98699d3 *R/emoji.R 5053fb4f83f7e4f94463dea449be7450 *R/font_fallback.R 8cf375361de8bedfaf4955e00ef310a9 *R/font_feature.R f08dbe6e31cb20ddff76802233549360 *R/font_info.R 6bed66bb46a33b842a95f6b7215b83cb *R/match_font.R 41637e675237fcbe35f0651859e570bd *R/register_font.R d7f3fc65c934ef1e8f790c7c3b2a60af *R/shape_string.R 450a8e74d61467c86628109be3551119 *R/sysdata.rda 165dfbd7c92ee330d2db50741351cbd8 *R/system_fonts.R c37f3cd43d3c984f5296a4e7d4805eb0 *R/systemfonts-package.R 570a35188f80282e2dcf272d7e6cc985 *R/zzz.R 0f4c77b50576fe87188e4315a5310260 *README.md 98ad2a6b6dc45aca832e6eb45f5d8701 *build/vignette.rds bc7baead3b6080f04dc81efd2fe1ff39 *cleanup 52b84dc885a7ea17a0fd068324d775a5 *configure cfe5eded1693d8ac61eb889b0f172855 *inst/doc/c_interface.R ff709403f071158d8ee8d30650dc5057 *inst/doc/c_interface.Rmd 8f3f637ba6b3e8d6cc108694344d1fe4 *inst/doc/c_interface.html e3514da4b456dfdddebe8f5489c01b62 *inst/include/systemfonts-ft.h 5924df9b947994952fa01dc979602f8f *inst/include/systemfonts.h a11ff11ac7db5df57660f50d65378bf2 *inst/unfont.ttf 4bfc3f4ad94e379a65f36a1cd9364381 *man/font_fallback.Rd 15ed44c9c419c96ae22dc32a09dc5b98 *man/font_feature.Rd 12c76979aff6f0bd13467a94ea3eaa03 *man/font_info.Rd c4332e3048542cbeda8a4a7f440fc124 *man/get_fallback.Rd a841e521cec7d4a455c97c7ee1564226 *man/glyph_info.Rd 75e018c1a3b0e79a2c562e411be05f72 *man/match_font.Rd 34693ca3cfabeae8649fe38bb6dbbc5c *man/register_font.Rd 992a55bdfcaeb7d05de814efe0f104b5 *man/register_variant.Rd ca64aa1bae2aa845146067a60d7af460 *man/reset_font_cache.Rd bcdb383fbedf598c6a0bc9060185d30b *man/shape_string.Rd 28140ce0092af1bba0a0cd5f8c3c0a50 *man/str_split_emoji.Rd 74916bb9316fb3b3ef6f9f9b6358dba4 *man/string_metrics_dev.Rd 0eecd59cfa105df9a1bd479cdda2e523 *man/string_width.Rd c72ad2a644aa183945b66164aee58ec6 *man/string_widths_dev.Rd 8c3dc7bc1e63c3195611e49364bea5da *man/system_fonts.Rd ca953f6fababbad60507592443b6b011 *man/systemfonts-package.Rd 7236e24bab89c4c730c18cc6f2b3416a *src/FontDescriptor.h bd0ae833d646b1fb1f5b4935cc0e1525 *src/Makevars.in 12de8e8c85bd56c7e339385494095874 *src/Makevars.win f21f62b8a7e300c356c8ac326552f0c2 *src/cache_lru.h a2c2da6400632a7bc5fdfd14d73b6a8c *src/cache_store.cpp 69b85b17e10134f34f962d12c6bb9f5e *src/cache_store.h b9b716df5de987ffda3523f5254f887f *src/caches.cpp 370ae88cc338f925b3e8ec399c0f0b65 *src/caches.h d1a4598c05a7ef718043b968c3d53f3c *src/cpp11.cpp 8e340c25049f84658747dc1ec0f0fd18 *src/dev_metrics.cpp 689e955be1d6b4baf0c9a0811ae3b9aa *src/dev_metrics.h 04b170936547e799a6875fda13577cb5 *src/emoji.cpp a82f523519ac1dd5809e8e5bdd6505d2 *src/emoji.h 54a478790815a2f59d9a76372547f18b *src/font_fallback.cpp 94411cf3eafd1c8ec3c59dcd24053197 *src/font_fallback.h c50816e9be1f20c6d1c9ec926b19ed4f *src/font_matching.cpp 829d67d351344d915b844f6fc0dc11e8 *src/font_matching.h 432e0b3844da87427f41a60176d91ad6 *src/font_metrics.cpp f21e79dd4b048af55cae1ff00e42ad68 *src/font_metrics.h b4fdb641afd2d854a6266e283c4ce030 *src/font_registry.cpp 8b56ef67884fa3b41f6205d30e8afcdd *src/font_registry.h b968d5ab865a471b4637d2008d02986b *src/ft_cache.cpp 77d890efe3c5705fd72104c0e89dd0bf *src/ft_cache.h cf5c2dd2dcccb5d26dcbe464e60136d9 *src/init.cpp a14e85b8afdaf681b19ac4fc2ec026c7 *src/mac/FontManagerMac.mm b3bedef5c2657632a2d7c4c0e6be898c *src/string_metrics.cpp e2a636534334f3ce1bf42187f2055476 *src/string_metrics.h 97c13bfe96a94b47d053f72516c0b2c1 *src/string_shape.cpp 816f23f7d26dd1ffec58794d662519bc *src/string_shape.h 2eeab25b421d60d1b5d4963865ff93a5 *src/types.h 87b7ab32eee2ecfe77a8eca1433d6e07 *src/unix/FontManagerLinux.cpp d6de2d619c1a22a5fc700fe05d32c6b3 *src/utils.h 882d23bb7c881e87eb82a0de7952d0da *src/win/DirectWriteFontManagerWindows.cpp 985d9d7ea74875a87ca2d329e23aca90 *src/win/FontManagerWindows.cpp 2b7eaaf1f6572e72dde8052c8c14412a *tests/testthat.R 67874f8c9e8d68433167033af2a26c34 *tests/testthat/test-match_font.R 928495dc1e966e04e0c4fc7c89e56056 *tests/testthat/test-system_fonts.R 4c4ae954ec118afe315b00b91f278391 *tools/winlibs.R ff709403f071158d8ee8d30650dc5057 *vignettes/c_interface.Rmd systemfonts/inst/0000755000176200001440000000000014510734620013627 5ustar liggesuserssystemfonts/inst/doc/0000755000176200001440000000000014510734620014374 5ustar liggesuserssystemfonts/inst/doc/c_interface.R0000644000176200001440000000036614510734620016766 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ----setup-------------------------------------------------------------------- library(systemfonts) systemfonts/inst/doc/c_interface.html0000644000176200001440000006464414510734620017542 0ustar liggesusers systemfonts C interface

systemfonts C interface

library(systemfonts)

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

Font matching

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

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

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

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

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

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

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

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

Glyph metrics

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

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

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

String width

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

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

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

String shape

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

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

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

Retrieving cached freetype face

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

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

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

Font fallback

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

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

Font Weight

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

int get_font_weight(
  const char* path,
  int index
)

Family name

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

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

Emoji location

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

void detect_emoji_embedding(
  const uint32_t* string, 
  int n, 
  int* embedding, 
  const char *path, 
  int index
)
systemfonts/inst/doc/c_interface.Rmd0000644000176200001440000001720414507500576017315 0ustar liggesusers--- title: "systemfonts C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{systemfonts C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(systemfonts) ``` Most of the functionality in systemfonts is intended to be used from compiled code to help e.g. graphic devices to resolve font specifications to a font file prior to rendering. systemfonts provide key functionality to get called at the C level by putting systemfonts in the `LinkingTo` field in the description and adding `#include ` to your C code. Make sure systemfonts is loaded before using it, e.g. by having `match_font()` imported into your package namespace. The different functionality will be discussed below ## Font matching The C equivalent of the `match_font()` R function is `locate_font()` with the following signature: ```C int locate_font( const char *family, int italic, int bold, char *path, int max_path_length ) ``` It takes a UTF-8 encoded string with the font family name, an int setting both italic and bold styles along with a char pointer to be filled with the located path and the maximum length it can hold. The return value is an int giving the index of the font in the font file. With the advent of systemfonts 0.3.0 fonts can now also have OpenType features attached to them through the use of `register_font()` or `register_variant()`. If you wish to support such features you can use an alternative to the above: ```C FontSettings locate_font_with_features( const char *family, int italic, int bold ) ``` The returned `FontSettings` struct will contain both the font location and index along with any OpenType feature settings. The struct (along with its `FontFeature` struct dependency) is shown below and is pretty self-documenting. Do not cache the `FontSettings` struct as the `features` array may be cleared at any time after the call has ended. systemfonts itself takes care of caching so this is not something you should be concerned with in your code. ```C struct FontFeature { char feature[4]; int setting; }; struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; ``` ## Glyph metrics The C equivalent of `glyph_info()` is `glyph_metrics()` with the following signature: ```C int glyph_metrics( uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width ) ``` It takes the glyph to measure as an int giving the UTF code of the glyph, along with a fontfile and index to identify the font to measure with. Further it takes a size in pt and a resolution in ppi. It will write the ascent, descent, and width in pts to the pointers passed in, and return `0` if the operation was successful. ## String width The C equivalent of the `string_width()` R function is also called `string_width()` with the following signature: ```C string_width( const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width ) ``` This function calculates the width of a string, ignoring any newlines (these are automatically being handled by the graphic engine). It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the width in pts to the passed in pointer and return 0 if successful. ## String shape A parred down version of `shape_string()` is accessible at the C level with `string_shape()`. It behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. ```C string_shape( const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length ) ``` `string_shape()` behaves more or less like `string_width()` above, but instead returns the location to write each glyph at relative to a (0, 0) origin. It takes a UTF-8 encoded string, along with a fontfile and index identifying the font to use for the calculation. It also take a size in pt and a res in ppi for setting the size. In addition it takes an include_bearing flag to control whether the bearings of the first and last character should be taken into account (this is recommended by the graphic engine). It will write the x and y location of each glyph in pts to the passed in arrays, stopping before the provided max_length and return 0 if successful. ## Retrieving cached freetype face A heavy part of text layouting is reading and parsing font files. systemfonts contains its own cache to make sure that parsing is kept at a minimum. If you want to use this cache to load and cache freetype face object (FT_Face) you can use `get_cached_face()`. This resides in a separate header (`systemfonts-ft.h`) because it requires FreeType to be linked in your package, which the rest of the C api does not. It will look in the cache for a face and size that matches your request and return that if found. If not, it will load it for you and add it to the cache, before returning it to you. `get_cached_face()` sets the passed in error pointer to 0 if successful. ```C get_cached_face( const char* fontfile, int index, double size, double res, int * error ) ``` Freetype uses reference counting to keep track of objects and the count is increased by a call to `get_cached_face()`. It is the responsiblity of the caller to decrease it once the face is no longer needed using `FT_Done_Face()`. ## Font fallback When rendering text it is not given that all the requested characters have a glyph in the given font. While one can elect to render a "missing glyph" glyph (often either an empty square or a questionmark in a tilted square) a better approach is often to find a font substitute that does contain the character and use that for rendering it. This function allows you to find a fallback font for a given string and font. The string should be stripped of characters that you already know how to render. The fallback font is returned as a FontSettings object, though features are always empty. ```C FontSettings get_fallback( const char* string, const char* path, int index ) ``` ## Font Weight When encoding text with CSS it may be necessary to know the exact weight of the font given by a file so that it may be reflected in the style sheet. This function takes a path and an index and returns the weight (100-900 in steps of 100) or 0 if it is undefined by the font. ```C int get_font_weight( const char* path, int index ) ``` ## Family name It may be beneficial to know the family name from a given path and index into a font. This can be obtained with `get_font_family()` which will write the name to the provided `char*` argument. It will return 0 if it was somehow unsuccessful. ```C int get_font_family( const char* path, int index, char* family, int max_length ) ``` ## Emoji location Figuring out which character in a string should be treated as an emoji is non-trivial due to the existence of emojis with text representation default etc. systemfonts allow you to get the embedding of emojis in a string based on the correct rules. ```C void detect_emoji_embedding( const uint32_t* string, int n, int* embedding, const char *path, int index ) ``` systemfonts/inst/include/0000755000176200001440000000000014507500576015261 5ustar liggesuserssystemfonts/inst/include/systemfonts-ft.h0000644000176200001440000000147714507500576020450 0ustar liggesusers#ifndef SYSTEMFONTS_FT_H #define SYSTEMFONTS_FT_H #define R_NO_REMAP #include #include FT_FREETYPE_H #include #include // Retrieve an FT_Face from the cache and assigns it to the face pointer. The // retrieved face should be destroyed with FT_Done_Face once no longer needed. // Returns 0 if successful. static inline FT_Face get_cached_face(const char* fontfile, int index, double size, double res, int* error) { static FT_Face (*p_get_cached_face)(const char*, int, double, double, int*) = NULL; if (p_get_cached_face == NULL) { p_get_cached_face = (FT_Face (*)(const char*, int, double, double, int*)) R_GetCCallable("systemfonts", "get_cached_face"); } return p_get_cached_face(fontfile, index, size, res, error); } #endif systemfonts/inst/include/systemfonts.h0000644000176200001440000001316314507500576020034 0ustar liggesusers#pragma once #define R_NO_REMAP #include #include #include #include #include struct FontFeature { char feature[4]; int setting; }; typedef struct FontFeature FontFeature; // A structure to pass around a single font with features (used by the C interface) struct FontSettings { char file[PATH_MAX + 1]; unsigned int index; const FontFeature* features; int n_features; }; typedef struct FontSettings FontSettings; // Get the file and index of a font given by its name, along with italic and // bold status. Writes filepath to `path` and returns the index static inline int locate_font(const char *family, int italic, int bold, char *path, int max_path_length) { static int (*p_locate_font)(const char*, int, int, char*, int) = NULL; if (p_locate_font == NULL) { p_locate_font = (int (*)(const char *, int, int, char *, int)) R_GetCCallable("systemfonts", "locate_font"); } return p_locate_font(family, italic, bold, path, max_path_length); } // Get the file and index of a font along with possible registered OpenType // features, returned as a FontSettings object. static inline FontSettings locate_font_with_features(const char *family, int italic, int bold) { static FontSettings (*p_locate_font_with_features)(const char*, int, int) = NULL; if (p_locate_font_with_features == NULL) { p_locate_font_with_features = (FontSettings (*)(const char *, int, int)) R_GetCCallable("systemfonts", "locate_font_with_features"); } return p_locate_font_with_features(family, italic, bold); } // Get ascent, descent, and width of a glyph, given by its unicode number, // fontfile and index, along with its size and the resolution. Returns 0 if // successful static inline int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, double res, double* ascent, double* descent, double* width) { static int (*p_glyph_metrics)(uint32_t, const char*, int, double, double, double*, double*, double*) = NULL; if (p_glyph_metrics == NULL) { p_glyph_metrics = (int (*)(uint32_t, const char*, int, double, double, double*, double*, double*)) R_GetCCallable("systemfonts", "glyph_metrics"); } return p_glyph_metrics(code, fontfile, index, size, res, ascent, descent, width); } // Calculate the width of a string based on a fontfile, index, size, and // resolution. Writes it to width, and returns 0 if successful static inline int string_width(const char* string, const char* fontfile, int index, double size, double res, int include_bearing, double* width) { static int (*p_string_width)(const char*, const char*, int, double, double, int, double*) = NULL; if (p_string_width == NULL) { p_string_width = (int (*)(const char*, const char*, int, double, double, int, double*)) R_GetCCallable("systemfonts", "string_width"); } return p_string_width(string, fontfile, index, size, res, include_bearing, width); } // Calculate glyph positions for a string based on a fontfile, index, size, and // resolution, and writes it to the x and y arrays. Returns 0 if successful. static inline int string_shape(const char* string, const char* fontfile, int index, double size, double res, double* x, double* y, unsigned int max_length) { static int (*p_string_shape)(const char*, const char*, int, double, double, double*, double*, unsigned int) = NULL; if (p_string_shape == NULL) { p_string_shape = (int (*)(const char*, const char*, int, double, double, double*, double*, unsigned int)) R_GetCCallable("systemfonts", "string_shape"); } return p_string_shape(string, fontfile, index, size, res, x, y, max_length); } // Get the file and index of a fallback font for the given string based on the // given font and index static inline FontSettings get_fallback(const char *string, const char *path, int index) { static FontSettings (*p_get_fallback)(const char*, const char*, int) = NULL; if (p_get_fallback == NULL) { p_get_fallback = (FontSettings (*)(const char*, const char*, int)) R_GetCCallable("systemfonts", "get_fallback"); } return p_get_fallback(string, path, index); } // Get the weight of the font as encoded in the OTT/2 table static inline int get_font_weight(const char *path, int index) { static int (*p_get_weight)(const char*, int) = NULL; if (p_get_weight == NULL) { p_get_weight = (int (*)(const char*, int)) R_GetCCallable("systemfonts", "font_weight"); } return p_get_weight(path, index); } // Get the family name of the font as encoded in the font file. The name is // written to the family argument, not exceeding `max_length` static inline int get_font_family(const char *path, int index, char* family, int max_length) { static int (*p_get_family)(const char*, int, char*, int) = NULL; if (p_get_family == NULL) { p_get_family = (int (*)(const char*, int, char*, int)) R_GetCCallable("systemfonts", "font_family"); } return p_get_family(path, index, family, max_length); } // Get the location of emojis written to the embedding array. A 0 indicate that // the codepoint is not to be treated as emoji, a 1 indicate that it should, static inline void detect_emoji_embedding(const uint32_t* string, int n, int* embedding, const char *path, int index) { static void (*p_detect_emoji_embedding)(const uint32_t*, int, int*, const char*, int) = NULL; if (p_detect_emoji_embedding == NULL) { p_detect_emoji_embedding = (void (*)(const uint32_t*, int, int*, const char*, int)) R_GetCCallable("systemfonts", "detect_emoji_embedding"); } p_detect_emoji_embedding(string, n, embedding, path, index); } systemfonts/inst/unfont.ttf0000644000176200001440000000310414507500576015664 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/cleanup0000755000176200001440000000005314510734621014226 0ustar liggesusers#!/bin/sh rm -f src/Makevars configure.log systemfonts/configure0000755000176200001440000000722014510734621014563 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