textshaping/0000755000176200001440000000000014511003546012607 5ustar liggesuserstextshaping/NAMESPACE0000644000176200001440000000034313737663254014047 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(get_font_features) export(shape_text) export(text_width) importFrom(systemfonts,match_font) importFrom(systemfonts,system_fonts) useDynLib(textshaping, .registration = TRUE) textshaping/LICENSE0000644000176200001440000000006113712577574013634 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: Thomas Lin Pedersen textshaping/tools/0000755000176200001440000000000014510736537013763 5ustar liggesuserstextshaping/tools/winlibs.R0000644000176200001440000000162014510736537015554 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') } textshaping/README.md0000644000176200001440000000345014127537604014102 0ustar liggesusers # textshaping [![CRAN status](https://www.r-pkg.org/badges/version/textshaping)](https://CRAN.R-project.org/package=textshaping) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![R-CMD-check](https://github.com/r-lib/textshaping/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/textshaping/actions) This package is a low level package that provides advanced text shaping capabilities to graphic devices. It is based on the [FriBidi](https://github.com/fribidi/fribidi) and [HarfBuzz](https://harfbuzz.github.io) libraries and provides full support for correctly shaping left-to-right, right-to-left, and bidirectional text with full support for all OpenType features such as ligatures, stylistic glyph substitutions, etc. For an example of a package that uses textshaping to support advanced text layout see [ragg](https://ragg.r-lib.org). A big thanks to [Behdad Esfahbod](http://behdad.org) who is the main author of both FriBidi and HarfBuzz and has been very helpful answering questions during the course of development. ## Installation You can install textshaping from CRAN with `install.packages("textshaping")`. For the development version you can install it from Github with devtools: ``` r devtools::install_github("r-lib/textshaping") ``` Note that you will need both the development versions of FriBidi and HarfBuzz to successfully compile it. ## Code of Conduct Please note that the textshaping project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. textshaping/man/0000755000176200001440000000000014147726476013405 5ustar liggesuserstextshaping/man/shape_text.Rd0000644000176200001440000001031214131537263016020 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_text.R \name{shape_text} \alias{shape_text} \title{Calculate glyph positions for strings} \usage{ shape_text( 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{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} 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_text(string) # Mix styles within the same string string <- c( "This string will have\na ", "very large", " text style\nin the middle" ) shape_text(string, id = c(1, 1, 1), size = c(12, 24, 12)) } textshaping/man/textshaping-package.Rd0000644000176200001440000000172414510740325017606 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/textshaping-package.R \docType{package} \name{textshaping-package} \alias{textshaping} \alias{textshaping-package} \title{textshaping: Bindings to the 'HarfBuzz' and 'Fribidi' Libraries for Text Shaping} \description{ Provides access to the text shaping functionality in the 'HarfBuzz' library and the bidirectional algorithm in the 'Fribidi' library. 'textshaping' is a low-level utility package mainly for graphic devices that expands upon the font tool-set provided by the 'systemfonts' package. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/r-lib/textshaping} \item Report bugs at \url{https://github.com/r-lib/textshaping/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomas.pedersen@posit.co} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) Other contributors: \itemize{ \item Posit, PBC [copyright holder, funder] } } \keyword{internal} textshaping/man/get_font_features.Rd0000644000176200001440000000213514510740325017357 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/font_features.R \name{get_font_features} \alias{get_font_features} \title{Get available OpenType features in a font} \usage{ get_font_features( family = "", italic = FALSE, bold = FALSE, 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{path, index}{path an index of a font file to circumvent lookup based on family and style} } \value{ A list with an element for each of the input fonts containing the supported feature tags for that font. } \description{ This is a simply functions that returns the available OpenType feature tags for one or more fonts. See \code{\link[systemfonts:font_feature]{font_feature()}} for more information on how to use the different feature with a font. } \examples{ # Select a random font on the system sys_fonts <- systemfonts::system_fonts() random_font <- sys_fonts$family[sample(nrow(sys_fonts), 1)] # Get the features get_font_features(random_font) } textshaping/man/text_width.Rd0000644000176200001440000000264714510740325016047 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_text.R \name{text_width} \alias{text_width} \title{Calculate the width of a string, ignoring new-lines} \usage{ text_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, 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') text_width(strings) } textshaping/man/figures/0000755000176200001440000000000014147725447015046 5ustar liggesuserstextshaping/man/figures/lifecycle-defunct.svg0000644000176200001440000000170414006512112021127 0ustar liggesuserslifecyclelifecycledefunctdefunct textshaping/man/figures/lifecycle-maturing.svg0000644000176200001440000000170614006512112021327 0ustar liggesuserslifecyclelifecyclematuringmaturing textshaping/man/figures/lifecycle-archived.svg0000644000176200001440000000170714006512112021267 0ustar liggesusers lifecyclelifecyclearchivedarchived textshaping/man/figures/lifecycle-questioning.svg0000644000176200001440000000171414006512112022045 0ustar liggesuserslifecyclelifecyclequestioningquestioning textshaping/man/figures/lifecycle-superseded.svg0000644000176200001440000000171314006512112021642 0ustar liggesusers lifecyclelifecyclesupersededsuperseded textshaping/man/figures/lifecycle-stable.svg0000644000176200001440000000167414006512112020757 0ustar liggesuserslifecyclelifecyclestablestable textshaping/man/figures/lifecycle-experimental.svg0000644000176200001440000000171614006512112022177 0ustar liggesuserslifecyclelifecycleexperimentalexperimental textshaping/man/figures/lifecycle-deprecated.svg0000644000176200001440000000171214006512112021576 0ustar liggesuserslifecyclelifecycledeprecateddeprecated textshaping/DESCRIPTION0000644000176200001440000000243414511003546014320 0ustar liggesusersPackage: textshaping Title: Bindings to the 'HarfBuzz' and 'Fribidi' Libraries for Text Shaping Version: 0.3.7 Authors@R: c( person("Thomas Lin", "Pedersen", , "thomas.pedersen@posit.co", role = c("cre", "aut"), comment = c(ORCID = "0000-0002-5147-4711")), person("Posit, PBC", role = c("cph", "fnd")) ) Description: Provides access to the text shaping functionality in the 'HarfBuzz' library and the bidirectional algorithm in the 'Fribidi' library. 'textshaping' is a low-level utility package mainly for graphic devices that expands upon the font tool-set provided by the 'systemfonts' package. License: MIT + file LICENSE URL: https://github.com/r-lib/textshaping BugReports: https://github.com/r-lib/textshaping/issues Depends: R (>= 3.2.0) Imports: systemfonts (>= 1.0.0) Suggests: covr, knitr, rmarkdown LinkingTo: cpp11 (>= 0.2.1), systemfonts (>= 1.0.0) VignetteBuilder: knitr Encoding: UTF-8 RoxygenNote: 7.2.3 SystemRequirements: freetype2, harfbuzz, fribidi NeedsCompilation: yes Packaged: 2023-10-09 09:06:16 UTC; thomas Author: Thomas Lin Pedersen [cre, aut] (), Posit, PBC [cph, fnd] Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2023-10-09 14:00:06 UTC textshaping/build/0000755000176200001440000000000014510741207013710 5ustar liggesuserstextshaping/build/vignette.rds0000644000176200001440000000032614510741207016250 0ustar liggesusersb```b`abb`b2 1# 'O+I-JKLN MA/I()H,KWpV+ES&lJFIn #include #include [[cpp11::register]] cpp11::writable::list get_face_features_c(cpp11::strings path, cpp11::integers index); textshaping/src/hb_shaper.cpp0000644000176200001440000000057113752727465016063 0ustar liggesusers#include "hb_shaper.h" #ifdef NO_HARFBUZZ_FRIBIDI void init_hb_shaper(DllInfo* dll) { } void unload_hb_shaper(DllInfo *dll) { } #else static HarfBuzzShaper* hb_shaper; HarfBuzzShaper& get_hb_shaper() { return *hb_shaper; } void init_hb_shaper(DllInfo* dll) { hb_shaper = new HarfBuzzShaper(); } void unload_hb_shaper(DllInfo *dll) { delete hb_shaper; } #endif textshaping/src/hb_shaper.h0000644000176200001440000000033613752727641015523 0ustar liggesusers#pragma once #include #include "string_shape.h" #ifndef NO_HARFBUZZ_FRIBIDI HarfBuzzShaper& get_hb_shaper(); #endif [[cpp11::init]] void init_hb_shaper(DllInfo* dll); void unload_hb_shaper(DllInfo *dll); textshaping/src/init.cpp0000644000176200001440000000017713716556166015073 0ustar liggesusers#include #include "hb_shaper.h" extern "C" void R_unload_textshaping(DllInfo *dll) { unload_hb_shaper(dll); } textshaping/src/cache_lru.h0000644000176200001440000000434513716460521015510 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) { 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--; _cache_map.erase(last->first); _cache_list.pop_back(); return true; } return false; } // 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; } // 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 inline void remove(key_t& key) { cache_map_it_t it = _cache_map.find(key); if (it == _cache_map.end()) { return; } _cache_list.erase(it->second); _cache_map.erase(it); } // Clear the cache inline void clear() { _cache_list.clear(); _cache_map.clear(); } private: size_t _max_size; 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; }; textshaping/src/utils.h0000644000176200001440000001430514046465236014725 0ustar liggesusers#pragma once #include #include #include #define R_NO_REMAP #define END_CPP11_NO_RETURN \ } \ catch (cpp11::unwind_exception & e) { \ err = e.token; \ } \ catch (std::exception & e) { \ strncpy(buf, e.what(), 8192 - 1); \ } \ catch (...) { \ strncpy(buf, "C++ error (unknown cause)", 8192 - 1); \ } \ if (buf[0] != '\0') { \ Rf_error("%s", buf); \ } else if (err != R_NilValue) { \ CPP11_UNWIND \ } /* 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; } /* srcsz = number of source characters, or -1 if 0-terminated sz = size of dest buffer in bytes returns # characters converted dest will only be '\0'-terminated if there is enough space. this is for consistency; imagine there are 2 bytes of space left, but the next character requires 3 bytes. in this case we could NUL-terminate, but in general we can't when there's insufficient space. therefore this function only NUL-terminates if all the characters fit, and there's space for the NUL as well. the destination string will never be bigger than the source string. */ inline int u8_toutf8(char *dest, int sz, const uint32_t *src, int srcsz) { uint32_t ch; int i = 0; char *dest_end = dest + sz; while (srcsz<0 ? src[i]!=0 : i < srcsz) { ch = src[i]; if (ch < 0x80) { if (dest >= dest_end) return i; *dest++ = (char)ch; } else if (ch < 0x800) { if (dest >= dest_end-1) return i; *dest++ = (ch>>6) | 0xC0; *dest++ = (ch & 0x3F) | 0x80; } else if (ch < 0x10000) { if (dest >= dest_end-2) return i; *dest++ = (ch>>12) | 0xE0; *dest++ = ((ch>>6) & 0x3F) | 0x80; *dest++ = (ch & 0x3F) | 0x80; } else if (ch < 0x110000) { if (dest >= dest_end-3) return i; *dest++ = (ch>>18) | 0xF0; *dest++ = ((ch>>12) & 0x3F) | 0x80; *dest++ = ((ch>>6) & 0x3F) | 0x80; *dest++ = (ch & 0x3F) | 0x80; } i++; } if (dest < dest_end) *dest = '\0'; return i; } /* End of Basic UTF-8 manipulation routines by Jeff Bezanson */ class UTF_UCS { std::vector buffer_ucs; std::vector buffer_utf; public: UTF_UCS() { // Allocate space in buffer buffer_ucs.resize(1024); buffer_utf.resize(1024, '\0'); } ~UTF_UCS() { } const uint32_t * convert_to_ucs(const char * string, int &n_conv) { if (string == NULL) { n_conv = 0; return buffer_ucs.data(); } int n_bytes = strlen(string) + 1; unsigned int max_size = n_bytes * 4; if (buffer_ucs.size() < max_size) { buffer_ucs.resize(max_size); } n_conv = u8_toucs(buffer_ucs.data(), max_size, string, -1); return buffer_ucs.data(); } const char * convert_to_utf(const uint32_t * string, unsigned int str_len, int &n_conv) { if (string == NULL) { n_conv = 0; return buffer_utf.data(); } unsigned int max_size = str_len * 4; if (buffer_utf.size() < max_size + 1) { buffer_utf.resize(max_size + 1, '\0'); } else { buffer_utf[max_size] = '\0'; } n_conv = u8_toutf8(buffer_utf.data(), max_size, string, str_len); return buffer_utf.data(); } }; textshaping/src/string_bidi.h0000644000176200001440000000017713752727746016076 0ustar liggesusers#pragma once #include #include std::vector get_bidi_embeddings(const uint32_t* string, int n_chars); textshaping/src/Makevars.win0000644000176200001440000000063614510736537015707 0ustar liggesusersRWINLIB = ../windows/harfbuzz PKG_CPPFLAGS = \ -I$(RWINLIB)/include/harfbuzz \ -I$(RWINLIB)/include/fribidi \ -I$(RWINLIB)/include/freetype2 PKG_LIBS = -L$(RWINLIB)/lib$(R_ARCH) -L$(RWINLIB)/lib \ -lfribidi -lfreetype -lharfbuzz -lfreetype -lpng -lbz2 -lz -lrpcrt4 -lgdi32 -luuid all: clean winlibs winlibs: "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" clean: rm -f $(OBJECTS) $(SHLIB) textshaping/src/string_shape.cpp0000644000176200001440000006632614510736537016621 0ustar liggesusers#ifndef NO_HARFBUZZ_FRIBIDI #include #include "string_shape.h" #include "string_bidi.h" #include #include #include UTF_UCS HarfBuzzShaper::utf_converter = UTF_UCS(); LRU_Cache > HarfBuzzShaper::bidi_cache = {1000}; LRU_Cache HarfBuzzShaper::shape_cache = {1000}; std::vector HarfBuzzShaper::glyph_id = {}; std::vector HarfBuzzShaper::glyph_cluster = {}; std::vector HarfBuzzShaper::string_id = {}; std::vector HarfBuzzShaper::x_pos = {}; std::vector HarfBuzzShaper::y_pos = {}; std::vector HarfBuzzShaper::x_mid = {}; std::vector HarfBuzzShaper::x_advance = {}; std::vector HarfBuzzShaper::x_offset = {}; std::vector HarfBuzzShaper::left_bear = {}; std::vector HarfBuzzShaper::right_bear = {}; std::vector HarfBuzzShaper::top_extend = {}; std::vector HarfBuzzShaper::bottom_extend = {}; std::vector HarfBuzzShaper::ascenders = {}; std::vector HarfBuzzShaper::descenders = {}; std::vector HarfBuzzShaper::may_break = {}; std::vector HarfBuzzShaper::must_break = {}; ShapeID HarfBuzzShaper::last_shape_id = {}; ShapeID HarfBuzzShaper::temp_shape_id = {}; ShapeInfo HarfBuzzShaper::last_shape_info = {}; bool HarfBuzzShaper::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(); int error = 0; FT_Face face = get_cached_face(fontfile, index, size, res, &error); if (error != 0) { error_code = error; return false; } hb_font_t *font = hb_ft_font_create(face, NULL); int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); std::vector embeddings = {}; if (n_chars > 1) { std::string key(string); if (!bidi_cache.get(key, embeddings)) { embeddings = get_bidi_embeddings(utc_string, n_chars); bidi_cache.add(key, embeddings); } } else { embeddings.push_back(0); } max_width = width; indent = ind; pen_x = indent; hanging = hang; space_before = before; space_after = after; cur_tracking = tracking; cur_res = res; cur_lineheight = lineheight; cur_align = align; cur_hjust = hjust; cur_vjust = vjust; int start = 0; for (size_t i = 0; i < embeddings.size(); ++i) { if (i == embeddings.size() - 1 || embeddings[i] != embeddings[i + 1]) { hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, utc_string, n_chars, start, i - start + 1); hb_buffer_guess_segment_properties(buffer); bool success = shape_glyphs(font, utc_string + start, i - start + 1); if (!success) { return false; } start = i + 1; } } hb_font_destroy(font); //FT_Done_Face(face); return true; } bool HarfBuzzShaper::add_string(const char* string, const char* fontfile, int index, double size, double tracking) { cur_string++; int error = 0; FT_Face face = get_cached_face(fontfile, index, size, cur_res, &error); if (error != 0) { error_code = error; return false; } hb_font_t *font = hb_ft_font_create(face, NULL); int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); std::vector embeddings = {}; if (n_chars > 1) { std::string key(string); if (!bidi_cache.get(key, embeddings)) { embeddings = get_bidi_embeddings(utc_string, n_chars); bidi_cache.add(key, embeddings); } } else { embeddings.push_back(0); } cur_tracking = tracking; int start = 0; for (size_t i = 0; i < embeddings.size(); ++i) { if (i == embeddings.size() - 1 || embeddings[i] != embeddings[i + 1]) { hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, utc_string, n_chars, start, i - start + 1); hb_buffer_guess_segment_properties(buffer); bool success = shape_glyphs(font, utc_string + start, i - start + 1); if (!success) { return false; } start = i + 1; } } hb_font_destroy(font); //FT_Done_Face(face); return true; } bool HarfBuzzShaper::finish_string() { if (glyph_id.empty()) { return true; } bool first_char = true; bool first_line = true; pen_x += indent; int last_space = -1; int32_t last_nonspace_width = 0; int32_t last_nonspace_bear = 0; int cur_line = 0; double line_height = 0; size_t glyph_counter = 0; int32_t max_descend = 0; int32_t max_ascend = 0; int32_t max_top_extend = 0; int32_t max_bottom_extend = 0; int32_t last_max_descend = 0; bool no_break_last = true; for (unsigned int i = 0; i < glyph_id.size(); ++i) { bool linebreak = must_break[i]; bool might_break = may_break[i]; bool last = i == glyph_id.size() - 1; bool soft_wrap = false; if (might_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 = !might_break; // Calculate top and bottom extend and ascender/descender // Soft wrapping? if (max_width > 0 && !first_char && pen_x + x_advance[i] > max_width && !might_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_offset[i]); 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) { int32_t 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 HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_info, double size, double res) { int n_features = font_info.n_features; std::vector features(n_features); if (n_features == 0) { temp_shape_id.string.assign(string); temp_shape_id.font.assign(font_info.file); temp_shape_id.index = font_info.index; temp_shape_id.size = size * res; if (temp_shape_id == last_shape_id) { return true; } if (shape_cache.get(temp_shape_id, last_shape_info)) { last_shape_id.string.swap(temp_shape_id.string); last_shape_id.font.swap(temp_shape_id.font); last_shape_id.index = temp_shape_id.index; last_shape_id.size = temp_shape_id.size; return true; } } else { for (int i = 0; i < n_features; ++i) { const char* tag = font_info.features[i].feature; features[i].tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); features[i].value = font_info.features[i].setting; features[i].start = 0; features[i].end = -1; } // Reset temp id so we don't haphazardly use a shaping with features temp_shape_id.string.clear(); temp_shape_id.font.clear(); temp_shape_id.index = 0; temp_shape_id.size = 0; } int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); std::vector embeddings = {}; if (n_chars > 1) { std::string key(string); if (!bidi_cache.get(key, embeddings)) { embeddings = get_bidi_embeddings(utc_string, n_chars); bidi_cache.add(key, embeddings); } } else { embeddings.push_back(0); } last_shape_info.x_pos.clear(); last_shape_info.glyph_id.clear(); last_shape_info.width = 0; last_shape_info.font.clear(); last_shape_info.fallbacks.clear(); last_shape_info.fallbacks.push_back(font_info); last_shape_info.fallback_scaling.clear(); bool may_have_emoji = false; for (int i = 0; i < n_chars; ++i) { if (utc_string[i] >= 8205) { may_have_emoji = true; break; } } if (may_have_emoji) { std::vector emoji_embeddings = {}; emoji_embeddings.resize(n_chars); detect_emoji_embedding(utc_string, n_chars, emoji_embeddings.data(), font_info.file, font_info.index); bool emoji_font_added = false; for (int i = 0; i < n_chars; ++i) { if (emoji_embeddings[i] == 1) { embeddings[i] = 2; if (!emoji_font_added) { last_shape_info.fallbacks.push_back(locate_font_with_features("emoji", 0, 0)); emoji_font_added = true; } } } } size_t embedding_start = 0; for (size_t i = 1; i <= embeddings.size(); ++i) { if (i == embeddings.size() || embeddings[i] != embeddings[i - 1]) { shape_embedding(utc_string, embedding_start, i, n_chars, size, res, features, embeddings[embedding_start] == 2); embedding_start = i; } } shape_cache.add(temp_shape_id, last_shape_info); last_shape_id.string.swap(temp_shape_id.string); last_shape_id.font.swap(temp_shape_id.font); last_shape_id.index = temp_shape_id.index; last_shape_id.size = temp_shape_id.size; //FT_Done_Face(face); return true; } void HarfBuzzShaper::reset() { glyph_id.clear(); glyph_cluster.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(); may_break.clear(); must_break.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 HarfBuzzShaper::shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars) { hb_shape(font, buffer, NULL, 0); unsigned int n_glyphs = 0; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); if (n_glyphs == 0) return true; glyph_id.reserve(n_glyphs); glyph_cluster.reserve(n_glyphs); string_id.reserve(n_glyphs); x_pos.reserve(n_glyphs); y_pos.reserve(n_glyphs); #if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 ascend = 0; descend = 0; #else hb_font_extents_t extent; hb_font_get_h_extents(font, &extent); ascend = extent.ascender; descend = extent.descender; #endif for (unsigned int i = 0; i < n_glyphs; ++i) { unsigned int cluster = glyph_info[i].cluster; glyph_cluster.push_back(cluster); glyph_id.push_back(glyph_info[i].codepoint); if (cluster < n_chars) { may_break.push_back(glyph_is_breaker(string[cluster])); must_break.push_back(glyph_is_linebreak(string[cluster])); } else { may_break.push_back(false); must_break.push_back(false); } string_id.push_back(cur_string); hb_glyph_extents_t extent; hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); int32_t x_off = glyph_pos[i].x_offset; int32_t x_adv = glyph_pos[i].x_advance; x_advance.push_back(x_adv + cur_tracking); left_bear.push_back(extent.x_bearing); right_bear.push_back(x_adv - (extent.x_bearing + extent.width)); top_extend.push_back(extent.y_bearing); bottom_extend.push_back(extent.height + extent.y_bearing); ascenders.push_back(ascend); descenders.push_back(descend); if (i == 0) { x_offset.push_back(0); } else { x_offset.push_back(x_off); } } return true; } bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, double size, double res, std::vector& features, bool emoji) { unsigned int embedding_size = end - start; if (embedding_size < 1) { return true; } int error = 0; FT_Face face = get_cached_face(last_shape_info.fallbacks[0].file, last_shape_info.fallbacks[0].index, size, res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[0].file, last_shape_info.fallbacks[0].index); error_code = error; return false; } if (last_shape_info.fallback_scaling.empty()) { double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; scaling *= family_scaling(face->family_name); last_shape_info.fallback_scaling.push_back(scaling); } if (emoji) { face = get_cached_face(last_shape_info.fallbacks[1].file, last_shape_info.fallbacks[1].index, size, res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[1].file, last_shape_info.fallbacks[1].index); error_code = error; return false; } if (last_shape_info.fallback_scaling.size() == 1) { double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; scaling *= family_scaling(face->family_name); last_shape_info.fallback_scaling.push_back(scaling); } } hb_font_t *font = hb_ft_font_create(face, NULL); unsigned int n_glyphs = 0; hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, string, string_length, start, embedding_size); hb_buffer_guess_segment_properties(buffer); hb_shape(font, buffer, features.data(), features.size()); hb_glyph_info_t *glyph_info = NULL; hb_glyph_position_t *glyph_pos = NULL; glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); if (n_glyphs == 0) { hb_font_destroy(font); return true; } bool ltr = true; for (unsigned int i = 1; i < n_glyphs; ++i) { if (glyph_info[i - 1].cluster == glyph_info[i].cluster) { continue; } ltr = glyph_info[i - 1].cluster < glyph_info[i].cluster; break; } unsigned int current_font = emoji ? 1 : 0; std::vector char_font(embedding_size, current_font); bool needs_fallback = false; bool any_resolved = false; annotate_fallbacks(current_font + 1, 0, char_font, glyph_info, n_glyphs, needs_fallback, any_resolved, ltr, start); if (!needs_fallback) { // Short route - use existing shaping glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); hb_font_destroy(font); return true; } hb_font_destroy(font); // Need to reset this in case the first annotation didn't find any hits in the first font any_resolved = true; // We need to figure out the font for each character, then redo shaping using that while (needs_fallback && any_resolved) { needs_fallback = false; any_resolved = false; ++current_font; unsigned int fallback_start = 0, fallback_end = 0; bool found = fallback_cluster(current_font, char_font, 0, fallback_start, fallback_end); if (!found) { // Think about handling this unlikely scenario; return false; } int error = 0; bool using_new = false; font = load_fallback(current_font, string, start + fallback_start, start + fallback_end, error, size, res, using_new); if (!using_new) { // We don't need to have success if we are trying an existing font any_resolved = true; } if (error != 0) { Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); error_code = error; return false; } do { hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, string, string_length, start + fallback_start, fallback_end - fallback_start); hb_buffer_guess_segment_properties(buffer); hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); if (n_glyphs > 0) { bool needs_fallback_2 = false; bool any_resolved_2 = false; annotate_fallbacks(current_font + 1, fallback_start, char_font, glyph_info, n_glyphs, needs_fallback_2, any_resolved_2, ltr, start); if (needs_fallback_2) needs_fallback = true; if (any_resolved_2) any_resolved = true; } found = fallback_cluster(current_font, char_font, fallback_end, fallback_start, fallback_end); } while (found); hb_font_destroy(font); } // Make sure char_font does not point to non-existing fonts for (size_t i = 0; i < char_font.size(); ++i) { if (char_font[i] >= last_shape_info.fallbacks.size()) { char_font[i] = 0; } } if (ltr) { current_font = char_font[0]; unsigned int text_run_start = 0; for (unsigned int i = 1; i <= embedding_size; ++i) { if (i == embedding_size || char_font[i] != current_font) { int error = 0; FT_Face face = get_cached_face(last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index, size, res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); error_code = error; return false; } font = hb_ft_font_create(face, NULL); hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, string, string_length, start + text_run_start, i - text_run_start); hb_buffer_guess_segment_properties(buffer); hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); hb_font_destroy(font); if (i < embedding_size) { current_font = char_font[i]; if (current_font >= last_shape_info.fallbacks.size()) current_font = 0; text_run_start = i; } } } } else { current_font = char_font.back(); int text_run_end = embedding_size; for (int i = text_run_end - 1; i >= 0; --i) { if (i <= 0 || char_font[i - 1] != current_font) { int error = 0; FT_Face face = get_cached_face(last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index, size, res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); error_code = error; return false; } font = hb_ft_font_create(face, NULL); hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, string, string_length, start + i, text_run_end - i); hb_buffer_guess_segment_properties(buffer); hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); hb_font_destroy(font); if (i > 0) { current_font = char_font[i - 1]; if (current_font >= last_shape_info.fallbacks.size()) current_font = 0; text_run_end = i; } } } } return true; } void HarfBuzzShaper::annotate_fallbacks(unsigned int font, unsigned int offset, std::vector& char_font, hb_glyph_info_t* glyph_info, unsigned int n_glyphs, bool& needs_fallback, bool& any_resolved, bool ltr, unsigned int string_offset) { unsigned int current_cluster = glyph_info[0].cluster; unsigned int cluster_start = 0; for (unsigned int i = 1; i <= n_glyphs; ++i) { if (i == n_glyphs || glyph_info[i].cluster != current_cluster) { unsigned int next_cluster; if (ltr) { next_cluster = i < n_glyphs ? glyph_info[i].cluster : char_font.size() + string_offset; } else { next_cluster = cluster_start > 0 ? glyph_info[cluster_start - 1].cluster : char_font.size() + string_offset; } bool has_glyph = true; for (unsigned int j = cluster_start; j < i; ++j) { if (glyph_info[j].codepoint == 0) { has_glyph = false; } } if (!has_glyph) { needs_fallback = true; for (unsigned int j = current_cluster; j < next_cluster; ++j) { char_font[j - string_offset] = font; } } else { any_resolved = true; } if (i < n_glyphs) { current_cluster = glyph_info[i].cluster; cluster_start = i; } } } } void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, unsigned int n_glyphs, hb_font_t* font, unsigned int font_id) { double scaling = last_shape_info.fallback_scaling[font_id]; if (scaling < 0) scaling = 1.0; hb_glyph_extents_t extent; int32_t x = last_shape_info.width; for (unsigned int i = 0; i < n_glyphs; ++i) { if (last_shape_info.x_pos.empty()) { hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); last_shape_info.left_bearing = extent.x_bearing * scaling; } last_shape_info.x_pos.push_back(x + (glyph_pos[i].x_offset) * scaling); last_shape_info.glyph_id.push_back(glyph_info[i].codepoint); last_shape_info.font.push_back(font_id); x += glyph_pos[i].x_advance * scaling; } last_shape_info.width = x; hb_font_get_glyph_extents(font, glyph_info[n_glyphs - 1].codepoint, &extent); last_shape_info.right_bearing = glyph_pos[n_glyphs - 1].x_advance - (extent.x_bearing + extent.width); last_shape_info.right_bearing *= scaling; } hb_font_t* HarfBuzzShaper::load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, double size, double res, bool& new_added) { new_added = false; if (font >= last_shape_info.fallbacks.size()) { int n_conv = 0; const char* fallback_string = utf_converter.convert_to_utf(string + start, end - start, n_conv); last_shape_info.fallbacks.push_back( get_fallback(fallback_string, last_shape_info.fallbacks[0].file, last_shape_info.fallbacks[0].index) ); new_added = true; } FT_Face face = get_cached_face(last_shape_info.fallbacks[font].file, last_shape_info.fallbacks[font].index, size, res, &error); if (error != 0) { return NULL; } if (font >= last_shape_info.fallback_scaling.size()) { double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; scaling *= family_scaling(face->family_name); last_shape_info.fallback_scaling.push_back(scaling); } return hb_ft_font_create(face, NULL); } bool HarfBuzzShaper::fallback_cluster(unsigned int font, std::vector& char_font, unsigned int from, unsigned int& start, unsigned int& end) { bool has_cluster = false; for (unsigned int i = from; i < char_font.size(); ++i) { if (char_font[i] == font) { start = i; has_cluster = true; break; } } for (unsigned int i = start + 1; i <= char_font.size(); ++i) { if (i == char_font.size() || char_font[i] != font) { end = i; break; } } return has_cluster; } #endif textshaping/src/string_shape.h0000644000176200001440000001477214004617714016255 0ustar liggesusers#pragma once #ifndef NO_HARFBUZZ_FRIBIDI #include #include #include FT_FREETYPE_H #include #include #include #include "utils.h" #include "cache_lru.h" struct ShapeID { std::string string; std::string font; unsigned int index; double size; inline ShapeID() : string(""), font(""), index(0), size(0.0) {} inline ShapeID(std::string _string, std::string _font, unsigned int _index, double _size) : string(_string), font(_font), index(_index), size(_size) {} inline ShapeID(const ShapeID& shape) : string(shape.string), font(shape.font), index(shape.index), size(shape.size) {} inline bool operator==(const ShapeID &other) const { return (index == other.index && size == other.size && string == other.string && font == other.font); } }; struct ShapeInfo { std::vector glyph_id; std::vector x_pos; std::vector font; std::vector fallbacks; std::vector fallback_scaling; int32_t width; int32_t left_bearing; int32_t right_bearing; }; namespace std { template <> struct hash { size_t operator()(const ShapeID & x) const { return std::hash()(x.string) ^ std::hash()(x.font) ^ std::hash()(x.index) ^ std::hash()(x.size); } }; } class HarfBuzzShaper { public: HarfBuzzShaper() : 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) { buffer = hb_buffer_create(); }; ~HarfBuzzShaper() { hb_buffer_destroy(buffer); }; static std::vector glyph_id; static std::vector glyph_cluster; static std::vector string_id; static std::vector x_pos; static std::vector y_pos; static std::vector x_mid; int32_t width; int32_t height; int32_t left_bearing; int32_t right_bearing; int32_t top_bearing; int32_t bottom_bearing; int32_t top_border; int32_t left_border; int32_t pen_x; int32_t pen_y; static ShapeInfo last_shape_info; 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_shape(const char* string, FontSettings font_info, double size, double res); private: static UTF_UCS utf_converter; static LRU_Cache > bidi_cache; static LRU_Cache shape_cache; static ShapeID last_shape_id; static ShapeID temp_shape_id; hb_buffer_t *buffer; double cur_lineheight; int cur_align; unsigned int cur_string; double cur_hjust; double cur_vjust; double cur_res; double cur_tracking; 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; static std::vector may_break; static std::vector must_break; std::vector line_left_bear; std::vector line_right_bear; std::vector line_width; std::vector line_id; int32_t top; int32_t bottom; int32_t ascend; int32_t descend; int32_t max_width; int32_t indent; int32_t hanging; int32_t space_before; int32_t space_after; void reset(); bool shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars); bool shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, double size, double res, std::vector& features, bool emoji); hb_font_t* load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, double size, double res, bool& new_added); bool fallback_cluster(unsigned int font, std::vector& char_font, unsigned int from, unsigned int& start, unsigned int& end); void annotate_fallbacks(unsigned int font, unsigned int offset, std::vector& char_font, hb_glyph_info_t* glyph_info, unsigned int n_glyphs, bool& needs_fallback, bool& any_resolved, bool ltr, unsigned int string_offset); void fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, unsigned int n_glyphs, hb_font_t* font, unsigned int font_id); inline double family_scaling(const char* family) { if (strcmp("Apple Color Emoji", family) == 0) { return 1.3; } else if (strcmp("Noto Color Emoji", family) == 0) { return 1.175; } return 1; } 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; } }; #endif textshaping/src/string_bidi.cpp0000644000176200001440000000130513752734160016407 0ustar liggesusers#include "string_bidi.h" #ifdef NO_HARFBUZZ_FRIBIDI std::vector get_bidi_embeddings(const uint32_t* string, int n_chars) { return {}; } #else #include std::vector get_bidi_embeddings(const uint32_t* string, int n_chars) { FriBidiCharType base_direction = FRIBIDI_TYPE_ON; std::vector embedding_levels(n_chars); FriBidiLevel max_level = fribidi_log2vis(string, n_chars, &base_direction, nullptr, nullptr, nullptr, embedding_levels.data()); (void) max_level; //shut up compiler about unused variables return {embedding_levels.begin(), embedding_levels.end()}; } #endif textshaping/src/face_feature.cpp0000644000176200001440000000450014060125503016507 0ustar liggesusers#include "face_feature.h" #ifdef NO_HARFBUZZ_FRIBIDI using namespace cpp11; writable::list get_face_features_c(strings path, integers index) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); return {}; } #else #include #include FT_FREETYPE_H #include #include #include #include #include using namespace cpp11; writable::list get_face_features_c(strings path, integers index) { writable::list features(path.size()); std::vector tags; unsigned int n_tags = 0; char tag_temp[5]; tag_temp[4] = '\0'; FT_Library library; FT_Face ft_face; FT_Error error = FT_Init_FreeType(&library); if (error != 0) { stop("Freetype could not be initialised"); } for (int i = 0; i < path.size(); ++i) { error = FT_New_Face(library, Rf_translateCharUTF8(path[i]), index[i], &ft_face); if (error != 0) { stop("Font could not be loaded"); } hb_face_t *face = hb_ft_face_create_referenced(ft_face); writable::strings font_tags; // Get GPOS tags n_tags = hb_ot_layout_table_get_feature_tags(face, HB_OT_TAG_GPOS, 0, NULL, NULL); tags.clear(); tags.reserve(n_tags); for (size_t j = 0; j < n_tags; ++j) { hb_tag_t tag = {}; tags.push_back(tag); } if (n_tags > 0) { n_tags = hb_ot_layout_table_get_feature_tags(face, HB_OT_TAG_GPOS, 0, &n_tags, &tags[0]); for (size_t j = 0; j < n_tags; ++j) { hb_tag_to_string(tags[j], tag_temp); font_tags.push_back(tag_temp); } } // Get GSUB tags — same as above n_tags = hb_ot_layout_table_get_feature_tags(face, HB_OT_TAG_GSUB, 0, NULL, NULL); tags.clear(); tags.reserve(n_tags); for (size_t j = 0; j < n_tags; ++j) { hb_tag_t tag = {}; tags.push_back(tag); } if (n_tags > 0) { n_tags = hb_ot_layout_table_get_feature_tags(face, HB_OT_TAG_GSUB, 0, &n_tags, &tags[0]); for (size_t j = 0; j < n_tags; ++j) { hb_tag_to_string(tags[j], tag_temp); font_tags.push_back(tag_temp); } } features[i] = (SEXP) font_tags; hb_face_destroy(face); } FT_Done_FreeType(library); return features; } #endif textshaping/src/string_metrics.cpp0000644000176200001440000003344014127557215017154 0ustar liggesusers#define R_NO_REMAP #include "string_metrics.h" #include "string_shape.h" #include "hb_shaper.h" #define CPP11_PARTIAL #include #include #include #include "utils.h" using namespace cpp11; #ifdef NO_HARFBUZZ_FRIBIDI list get_string_shape_c(strings string, integers id, strings path, integers index, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); writable::data_frame string_df({ "string"_nm = writable::logicals(), "width"_nm = writable::logicals(), "height"_nm = writable::logicals(), "left_bearing"_nm = writable::logicals(), "right_bearing"_nm = writable::logicals(), "top_bearing"_nm = writable::logicals(), "bottom_bearing"_nm = writable::logicals(), "left_border"_nm = writable::logicals(), "top_border"_nm = writable::logicals(), "pen_x"_nm = writable::logicals(), "pen_y"_nm = writable::logicals() }); string_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); writable::data_frame info_df({ "glyph"_nm = writable::logicals(), "index"_nm = writable::logicals(), "metric_id"_nm = writable::logicals(), "string_id"_nm = writable::logicals(), "x_offset"_nm = writable::logicals(), "y_offset"_nm = writable::logicals(), "x_midpoint"_nm = writable::logicals() }); info_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); return writable::list({ "shape"_nm = info_df, "metrics"_nm = string_df }); } doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals include_bearing) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); return {}; } int ts_string_width(const char* string, FontSettings font_info, double size, double res, int include_bearing, double* width) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); *width = 0.0; return 0; } int ts_string_shape(const char* string, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); loc.clear(); id.clear(); cluster.clear(); font.clear(); fallbacks.clear(); return 0; } int ts_string_shape_old(const char* string, FontSettings font_info, double size, double res, double* x, double* y, int* id, int* n_glyphs, unsigned int max_length) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); *n_glyphs = 0; return 0; } #else list get_string_shape_c(strings string, integers id, strings path, integers index, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles 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; // Return Columns writable::integers glyph, glyph_id, metric_id, string_id; writable::doubles x_offset, y_offset, x_midpoint, widths, heights, left_bearings, right_bearings, top_bearings, bottom_bearings, left_border, top_border, pen_x, pen_y; // Shape the text int cur_id = id[0] - 1; // make sure it differs from first bool success = false; HarfBuzzShaper& shaper = get_hb_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) { Rf_error("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) { Rf_error("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(STRING_ELT(path, i)), shaper.error_code); } } bool store_string = i == n_strings - 1 || cur_id != INTEGER(id)[i + 1]; if (store_string) { success = shaper.finish_string(); if (!success) { Rf_error("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_cluster[j]); glyph_id.push_back((int) shaper.glyph_id[j]); metric_id.push_back(pen_x.size() + 1); 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); } } writable::strings str(pen_x.size()); writable::data_frame string_df({ "string"_nm = (SEXP) str, "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 }); string_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); writable::data_frame info_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 }); info_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); return writable::list({ "shape"_nm = info_df, "metrics"_nm = string_df }); } doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals 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]; writable::doubles widths; int error = 1; double width = 0; for (int i = 0; i < n_strings; ++i) { error = string_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 (error) { Rf_error("Failed to calculate width of string (%s) with font file (%s) with freetype error %i", Rf_translateCharUTF8(string[i]), Rf_translateCharUTF8(path[i]), error); } widths.push_back(width); } return widths; } int ts_string_width(const char* string, FontSettings font_info, double size, double res, int include_bearing, double* width) { BEGIN_CPP11 HarfBuzzShaper& shaper = get_hb_shaper(); bool success = shaper.single_line_shape( string, font_info, size, res ); if (!success) { return shaper.error_code; } int32_t width_tmp = shaper.last_shape_info.width; if (!include_bearing) { width_tmp -= shaper.last_shape_info.left_bearing; width_tmp -= shaper.last_shape_info.right_bearing; } *width = double(width_tmp) / 64.0; END_CPP11_NO_RETURN return 0; } int ts_string_shape(const char* string, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling) { BEGIN_CPP11 HarfBuzzShaper& shaper = get_hb_shaper(); bool success = shaper.single_line_shape( string, font_info, size, res ); if (!success) { return shaper.error_code; } int n_glyphs = shaper.last_shape_info.x_pos.size(); loc.clear(); if (n_glyphs == 0) { id.clear(); font.clear(); fallbacks.clear(); fallback_scaling.clear(); } else { for (int i = 0; i < n_glyphs; ++i) { loc.push_back({ double(shaper.last_shape_info.x_pos[i]) / 64.0, 0.0 }); } id.assign(shaper.last_shape_info.glyph_id.begin(), shaper.last_shape_info.glyph_id.end()); font.assign(shaper.last_shape_info.font.begin(), shaper.last_shape_info.font.end()); fallbacks.assign(shaper.last_shape_info.fallbacks.begin(), shaper.last_shape_info.fallbacks.end()); fallback_scaling.assign(shaper.last_shape_info.fallback_scaling.begin(), shaper.last_shape_info.fallback_scaling.end()); } END_CPP11_NO_RETURN return 0; } int ts_string_shape_old(const char* string, FontSettings font_info, double size, double res, double* x, double* y, int* id, int* n_glyphs, unsigned int max_length) { int result = 0; BEGIN_CPP11 std::vector _loc; std::vector _id; std::vector _cluster; std::vector _font; std::vector _fallbacks; std::vector _fallback_scaling; result = ts_string_shape(string, font_info, size, res, _loc, _id, _cluster, _font, _fallbacks, _fallback_scaling); if (result == 0) { *n_glyphs = max_length > _loc.size() ? _loc.size() : max_length; for (int i = 0; i < *n_glyphs; ++i) { x[i] = _loc[i].x; y[i] = _loc[i].y; id[i] = (int) _id[i]; } } END_CPP11_NO_RETURN return result; } #endif void export_string_metrics(DllInfo* dll) { R_RegisterCCallable("textshaping", "ts_string_width", (DL_FUNC)ts_string_width); R_RegisterCCallable("textshaping", "ts_string_shape_new", (DL_FUNC)ts_string_shape); R_RegisterCCallable("textshaping", "ts_string_shape", (DL_FUNC)ts_string_shape_old); } textshaping/src/string_metrics.h0000644000176200001440000000321614060125105016600 0ustar liggesusers#pragma once #define R_NO_REMAP #include #include #include #include #include #include #include using namespace cpp11; namespace textshaping { struct Point { double x; double y; }; } [[cpp11::register]] list get_string_shape_c(strings string, integers id, strings path, integers index, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after); [[cpp11::register]] doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals include_bearing); int ts_string_width(const char* string, FontSettings font_info, double size, double res, int include_bearing, double* width); int ts_string_shape(const char* string, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling); int ts_string_shape_old(const char* string, FontSettings font_info, double size, double res, double* x, double* y, int* id, int* n_glyphs, unsigned int max_length); [[cpp11::init]] void export_string_metrics(DllInfo* dll); textshaping/src/cpp11.cpp0000644000176200001440000000623214510740320015026 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "cpp11/declarations.hpp" #include // face_feature.h cpp11::writable::list get_face_features_c(cpp11::strings path, cpp11::integers index); extern "C" SEXP _textshaping_get_face_features_c(SEXP path, SEXP index) { BEGIN_CPP11 return cpp11::as_sexp(get_face_features_c(cpp11::as_cpp>(path), cpp11::as_cpp>(index))); END_CPP11 } // string_metrics.h list get_string_shape_c(strings string, integers id, strings path, integers index, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after); extern "C" SEXP _textshaping_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 doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals include_bearing); extern "C" SEXP _textshaping_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[] = { {"_textshaping_get_face_features_c", (DL_FUNC) &_textshaping_get_face_features_c, 2}, {"_textshaping_get_line_width_c", (DL_FUNC) &_textshaping_get_line_width_c, 6}, {"_textshaping_get_string_shape_c", (DL_FUNC) &_textshaping_get_string_shape_c, 16}, {NULL, NULL, 0} }; } void init_hb_shaper(DllInfo* dll); void export_string_metrics(DllInfo* dll); extern "C" attribute_visible void R_init_textshaping(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); init_hb_shaper(dll); export_string_metrics(dll); R_forceSymbols(dll, TRUE); } textshaping/src/Makevars.in0000644000176200001440000000014214510736537015510 0ustar liggesusersPKG_CPPFLAGS = -DNDEBUG @cflags@ PKG_LIBS = @libs@ all: clean clean: rm -f $(SHLIB) $(OBJECTS) textshaping/vignettes/0000755000176200001440000000000014510741210014613 5ustar liggesuserstextshaping/vignettes/c_interface.Rmd0000644000176200001440000001024314006506145017527 0ustar liggesusers--- title: "textshaping C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{textshaping C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` textshaping is predominantly intended to be used by other packages implementing graphic devicees and calling it from the C level. As such it exports a set of functions that match the needs of graphic devices. The C API builds upon that of systemfonts and you'll thus need to link to both packages to access it succesfully. This is done with the `LinkingTo` field in the `DESCRIPTION` file: ``` LinkingTo: systemfonts, textshaping ``` You will further need to make sure that both packages are loaded when you need to use the C API. This is most easily done by importing a function from each package into your namespace. In your C/C++ code you'll then have `#include ` to get access to the functions described below. The functions are available in the `textshaping` namespace. The C API expects fonts to be given as `FontSettings` structs which can be obtained from the systemfonts C API with `locate_font_with_features()`. This makes it possible to both get access to the font file location along with potential OpenType features registered to the font. ## String width ```C int string_width( const char* string, FontSettings font_info, 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 FontSettings struct to use for shaping the string before calculating the width. 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 ```C int string_shape( const char* string, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling ) ``` This function takes care of all the nitty-gritty of shaping a single line of text. It takes the same font information input as `string_width()`, that is, a `FontSettings` struct and size and res. It further accepts a number of vectors where the shaping information will be written. `loc` will end up containing the location of each glyph in pts starting from a (0, 0) origin. Since the graphic engine only pass single lines to the graphic device at a time then line breaking is not handled and for now all returned y positions are set to 0.0 (this may change in the future depending on the development of the graphic engine). The glyph id in the font file will be written to the `id` vector You will need to use this to look up the glyph to render instead of relying on the characters in the input string due to the potential substitution and merging of glyphs happening during shaping. The `cluster` array is currently unused (and will thus not be touched) but may in the future contain identifications of which character in the input string relates to the provided glyphs. The `font`, `fallback`, and `fallback_scaling` vectors will be filled with information about the selected fonts for the shaping. The `font` vector will map each glyph to a font in the `fallback` vector. The first element in the `fallback` vector will be the requested font, and if any additional elements exist it will be due to font fallback occurring. The `fallback_scaling` vector is holding information about how the shaping of non-scalable fonts has been scaled. It contains one element for each elements in `fallback`. If the value is negative the font is scalable and no scaling of the metrics have occurred. If it is positive it is the value that has been multiplied to the glyph metrics. textshaping/R/0000755000176200001440000000000014510740320013005 5ustar liggesuserstextshaping/R/shape_text.R0000644000176200001440000002212014006512173015274 0ustar liggesusers#' Calculate glyph positions for strings #' #' @description #' `r lifecycle::badge('experimental')` #' #' 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 systemfonts::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_text(string) #' #' # Mix styles within the same string #' string <- c( #' "This string will have\na ", #' "very large", #' " text style\nin the middle" #' ) #' #' shape_text(string, id = c(1, 1, 1), size = c(12, 24, 12)) #' shape_text <- 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 <- systemfonts::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(systemfonts::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) ) if (nrow(shape$shape) == 0) return(shape) 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 systemfonts::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') #' text_width(strings) #' text_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 <- systemfonts::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(systemfonts::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) ) } textshaping/R/textshaping-package.R0000644000176200001440000000044213734677572017110 0ustar liggesusers#' @keywords internal "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @useDynLib textshaping, .registration = TRUE #' @importFrom systemfonts system_fonts ## usethis namespace: end NULL textshaping/R/aaa.R0000644000176200001440000000035013753507707013672 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"))`' ) } textshaping/R/cpp11.R0000644000176200001440000000122514510740320014054 0ustar liggesusers# Generated by cpp11: do not edit by hand get_face_features_c <- function(path, index) { .Call(`_textshaping_get_face_features_c`, path, index) } get_string_shape_c <- function(string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) { .Call(`_textshaping_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(`_textshaping_get_line_width_c`, string, path, index, size, res, include_bearing) } textshaping/R/font_features.R0000644000176200001440000000316213720020600015770 0ustar liggesusers#' Get available OpenType features in a font #' #' This is a simply functions that returns the available OpenType feature tags #' for one or more fonts. See [`font_feature()`][systemfonts::font_feature] for #' more information on how to use the different feature with a font. #' #' @inheritParams systemfonts::font_info #' #' @return A list with an element for each of the input fonts containing the #' supported feature tags for that font. #' #' @importFrom systemfonts match_font #' @export #' #' @examples #' # Select a random font on the system #' sys_fonts <- systemfonts::system_fonts() #' random_font <- sys_fonts$family[sample(nrow(sys_fonts), 1)] #' #' # Get the features #' get_font_features(random_font) #' get_font_features <- function(family = '', italic = FALSE, bold = FALSE, path = NULL, index = 0) { if (is.null(path)) { full_length <- max(length(family), length(italic), length(bold)) if (full_length == 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)) path <- rep_len(path, full_length) index <- rep_len(index, full_length) } lapply(get_face_features_c(as.character(path), as.integer(index)), unique) } textshaping/NEWS.md0000644000176200001440000000251714510740772013722 0ustar liggesusers# textshaping 0.3.7 * Prepare for Arm Windows # textshaping 0.3.6 * Fix a bug in fallback font loading which would crash the process if the font failed to load (#23) * Fixed bug that would reset fallback to the original font for short strings (#25) # textshaping 0.3.5 * Address an UBCSAN issue in packages linking to textshaping * Remove a few compiler warnings # textshaping 0.3.4 * Prepare textshaping for UCRT support * Address upstream changes in cpp11 # textshaping 0.3.3 * Support static linking on macOS (#17, @jeroen) # textshaping 0.3.2 * Avoid overindexing fallbacks when no fallback is found # textshaping 0.3.1 * Try to avoid ASAN issue reported by CRAN # textshaping 0.3.0 * Add support for performing font fallback as part of the single-line shaping * Provide support for non-scalable fonts # textshaping 0.2.1 * Fix issues with the Solaris mock solution # textshaping 0.2.0 * Update C API to prepare for font fallback * Make sure it compiles on Solaris without system dependencies # textshaping 0.1.2 * Fix a bug in the interaction with the systemfonts font cache that could cause random crashes on some mac installations. # textshaping 0.1.1 * Small changes to comply with next cpp11 version # textshaping 0.1.0 * First release. Provide access to HarfBuzz shaping and FriBidi bidirectional script support. textshaping/MD50000644000176200001440000000500114511003546013113 0ustar liggesusers5fda8bf1f24499b2eacce69ad49fe917 *DESCRIPTION 38658af2499f1780bb9f1586ab95a939 *LICENSE fc0493ada33f1e1178890b4f0469e120 *NAMESPACE f272bd9a9d92a6e33de244d0634bf6dd *NEWS.md 3cdfcedc32dc8d2928eff42ab0a3b6bf *R/aaa.R 4cce499f2c314a68991f0cddfd5c7d18 *R/cpp11.R 69bc51cc86c2aaf6b57c6cf9afa01c42 *R/font_features.R 76bed6d3d027641c019531490e0a1c07 *R/shape_text.R 377383f1194400cd367aa2179278e110 *R/textshaping-package.R a871008abd64e26780eb0ef6110f8a3e *README.md 6a6339732cbd1c52f590d6259021af8c *build/vignette.rds 8fedab5931036be90e6ff98113518359 *cleanup b8ae4f780f000469c9767692fc9566d0 *configure 3c9e2d06e8560272e30c98b0c827478b *inst/doc/c_interface.R 53f397f83d34bac25a463944ebed33cd *inst/doc/c_interface.Rmd 61ed44df4d30306f4143e2faa2e33224 *inst/doc/c_interface.html 01b664ff9df4ae7d423f1bd0c14100fb *inst/include/textshaping.h cb1e46f469cfbbbde29c8b5113e1d789 *man/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *man/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *man/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 53b3f893324260b737b3c46ed2a0e643 *man/figures/lifecycle-stable.svg 1c1fe7a759b86dc6dbcbe7797ab8246c *man/figures/lifecycle-superseded.svg c133a270dc1b71b167024fd39dab737f *man/get_font_features.Rd 036740619525db3fe3e3b5dea958da41 *man/shape_text.Rd 215b7e33ec51984e374dc9285e379aeb *man/text_width.Rd 7b5f7475e9625d04808b9ca2d6923660 *man/textshaping-package.Rd cc18986c4e1452a0271a288286379ef4 *src/Makevars.in e454b8d8e9abd7b89a0204782134a1f4 *src/Makevars.win 48efff0cee3d1c6f89a3b5ff3354f715 *src/cache_lru.h 68b8239ba1f0ba79cfaf02581c7eda66 *src/cpp11.cpp a9c63ad033e0923f693b947a8b6db973 *src/face_feature.cpp b828ba4493df04539e8c025afe9a399b *src/face_feature.h 85c1c789268626413a7f6c83fc2d97fc *src/hb_shaper.cpp 7dcce7900ea26f0b6ddf77500c123eca *src/hb_shaper.h 39054766e1da4fbe98f064a018a81958 *src/init.cpp 13e1783c7b9198be0d421f2a6fd30f39 *src/string_bidi.cpp 96cab9d24d19732554287f96e75fd14a *src/string_bidi.h 86bee2b79a2128f76c894202fbec51f5 *src/string_metrics.cpp e41bab9452b13b4d65f780a891d27880 *src/string_metrics.h 51a6f4e26c152cce3adb142fea924497 *src/string_shape.cpp dd7664858e6e86f07af92ad11ea425fc *src/string_shape.h a97ba99eaca7f130d80de3dc3743cd7d *src/utils.h 4c4ae954ec118afe315b00b91f278391 *tools/winlibs.R 53f397f83d34bac25a463944ebed33cd *vignettes/c_interface.Rmd textshaping/inst/0000755000176200001440000000000014510741207013566 5ustar liggesuserstextshaping/inst/doc/0000755000176200001440000000000014510741207014333 5ustar liggesuserstextshaping/inst/doc/c_interface.R0000644000176200001440000000021714510741207016720 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) textshaping/inst/doc/c_interface.html0000644000176200001440000003605514510741207017474 0ustar liggesusers textshaping C interface

textshaping C interface

textshaping is predominantly intended to be used by other packages implementing graphic devicees and calling it from the C level. As such it exports a set of functions that match the needs of graphic devices. The C API builds upon that of systemfonts and you’ll thus need to link to both packages to access it succesfully. This is done with the LinkingTo field in the DESCRIPTION file:

LinkingTo: 
    systemfonts,
    textshaping

You will further need to make sure that both packages are loaded when you need to use the C API. This is most easily done by importing a function from each package into your namespace.

In your C/C++ code you’ll then have #include <textshaping.h> to get access to the functions described below. The functions are available in the textshaping namespace.

The C API expects fonts to be given as FontSettings structs which can be obtained from the systemfonts C API with locate_font_with_features(). This makes it possible to both get access to the font file location along with potential OpenType features registered to the font.

String width

int string_width(
  const char* string, 
  FontSettings font_info, 
  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 FontSettings struct to use for shaping the string before calculating the width. 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

int string_shape(
  const char* string, 
  FontSettings font_info,
  double size, 
  double res, 
  std::vector<Point>& loc,
  std::vector<uint32_t>& id, 
  std::vector<int>& cluster,
  std::vector<unsigned int>& font, 
  std::vector<FontSettings>& fallbacks,
  std::vector<double>& fallback_scaling
)

This function takes care of all the nitty-gritty of shaping a single line of text. It takes the same font information input as string_width(), that is, a FontSettings struct and size and res. It further accepts a number of vectors where the shaping information will be written. loc will end up containing the location of each glyph in pts starting from a (0, 0) origin. Since the graphic engine only pass single lines to the graphic device at a time then line breaking is not handled and for now all returned y positions are set to 0.0 (this may change in the future depending on the development of the graphic engine). The glyph id in the font file will be written to the id vector You will need to use this to look up the glyph to render instead of relying on the characters in the input string due to the potential substitution and merging of glyphs happening during shaping. The cluster array is currently unused (and will thus not be touched) but may in the future contain identifications of which character in the input string relates to the provided glyphs. The font, fallback, and fallback_scaling vectors will be filled with information about the selected fonts for the shaping. The font vector will map each glyph to a font in the fallback vector. The first element in the fallback vector will be the requested font, and if any additional elements exist it will be due to font fallback occurring. The fallback_scaling vector is holding information about how the shaping of non-scalable fonts has been scaled. It contains one element for each elements in fallback. If the value is negative the font is scalable and no scaling of the metrics have occurred. If it is positive it is the value that has been multiplied to the glyph metrics.

textshaping/inst/doc/c_interface.Rmd0000644000176200001440000001024314006506145017241 0ustar liggesusers--- title: "textshaping C interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{textshaping C interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` textshaping is predominantly intended to be used by other packages implementing graphic devicees and calling it from the C level. As such it exports a set of functions that match the needs of graphic devices. The C API builds upon that of systemfonts and you'll thus need to link to both packages to access it succesfully. This is done with the `LinkingTo` field in the `DESCRIPTION` file: ``` LinkingTo: systemfonts, textshaping ``` You will further need to make sure that both packages are loaded when you need to use the C API. This is most easily done by importing a function from each package into your namespace. In your C/C++ code you'll then have `#include ` to get access to the functions described below. The functions are available in the `textshaping` namespace. The C API expects fonts to be given as `FontSettings` structs which can be obtained from the systemfonts C API with `locate_font_with_features()`. This makes it possible to both get access to the font file location along with potential OpenType features registered to the font. ## String width ```C int string_width( const char* string, FontSettings font_info, 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 FontSettings struct to use for shaping the string before calculating the width. 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 ```C int string_shape( const char* string, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling ) ``` This function takes care of all the nitty-gritty of shaping a single line of text. It takes the same font information input as `string_width()`, that is, a `FontSettings` struct and size and res. It further accepts a number of vectors where the shaping information will be written. `loc` will end up containing the location of each glyph in pts starting from a (0, 0) origin. Since the graphic engine only pass single lines to the graphic device at a time then line breaking is not handled and for now all returned y positions are set to 0.0 (this may change in the future depending on the development of the graphic engine). The glyph id in the font file will be written to the `id` vector You will need to use this to look up the glyph to render instead of relying on the characters in the input string due to the potential substitution and merging of glyphs happening during shaping. The `cluster` array is currently unused (and will thus not be touched) but may in the future contain identifications of which character in the input string relates to the provided glyphs. The `font`, `fallback`, and `fallback_scaling` vectors will be filled with information about the selected fonts for the shaping. The `font` vector will map each glyph to a font in the `fallback` vector. The first element in the `fallback` vector will be the requested font, and if any additional elements exist it will be due to font fallback occurring. The `fallback_scaling` vector is holding information about how the shaping of non-scalable fonts has been scaled. It contains one element for each elements in `fallback`. If the value is negative the font is scalable and no scaling of the metrics have occurred. If it is positive it is the value that has been multiplied to the glyph metrics. textshaping/inst/include/0000755000176200001440000000000014147726340015220 5ustar liggesuserstextshaping/inst/include/textshaping.h0000644000176200001440000000640414060125341017717 0ustar liggesusers#pragma once #define R_NO_REMAP #include #include #include #include #include #include #include #include namespace textshaping { struct Point { double x; double y; }; // 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, FontSettings font_info, double size, double res, int include_bearing, double* width) { static int (*p_ts_string_width)(const char*, FontSettings, double, double, int, double*) = NULL; if (p_ts_string_width == NULL) { p_ts_string_width = (int (*)(const char*, FontSettings, double, double, int, double*)) R_GetCCallable("textshaping", "ts_string_width"); } return p_ts_string_width(string, font_info, size, res, include_bearing, width); } // Calculate glyph positions and id in the font file 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, FontSettings font_info, double size, double res, std::vector& loc, std::vector& id, std::vector& cluster, std::vector& font, std::vector& fallbacks, std::vector& fallback_scaling) { static int (*p_ts_string_shape)(const char*, FontSettings, double, double, std::vector&, std::vector&, std::vector&, std::vector&, std::vector&, std::vector&) = NULL; if (p_ts_string_shape == NULL) { p_ts_string_shape = (int (*)(const char*, FontSettings, double, double, std::vector&, std::vector&, std::vector&, std::vector&, std::vector&, std::vector&)) R_GetCCallable("textshaping", "ts_string_shape_new"); } return p_ts_string_shape(string, font_info, size, res, loc, id, cluster, font, fallbacks, fallback_scaling); } } // Old API --------------------------------------------------------------------- static inline int ts_string_width(const char* string, FontSettings font_info, double size, double res, int include_bearing, double* width) { return textshaping::string_width(string, font_info, size, res, include_bearing, width); } static inline int ts_string_shape(const char* string, FontSettings font_info, double size, double res, double* x, double* y, int* id, int* cluster, int* n_glyphs, unsigned int max_length) { static int (*p_ts_string_shape)(const char*, FontSettings, double, double, double*, double*, int*, int*, unsigned int) = NULL; if (p_ts_string_shape == NULL) { p_ts_string_shape = (int (*)(const char*, FontSettings, double, double, double*, double*, int*, int*, unsigned int)) R_GetCCallable("textshaping", "ts_string_shape"); } return p_ts_string_shape(string, font_info, size, res, x, y, id, n_glyphs, max_length); } textshaping/cleanup0000755000176200001440000000004714510741210014161 0ustar liggesusers#!/bin/sh rm -f autobrew configure.log textshaping/configure0000755000176200001440000000632714510741210014522 0ustar liggesusers#! /bin/sh # Anticonf (tm) script by Jeroen Ooms and Thomas Lin Pedersen(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 PKG_CONFIG_NAME="harfbuzz freetype2 fribidi" PKG_DEB_NAME="libharfbuzz-dev libfribidi-dev" PKG_RPM_NAME="harfbuzz-devel fribidi-devel" PKG_CSW_NAME="libharfbuzz_dev libfribidi_dev" PKG_BREW_NAME="harfbuzz fribidi" PKG_TEST_HEADER="#include " PKG_LIBS="-lfreetype -lharfbuzz -lfribidi -lpng" # Support static linking for binary packages on MacOS if [ `uname` = "Darwin" ]; then PKG_CONFIG_NAME="--static $PKG_CONFIG_NAME" 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/freetype2 -I$BREWDIR/include/harfbuzz -I$BREWDIR/include/glib-2.0 -I$BREWDIR/include/fribidi" else curl -sfL "https://autobrew.github.io/scripts/harfbuzz" > 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 "$PKG_TEST_HEADER" | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - >/dev/null 2>configure.log # Customize the error if [ $? -ne 0 ]; then if [ "`uname`" = "SunOS" ]; then PKG_CFLAGS="$PKG_CFLAGS -DNO_HARFBUZZ_FRIBIDI" PKG_LIBS="" else 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 fi # Write to Makevars sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars # Success exit 0