textshaping/0000755000176200001440000000000015072136402012610 5ustar liggesuserstextshaping/tests/0000755000176200001440000000000014741232310013747 5ustar liggesuserstextshaping/tests/testthat/0000755000176200001440000000000015072136402015612 5ustar liggesuserstextshaping/tests/testthat/test-bidi.R0000644000176200001440000000346415055530337017636 0ustar liggesuserstest_that("Textshaping follows UNICODE Bidi conventions", { # First test from the unicode test file test_string <- gsub( " ", "\\u", "' 05D0 05D1 0028 05D2 05D3 005B 0026 0065 0066 005D 002E 0029 0067 0068'", fixed = TRUE ) test_string <- eval(parse(text = test_string)) shape <- shape_text(test_string, direction = "ltr") expect_equal( shape$shape$glyph, c(2L, 1L, 3L, 5L, 4L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L) ) skip_on_cran() skip_on_ci() # Change to FALSE to run ALL tests locally skip_if(TRUE, "Skipping time consuming UNICODE Bidi tests") test_url <- "https://www.unicode.org/Public/UCD/latest/ucd/BidiCharacterTest.txt" tests <- read.csv2(test_url, header = FALSE, comment.char = "#") names(tests) <- c( "unicode", "ltr_set", "ltr_detected", "bidi_embedding", "visual_order" ) tests$visual_order <- lapply( strsplit(tests$visual_order, " ", TRUE), as.integer ) for (i in seq_len(nrow(tests))[-1]) { test_string <- paste0( "\"\\u", gsub(" ", "\\u", tests$unicode[i], fixed = TRUE), "\"" ) test_string <- eval(parse(text = test_string)) dir <- c("ltr", "rtl", "auto")[tests$ltr_set[i] + 1] shape <- shape_text(test_string, direction = dir) expect_equal( shape$metrics$ltr, tests$ltr_detected[i] == 0, label = paste0("calculated direction [test ", i, "]") ) true_ordering <- tests$visual_order[[i]] + 1L # We do the below to make sure that any ligature substitutions doesn't affect the # changes true_ordering <- intersect(true_ordering, shape$shape$glyph) calc_ordering <- intersect(shape$shape$glyph, true_ordering) expect_equal( calc_ordering, true_ordering, label = paste0("calculated visual order [test ", i, "]") ) } }) textshaping/tests/testthat.R0000644000176200001440000000062214741232310015732 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview # * https://testthat.r-lib.org/articles/special-files.html library(testthat) library(textshaping) test_check("textshaping") textshaping/MD50000644000176200001440000000677115072136402013133 0ustar liggesusers225510ccb6c2298ce7a92568de534604 *DESCRIPTION 0e91e607e107aa84ac57ec6d39ec2216 *LICENSE 013be8d63ca34a15b45aa279cafdd6a3 *NAMESPACE eaa12e825c508a1cc2489e34969473af *NEWS.md 3cdfcedc32dc8d2928eff42ab0a3b6bf *R/aaa.R 969434a804afc62450115fa96598e125 *R/cpp11.R b6f26d26753585856a3f272c6ffe0b70 *R/font_features.R c7366024ffe0a4d2b7b10d63fd437fb8 *R/lorem_text.R 490c528fb6c8536e48040625d14f8416 *R/shape_text.R 96b857041aa330e0f962e75583d38f60 *R/textshaping-package.R 5cf129df607f93f19f6e11cba6dd4558 *README.md 3f2da9d484be8deaafd7c2ee2f24a560 *build/vignette.rds 8fedab5931036be90e6ff98113518359 *cleanup 4872a7958fbc08cb1caacc64e28870d0 *configure ca78ea2b731dc25b7695c582ece4144c *inst/doc/c_interface.R 58e9be03c821e25a380bd7f103ae104f *inst/doc/c_interface.Rmd 500858d85a94593b5ef94e1a96cf9e27 *inst/doc/c_interface.html 01b664ff9df4ae7d423f1bd0c14100fb *inst/include/textshaping.h c52e3bd29c3578621f61c25d61368a68 *inst/lorem/README 38dc83a5af03bbfcfdad3a0a04c9f090 *inst/lorem/arabic.txt f8214c06a90cc3b4c7be21047e48d0dd *inst/lorem/armenian.txt fd02df69811075dc648f65b88d506c3d *inst/lorem/chinese.txt 8d14c274037e97d332351ce1f47d3cb1 *inst/lorem/cyrillic.txt 94a0ea36478d8f5843ac4e29afdcef2e *inst/lorem/devanagari.txt 00b9b2713e740fc090d9e6c472698df1 *inst/lorem/georgian.txt f35e6e61ba9c7e31ada2dabaee60b703 *inst/lorem/greek.txt 212dc5bce45cbcfadb6db97b165466f8 *inst/lorem/hangul.txt 7455830e83a8d95e458ca3a55e5f9489 *inst/lorem/hebrew.txt 2874e13267f804784b8f886cd44b8ab2 *inst/lorem/kana.txt 797470e1083a897096a7f7355a01722f *inst/lorem/latin.txt a1cbaf3f328e8d74e747faacf640c7fc *man/figures/lifecycle-archived.svg 6f521fb1819410630e279d1abf88685a *man/figures/lifecycle-defunct.svg 391f696f961e28914508628a7af31b74 *man/figures/lifecycle-deprecated.svg 691b1eb2aec9e1bec96b79d11ba5e631 *man/figures/lifecycle-experimental.svg 405e252e54a79b33522e9699e4e9051c *man/figures/lifecycle-maturing.svg f41ed996be135fb35afe00641621da61 *man/figures/lifecycle-questioning.svg 306bef67d1c636f209024cf2403846fd *man/figures/lifecycle-soft-deprecated.svg ed42e3fbd7cc30bc6ca8fa9b658e24a8 *man/figures/lifecycle-stable.svg bf2f1ad432ecccee3400afe533404113 *man/figures/lifecycle-superseded.svg fb2bf4943fb36c12c583bc57252567df *man/get_font_features.Rd b860bf64b7addfdd4fcd3b66efd9de5a *man/lorem_text.Rd eab262fa474513a68e230144318798f9 *man/plot_shape.Rd b7dd859673e1bd029c78d73e843b2eb8 *man/shape_text.Rd e2a690999c88f9a46284c5eeb7edbf03 *man/text_width.Rd 9bcd8211ffb65d3ad2536792904d4399 *man/textshaping-package.Rd cc18986c4e1452a0271a288286379ef4 *src/Makevars.in cd56941bbdb42cbd678284b12d10b380 *src/Makevars.win 48efff0cee3d1c6f89a3b5ff3354f715 *src/cache_lru.h 937ef8582cd2788d927ee8b096f70a2d *src/cpp11.cpp 91f4e79051f02569f1f4f32fe08d802a *src/face_feature.cpp b828ba4493df04539e8c025afe9a399b *src/face_feature.h 85c1c789268626413a7f6c83fc2d97fc *src/hb_shaper.cpp 7dcce7900ea26f0b6ddf77500c123eca *src/hb_shaper.h 39054766e1da4fbe98f064a018a81958 *src/init.cpp 2e1b3a20345333763ad451f19531385e *src/string_bidi.cpp e162d923eab0302229b170f99266a5f6 *src/string_bidi.h 41044fde5c0da1fdb7f98a39d528f77e *src/string_metrics.cpp 139ddaa1a7b38731ea2f322eb7ca3270 *src/string_metrics.h 153f0e284233cf5c7c357214faef8b1a *src/string_shape.cpp c6b8c308c84723f04aa71d4e08ca7f8a *src/string_shape.h 92bdd0993fe0604991aeb22ed2f7bd1b *src/utils.h 388e9e9944b104092093b05f8276aed9 *tests/testthat.R f9eef1f1fec6de82cf9ed9d55b726782 *tests/testthat/test-bidi.R e9c27e824fbf0cb0d291267db64c23ff *tools/winlibs.R 58e9be03c821e25a380bd7f103ae104f *vignettes/c_interface.Rmd textshaping/R/0000755000176200001440000000000015066762501013021 5ustar liggesuserstextshaping/R/font_features.R0000644000176200001440000000251715002133362016000 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_fonts #' @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)) fonts <- match_fonts( rep_len(family, full_length), rep_len(italic, full_length), ifelse(rep_len(bold, full_length), "bold", "normal") ) path <- fonts$path index <- fonts$index } else { full_length <- max(length(path), length(index)) 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/R/aaa.R0000644000176200001440000000035014737506142013665 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.R0000644000176200001440000000137715066762501014100 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, features, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after, direction, soft_wrap, hard_wrap) { .Call(`_textshaping_get_string_shape_c`, string, id, path, index, features, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after, direction, soft_wrap, hard_wrap) } get_line_width_c <- function(string, path, index, size, res, include_bearing, features) { .Call(`_textshaping_get_line_width_c`, string, path, index, size, res, include_bearing, features) } textshaping/R/shape_text.R0000644000176200001440000003570315022221333015301 0ustar liggesusers#' Calculate glyph positions for strings #' #' Performs advanced text shaping of strings including font fallbacks, #' bidirectional script support, word wrapping and various character and #' paragraph level formatting settings. #' #' @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::match_fonts #' @param features A [systemfonts::font_feature()] object or a list of them, #' giving the OpenType font features to set #' @param size The size in points to use for the font #' @param res The resolution to use when doing the shaping. Should optimally #' match the resolution used when rendering the glyphs. #' @param lineheight A multiplier for the lineheight #' @param align Within text box alignment, either `'auto'`, `'left'`, `'center'`, #' `'right'`, `'justified'`, `'justified-left'`, `'justified-right'`, #' `'justified-center'`, or `'distributed'`. `'auto'` and `'justified'` will #' chose the left or right version depending on the direction of the text. #' @param hjust,vjust The justification of the textbox surrounding the text #' @param max_width The requested with of the string in inches. Setting this to #' something other than `NA` will turn on word wrapping. #' @param tracking Tracking of the glyphs (space adjustment) measured in 1/1000 #' em. #' @param indent The indent of the first line in a paragraph measured in inches. #' @param hanging The indent of the remaining lines in a paragraph measured in #' inches. #' @param space_before,space_after The spacing above and below a paragraph, #' measured in points #' @param direction The overall directional flow of the text. The default #' (`"auto"`) will guess the direction based on the content of the string. Use #' `"ltr"` (left-to-right) and `"rtl"` (right-to-left) to turn detection of and #' set it manually. #' @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 placement of the the first character contributing to the glyph within the string} #' \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{font_path}{The path to the font file used during shaping of the glyph} #' \item{font_index}{The index of the font used to shape the glyph in the font file} #' \item{font_size}{The size of the font used during shaping} #' \item{advance}{The advancement amount to the next glyph} #' \item{ascender}{The ascend of the font used for the glyph. This does not measure the actual glyph} #' \item{descender}{The descend of the font used for the glyph. This does not measure the actual 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} #' \item{ltr}{The global direction of the string. If `TRUE` then it is left-to-right, otherwise it is right-to-left} #' } #' #' @export #' @importFrom systemfonts font_feature match_fonts #' #' @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, weight = 'normal', width = 'undefined', features = font_feature(), size = 12, res = 72, lineheight = 1, align = 'auto', hjust = 0, vjust = 0, max_width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, direction = "auto", path = NULL, index = 0, bold = deprecated() ) { n_strings = length(strings) if (is.null(id)) id <- seq_len(n_strings) id <- rep_len(id, n_strings) id <- match(id, unique(id)) if (anyNA(id)) { stop('id must be a vector of valid integers', call. = FALSE) } ido <- order(id) id <- id[ido] strings <- as.character(strings)[ido] if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "0.4.0", "shape_text(bold)", "shape_text(weight='bold')" ) weight <- ifelse(bold, "bold", "normal") } if (inherits(features, 'font_feature')) features <- list(features) features <- rep_len(features, n_strings) if (is.null(path)) { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) weight <- rep_len(weight, n_strings) width <- rep_len(width, n_strings) loc <- match_fonts(family, italic, weight, width) path <- loc$path[ido] index <- loc$index[ido] features <- Map(c, loc$features, features)[ido] } else { path <- rep_len(path, n_strings)[ido] index <- rep_len(index, n_strings)[ido] features <- features[ido] } size <- rep_len(size, n_strings)[ido] res <- rep_len(res, n_strings)[ido] lineheight <- rep_len(lineheight, n_strings)[ido] align <- if (length(align) != 0) match.arg( align, c( 'left', 'center', 'right', 'justified-left', 'justified-center', 'justified-right', 'distributed', 'auto', 'justified' ), TRUE ) align <- match( align, c( 'left', 'center', 'right', 'justified-left', 'justified-center', 'justified-right', 'distributed', 'auto', 'justified' ) ) align <- rep_len(align, n_strings)[ido] hjust <- rep_len(hjust, n_strings)[ido] vjust <- rep_len(vjust, n_strings)[ido] max_width <- rep_len(max_width, n_strings)[ido] max_width[is.na(max_width)] <- -1 tracking <- rep_len(tracking, n_strings)[ido] indent <- rep_len(indent, n_strings)[ido] hanging <- rep_len(hanging, n_strings)[ido] space_before <- rep_len(space_before, n_strings)[ido] space_after <- rep_len(space_after, n_strings)[ido] direction <- if (length(direction) != 0) match.arg(direction, c('auto', 'ltr', 'rtl'), TRUE) direction <- match(direction, c('auto', 'ltr', 'rtl')) - 1L direction <- rep_len(direction, n_strings)[ido] max_width <- max_width * res tracking <- tracking * res indent <- indent * res hanging <- hanging * res space_before <- space_before * res / 72 space_after <- space_after * res / 72 soft_wraps <- lapply( stringi::stri_locate_all_boundaries( strings, omit_no_match = TRUE, skip_line_hard = TRUE ), function(x) x[, 2] ) hard_wraps <- lapply( stringi::stri_locate_all_boundaries( strings, omit_no_match = TRUE, skip_line_soft = TRUE ), function(x) x[, 2] ) 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), features, as.numeric(size), as.numeric(res), as.numeric(lineheight), as.integer(align) - 1L, as.numeric(hjust), as.numeric(vjust), as.numeric(max_width), as.numeric(tracking), as.numeric(indent), as.numeric(hanging), as.numeric(space_before), as.numeric(space_after), as.integer(direction), soft_wraps, hard_wraps ) if (nrow(shape$shape) == 0) return(shape) shape$metrics$string <- vapply( split(strings, id), paste, character(1), collapse = '' ) shape$metrics[-c(1, 12)] <- lapply( shape$metrics[-c(1, 12)], function(x) x * 72 / res[!duplicated(id)] ) shape$shape$string_id <- ido[ (cumsum(c(0, rle(id)$lengths)) + 1)[shape$shape$metric_id] + shape$shape$string_id - 1 ] shape$shape <- shape$shape[order(shape$shape$string_id), , drop = FALSE] res_mod <- (72 / res[shape$shape$string_id]) shape$shape$x_offset <- shape$shape$x_offset * res_mod shape$shape$y_offset <- shape$shape$y_offset * res_mod shape$shape$advance <- shape$shape$advance * res_mod shape$shape$ascender <- shape$shape$ascender * res_mod shape$shape$descender <- shape$shape$descender * res_mod shape } #' Calculate the width of a string, ignoring new-lines #' #' This is a very simple alternative to [systemfonts::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 #' have already been split by `\n`. Input is recycled to the length of #' `strings`. #' #' @inheritParams shape_text #' @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 #' @importFrom systemfonts match_fonts #' #' @examples #' strings <- c('A short string', 'A very very looong string') #' text_width(strings) #' text_width <- function( strings, family = '', italic = FALSE, weight = 'normal', width = 'undefined', features = font_feature(), size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0, bold = deprecated() ) { n_strings <- length(strings) if (lifecycle::is_present(bold)) { lifecycle::deprecate_soft( "0.4.1", "text_width(bold)", "text_width(weight='bold')" ) weight <- ifelse(bold, "bold", "normal") } if (inherits(features, 'font_feature')) features <- list(features) features <- rep_len(features, n_strings) if (is.null(path)) { family <- rep_len(family, n_strings) italic <- rep_len(italic, n_strings) weight <- rep_len(weight, n_strings) width <- rep_len(width, n_strings) loc <- match_fonts(family, italic, weight, width) path <- loc$path index <- loc$index features <- Map(c, loc$features, features) } else { path <- rep_len(path, n_strings) index <- rep_len(index, n_strings) } size <- rep_len(size, n_strings) res <- rep_len(res, n_strings) 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), features ) } #' Preview shaped text and the metrics for the text box #' #' This function allows you to preview the layout that [shape_text()] #' calculates. It is purely meant as a sanity check to make sure that the values #' calculated are sensible and shouldn't be used as a plotting function for #' rendering text on its own. #' #' @param shape The output of a call to [shape_text()] #' @param id The index of the text run to show in case `shape` contains #' multiples #' #' @return This function is called for its side effects #' #' @export #' #' @examples #' arab_text <- lorem_text("arabic", 2) #' shape <- shape_text( #' arab_text, #' max_width = 5, #' indent = 0.2 #' ) #' #' try( #' plot_shape(shape) #' ) #' plot_shape <- function(shape, id = 1) { if ( !requireNamespace("grDevices", quietly = TRUE) || utils::packageVersion("grDevices") < package_version("4.3.0") ) { stop("This function requires grDevices 4.3.0 or above") } if ( !requireNamespace("grid", quietly = TRUE) || utils::packageVersion("grid") < package_version("4.3.0") ) { stop("This function requires grid 4.3.0 or above") } has_glyph_support <- grDevices::dev.capabilities()$glyphs if (is.na(has_glyph_support)) { warning("The device does not report whether it supports rendering glyphs") } else if (!isTRUE(has_glyph_support)) { stop("The current device doesn't support rendering glyphs") } glyphFont <- utils::getFromNamespace("glyphFont", "grDevices") glyphFontList <- utils::getFromNamespace("glyphFontList", "grDevices") glyphInfo <- utils::getFromNamespace("glyphInfo", "grDevices") glyphAnchor <- utils::getFromNamespace("glyphAnchor", "grDevices") grid.glyph <- utils::getFromNamespace("grid.glyph", "grid") if ( !is.numeric(id) || length(id) != 1 || id <= 0 || id %% 1 != 0 || id > nrow(shape$metrics) ) { stop("`id` must be an integer pointing to a paragraph in `shape`") } glyphs <- shape$shape[shape$shape$metric_id == id, ] box <- shape$metrics[id, ] font_id <- paste0(glyphs$font_path, "&", glyphs$font_index) font_match <- match(font_id, unique(font_id)) unique_font <- !duplicated(font_id) fonts <- Map( glyphFont, glyphs$font_path[unique_font], glyphs$font_index[unique_font], "", 0, "" ) fonts <- do.call(glyphFontList, fonts) glyphs <- glyphInfo( id = glyphs$index, x = glyphs$x_offset, y = glyphs$y_offset, font = font_match, size = glyphs$font_size, fontList = fonts, width = box$width, height = -box$height, hAnchor = glyphAnchor(0, "left"), vAnchor = glyphAnchor(0, "bottom") ) grid::grid.newpage() vp <- grid::viewport( width = box$width, height = box$height, default.units = "bigpts" ) grid::pushViewport(vp) grid::grid.rect(gp = grid::gpar(fill = "lightgrey", col = NA)) grid::grid.rect( x = box$left_bearing, y = box$bottom_bearing, width = box$width - box$left_bearing - box$right_bearing, height = box$height - box$top_bearing - box$bottom_bearing, hjust = 0, vjust = 0, default.units = "bigpts", gp = grid::gpar(fill = NA, col = "darkgrey", lty = 2) ) grid.glyph( glyphs, x = 0, y = 0, hjust = 0, vjust = 0 ) grid::grid.points( x = box$pen_x, y = box$pen_y, default.units = "bigpts", pch = 16, gp = grid::gpar(col = "red", cex = 0.5) ) } textshaping/R/textshaping-package.R0000644000176200001440000000050614737546303017100 0ustar liggesusers#' @keywords internal "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @importFrom lifecycle deprecated #' @importFrom systemfonts system_fonts #' @useDynLib textshaping, .registration = TRUE ## usethis namespace: end NULL textshaping/R/lorem_text.R0000644000176200001440000000552515022221333015316 0ustar liggesusers#' Get gibberish text in various scripts #' #' Textshaping exists partly to allow all the various scripts that exists in the #' world to be used in R graphics. This function returns gibberish filler text #' (lorem ipsum text) in various scripts for testing purpose. Some of these are #' transliterations of the original lorem ipsum text while others are based an #' a distribution model. #' #' @param script A string giving the script to fetch gibberish for #' @param n The number of paragraphs to fetch. Each paragraph will be its own #' element in the returned character vector. #' #' @return a character vector of length `n` #' #' @export #' #' @references https://generator.lorem-ipsum.info #' #' @examples #' # Defaults to standard lorem ipsum #' lorem_text() #' #' # Get two paragraphs of hangul (Korean) #' lorem_text("hangul", 2) #' #' # Get gibberish bi-directional text #' lorem_bidi() #' lorem_text <- function( script = c( "latin", "chinese", "arabic", "devanagari", "cyrillic", "kana", "hangul", "greek", "hebrew", "armenian", "georgian" ), n = 1 ) { script <- match.arg(script) file <- paste0(script, ".txt") file <- system.file("lorem", file, package = "textshaping") rep_len(readLines(file), n) } #' @rdname lorem_text #' #' @param ltr,rtl scripts to use for left-to-right and right-to-left text #' @param ltr_prop The approximate proportion of left-to-right text in the final #' string #' @export #' lorem_bidi <- function( ltr = c( "latin", "chinese", "devanagari", "cyrillic", "kana", "hangul", "greek", "armenian", "georgian" ), rtl = c("arabic", "hebrew"), ltr_prop = 0.9, n = 1 ) { ltr <- match.arg(ltr) rtl = match.arg(rtl) ltr_split <- if (ltr %in% c("chinese", "kana", "hangul")) "" else " " ltr <- lorem_text(ltr, n) rtl <- lorem_text(rtl, n) mapply( function(ltr, rtl) { if (ltr_prop >= 0.5) { prop <- ltr_prop main <- ltr sub <- rtl sub_merge <- " " main_merge <- ltr_split } else { prop <- 1 - ltr_prop main <- rtl sub <- ltr sub_merge <- ltr_split main_merge <- " " } n_insert <- min(ceiling(length(main) * (1 - prop)), length(sub)) n_chunks <- ceiling(stats::runif(1, n_insert * (prop - 0.5), n_insert)) sub <- lapply( split( sub[seq_len(n_insert)], sort(c( seq_len(n_chunks), sample(n_chunks, n_insert - n_chunks, TRUE) )) ), paste, collapse = sub_merge ) insertions <- sort(sample(length(main), n_chunks)) for (i in rev(seq_along(insertions))) { main <- append(main, sub[[i]], insertions[i]) } paste(main, collapse = main_merge) }, ltr = strsplit(ltr, ltr_split), rtl = strsplit(rtl, " ") ) } textshaping/cleanup0000755000176200001440000000004715072133302014162 0ustar liggesusers#!/bin/sh rm -f autobrew configure.log textshaping/vignettes/0000755000176200001440000000000015072133301014613 5ustar liggesuserstextshaping/vignettes/c_interface.Rmd0000644000176200001440000001024415022221333017520 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 devices 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 successfully. 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/src/0000755000176200001440000000000015072133301013372 5ustar liggesuserstextshaping/src/hb_shaper.h0000644000176200001440000000033614737506142015516 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/hb_shaper.cpp0000644000176200001440000000057114737506142016052 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/Makevars.win0000644000176200001440000000133514737706562015712 0ustar liggesusersPKG_CONFIG_NAME = harfbuzz freetype2 fribidi PKG_CONFIG ?= $(BINPREF)pkg-config PKG_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_CONFIG_NAME)) ifneq ($(PKG_LIBS),) $(info using $(PKG_CONFIG_NAME) from Rtools) PKG_CPPFLAGS := $(subst -mms-bitfields,,$(shell $(PKG_CONFIG) --cflags $(PKG_CONFIG_NAME))) else RWINLIB = ../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 endif all: $(SHLIB) $(OBJECTS): $(RWINLIB) $(RWINLIB): "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" clean: rm -f $(SHLIB) $(OBJECTS) textshaping/src/init.cpp0000644000176200001440000000017714737546303015067 0ustar liggesusers#include #include "hb_shaper.h" extern "C" void R_unload_textshaping(DllInfo *dll) { unload_hb_shaper(dll); } textshaping/src/face_feature.cpp0000644000176200001440000000475314737506142016536 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) { if (path.size() != index.size()) { cpp11::stop("`path` and `index` must be the same length"); } writable::list features(path.size()); if (path.size() == 0) { return features; // Early exit } 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.h0000644000176200001440000000353114737506142016617 0ustar liggesusers#pragma once #define R_NO_REMAP #include #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, list_of features, 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, integers direction, list_of soft_wrap, list_of hard_wrap); [[cpp11::register]] doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals include_bearing, list_of features); 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/string_metrics.cpp0000644000176200001440000003651715002127054017147 0ustar liggesusers#include "R_ext/Arith.h" #include "cpp11/logicals.hpp" #include "cpp11/protect.hpp" #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, list_of features, 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, integers direction, list_of soft_wrap, list_of hard_wrap) { 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 std::vector< std::vector > create_font_features(list_of features) { std::vector< std::vector > res; for (R_xlen_t i = 0; i < features.size(); ++i) { res.emplace_back(); strings tags = as_cpp(features[i][0]); integers vals = as_cpp(features[i][1]); for (R_xlen_t j = 0; j < tags.size(); ++j) { const char* f = Rf_translateCharUTF8(tags[j]); res.back().push_back({{f[0], f[1], f[2], f[3]}, vals[j]}); } } return res; } std::vector create_font_settings(strings path, integers index, std::vector< std::vector >& features) { std::vector res; if (path.size() != index.size() || path.size() != features.size()) { cpp11::stop("`path`, `index`, and `features` must all be of the same length"); } for (R_xlen_t i = 0; i < path.size(); ++i) { res.emplace_back(); strncpy(res.back().file, Rf_translateCharUTF8(path[i]), PATH_MAX); res.back().file[PATH_MAX] = '\0'; res.back().index = index[i]; res.back().features = features[i].data(); res.back().n_features = features[i].size(); } return res; } list get_string_shape_c(strings string, integers id, strings path, integers index, list_of features, 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, integers direction, list_of soft_wrap, list_of hard_wrap) { int n_strings = string.size(); // Return Columns writable::integers glyph, glyph_id, metric_id, string_id, fontindex; writable::doubles x_offset, y_offset, widths, heights, left_bearings, right_bearings, top_bearings, bottom_bearings, left_border, top_border, pen_x, pen_y, fontsize, advance, ascender, descender; writable::strings fontpath, str; writable::logicals ltr; if (n_strings != 0) { if (n_strings != id.size() || n_strings != path.size() || n_strings != index.size() || n_strings != features.size() || n_strings != size.size() || n_strings != res.size() || n_strings != lineheight.size() || n_strings != align.size() || n_strings != hjust.size() || n_strings != vjust.size() || n_strings != width.size() || n_strings != tracking.size() || n_strings != indent.size() || n_strings != hanging.size() || n_strings != space_before.size() || n_strings != space_after.size() || n_strings != direction.size() || n_strings != soft_wrap.size() || n_strings != hard_wrap.size() ) { cpp11::stop("All input must be the same size"); } auto all_features = create_font_features(features); auto fonts = create_font_settings(path, index, all_features); // Shape the text int cur_id = id[0] - 1; // make sure it differs from first bool success = false; HarfBuzzShaper& shaper = get_hb_shaper(); std::vector soft, hard; for (int i = 0; i < n_strings; ++i) { const char* this_string = Rf_translateCharUTF8(string[i]); int this_id = id[i]; soft.assign(soft_wrap[i].begin(), soft_wrap[i].end()); hard.assign(hard_wrap[i].begin(), hard_wrap[i].end()); if (cur_id == this_id) { success = shaper.add_string(this_string, fonts[i], size[i], tracking[i], cpp11::is_na(string[i]), soft, hard); if (!success) { cpp11::stop("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(path[i]), shaper.error_code); } } else { cur_id = this_id; success = shaper.shape_string(this_string, fonts[i], size[i], res[i], lineheight[i], align[i], hjust[i], vjust[i], width[i] * 64.0, tracking[i], indent[i] * 64.0, hanging[i] * 64.0, space_before[i] * 64.0, space_after[i] * 64.0, cpp11::is_na(string[i]), direction[i], soft, hard); if (!success) { cpp11::stop("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) { cpp11::stop("Failed to finalise string shaping"); } int n_glyphs = shaper.glyph_id.size(); for (int j = 0; j < n_glyphs; j++) { glyph.push_back((int) shaper.glyph_cluster[j] + 1); glyph_id.push_back((int) (shaper.glyph_id[j] == SPACER_CHAR ? R_NaInt : 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); fontpath.push_back(shaper.fontfile[j]); fontindex.push_back((int) shaper.fontindex[j]); fontsize.push_back(shaper.fontsize[j]); advance.push_back(double(shaper.advance[j]) / 64.0); ascender.push_back(double(shaper.ascender[j]) / 64.0); descender.push_back(double(shaper.descender[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); ltr.push_back(shaper.dir == 1); } } str = writable::strings(pen_x.size()); } writable::data_frame string_df({ "string"_nm = str, "width"_nm = widths, "height"_nm = heights, "left_bearing"_nm = left_bearings, "right_bearing"_nm = right_bearings, "top_bearing"_nm = top_bearings, "bottom_bearing"_nm = bottom_bearings, "left_border"_nm = left_border, "top_border"_nm = top_border, "pen_x"_nm = pen_x, "pen_y"_nm = pen_y, "ltr"_nm = ltr }); string_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); writable::data_frame info_df({ "glyph"_nm = glyph, "index"_nm = glyph_id, "metric_id"_nm = metric_id, "string_id"_nm = string_id, "x_offset"_nm = x_offset, "y_offset"_nm = y_offset, "font_path"_nm = fontpath, "font_index"_nm = fontindex, "font_size"_nm = fontsize, "advance"_nm = advance, "ascender"_nm = ascender, "descender"_nm = descender }); 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, list_of features) { int n_strings = string.size(); writable::doubles widths; if (n_strings != 0) { if (n_strings != path.size() || n_strings != index.size() || n_strings != features.size() || n_strings != size.size() || n_strings != res.size() || n_strings != include_bearing.size() ) { cpp11::stop("All input must be the same size"); } auto all_features = create_font_features(features); auto fonts = create_font_settings(path, index, all_features); int error = 1; double width = 0; for (int i = 0; i < n_strings; ++i) { error = ts_string_width( Rf_translateCharUTF8(string[i]), fonts[i], size[i], res[i], static_cast(include_bearing[0]), &width ); if (error) { cpp11::stop("Failed to calculate width of string (%s) with font file (%s) with freetype error %i", Rf_translateCharUTF8(string[i]), Rf_translateCharUTF8(path[i]), 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(); shaper.error_code = 0; EmbedInfo string_shape = shaper.shape_single_line(string, font_info, size, res); if (shaper.error_code != 0) { return shaper.error_code; } int32_t width_tmp = 0; for (size_t i = 0; i < string_shape.glyph_id.size(); ++i) { width_tmp += string_shape.x_advance[i]; } if (!include_bearing) { width_tmp -= string_shape.x_bear[0]; width_tmp -= string_shape.x_advance.back() - string_shape.x_bear.back() - string_shape.width.back(); } *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(); shaper.error_code = 0; EmbedInfo string_shape = shaper.shape_single_line(string, font_info, size, res); if (shaper.error_code != 0) { return shaper.error_code; } size_t n_glyphs = string_shape.glyph_id.size(); loc.clear(); id.clear(); font.clear(); fallbacks.clear(); fallback_scaling.clear(); int32_t x = 0; int32_t y = 0; for (size_t i = 0; i < n_glyphs; ++i) { loc.push_back({ double(x + string_shape.x_offset[i]) / 64.0, double(y + string_shape.y_offset[i]) / 64.0 }); x += string_shape.x_advance[i]; y += string_shape.y_advance[i]; } id.assign(string_shape.glyph_id.begin(), string_shape.glyph_id.end()); font.assign(string_shape.font.begin(), string_shape.font.end()); fallbacks.assign(string_shape.fallbacks.begin(), string_shape.fallbacks.end()); fallback_scaling.assign(string_shape.fallback_scaling.begin(), string_shape.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_bidi.cpp0000644000176200001440000000157414737506142016420 0ustar liggesusers#include "string_bidi.h" #include #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 std::vector& string, int& direction) { FriBidiCharType base_direction = direction == 0 ? FRIBIDI_TYPE_ON : (direction == 1 ? FRIBIDI_TYPE_LTR : FRIBIDI_TYPE_RTL); std::vector embedding_levels(string.size()); FriBidiLevel max_level = fribidi_log2vis(string.data(), string.size(), &base_direction, nullptr, nullptr, nullptr, embedding_levels.data()); (void) max_level; //shut up compiler about unused variables direction = FRIBIDI_IS_RTL(base_direction) ? 2 : 1; return {embedding_levels.begin(), embedding_levels.end()}; } #endif textshaping/src/string_bidi.h0000644000176200001440000000021714737506142016056 0ustar liggesusers#pragma once #include #include std::vector get_bidi_embeddings(const std::vector& string, int& direction); textshaping/src/cache_lru.h0000644000176200001440000000434514737506142015514 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.h0000644000176200001440000001430514737506142014724 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) { \ R_ContinueUnwind(err); \ } /* 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/cpp11.cpp0000644000176200001440000000720415066762501015042 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, list_of features, 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, integers direction, list_of soft_wrap, list_of hard_wrap); extern "C" SEXP _textshaping_get_string_shape_c(SEXP string, SEXP id, SEXP path, SEXP index, SEXP features, 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, SEXP direction, SEXP soft_wrap, SEXP hard_wrap) { 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>>(features), 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), cpp11::as_cpp>(direction), cpp11::as_cpp>>(soft_wrap), cpp11::as_cpp>>(hard_wrap))); END_CPP11 } // string_metrics.h doubles get_line_width_c(strings string, strings path, integers index, doubles size, doubles res, logicals include_bearing, list_of features); extern "C" SEXP _textshaping_get_line_width_c(SEXP string, SEXP path, SEXP index, SEXP size, SEXP res, SEXP include_bearing, SEXP features) { 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), cpp11::as_cpp>>(features))); 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, 7}, {"_textshaping_get_string_shape_c", (DL_FUNC) &_textshaping_get_string_shape_c, 20}, {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.in0000644000176200001440000000014214672302657015511 0ustar liggesusersPKG_CPPFLAGS = -DNDEBUG @cflags@ PKG_LIBS = @libs@ all: clean clean: rm -f $(SHLIB) $(OBJECTS) textshaping/src/string_shape.h0000644000176200001440000005034615066735715016265 0ustar liggesusers#pragma once #include "R_ext/Error.h" #ifndef NO_HARFBUZZ_FRIBIDI #include "cpp11/protect.hpp" #include #include #include #include #include #include #include FT_FREETYPE_H #include FT_SIZES_H #include #include #include #include #include "utils.h" #include "cache_lru.h" static const uint32_t EMPTY_CHAR = 0xffffffff; // Largest possible value. Unlikely any font would use that static const uint32_t SPACER_CHAR = 0xffffffff - 1; // Second largest possible value. Also unlikely any font would use that static const bool ft_compat = systemfonts::ver2::check_ft_version(); struct FT_Library_Safe { FT_Library library; FT_Library_Safe() { if (!ft_compat) FT_Init_FreeType(&library); } ~FT_Library_Safe() { if (!ft_compat) FT_Done_FreeType(library); } }; static const FT_Library_Safe ft; struct ShapeID { size_t string_hash; size_t embed_hash; std::string font; unsigned int index; double size; double tracking; inline ShapeID() : string_hash(0), embed_hash(0), font(""), index(0), size(0.0), tracking(0.0) {} inline ShapeID(const ShapeID& shape) : string_hash(shape.string_hash), embed_hash(shape.embed_hash), font(shape.font), index(shape.index), size(shape.size), tracking(shape.tracking) {} inline bool operator==(const ShapeID &other) const { return string_hash == other.string_hash && embed_hash == other.embed_hash && index == other.index && size == other.size && font == other.font && tracking == other.tracking; } }; struct BidiID { size_t string_hash; int direction; inline bool operator==(const BidiID &other) const { return string_hash == other.string_hash && direction == other.direction; } }; struct EmbedInfo { std::vector glyph_id; std::vector glyph_cluster; std::vector string_id; std::vector x_advance; std::vector y_advance; std::vector x_offset; std::vector y_offset; std::vector x_bear; std::vector y_bear; std::vector width; std::vector height; std::vector ascenders; std::vector descenders; std::vector is_blank; std::vector may_break; std::vector may_stretch; std::vector font; std::vector fallbacks; std::vector fallback_size; std::vector fallback_scaling; size_t embedding_level; int32_t full_width; bool terminates_paragraph; void add(const EmbedInfo& other, bool check = true) { if (check && embedding_level != other.embedding_level) { cpp11::stop("Unable to merge embeddings of different levels"); } if (check && terminates_paragraph) { cpp11::stop("Can't combine embeddings past termination point"); } glyph_id.insert(glyph_id.end(), other.glyph_id.begin(), other.glyph_id.end()); glyph_cluster.insert(glyph_cluster.end(), other.glyph_cluster.begin(), other.glyph_cluster.end()); string_id.insert(string_id.end(), other.string_id.begin(), other.string_id.end()); x_advance.insert(x_advance.end(), other.x_advance.begin(), other.x_advance.end()); y_advance.insert(y_advance.end(), other.y_advance.begin(), other.y_advance.end()); x_offset.insert(x_offset.end(), other.x_offset.begin(), other.x_offset.end()); y_offset.insert(y_offset.end(), other.y_offset.begin(), other.y_offset.end()); x_bear.insert(x_bear.end(), other.x_bear.begin(), other.x_bear.end()); y_bear.insert(y_bear.end(), other.y_bear.begin(), other.y_bear.end()); width.insert(width.end(), other.width.begin(), other.width.end()); height.insert(height.end(), other.height.begin(), other.height.end()); ascenders.insert(ascenders.end(), other.ascenders.begin(), other.ascenders.end()); descenders.insert(descenders.end(), other.descenders.begin(), other.descenders.end()); is_blank.insert(is_blank.end(), other.is_blank.begin(), other.is_blank.end()); may_break.insert(may_break.end(), other.may_break.begin(), other.may_break.end()); may_stretch.insert(may_stretch.end(), other.may_stretch.begin(), other.may_stretch.end()); size_t current_font_size = font.size(); font.insert(font.end(), other.font.begin(), other.font.end()); std::for_each(font.begin() + current_font_size, font.end(), [this](unsigned int& f) { f+=this->fallbacks.size();}); fallbacks.insert(fallbacks.end(), other.fallbacks.begin(), other.fallbacks.end()); fallback_size.insert(fallback_size.end(), other.fallback_size.begin(), other.fallback_size.end()); fallback_scaling.insert(fallback_scaling.end(), other.fallback_scaling.begin(), other.fallback_scaling.end()); full_width += other.full_width; terminates_paragraph = other.terminates_paragraph; } void split(size_t from, size_t to, EmbedInfo& into) { into.embedding_level = embedding_level; into.terminates_paragraph = false; into.glyph_id.insert(into.glyph_id.end(), glyph_id.begin() + from, glyph_id.begin() + to); glyph_id.erase(glyph_id.begin() + from, glyph_id.begin() + to); into.glyph_cluster.insert(into.glyph_cluster.end(), glyph_cluster.begin() + from, glyph_cluster.begin() + to); glyph_cluster.erase(glyph_cluster.begin() + from, glyph_cluster.begin() + to); into.string_id.insert(into.string_id.end(), string_id.begin() + from, string_id.begin() + to); string_id.erase(string_id.begin() + from, string_id.begin() + to); into.x_advance.insert(into.x_advance.end(), x_advance.begin() + from, x_advance.begin() + to); x_advance.erase(x_advance.begin() + from, x_advance.begin() + to); into.y_advance.insert(into.y_advance.end(), y_advance.begin() + from, y_advance.begin() + to); y_advance.erase(y_advance.begin() + from, y_advance.begin() + to); into.x_offset.insert(into.x_offset.end(), x_offset.begin() + from, x_offset.begin() + to); x_offset.erase(x_offset.begin() + from, x_offset.begin() + to); into.y_offset.insert(into.y_offset.end(), y_offset.begin() + from, y_offset.begin() + to); y_offset.erase(y_offset.begin() + from, y_offset.begin() + to); into.x_bear.insert(into.x_bear.end(), x_bear.begin() + from, x_bear.begin() + to); x_bear.erase(x_bear.begin() + from, x_bear.begin() + to); into.y_bear.insert(into.y_bear.end(), y_bear.begin() + from, y_bear.begin() + to); y_bear.erase(y_bear.begin() + from, y_bear.begin() + to); into.width.insert(into.width.end(), width.begin() + from, width.begin() + to); width.erase(width.begin() + from, width.begin() + to); into.height.insert(into.height.end(), height.begin() + from, height.begin() + to); height.erase(height.begin() + from, height.begin() + to); into.ascenders.insert(into.ascenders.end(), ascenders.begin() + from, ascenders.begin() + to); ascenders.erase(ascenders.begin() + from, ascenders.begin() + to); into.descenders.insert(into.descenders.end(), descenders.begin() + from, descenders.begin() + to); descenders.erase(descenders.begin() + from, descenders.begin() + to); into.is_blank.insert(into.is_blank.end(), is_blank.begin() + from, is_blank.begin() + to); is_blank.erase(is_blank.begin() + from, is_blank.begin() + to); into.may_break.insert(into.may_break.end(), may_break.begin() + from, may_break.begin() + to); may_break.erase(may_break.begin() + from, may_break.begin() + to); into.may_stretch.insert(into.may_stretch.end(), may_stretch.begin() + from, may_stretch.begin() + to); may_stretch.erase(may_stretch.begin() + from, may_stretch.begin() + to); into.font.insert(into.font.end(), font.begin() + from, font.begin() + to); font.erase(font.begin() + from, font.begin() + to); into.fallbacks.insert(into.fallbacks.end(), fallbacks.begin(), fallbacks.end()); into.fallback_size.insert(into.fallback_size.end(), fallback_size.begin(), fallback_size.end()); into.fallback_scaling.insert(into.fallback_scaling.end(), fallback_scaling.begin(), fallback_scaling.end()); into.full_width = std::accumulate(into.x_advance.begin(), into.x_advance.end(), int32_t(0)); full_width -= into.full_width; } uint32_t pop() { uint32_t cluster; if (embedding_level % 2 == 0) { cluster = glyph_cluster.back(); glyph_id.pop_back(); glyph_cluster.pop_back(); string_id.pop_back(); x_advance.pop_back(); y_advance.pop_back(); x_offset.pop_back(); y_offset.pop_back(); x_bear.pop_back(); y_bear.pop_back(); width.pop_back(); height.pop_back(); ascenders.pop_back(); descenders.pop_back(); is_blank.pop_back(); may_break.pop_back(); may_stretch.pop_back(); font.pop_back(); } else { // This is not efficient but we would generally only do this on small embeddings cluster = glyph_cluster.front(); glyph_id.erase(glyph_id.begin()); glyph_cluster.erase(glyph_cluster.begin()); string_id.erase(string_id.begin()); x_advance.erase(x_advance.begin()); y_advance.erase(y_advance.begin()); x_offset.erase(x_offset.begin()); y_offset.erase(y_offset.begin()); x_bear.erase(x_bear.begin()); y_bear.erase(y_bear.begin()); width.erase(width.begin()); height.erase(height.begin()); ascenders.erase(ascenders.begin()); descenders.erase(descenders.begin()); is_blank.erase(is_blank.begin()); may_break.erase(may_break.begin()); may_stretch.erase(may_stretch.begin()); font.erase(font.begin()); } return cluster; } }; struct ShapeInfo { size_t run_start; size_t run_end; FontSettings font_info; unsigned int index; double size; double res; double tracking; std::vector embeddings; inline ShapeInfo() : run_start(0), run_end(0), font_info(), index(0), size(0), res(0), tracking(0), embeddings({}) {} inline ShapeInfo(size_t _run_start, size_t _run_end, FontSettings& _font_info, unsigned int _index, double _size, double _res, double _tracking) : run_start(_run_start), run_end(_run_end), font_info(_font_info), index(_index), size(_size), res(_res), tracking(_tracking), embeddings({}) {} void add_index(unsigned int ind) { index = ind; for (auto iter = embeddings.begin(); iter != embeddings.end(); ++iter) { iter->string_id.clear(); for (auto iter2 = iter->glyph_id.begin(); iter2 != iter->glyph_id.end(); ++iter2) { iter->string_id.push_back(ind); } } } }; template inline size_t vector_hash(Iterator begin, Iterator end) { size_t answer = end - begin; for (auto iter = begin; iter != end; ++iter) { uint32_t x = *iter; x = ((x >> 16) ^ x) * 0x45d9f3b; x = ((x >> 16) ^ x) * 0x45d9f3b; x = (x >> 16) ^ x; answer ^= x + 0x9e3779b9 + (answer << 6) + (answer >> 2); } return answer; } namespace std { template <> struct hash { size_t operator()(const ShapeID & x) const { return x.string_hash ^ x.embed_hash ^ std::hash()(x.font) ^ std::hash()(x.index) ^ std::hash()(x.size) ^ std::hash()(x.tracking); } }; template <> struct hash { size_t operator()(const BidiID & x) const { return x.string_hash ^ std::hash()(x.direction); } }; } class HarfBuzzShaper { public: HarfBuzzShaper() : // Public glyph_id(), glyph_cluster(), fontfile(), fontindex(), fontsize(), string_id(), x_pos(), y_pos(), advance(), ascender(), descender(), line_must_break(), 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), dir(0), // Private full_string(), bidi_embedding(), soft_break(), hard_break(), cur_lineheight(0.0), cur_align(0), cur_string(0), cur_hjust(0.0), cur_vjust(0.0), cur_res(0.0), shape_infos(), may_stretch(), 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) { if (!ft_compat) { Rprintf("systemfonts and textshaping have been compiled with different versions of Freetype. Because of this, textshaping will not use the font cache provided by systemfonts"); } buffer = hb_buffer_create(); }; ~HarfBuzzShaper() { hb_buffer_destroy(buffer); }; std::vector glyph_id; std::vector glyph_cluster; std::vector fontfile; std::vector fontindex; std::vector fontsize; std::vector string_id; std::vector x_pos; std::vector y_pos; std::vector advance; std::vector ascender; std::vector descender; std::vector line_must_break; 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; int error_code; int dir; bool shape_string(const char* string, FontSettings& font_info, 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 spacer, int direction, std::vector& soft_wrap, std::vector& hard_wrap); bool add_string(const char* string, FontSettings& font_info, double size, double tracking, bool spacer, std::vector& soft_wrap, std::vector& hard_wrap); bool add_spacer(FontSettings& font_info, double height, double width, uint32_t filler = SPACER_CHAR); bool finish_string(); void shape_text_run(ShapeInfo &text_run, bool ltr); EmbedInfo shape_single_line(const char* string, FontSettings& font_info, double size, double res); private: std::vector full_string; std::vector bidi_embedding; static UTF_UCS utf_converter; static LRU_Cache > bidi_cache; static LRU_Cache shape_cache; std::set soft_break; std::set hard_break; hb_buffer_t *buffer; double cur_lineheight; int cur_align; unsigned int cur_string; double cur_hjust; double cur_vjust; double cur_res; std::vector shape_infos; std::vector may_stretch; 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(); std::list combine_embeddings(std::vector& shapes, int& direction); bool shape_embedding(unsigned int start, unsigned int end, std::vector& features, int dir, ShapeInfo& shape_info, std::vector& fallbacks, std::vector& fallback_sizes, std::vector& fallback_scales); hb_font_t* load_fallback(unsigned int font, unsigned int start, unsigned int end, int& error, bool& new_added, ShapeInfo& shape_info, std::vector& fallbacks, std::vector& fallback_sizes, std::vector& fallback_scales); 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, unsigned int cluster_offset, ShapeInfo& shape_info, std::vector& fallback_sizes, std::vector& fallback_scales); void fill_glyph_info(EmbedInfo& embedding); FT_Face get_font_sizing(FontSettings& font_info, double size, double res, std::vector& sizes, std::vector& scales); void insert_hyphen(EmbedInfo& embedding, size_t where); bool has_valid_break(const EmbedInfo& embedding, int32_t width, size_t& break_pos, bool force); void rearrange_embeddings(std::list& line); std::list get_next_line_at_width(int32_t width, std::list& all_embeddings, bool& hard_break, uint32_t& break_char); void do_alignment(bool ltr); 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(int32_t id) const { switch (id) { case 10: return true; // Line feed case 11: return true; // Vertical tab case 12: return true; // Form feed case 13: return true; // Carriage return case 133: return true; // Next line case 8232: return true; // Line Separator case 8233: return true; // Paragraph Separator } return false; } inline bool glyph_is_breaker(int32_t id) const { switch (id) { case 9: return true; // Horizontal tab case 32: return true; // Space case 45: return true; // Hyphen-minus case 173: return true; // Soft hyphen case 5760: return true; // Ogham Space Mark case 6158: return true; // Mongolian Vowel Separator case 8192: return true; // En Quad case 8193: return true; // Em Quad case 8194: return true; // En Space case 8195: return true; // Em Space case 8196: return true; // Three-Per-Em Space case 8197: return true; // Four-Per-Em Space case 8198: return true; // Six-Per-Em Space case 8200: return true; // Punctuation Space case 8201: return true; // Thin Space case 8202: return true; // Hair Space case 8203: return true; // Zero Width Space case 8204: return true; // Zero Width Non-Joiner case 8205: return true; // Zero Width Joiner case 8208: return true; // Hyphen case 8287: return true; // Medium Mathematical Space case 12288: return true; // Ideographic Space } return false; } inline bool glyph_is_blank(int32_t id) const { switch (id) { case 9: return true; // Horizontal tab case 10: return true; // Line feed case 11: return true; // Vertical tab case 12: return true; // Form feed case 13: return true; // Carriage return case 32: return true; // Space case 133: return true; // Next line case 5760: return true; // Ogham Space Mark case 6158: return true; // Mongolian Vowel Separator case 8192: return true; // En Quad case 8193: return true; // Em Quad case 8194: return true; // En Space case 8195: return true; // Em Space case 8196: return true; // Three-Per-Em Space case 8197: return true; // Four-Per-Em Space case 8198: return true; // Six-Per-Em Space case 8200: return true; // Punctuation Space case 8201: return true; // Thin Space case 8202: return true; // Hair Space case 8203: return true; // Zero Width Space case 8204: return true; // Zero Width Non-Joiner case 8205: return true; // Zero Width Joiner case 8232: return true; // Line Separator case 8233: return true; // Paragraph Separator case 8287: return true; // Medium Mathematical Space case 12288: return true; // Ideographic Space } return false; } inline bool glyph_may_stretch(int32_t id) const { switch (id) { case 32: return true; // Space } return false; } inline bool glyph_may_soft_break(int index) const { return soft_break.find(index) != soft_break.end(); } inline bool glyph_must_hard_break(int index) const { return hard_break.find(index) != hard_break.end(); } }; #endif textshaping/src/string_shape.cpp0000644000176200001440000014145315066762455016621 0ustar liggesusers#ifndef NO_HARFBUZZ_FRIBIDI #include #include #include #include #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}; FT_Face get_cached_or_new_face(const char* fontfile, int index, double size, double res, int* error) { if (ft_compat) { return get_cached_face(fontfile, index, size, res, error); } FT_Face new_face; FT_Error err = FT_New_Face(ft.library, fontfile, index, &new_face); if (err != 0) { err = FT_New_Face(ft.library, fontfile, 0, &new_face); if (err != 0) { *error = err; return new_face; } } if (FT_IS_SCALABLE(new_face)) { err = FT_Set_Char_Size(new_face, 0, size * 64, res, res); if (err != 0) { *error = err; FT_Done_Face(new_face); } } else { if (new_face->num_fixed_sizes == 0) { *error = 23; FT_Done_Face(new_face); return new_face; } int best_match = 0; int diff = 1e6; int scaled_size = 64 * size * res / 72; int largest_size = 0; int largest_ind = -1; bool found_match = false; for (int i = 0; i < new_face->num_fixed_sizes; ++i) { if (new_face->available_sizes[i].size > largest_size) { largest_ind = i; } int ndiff = new_face->available_sizes[i].size - scaled_size; if (ndiff >= 0 && ndiff < diff) { best_match = i; diff = ndiff; found_match = true; } } if (!found_match && scaled_size >= largest_size) { best_match = largest_ind; } err = FT_Select_Size(new_face, best_match); if (err != 0) { *error = err; FT_Done_Face(new_face); } } return new_face; } bool HarfBuzzShaper::shape_string(const char* string, FontSettings& font_info, 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 spacer, int direction, std::vector& soft_wrap, std::vector& hard_wrap) { reset(); // Set global settings max_width = width + 1; // To prevent rounding errors indent = ind; hanging = hang; space_before = before; space_after = after; cur_res = res; cur_lineheight = lineheight; cur_align = align; cur_hjust = hjust; cur_vjust = vjust; dir = direction; return add_string(string, font_info, size, tracking, spacer, soft_wrap, hard_wrap); } bool HarfBuzzShaper::add_string(const char* string, FontSettings& font_info, double size, double tracking, bool spacer, std::vector& soft_wrap, std::vector& hard_wrap) { if (spacer) { return add_spacer(font_info, size, tracking); } size_t run_start = full_string.size(); // Convert string to UTC and add it to the global string int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); if (n_chars == 0) { // Empty run - we treat is as a 0-width spacer to capture the font ascend and descend return add_spacer(font_info, size, 0, EMPTY_CHAR); } full_string.insert(full_string.end(), utc_string, utc_string + n_chars); size_t run_end = full_string.size(); unsigned int index = shape_infos.size(); // Add precalculated soft and hard break points to the sets for (auto iter = soft_wrap.begin(); iter != soft_wrap.end(); ++iter) { soft_break.insert(run_start + (*iter) - 1); } for (auto iter = hard_wrap.begin(); iter != hard_wrap.end(); ++iter) { hard_break.insert(run_start + (*iter) - 1); } // Add run info to list shape_infos.emplace_back(run_start, run_end, font_info, index, size, cur_res, tracking); //FT_Done_Face(face); return true; } bool HarfBuzzShaper::add_spacer(FontSettings& font_info, double height, double width, uint32_t filler) { width *= 64.0 / 72.0; int32_t ascend = height * 64.0 * cur_res / 72.0; int32_t descend = 0; #if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 #else int error = 0; FT_Face face = get_cached_or_new_face(font_info.file, font_info.index, height, cur_res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", font_info.file, font_info.index); error_code = error; } else { hb_font_t *font = hb_ft_font_create_referenced(face); hb_font_extents_t fextent; hb_font_get_h_extents(font, &fextent); ascend = fextent.ascender; descend = fextent.descender; hb_font_destroy(font); } FT_Done_Face(face); #endif FontSettings dummy_font = {"", 0, NULL, 0}; ShapeInfo info; info.index = shape_infos.size(); info.run_start = info.run_end = full_string.size(); // A spacer is encoded as an "unknown glyph" with ascend giving the height and // width/x-advance giving the width info.font_info = font_info; info.embeddings.push_back({ {filler}, // glyph_id {0}, // glyph_cluster {info.index}, // string_id {int32_t(width)}, // x_advance {0}, // y_advance {0}, // x_offset {0}, // y_offset {0}, // x_bear {ascend}, // y_bear {int32_t(width)}, // width {ascend - descend}, // height {ascend}, // ascenders {descend}, // descenders {false}, // is_blank {false}, // may_break {false}, // may_stretch {0}, // font {dummy_font}, // fallbacks {height}, // fallback_size {-1}, // fallback_scaling 0, // ltr int32_t(width), // full_width false // Not a line breaker in itself }); shape_infos.push_back(info); return true; } bool HarfBuzzShaper::finish_string() { if (shape_infos.empty()) { return true; } pen_x = 0; pen_y = -space_before; int32_t cur_line_indent = indent; int32_t previous_line_descend = 0; int32_t line_ascend = 0; bool hard_break = false; uint32_t break_char; int32_t line_left_extra = 0; std::list line; auto final_embeddings = combine_embeddings(shape_infos, dir); bool ltr = dir != 2; // If alignment depends on direction update the alignment now if (cur_align == 7) cur_align = ltr ? 0 : 2; if (cur_align == 8) cur_align = ltr ? 3 : 5; // Lay out one line at a time while (!final_embeddings.empty()) { // Retrieve the next line from the embedding list line = get_next_line_at_width(max_width - cur_line_indent, final_embeddings, hard_break, break_char); bool first_char = true; // If ltr indent goes to the left if (ltr) pen_x += cur_line_indent; // Initialise line stats line_width.push_back(pen_x); line_left_bear.push_back(0); line_right_bear.push_back(0); line_must_break.push_back(hard_break); size_t line_start_index = x_pos.size(); line_left_extra = 0; for (auto iter = line.begin(); iter != line.end(); ++iter) { for (size_t i = 0; i < iter->glyph_id.size(); ++i) { // If rtl we need to treat preceding blank glyphs like we treat terminal // blank glyphs in ltr. We record how long we are in the line and move it // all backwards once we hit the first non-blank if (first_char && !ltr) { if (iter->is_blank[i]) { line_left_extra = pen_x; } else { for (size_t j = line_start_index; j < x_pos.size(); ++j) { x_pos[j] -= line_left_extra; } pen_x = 0; } } if (iter->glyph_id[i] != EMPTY_CHAR) { // Avoid adding made up glyph info for empty text runs glyph_id.push_back(iter->glyph_id[i]); glyph_cluster.push_back(iter->glyph_cluster[i]); fontfile.push_back(iter->fallbacks[iter->font[i]].file); fontindex.push_back(iter->fallbacks[iter->font[i]].index); fontsize.push_back(iter->fallback_size[iter->font[i]]); advance.push_back(iter->x_advance[i]); ascender.push_back(iter->ascenders[i]); descender.push_back(iter->descenders[i]); string_id.push_back(iter->string_id[i]); line_id.push_back(line_width.size() - 1); x_pos.push_back(pen_x + iter->x_offset[i]); may_stretch.push_back(iter->may_stretch[i]); } line_ascend = std::max(line_ascend, iter->ascenders[i]); // Only needed for first line if (line_width.size() == 1) { top_bearing = std::max(top_bearing, iter->y_bear[i]); } // Only needed for last line if (final_embeddings.empty()) { bottom_bearing = std::min(bottom_bearing, iter->height[i] + iter->y_bear[i]); } // If first character record left bearing if (first_char) { line_left_bear.back() = iter->x_bear[i]; } // Advance pen to next position pen_x += iter->x_advance[i]; // Continuously update this info if (!iter->is_blank[i]) { line_right_bear.back() = iter->x_advance[i] - iter->x_bear[i] - iter->width[i]; line_width.back() = pen_x; } // Set to false at the first non-blank glyph if (!iter->is_blank[i]) first_char = false; } } // We now know the height of the line. Update pen_y and record y_pos // Also update max_descend for next line to use pen_y -= (line_ascend - previous_line_descend) * (line_width.size() == 1 ? 1 : cur_lineheight); for (auto iter = line.begin(); iter != line.end(); ++iter) { for (size_t i = 0; i < iter->glyph_id.size(); ++i) { if (iter->glyph_id[i] != EMPTY_CHAR) { // Avoid adding made up glyph info for empty text runs y_pos.push_back(pen_y + iter->y_offset[i]); } previous_line_descend = std::min(previous_line_descend, iter->descenders[i]); } } // If first line, normalise top_bearing to 0 if (line_width.size() == 1) top_bearing = -pen_y - top_bearing; // If rtl indent goes to the right if (!ltr) { line_width.back() = pen_x + cur_line_indent; } // Reset pen to next line if (!final_embeddings.empty() || hard_break) { cur_line_indent = hard_break ? indent : hanging; pen_x = 0; line_ascend = 0; if (hard_break) pen_y -= space_after + space_before; } } if (hard_break) { // Last line ended with a line break. We move pen_y down based on last glyph size size_t last_glyph = line.back().embedding_level % 2 == 0 ? line.back().glyph_id.size() : 0; int32_t line_height = (line.back().ascenders[last_glyph] - line.back().descenders[last_glyph]) * cur_lineheight; pen_y -= line_height; bottom_bearing += line_height; pen_x = indent; line_left_extra = 0; line_width.push_back(pen_x); } // If rtl, the pen is placed at the left if (!ltr) pen_x = -line_left_extra; // Calculate overall box stats bottom_bearing += space_after - previous_line_descend; int32_t bottom = pen_y + previous_line_descend - space_after; 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; height = -bottom; do_alignment(ltr); // Figure out additional space to left or right to add to bearing double width_diff = width - line_width[max_width_ind]; if (cur_align == 1) { width_diff /= 2; } left_bearing = cur_align == 0 || cur_align == 3 || cur_align == 5 ? *std::min_element(line_left_bear.begin(), line_left_bear.end()) : line_left_bear[max_width_ind] + width_diff; right_bearing = cur_align == 2 || cur_align == 4 || cur_align == 5 ? *std::min_element(line_right_bear.begin(), line_right_bear.end()) : line_right_bear[max_width_ind] + width_diff; // Move glyphs and borders according to overall box justification left_border = - cur_hjust * width; pen_x += left_border; for (size_t i = 0; i < x_pos.size(); ++i) { x_pos[i] += left_border; } for (size_t i = 0; i < x_pos.size(); ++i) { y_pos[i] += - bottom - cur_vjust * height; } top_border += - bottom - cur_vjust * height; pen_y += - bottom - cur_vjust * height; return true; } void HarfBuzzShaper::reset() { full_string.clear(); bidi_embedding.clear(); glyph_id.clear(); glyph_cluster.clear(); fontfile.clear(); fontindex.clear(); fontsize.clear(); string_id.clear(); x_pos.clear(); y_pos.clear(); advance.clear(); ascender.clear(); descender.clear(); line_left_bear.clear(); line_right_bear.clear(); line_width.clear(); line_id.clear(); line_must_break.clear(); may_stretch.clear(); shape_infos.clear(); soft_break.clear(); hard_break.clear(); pen_x = 0; pen_y = 0; top = 0; bottom = 0; ascend = 0; descend = 0; left_bearing = 0; right_bearing = 0; top_bearing = 0; bottom_bearing = 0; width = 0; height = 0; top_border = 0; left_border = 0; cur_string = 0; error_code = 0; dir = 0; cur_lineheight = 0.0; cur_align = 0; cur_hjust = 0.0; cur_vjust = 0.0; cur_res = 0.0; top = 0; bottom = 0; max_width = 0; indent = 0; hanging = 0; space_before = 0; space_after = 0; } std::list HarfBuzzShaper::combine_embeddings(std::vector& shapes, int& direction) { // Find bidi embeddings and determine the overall direction of the text if (full_string.size() > 1) { // If we have more than one char we find bidi embeddings // We append the direction to the end in the cache so we can read it back BidiID key = {vector_hash(full_string.begin(), full_string.end()), direction}; if (!bidi_cache.get(key, bidi_embedding)) { bidi_embedding = get_bidi_embeddings(full_string, direction); bidi_embedding.push_back(direction); bidi_cache.add(key, bidi_embedding); bidi_embedding.pop_back(); } else { direction = bidi_embedding.back(); bidi_embedding.pop_back(); } } else { bidi_embedding = std::vector(full_string.size(), 0); } // If size < 2 we haven't done any guessing and we assume ltr (it doesn't matter anyway) bool ltr = direction != 2; // Shape all embeddings and collect them in a single vector std::list all_embeddings; unsigned int i = 0; for (auto iter = shapes.begin(); iter != shapes.end(); ++iter) { if (iter->embeddings.size() == 0) { // avoid shaping spacers shape_text_run(*iter, ltr); } else { // We adopt the embedding level of the prior embedding for spacers int level = all_embeddings.size() == 0 ? (ltr ? 0 : 1) : all_embeddings.back().embedding_level; iter->embeddings[0].embedding_level = level; } iter->add_index(i++); all_embeddings.insert(all_embeddings.end(), std::make_move_iterator(iter->embeddings.begin()), std::make_move_iterator(iter->embeddings.end())); iter->embeddings.clear(); } // Shortcut for simplest case if (all_embeddings.size() == 1) return all_embeddings; // Reverse ordering of consecutive embeddings in rtl // This is needed to keep their internal order during shaping // Further collapses all consequtive runs into one embedding to simplify the final shaping operation auto run_start = all_embeddings.begin(); int run_embed_level = run_start->embedding_level; std::list final_embeddings; for (auto iter = std::next(run_start); iter != all_embeddings.end(); ++iter) { int cur_embed_level = iter->embedding_level; if (std::prev(iter)->terminates_paragraph || run_embed_level != cur_embed_level) { if (run_embed_level % 2 != 0) { std::reverse(run_start, iter); } run_embed_level = cur_embed_level; for (auto iter2 = std::next(run_start); iter2 != iter; ++iter2) { run_start->add(*iter2); } final_embeddings.push_back(std::move(*run_start)); run_start = iter; } } if (run_embed_level % 2 != 0) std::reverse(run_start, all_embeddings.end()); for (auto iter2 = std::next(run_start); iter2 != all_embeddings.end(); ++iter2) { run_start->add(*iter2); } final_embeddings.push_back(std::move(*run_start)); return final_embeddings; } void HarfBuzzShaper::shape_text_run(ShapeInfo &text_run, bool ltr) { int n_features = text_run.font_info.n_features; std::vector features(n_features); ShapeID run_id; if (n_features == 0) { // No features. This is a simple string and if we have already seen it it may be in the cache run_id.string_hash = vector_hash(full_string.begin() + text_run.run_start, full_string.begin() + text_run.run_end); run_id.embed_hash = vector_hash(bidi_embedding.begin() + text_run.run_start, bidi_embedding.begin() + text_run.run_end); run_id.font.assign(text_run.font_info.file); run_id.index = text_run.font_info.index; run_id.size = text_run.size * text_run.res; run_id.tracking = text_run.tracking; if (shape_cache.get(run_id, text_run)) { return; } } else { // There are features. We parse them into the correct format for (int i = 0; i < n_features; ++i) { const char* tag = text_run.font_info.features[i].feature; features[i].tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); features[i].value = text_run.font_info.features[i].setting; features[i].start = 0; features[i].end = -1; } } // Structures to keep font info in std::vector fallback = {text_run.font_info}; std::vector fallback_size; std::vector fallback_scaling; // Get scaling and sizing info for the default font if (!get_font_sizing(fallback.back(), text_run.size, text_run.res, fallback_size, fallback_scaling)) { return; } size_t n_chars = text_run.run_end - text_run.run_start; if (n_chars == 0) { // Empty string. We record the sizing of the font for use when calculating line height EmbedInfo embedding; embedding.ascenders.push_back(0); embedding.descenders.push_back(0); #if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 #else int error = 0; FT_Face face = get_cached_or_new_face(text_run.font_info.file, text_run.font_info.index, text_run.size, text_run.res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", text_run.font_info.file, text_run.font_info.index); error_code = error; } else { hb_font_t *font = hb_ft_font_create_referenced(face); hb_font_extents_t fextent; hb_font_get_h_extents(font, &fextent); embedding.ascenders[0] = fextent.ascender; embedding.descenders[0] = fextent.descender; hb_font_destroy(font); } FT_Done_Face(face); #endif text_run.embeddings.push_back(embedding); return; } // Heuristic check to see if the string might contain emoji chars bool may_have_emoji = false; for (int i = text_run.run_start; i < text_run.run_end; ++i) { if (full_string[i] >= 8205) { may_have_emoji = true; break; } } if (may_have_emoji) { // If it may have emojis go through string and detect any // These will be recorded as negative embeddings // This depends on the font so we can't cache this as part of the embedding std::vector emoji_embeddings = {}; emoji_embeddings.resize(n_chars); detect_emoji_embedding(full_string.data() + text_run.run_start, n_chars, emoji_embeddings.data(), text_run.font_info.file, text_run.font_info.index); bool emoji_font_added = false; for (int i = 0; i < n_chars; ++i) { if (emoji_embeddings[i] == 1) { bidi_embedding[i] *= -1; if (!emoji_font_added) { // Add the system emoji font to fallbacks fallback.push_back(locate_font_with_features("emoji", 0, 0)); if (!get_font_sizing(fallback.back(), text_run.size, text_run.res, fallback_size, fallback_scaling)) { return; } emoji_font_added = true; } } } } size_t embedding_start = text_run.run_start; for (size_t i = text_run.run_start + 1; i <= text_run.run_end; ++i) { // Shape all embeddings separately bool terminal = glyph_must_hard_break(i - 1); if (terminal || i == text_run.run_end || bidi_embedding[i] != bidi_embedding[i - 1]) { bool success = shape_embedding( embedding_start, i, features, bidi_embedding[embedding_start], text_run, fallback, fallback_size, fallback_scaling ); if (!success) return; if (!text_run.embeddings.empty() && terminal) { text_run.embeddings.back().terminates_paragraph = terminal; text_run.embeddings.back().full_width -= text_run.embeddings.back().x_advance.back(); } embedding_start = i; } } if (n_features == 0) { // If simply shape add to cache shape_cache.add(run_id, text_run); } //FT_Done_Face(face); return; } EmbedInfo HarfBuzzShaper::shape_single_line(const char* string, FontSettings& font_info, double size, double res) { // Fast version when we know we don't need word wrap and alignment // Used by graphics devices reset(); int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); full_string = {utc_string, utc_string + n_chars}; std::vector shapes = {ShapeInfo(0, full_string.size(), font_info, 0, size, res, 0)}; int direction = 0; auto final_embeddings = combine_embeddings(shapes, direction); if (final_embeddings.size() == 0) return EmbedInfo(); rearrange_embeddings(final_embeddings); // Combine all embeddings into one for (auto iter = std::next(final_embeddings.begin()); iter != final_embeddings.end(); ++iter) { final_embeddings.front().add(*iter, false); } return final_embeddings.front(); } bool HarfBuzzShaper::shape_embedding(unsigned int start, unsigned int end, std::vector& features, int dir, ShapeInfo& shape_info, std::vector& fallbacks, std::vector& fallback_sizes, std::vector& fallback_scales) { unsigned int embedding_size = end - start; if (embedding_size < 1) { return true; } int error = 0; // Load main font (emoji if dir is negative) // Shouldn't be able to fail as we have already tried to load it in the calling function FT_Face face = get_cached_or_new_face( fallbacks[dir < 0 ? 1 : 0].file, fallbacks[dir < 0 ? 1 : 0].index, shape_info.size, shape_info.res, &error ); if (error) { return false; } hb_font_t *font = hb_ft_font_create_referenced(face); FT_Done_Face(face); // Do a first run of shaping. Hopefully it's enough unsigned int n_glyphs = 0; hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, full_string.data(), full_string.size(), start, embedding_size); hb_buffer_guess_segment_properties(buffer); hb_buffer_set_direction(buffer, dir % 2 == 0 ? HB_DIRECTION_LTR : HB_DIRECTION_RTL); hb_glyph_info_t *glyph_info = NULL; hb_shape(font, buffer, features.data(), features.size()); 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; } shape_info.embeddings.emplace_back(); shape_info.embeddings.back().embedding_level = std::abs(dir); shape_info.embeddings.back().terminates_paragraph = false; shape_info.embeddings.back().full_width = 0; bool embed_is_ltr = dir % 2 == 0; // See if all characters hav been found in the default font unsigned int current_font = dir < 0 ? 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, embed_is_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, start, shape_info, fallback_sizes, fallback_scales); fill_glyph_info(shape_info.embeddings.back()); hb_font_destroy(font); shape_info.embeddings.back().fallbacks = fallbacks; shape_info.embeddings.back().fallback_size = fallback_sizes; shape_info.embeddings.back().fallback_scaling = fallback_scales; 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; // Find the next fallback font to try out (starting with default) ++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) { // This shouldn't happen but oh well. We give up on fallbacks and shape with what we got break; } int error = 0; bool using_new = false; font = load_fallback(current_font, start + fallback_start, start + fallback_end, error, using_new, shape_info, fallbacks, fallback_sizes, fallback_scales); 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", fallbacks[current_font].file, fallbacks[current_font].index); error_code = error; shape_info.embeddings.pop_back(); return false; } do { // Go through not yet resolved chars and see if the current fallback can resolve it hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, full_string.data(), full_string.size(), start + fallback_start, fallback_end - fallback_start); hb_buffer_guess_segment_properties(buffer); hb_buffer_set_direction(buffer, dir % 2 == 0 ? HB_DIRECTION_LTR : HB_DIRECTION_RTL); 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, embed_is_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] >= fallbacks.size()) { char_font[i] = 0; } } // The following two blocks only differ in the direction of operation if (embed_is_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) { // Move on until we hit a new font. Then shape everything up until current point // and add to embedding struct int error = 0; FT_Face face = get_cached_or_new_face(fallbacks[current_font].file, fallbacks[current_font].index, shape_info.size, shape_info.res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", fallbacks[current_font].file, fallbacks[current_font].index); error_code = error; return false; } font = hb_ft_font_create_referenced(face); FT_Done_Face(face); hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, full_string.data(), full_string.size(), start + text_run_start, i - text_run_start); hb_buffer_guess_segment_properties(buffer); hb_buffer_set_direction(buffer, dir % 2 == 0 ? HB_DIRECTION_LTR : HB_DIRECTION_RTL); 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, start + text_run_start, shape_info, fallback_sizes, fallback_scales); fill_glyph_info(shape_info.embeddings.back()); hb_font_destroy(font); if (i < embedding_size) { current_font = char_font[i]; if (current_font >= 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_or_new_face(fallbacks[current_font].file, fallbacks[current_font].index, shape_info.size, shape_info.res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", fallbacks[current_font].file, fallbacks[current_font].index); error_code = error; return false; } font = hb_ft_font_create_referenced(face); FT_Done_Face(face); hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, full_string.data(), full_string.size(), start + i, text_run_end - i); hb_buffer_guess_segment_properties(buffer); hb_buffer_set_direction(buffer, dir % 2 == 0 ? HB_DIRECTION_LTR : HB_DIRECTION_RTL); 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, start + i, shape_info, fallback_sizes, fallback_scales); fill_glyph_info(shape_info.embeddings.back()); hb_font_destroy(font); if (i > 0) { current_font = char_font[i - 1]; if (current_font >= fallbacks.size()) current_font = 0; text_run_end = i; } } } } shape_info.embeddings.back().fallbacks = fallbacks; shape_info.embeddings.back().fallback_size = fallback_sizes; shape_info.embeddings.back().fallback_scaling = fallback_scales; return true; } // Load a font from the fallback vector with the correct sizing etc hb_font_t* HarfBuzzShaper::load_fallback(unsigned int font, unsigned int start, unsigned int end, int& error, bool& new_added, ShapeInfo& shape_info, std::vector& fallbacks, std::vector& fallback_sizes, std::vector& fallback_scales) { new_added = false; // Font should only be able to be maximally the size of fallbacks if (font >= fallbacks.size()) { int n_conv = 0; const char* fallback_string = utf_converter.convert_to_utf(full_string.data() + start, end - start, n_conv); fallbacks.push_back( get_fallback(fallback_string, fallbacks[0].file, fallbacks[0].index) ); new_added = true; } FT_Face face = get_font_sizing(fallbacks[font], shape_info.size, shape_info.res, fallback_sizes, fallback_scales); hb_font_t* hbfont = hb_ft_font_create_referenced(face); FT_Done_Face(face); return hbfont; } // Find the next run of text for a specific fallback font 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; } // Find areas in the string that aren't covered by current font 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 && !glyph_must_hard_break(current_cluster)) { 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; } } } } // Add shaping info to the embedding structure 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, unsigned int cluster_offset, ShapeInfo& shape_info, std::vector& fallback_sizes, std::vector& fallback_scales) { double scaling = fallback_scales[font_id]; if (scaling < 0) scaling = 1.0; double tracking = shape_info.tracking * fallback_sizes[font_id] / 1000; #if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 ascend = 0; descend = 0; #else hb_font_extents_t fextent; hb_font_get_h_extents(font, &fextent); ascend = fextent.ascender; descend = fextent.descender; #endif hb_glyph_extents_t extent; EmbedInfo& embedding = shape_info.embeddings.back(); int new_size = embedding.glyph_id.size() + n_glyphs; embedding.glyph_id.reserve(new_size); embedding.glyph_cluster.reserve(new_size); embedding.x_offset.reserve(new_size); embedding.y_offset.reserve(new_size); embedding.x_advance.reserve(new_size); embedding.y_advance.reserve(new_size); embedding.x_bear.reserve(new_size); embedding.y_bear.reserve(new_size); embedding.width.reserve(new_size); embedding.height.reserve(new_size); embedding.ascenders.reserve(new_size); embedding.descenders.reserve(new_size); embedding.font.reserve(new_size); for (unsigned int i = 0; i < n_glyphs; ++i) { embedding.glyph_id.push_back(glyph_info[i].codepoint); embedding.glyph_cluster.push_back(glyph_info[i].cluster); embedding.x_offset.push_back(glyph_pos[i].x_offset * scaling); embedding.y_offset.push_back(glyph_pos[i].y_offset * scaling); embedding.x_advance.push_back(glyph_pos[i].x_advance * scaling + tracking); embedding.y_advance.push_back(glyph_pos[i].y_advance * scaling); embedding.full_width += embedding.x_advance.back(); hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); embedding.x_bear.push_back(extent.x_bearing * scaling); embedding.y_bear.push_back(extent.y_bearing * scaling); embedding.width.push_back(extent.width * scaling); embedding.height.push_back(extent.height * scaling); embedding.ascenders.push_back(ascend * scaling); embedding.descenders.push_back(descend * scaling); embedding.font.push_back(font_id); } } // Add line breaking/stretching info to embedding structure void HarfBuzzShaper::fill_glyph_info(EmbedInfo& embedding) { for (size_t i = embedding.is_blank.size(); i < embedding.glyph_cluster.size(); ++i) { int32_t cluster = embedding.glyph_cluster[i]; if (cluster < full_string.size()) { embedding.is_blank.push_back(glyph_is_blank(full_string[cluster])); embedding.may_break.push_back(glyph_may_soft_break(cluster)); embedding.may_stretch.push_back(glyph_may_stretch(full_string[cluster])); } else { embedding.is_blank.push_back(false); embedding.may_break.push_back(false); embedding.may_stretch.push_back(false); } } } inline FT_Face HarfBuzzShaper::get_font_sizing(FontSettings& font_info, double size, double res, std::vector& sizes, std::vector& scales) { int error = 0; FT_Face face = get_cached_or_new_face(font_info.file, font_info.index, size, res, &error); if (error != 0) { Rprintf("Failed to get face: %s, %i\n", font_info.file, font_info.index); error_code = error; return nullptr; } double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 * res / 72.0 / face->size->metrics.height; double fscaling = family_scaling(face->family_name); scales.push_back(scaling * fscaling); sizes.push_back(size * fscaling); return face; } void HarfBuzzShaper::insert_hyphen(EmbedInfo& embedding, size_t where) { int error = 0; // Load main font (emoji if dir is negative) // Shouldn't be able to fail as we have already tried to load it in the calling function FT_Face face = get_cached_or_new_face( embedding.fallbacks[embedding.font[where]].file, embedding.fallbacks[embedding.font[where]].index, embedding.fallback_size[embedding.font[where]], shape_infos[0].res, &error ); if (error) { return; } double scaling = embedding.fallback_scaling[embedding.font[where]]; if (scaling < 0) scaling = 1.0; hb_font_t *font = hb_ft_font_create_referenced(face); FT_Done_Face(face); hb_codepoint_t glyph = 0; hb_bool_t found = hb_font_get_glyph(font, 8208, 0, &glyph); // True hyphen; if (!found) found = hb_font_get_glyph(font, 45, 0, &glyph); // Hyphen minus if (!found) return; // No hyphen-like glyph in font embedding.glyph_id[where] = glyph; hb_position_t x = hb_font_get_glyph_h_advance(font, glyph); hb_position_t y = 0; embedding.x_advance[where] = x * scaling; if (embedding.glyph_cluster[where] > 0) { hb_font_get_glyph_kerning_for_direction(font, full_string[embedding.glyph_cluster[where] - 1], glyph, embedding.embedding_level % 2 == 0 ? HB_DIRECTION_LTR : HB_DIRECTION_RTL, &x, &y); } else { x = 0; } embedding.x_offset[where] = x * scaling; embedding.y_offset[where] = y * scaling; hb_glyph_extents_t extent; hb_font_get_glyph_extents(font, glyph, &extent); embedding.x_bear[where] = extent.x_bearing * scaling; embedding.y_bear[where] = extent.y_bearing * scaling; embedding.width[where] = extent.width * scaling; embedding.height[where] = extent.height * scaling; hb_font_destroy(font); } bool HarfBuzzShaper::has_valid_break(const EmbedInfo& embedding, int32_t width, size_t& break_pos, bool force) { bool has_break = false; int32_t w = 0; if (embedding.embedding_level % 2 == 0) { // ltr break_pos = 0; for (size_t i = 0; i < embedding.glyph_id.size(); ++i) { // If the glyph is blank we don't care if it extends beyond the max width if (embedding.is_blank[i] && embedding.may_break[i]) { break_pos = i; has_break = true; } w += embedding.x_advance[i]; if (w > width) { if (force) { // If forcing we need to consume at least one glyph break_pos = std::max(i - 1, size_t(1)); return true; } return has_break; } // If it isn't blank we wait if (!embedding.is_blank[i] && embedding.may_break[i]) { break_pos = i; has_break = true; } } } else { for (size_t i = embedding.glyph_id.size(); i > 0; --i) { // If the glyph is blank we don't care if it extends beyond the max width if (embedding.is_blank[i - 1] && embedding.may_break[i - 1]) { break_pos = i - 1; has_break = true; } w += embedding.x_advance[i - 1]; if (w > width) { if (force) { // If forcing we need to consume at least one glyph break_pos = std::min(i, embedding.glyph_id.size() - 2); return true; } return has_break; } // If it isn't blank we wait if (!embedding.is_blank[i - 1] && embedding.may_break[i - 1]) { break_pos = i - 1; has_break = true; } } } return has_break; } void HarfBuzzShaper::rearrange_embeddings(std::list& line) { static std::vector::iterator> embed_stack(125); // Max nesting level allowed by ICU bidi algo if (line.size() < 2) return; // Nothing to do embed_stack[0] = line.begin(); size_t current_embed = 0; for (auto iter = line.begin(); iter != line.end(); ++iter) { size_t cur_embed_level = iter->embedding_level; // Nothing to do here but also shouldn't happen if (cur_embed_level == current_embed) continue; if (cur_embed_level > current_embed) { // We fill the stack with current position up to the embeddings level while (current_embed != cur_embed_level) { ++current_embed; embed_stack[current_embed] = iter; } } else { // We are exiting a run. Need to reverse everything above while (current_embed != cur_embed_level) { std::reverse(embed_stack[current_embed], iter); --current_embed; } } } // We need to reverse until we hit 0 while (current_embed != 0) { std::reverse(embed_stack[current_embed], line.end()); --current_embed; } } std::list HarfBuzzShaper::get_next_line_at_width(int32_t width, std::list& all_embeddings, bool& hard_break, uint32_t& break_char) { std::list line; if (width < 0) { // No limit on width auto has_break = all_embeddings.end(); for (auto iter = all_embeddings.begin(); iter != all_embeddings.end(); ++iter) { if (iter->terminates_paragraph) { has_break = iter; break; } } if (has_break == all_embeddings.end()) { // No line breaks. Use the full embedding line.swap(all_embeddings); hard_break = false; } else { line.splice(line.begin(), all_embeddings, all_embeddings.begin(), std::next(has_break)); hard_break = true; break_char = has_break->pop(); } } else { auto iter = all_embeddings.begin(); int32_t cum_width = 0; while (cum_width <= width && iter != all_embeddings.end()) { cum_width += iter->full_width; ++iter; if (std::prev(iter)->terminates_paragraph) break; } if (cum_width > width) { // Last embedding needs to be cut if possible hard_break = false; auto orig_iter = iter; auto orig_width = cum_width; size_t break_at = 0; size_t from, to; bool force = false; while (true) { --iter; cum_width -= iter->full_width; // Search for a break in the embedding that satisfies our width constraint if (has_valid_break(*iter, width - cum_width, break_at, force)) { if (iter->embedding_level % 2 == 0) { from = 0; to = break_at + 1; } else { from = break_at; to = iter->glyph_id.size(); } // Did we break at soft hyphen? bool is_shy = full_string[iter->glyph_cluster[break_at]] == 173; // We found one. Split the first part into half tail and assemble line line.splice(line.begin(), all_embeddings, all_embeddings.begin(), iter); line.emplace_back(); iter->split(from, to, line.back()); if (is_shy) { // Substitute soft hyphen with hyphen size_t at = line.back().embedding_level % 2 == 0 ? line.back().glyph_id.size() - 1 : 0; insert_hyphen(line.back(), at); } break; } // We shouldn't hit the below condition since force == true should find a break immediately if (iter == all_embeddings.begin() && force) break; if (iter == all_embeddings.begin()) { // We didn't find a break. Reset and set force = true iter = orig_iter; cum_width = orig_width; force = true; } } if (line.empty()) { // If we end here we completely failed to fit a single glyph to the line cpp11::stop("Failed to wrap lines"); } } else if (iter == all_embeddings.end()) { hard_break = all_embeddings.back().terminates_paragraph; line.swap(all_embeddings); // It all fits on the line } else { // It got stopped by a hard line break hard_break = true; break_char = std::prev(iter)->pop(); line.splice(line.begin(), all_embeddings, all_embeddings.begin(), iter); } } rearrange_embeddings(line); return line; } void HarfBuzzShaper::do_alignment(bool ltr) { if (cur_align == 1 || cur_align == 2) { // Standard center or right justify // Move x_pos based on the linewidth of the line and the full width of the text for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; int32_t lwd = line_width[index]; x_pos[i] = cur_align == 1 ? x_pos[i] + width/2 - lwd/2 : x_pos[i] + width - lwd; } // Do the same with pen position pen_x = cur_align == 1 ? pen_x + width/2 - line_width.back()/2 : pen_x + width - line_width.back(); } if (cur_align == 3 || cur_align == 4 || cur_align == 5) { // Justified alignment std::vector n_stretches(line_width.size(), 0); std::vector no_stretch(line_width.size(), false); for (size_t i = 0; i < x_pos.size(); ++i) { // Loop through all glyphs to figure out number of stretches per line and if // stretches are allowed (not for last line or lines with forced linebreak) int index = line_id[i]; no_stretch[index] = no_stretch[index] || index == line_width.size() - 1 || line_must_break[index]; if (may_stretch[i] && i-1 < x_pos.size() && index == line_id[i+1]) { n_stretches[index]++; } } int32_t cum_move = 0; for (size_t i = 0; i < x_pos.size(); ++i) { // Loop through glyphs, spreading them out int index = line_id[i]; int32_t lwd = line_width[index]; if (no_stretch[index] || n_stretches[index] == 0) { // If line may not stretch instead move it according to alignment if (cur_align == 4) { x_pos[i] = x_pos[i] + width/2 - lwd/2; } else if (cur_align == 5) { x_pos[i] = x_pos[i] + width - lwd; } continue; } if (i == 0 || line_id[i-1] != index) { // New line, reset stretch counter cum_move = 0; } // Move x_pos according to the cummulative move counter x_pos[i] += cum_move; if (may_stretch[i]) { // If white space the counter gets increased cum_move += (width - line_width[index]) / n_stretches[index]; } } // Update pen_x to match position of last line if (ltr) { // If last line is empty ignore cum_move pen_x += line_id.back() == line_width.size() - 1 ? cum_move : 0; } if (no_stretch.back() || n_stretches.back() == 0) { if (cur_align == 4) { pen_x += width/2 - line_width.back()/2; } else if (cur_align == 5) { pen_x += width - line_width.back(); } } // Update line width of all stretched lines to match the full width of the textbox for (size_t i = 0; i < line_width.size(); ++i) { if (!no_stretch[i] && n_stretches[i] != 0) line_width[i] = width; } } if (cur_align == 6) { // Distribute glyphs evenly over line std::vector n_glyphs(line_width.size(), 0); for (size_t i = 0; i < x_pos.size(); ++i) { // Count number of glyphs on each line (not including carriage return) int index = line_id[i]; if (!line_must_break[index] && (i == x_pos.size()-1 || index == line_id[i+1])) { n_glyphs[index]++; } } // Spread out glyphs according to an accumulating spread factor int32_t cum_move = 0; for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; if (i == 0 || line_id[i-1] != index) { cum_move = 0; } x_pos[i] += cum_move; cum_move += (width - line_width[index]) / (n_glyphs[index]-1); } pen_x += cum_move; // Update line width of all stretched lines to match the full width of the textbox for (size_t i = 0; i < line_width.size(); ++i) { if (n_glyphs[i] != 0) line_width[i] = width; } } } #endif textshaping/src/face_feature.h0000644000176200001440000000031714737506142016173 0ustar liggesusers#pragma once #include #include #include [[cpp11::register]] cpp11::writable::list get_face_features_c(cpp11::strings path, cpp11::integers index); textshaping/NAMESPACE0000644000176200001440000000054314737546303014045 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(get_font_features) export(lorem_bidi) export(lorem_text) export(plot_shape) export(shape_text) export(text_width) importFrom(lifecycle,deprecated) importFrom(systemfonts,font_feature) importFrom(systemfonts,match_fonts) importFrom(systemfonts,system_fonts) useDynLib(textshaping, .registration = TRUE) textshaping/LICENSE0000644000176200001440000000006115002133362013605 0ustar liggesusersYEAR: 2025 COPYRIGHT HOLDER: textshaping authors textshaping/NEWS.md0000644000176200001440000000703415072133245013714 0ustar liggesusers# textshaping 1.0.4 * Guard against issues related to incompatible versions of freetype in systemfonts and textshaping (#68). # textshaping 1.0.3 * Fixed a signed integer overflow in the the fix for ragg#193 # textshaping 1.0.2 * Fixed a bug in bidi embedding arrangement when shaping a single line (ggplot2#6497) * Fixed a bug in shape caching due to a weak vector hash implementation (ragg#193) * Fixed a bug in line positioning when line containes mix of different sizes # textshaping 1.0.1 * Fixed a bug where hard line breaks where ignored if the line consisted of multiple embeddings (marquee#58) * Fixed a bug where newline characters would increase the width of a line and lead to an empty line getting inserted in some situations (marquee#55) # textshaping 1.0.0 * Added `lorem_text()` and `lorem_bidi()` for generating nonsense text in various scripts * Added `plot_shape()` to plot the result of a shaping along with the metrics * Rewrite of the shaping engine to honor global direction of text. It introduces a `direction` argument to `shape_text()` that defaults to `auto`, meaning that it is deduced from the content of the shaped text. `align` gets two new settings that responds to the global direction of the text. `"auto"` will chose between `"left"` and `"right"` and `"justified"` will choose between `"justified-left"` and `"justified-right"` depending of the global direction is ltr or rtl. Lastly the soft break locations are now based on ICU and thus better support ideographic scripts such as Han/Kanji/Hangul. * Textshaping now properly supports soft hyphens in that a hyphen is rendered if a soft-wrap happens at a soft hyphen (#52) # textshaping 0.4.1 * Make compiled code somewhat less assumptive about the correctness of the input * Fix a bug from too aggressive early exiting shaping of strings with no max width (#45) * Fixed a mismatch between the default values of the `width` argument in `shape_text()` and `systemfonts::match_fonts()` (#44) * Updated `text_width()` to take the same inputs as `shape_text()` # textshaping 0.4.0 * Full rewrite of `shape_text()` to allow proper font-fallback, bidi text support, support for font-features, spacers, new align settings, etc. # 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/inst/0000755000176200001440000000000015072133301013560 5ustar liggesuserstextshaping/inst/include/0000755000176200001440000000000014737546303015224 5ustar liggesuserstextshaping/inst/include/textshaping.h0000644000176200001440000000640414737546303017737 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/inst/lorem/0000755000176200001440000000000014737311031014703 5ustar liggesuserstextshaping/inst/lorem/README0000644000176200001440000000010414737311031015556 0ustar liggesusersText in this folder generated at https://generator.lorem-ipsum.info textshaping/inst/lorem/greek.txt0000644000176200001440000001133614737311031016545 0ustar liggesusersΛορεμ ιπσθμ δολορ σιτ αμετ, σθμο vερτερεμ ετ εαμ, αδ vελ θλλθμ cονστιτθτο, cθ σεδ σιμθλ αππετερε. Μει προβο ποσσε ιγνοτα αν. Εοσ ει ομνεσ vιδισσε, ιν θσθ vελιτ απεριρι. Περ vιταε αθδιαμ εθ. Ετ cασε δολορε σθσcιπιαντθρ ηισ, ει cορπορα πηαεδρθμ ιθσ, vελ εσσεντ ιριθρε cονcεπταμ θτ. Αν qθιδαμ φεθγιατ cονστιτθαμ προ, θσθ φαcετε δελενιτι αππαρεατ ιν, σιτ ενιμ qθαεστιο δισσεντιετ τε. Πριμισ ερθδιτι νεc cθ, ετ αβηορρεαντ ηονεστατισ ιντερπρεταρισ εστ. Μελ θτ σθασ ηαβεο τιμεαμ, vερο ταcιματεσ αδ νεc, vελ νε τριτανι qθαερενδθμ. Ει ρεβθμ ετιαμ qθο, εαμ αν τεμπορ αλτερθμ, ηισ ταμqθαμ εξπλιcαρι θτ. Ερρεμ σιμιλιqθε ηασ αν. Μελ σιμθλ περτιναξ αδ. Vιμ εα ετιαμ μαζιμ σcριπτορεμ, vελ αφφερτ αccθσαμ λθπτατθμ νε. Μαγνα cιvιβθσ σενσεριτ εοσ νε, ατ σιτ qθοτ ερρορ ιμπερδιετ. Cθ cθμ μελιθσ λατινε vιτθπερατοριβθσ. Ατ qθο νθλλαμ vολθπταρια cοτιδιεqθε, ιδ vιρισ αccομμοδαρε νεc, ιθσ εα μαζιμ οφφενδιτ μνεσαρcηθμ. Μελ αν πθρτο λεγερε, ναμ εξ τατιον λαβιτθρ τορqθατοσ. Αδ νθλλαμ ρεφορμιδανσ μελ, νε σεδ γραεcισ qθαεστιο σπλενδιδε, ατ εοσ cομμοδο αντιοπαμ ρεφορμιδανσ. Ιδ προβο αδvερσαριθμ εθμ, εθμ ει ρεcθσαβο αccομμοδαρε, εαμ εσσε ιλλθδ ποπθλο αν. Επιcθρι vολθπταρια qθι αδ, νθσqθαμ vολθπτθα προ ει. Αδ ερθδιτι ερροριβθσ προ, δθο cθ vιρισ ελειφενδ. Θρβανιτασ ελοqθεντιαμ cοντεντιονεσ νε qθο. Ετ σιτ πθταντ αccθμσαν ιμπερδιετ. Εστ οδιο αλτερθμ ιν. Αδ δισσεντιθντ αccομμοδαρε εστ. Πριμα οφφενδιτ τεμποριβθσ εστ εθ. Εθμ νισλ αδμοδθμ τινcιδθντ νε, πρι ποσσε vολθπταρια ατ. Cθ νιβη αλιqθιπ ελειφενδ μει. Βρθτε οπορτερε μελ νο, εριπθιτ πρινcιπεσ αδιπισcινγ ιν δθο, ιν τιμεαμ vολθμθσ πρι. Ταλε διcιτ ετ εστ. Vεριτθσ εθριπιδισ εθμ cθ, προβατθσ σαλθτανδι δεμοcριτθμ vιμ εθ. Εθ ωισι λαβορε cονστιτθαμ vιμ, ελιτ ιριθρε qθο νο. Δενιqθε δεφινιτιονεμ ατ ιθσ. Ηασ θτ δθισ ιριθρε αππελλαντθρ, τε αλτερα τηεοπηραστθσ cθμ. Εστ ετ qθασ πηαεδρθμ. Οπορτεατ cοτιδιεqθε νε εοσ, ναμ qθισ cομπρεηενσαμ αδ. Ιδ vισ σαπιεντεμ σιμιλιqθε μαιεστατισ, ωισι θταμθρ δισπθτανδο νε προ. Ποσσε μολεστιαε σεντεντιαε ηασ ετ, cιvιβθσ ομνεσqθε σθαvιτατε θσθ νε. Εξ πρι vελιτ απειριαν, ζριλ ορνατθσ πονδερθμ αν εοσ. Ελειφενδ περιcθλισ cονcλθδατθρqθε νεc εθ, εξ μελ ελιτ αθδιαμ ποστθλαντ. Cθ δολορ διcθντ qθι, εξ ινανι λιβρισ ειρμοδ δθο. Τε ελιγενδι πατριοqθε τεμποριβθσ εοσ, σιμθλ οcθρρερετ cθ εοσ, πηαεδρθμ σενσεριτ νε qθι. Σεα λιβερ λαβορεσ αππετερε νο. Εα μει vιδισσε βλανδιτ vερτερεμ. Εστ cθ ομιτταμ μολεστιε ελαβοραρετ, δελενιτι περσεcθτι ιθσ εα, cθ δθο vερι τατιον λεγενδοσ. Αδ ναμ πλατονεμ σιμιλιqθε. Εοσ πλαcερατ σινγθλισ σcριπτορεμ ιδ, μελ εξ qθοδσι ινστρθcτιορ. Ιθσ εξ ελειφενδ ινcιδεριντ. Qθοδ οδιο ατ εαμ, διcο δελενιτι cονcεπταμ qθι εξ, εvερτι μεντιτθμ μει ιδ. Τε qθασ εριπθιτ μολεστιαε σεα. Εα ιπσθμ λορεμ ηισ, νεc αδ νονθμεσ ελειφενδ ιμπερδιετ. Vιμ νιβη αθγθε αν, εαμ ετ vοcιβθσ cονcεπταμ. textshaping/inst/lorem/hangul.txt0000644000176200001440000000706714737311031016734 0ustar liggesusers탄핵소추의 의결을 받은 자는 탄핵심판이 있을 때까지 그 권한행사가 정지된다. 국무총리 또는 국무위원이 출석요구를 받은 때에는 국무위원 또는 정부위원으로 하여금 출석·답변하게 할 수 있다, 대통령후보자가 1인일 때에는 그 득표수가 선거권자 총수의 3분의 1 이상이 아니면 대통령으로 당선될 수 없다. 교육의 자주성·전문성·정치적 중립성 및 대학의 자율성은 법률이 정하는 바에 의하여 보장된다. 대통령이 궐위되거나 사고로 인하여 직무를 수행할 수 없을 때에는 국무총리, 사형을 선고한 경우에는 그러하지 아니하다. 모든 국민의 재산권은 보장된다. 모든 국민은 법률이 정하는 바에 의하여 선거권을 가진다. 주거에 대한 압수나 수색을 할 때에는 검사의 신청에 의하여 법관이 발부한 영장을 제시하여야 한다, 국무총리는 국무위원의 해임을 대통령에게 건의할 수 있다, 법원의 내부규율과 사무처리에 관한 규칙을 제정할 수 있다. 그 자율적 활동과 발전을 보장한다. 통신·방송의 시설기준과 신문의 기능을 보장하기 위하여 필요한 사항은 법률로 정한다. 헌법개정안은 국회가 의결한 후 30일 이내에 국민투표에 붙여 국회의원선거권자 과반수의 투표와 투표자 과반수의 찬성을 얻어야 한다. 3인은 대법원장이 지명하는 자를 임명한다. 다만. 제2항과 제3항의 처분에 대하여는 법원에 제소할 수 없다. 국회는 헌법 또는 법률에 특별한 규정이 없는 한 재적의원 과반수의 출석과 출석의원 과반수의 찬성으로 의결한다. 모든 국민은 종교의 자유를 가진다, 국회의원은 법률이 정하는 직을 겸할 수 없다. 이 헌법을 시행하기 위하여 필요한 법률의 제정·개정과 이 헌법에 의한 대통령 및 국회의원의 선거 기타 이 헌법시행에 관한 준비는 이 헌법시행 전에 할 수 있다. 국가는 법률이 정하는 바에 의하여 정당운영에 필요한 자금을 보조할 수 있다. 국군의 외국에의 파견 또는 외국군대의 대한민국 영역안에서의 주류에 대한 동의권을 가진다, 교육의 자주성·전문성·정치적 중립성 및 대학의 자율성은 법률이 정하는 바에 의하여 보장된다. 200인 이상으로 한다. 국정감사 및 조사에 관한 절차 기타 필요한 사항은 법률로 정한다. 직전대통령이 없을 때에는 대통령이 지명한다. 국가 및 법률이 정한 단체의 회계검사와 행정기관 및 공무원의 직무에 관한 감찰을 하기 위하여 대통령 소속하에 감사원을 둔다. 국회의원의 수는 법률로 정하되. 제1항의 지시를 받은 당해 행정기관은 이에 응하여야 한다. 직전대통령이 없을 때에는 대통령이 지명한다. 대한민국의 경제질서는 개인과 기업의 경제상의 자유와 창의를 존중함을 기본으로 한다. 국군은 국가의 안전보장과 국토방위의 신성한 의무를 수행함을 사명으로 하며. 탄핵의 결정. 군인은 현역을 면한 후가 아니면 국무총리로 임명될 수 없다. 연소자의 근로는 특별한 보호를 받는다. 모든 국민은 보건에 관하여 국가의 보호를 받는다. 시장의 지배와 경제력의 남용을 방지하며, 국가는 법률이 정하는 바에 의하여 재외국민을 보호할 의무를 진다. 어떠한 형태로도 이를 창설할 수 없다. textshaping/inst/lorem/hebrew.txt0000644000176200001440000000773014737311031016727 0ustar liggesusersאו כדי הבאים מדויקים. לחיבור ליצירתה תנך או. את כתב התפתחות לימודים, מה עוד כימיה תבניות איטליה, צעד של כניסה המזנון ייִדיש. כדי או והוא כלשהו רביעי, אחר קהילה מבוקשים מה, רומנית בלשנות דת זכר. על ארץ שונה לתרום למתחילים, ארץ כניסה לעריכת או. ויש דת מדינות אתנולוגיה לרפובליקה. המלחמה טכנולוגיה אם לוח, של רפואה משופרות אנתרופולוגיה שתי, הראשי ויקימדיה את אתה. שער את הרוח מחליטה רומנית. אל לכאן שדרות ניהול אתה, דרכה ישראל משפטית בדף את. אחד על מחליטה ייִדיש והגולשים, פולנית פילוסופיה שכל אל. שתפו כלכלה צרפתית דת אנא, את כלל קהילה ובמתן משפטית. רבה דת כניסה האטמוספירה, גם התוכן קודמות לחשבון זאת. מיזמי תרבות שאלות בה קרן, שער את באגים אחרות. צילום הראשי של היא. ואמנות רומנית תיקונים היא ב. ובמתן המלחמה שימושי ארץ מה. של מדע בהבנה אחרות וקשקש, סרבול בארגז האטמוספירה מלא על. בהבנה ליצירתה ב שער, מתן חשמל פיסול חינוך את. על אחר סרבול אחרות. המשפט ואלקטרוניקה גם רבה, סדר מפתח אינו משפטית על. טיפול ננקטת דת זכר, בגרסה בדפים המדינה אם כלל. מה יוני לערוך יוצרים ארץ. ראשי למאמרים שמו אל. דת ציור שאלות קרן, והנדסה התפתחות האנציקלופדיה דת מתן. גם והוא אחרות ביוטכנולוגיה תנך, שדרות גרמנית מיוחדים דת שתי, אתה מה הרוח בויקיפדיה אתנולוגיה. בה שאלות בעברית ואלקטרוניקה מדע, לחבר לעתים צעד או. כתב בה תבניות שינויים לויקיפדיה. כתב מרצועת אווירונאוטיקה בה, נפלו רקטות מה מתן, קהילה ופיתוחה ויקימדיה צ'ט בה. ב מתן ספורט חבריכם גיאוגרפיה, מדע ב והנדסה תקשורת שימושי. זאת התוכן אודות למתחילים על. היא בה בשפות המלחמה הקהילה, בדף של לערך וקשקש. ציור להפוך כתב דת, או ניווט העמוד וקשקש מדע, ב כלכלה העריכהגירסאות מדע. גם מדע החלל לחבר רקטות, כלים רוסית אחר של. בדף והנדסה רב־לשוני אנציקלופדיה מה, צעד על זקוק לערכים. הנדסת לחיבור אספרנטו ב שכל, צעד הרוח קרימינולוגיה על. מתן תיבת ולחבר מיוחדים של. בחירות ויקיפדיה פסיכולוגיה כלל או, ויש בלשנות שינויים בה, רבה לחבר כיצד את. מתן קהילה ביוטכנולוגיה דת. בשפות לחיבור של אחד, נפלו איטליה בה אחר. ב כדי טבלאות אינטרנט, רבה שפות דפים מה. מלא את כלשהו שימושי אתנולוגיה, שיתופית מועמדים למאמרים או ארץ. רבה עזרה העברית קצרמרים מה, שמות כלשהו בישול של זכר. של עוד אקראי רומנית. מתן הנדסת מוסיקה וספציפיים דת, כתב הארץ לרפובליקה אם, כלשהו וספציפיים ויש על. עמוד בגרסה בקלות בה אנא. מה לוח תוכל טיפול וקשקש, שכל תקשורת טכנולוגיה את. את המקובל איטליה לויקיפדים אחר. מה שתי אחרים המדינה אינטרנט. לערוך בישול הבאים דת רבה. כדי על בהבנה יוצרים. textshaping/inst/lorem/chinese.txt0000644000176200001440000001113614737311031017064 0ustar liggesusers済護髄分望善津激詐約外堀脱子更雨。盤守基放著算治統図稿開態口除人政委卒半。携投世舗阪研腰馬汚担送万。江払州禁町履随抗芸著赤月車芸。取金作察報動泰話魁全改話記快博。答無禁政特込備受違下父事覧来名動地利読。点宮賞東投別呼転南報満育要属出千日報。更実図書港発真垂訃書第止微広強親。文雪企故準後多代終価聞事席方地閲本文。 会輪向毎武調内耕復区竹色決記足掲。況供邦取年合信治治隊入大容雲員調進的。歳山記実団田俊顔備山彼言。幕見施亜憩係完索建考存庫試討委者覇間。勝演見経中晋社業林余伝作受済若端群経。象庁勝立主量庫際要映昌記責本満。快放計山在希報行聞指師提信方覧拠。選沢告行版湿部万知家能常。読画読構発涙審活伝男委典今就師右指無案然。 建観母個探載決外更能容技。容損書育宿詳案物砂沼賞闘階有止平。治生西恵短芸色上江序起掲組除羅。紙供月表属通前所分減銀画情予新著新査賃。規安大殺速望均誘間改示堀勤賀市撮。暮講起子更新高警主秀議座本印信読型映通。理寛倉獲策章芸乱宣彼報意供安着。市投渉記今罪北騰意細棄彦宣産能航会。識職田喜新博制講告催備通。 年手補健社内山治父事佐台決応味扱。程戦次引話文観視派率裕縄社長。恐刑聞終葉拠西豪安東況元解。誌人謙伸像阻真趣玲便将合予取再毎祈着。校山回感団強掲三堅若分足欄却読思駐家済三。女暑由許己面工国寺四就立動九受京忠。木読常通習公抗質朝東入藤治回導堀木転。后際屋聞強次続育配活歓天被大泊木非。観査製禁政覧情前問作誇後寄安取動高。 時就報応対堀告物紙文強趣指措台多教芸例担。継版家者暮東苑抜旬代本嶺違面佐。断予謙背解崖場護率合線上申毎必。伊消弁隠素来氏冬豪重輸顔故伏毒聞定集国。全察江全芸均面全黒処行誇健発改逮来都加。鈴権会利並社田策四和福質車右一。毎平性用識戸映字闘上喚主谿。途覧減副万憲産楽党斎言泊田目国九。探済改企速幹結案出入側減様路策重呂法。 権格琴心競町億明場会予比断警隆州十面教回。競篤上覧集掲警逆発栃際海様大。総円逮想連英初健月情提遠写難麻塀月録芸。男小勢最盤条告部経男者必全無。黒読一口感止放交専内反一意刊料礎談想試。昔際柱稲当天計分負着堀勇暮法平武略写。開写断与大延上漏北国周津板社作稿政年済。載放相支来酒選管物欺紙報到梨結。治必真海再産関悪表暗選広。 禁合試情松皿権戸光制新地止。点何小活協魚康将安感速難新辺素。純三難真佐歌童長選割引意棋載格田講慈投所。懸文育月機治育禁女記銀音緊放高表方。面道一案同文雑観周載浦土容誌威掲発。停母全松虎済管表毎逆木大山焼見建冷理。法務連約権交口職催戦元開流漏京清懐肉済。求奈見展的生日康玉明写文。経潜紹季案日理耐母自上格西。 無滋細特込金守田聞憤記読中事都格。今証読発球課読方横品安法断強古列半可権。指芸売経全部用俊転玲超告。中敗汁趣応迫京花不写権伊座報秀。育科地品問本園天軍未辞目江。都面良算間毎済罪集際然問。数筑期録療作看聞担兵能入茨国求間移。大産次聞求西母近際学行害台書転。化緩投馬権並過売地出決撤崎積金。贈堀陸島前工実読問本稼利。 時禁死試推員担約種振之現同。地今聴小関文講速治記然投図大藤全。阻治症入際展発極入班負機査隠圧最。業真質禁計診松限冨京玲夢黒支祝待結。港年売掘規報熊主設止者襲社筆哲事主鍋指。引係等渡総正初久体何報問応助日住係突月敦。新必極並因紙壊道籍次少支後快治大車富。無敗時紙念黒緑作人大車際今面切。造障読月戸載真人品施輪訳支惑使断。 年基奈想裏家好樹展授会成動探。潜道介禁分変操務知禁携管念。子像康話毎情担年般案発退本付促首烈社年。権普本厚供繊録多松定年北量。太日歌始僚系符算権田手業自学議線権名育突。出吾月上形聞他壁親県最貧予宿第明圏。独覚楽転藤慈面権消束来請就注述。試大掲久税然朝県隊駒発目円好入惑最実条施。臨理断黒人月定入歳権継手電思体騰告増権。 textshaping/inst/lorem/arabic.txt0000644000176200001440000001112114737311031016661 0ustar liggesusersبـ جُل وحتى المتّبعة. حيث كنقطة الأهداف الأبرياء ثم, وبدأت إتفاقية الانجليزية جهة أي, كما الأجل الشتاء السيطرة أن. تم إجلاء الشهير العالمية بعد, ولم تطوير أعلنت التخطيط في. تحت ومضى تطوير أم, الى عالمية واتّجه و. تحرّك أجزاء تم انه, كل شيء إستعمل الإقتصادية. ٣٠ وتم الشرقية استعملت, إذ شيء والديون وانتهاءً. غريمه الطريق بالجانب أسر عل, حدى ٣٠ لكون الدول الباهضة. بـ عدم كانتا الأحمر لإعادة, أي تلك يعبأ الثانية. قدما وتزويده الثالث، و أسر. هو بلا أثره، اكتوبر, جيما الولايات أي دول, دون جورج واشتدّت ان. شرسة جزيرتي الأوروبية، هو تحت, ألمّ أواخر يكن إذ. كل به، بأضرار الإتحاد الأمريكي. إيو بقعة عملية اسبوعين عل, ٠٨٠٤ فاتّبع الشرقية بـ حتى. تم أمّا بالفشل يتم, من وبعض ليرتفع ضرب, دول سابق الهجوم ما. شدّت الحدود وتم عن, هو وصغار يتبقّ جديداً ذات. كلّ لم بوابة شاسعة العالم،. النزاع الشهيرة دنو لم. تم سياسة هاربر دون, عُقر جديدة الإمداد وقد عن, ثم وصل جيوب أراضي وبالتحديد،. هو يعبأ لعدم الشتاء، وقد, ومن هو احداث وأزيز للإتحاد, بال لم كردة الشطر بالولايات. بل أما الهجوم والعتاد, هذا أي وكسبت إيطاليا, وقد وحتّى الأجل وقدّموا ثم. لم انه مارد قامت. الأمم وتنامت الرئيسية ان بلا, هو دنو عملية التجارية, مكن ثم أطراف واندونيسيا،. لأداء للإتحاد والفرنسي دنو عل, بعض أي تونس وبلجيكا،, كردة أجزاء ومحاولة وتم ما. واقتصار بالتوقيع لمّ تم, وصل وبلجيكا، ايطاليا، الاندونيسية بـ. كرسي ليرتفع السيطرة بـ فصل, مما بـ ميناء الساحة وفرنسا. عل حتى الإتحاد الفرنسية, جمعت بفرض ويكيبيديا يكن في. هذا الشهيرة ومحاولة الدولارات قد, يبق قررت تسمّى اتفاق عل. علاقة الصينية بريطانيا-فرنسا أن فصل. مع إبّان ارتكبها مكن, وبعض الشهير الاندونيسية هذه أي. أخر في أثره، التقليدي, أسر مع والحزب وتنامت استرجاع, نهاية الإطلاق في كلّ. بل لمحاكم أفريقيا لكل, شعار الحكم الفرنسية بل انه, دون لعملة الستار المشتّتون أم. وقرى وقام تصرّف قد انه. تلك الجو أجزاء أي, والفلبين الإيطالية مدن بـ. هو الجنود والفرنسي حيث. كان الآلاف الانجليزية مع, أن بالفشل مقاومة ليرتفع نفس. بين ممثّلة الشتوية للإتحاد و. لان ان شدّت شاسعة, كان تزامناً الاندونيسية مع. بأضرار والكساد ولاتّساع قد عدد. جسيمة اليها بـ مما, كرسي الصفحة الأرضية إيو عن. على أراض بقيادة وحرمان بـ, أما عقبت وبداية اتفاقية كل, و النزاع تغييرات تعد. بحق قِبل ممثّلة الشتاء، أن. ٣٠ ليركز حاملات قُدُماً نفس. يطول شرسة العاصمة من فصل, بل صفحة أحكم الأعمال نفس. تمهيد وبداية الأرواح نفس هو, تحرير وتنامت إيطاليا انه مع. ٢٠٠٤ وسوء وقبل أسر عل, جديدة الشطر عل ولم, الدول وبحلول الطرفين ان يكن. و فقامت اسبوعين كلّ, أسيا مشروط ا كلّ من, كردة إعادة معاملة تلك هو. الآخر اليميني والإتحاد أي به،, الله احداث فقد تم. مع يتمكن المدن دول. و العسكري الإحتفاظ ومن, إيو أحدث أصقاع الصينية أم. مع تكتيكاً والمانيا ضرب, تشكيل علاقة لفرنسا هو بلا. ان بلا شموليةً والمانيا الشّعبين. وبدون للصين بها إذ, فرنسا الصعداء ان وتم. textshaping/inst/lorem/latin.txt0000644000176200001440000000522614737311031016560 0ustar liggesusersLorem ipsum dolor sit amet, et vidisse accusata conclusionemque qui. Saepe euripidis ea cum. Liber urbanitas ex eum. Rebum pericula cu per. Nec quod tritani at. Congue munere dictas at qui, ne elaboraret appellantur quo, pro id dolorum splendide aliquando. Per ne errem omittantur conclusionemque. Et eum novum dolores consectetuer, nam viris oblique deseruisse ut. Sed an hinc eripuit oportere, te nam idque movet conceptam. Nam ad regione singulis liberavisse, quaeque delenit sententiae pro ea. Diam iriure evertitur vim te. Nam diam tantas ut, te cetero volumus sea, quidam salutatus et per. Ex has fugit vitae animal. Vel posse omittam id, eos te habeo iudicabit. Euripidis voluptaria ullamcorper eu ius, no eos justo oratio saperet. Est diam sententiae ut, ne nam porro probatus, ex paulo nobis referrentur vis. Pri in lucilius forensibus scribentur. Usu cu sonet doming nominavi. Mea et elitr nominavi, id abhorreant deseruisse pro. Modo hinc ei sed, eius cotidieque no vix. Nonumy recteque intellegat ex cum. Sed ad semper consequat instructior, doming aeterno gloriatur has ut. Vel verear mandamus inciderint ei. Vocent nostrud vocibus nec ei, ius ei odio complectitur, vis possit laoreet et. Mel nisl vivendo platonem no, at cum veri solum posse. Mei in nisl oblique ponderum. Eu prima vocibus recteque eam, audiam oporteat adipiscing vel ei. Sed animal corpora disputationi ea, at vivendo epicuri his. Feugait offendit eos et. Eum nobis essent necessitatibus at, alia dicant sed in, vim habeo summo id. Usu tollit ubique euismod no, ius quod recteque salutatus no. Ad vim quidam delicata. Aeterno oporteat mel ea. His duis mutat expetendis eu, vix et copiosae rationibus. Odio stet agam qui id, eu eum possit principes intellegam. Nisl signiferumque ex quo. Alii minim vis cu. Atomorum electram conclusionemque duo te, ne tota perpetua pro. Case eius fastidii qui ne. Mea ei clita omnes signiferumque. Nec at elit pertinax, homero molestiae aliquando ex per. Id recusabo inimicus accommodare cum. Ius clita noluisse neglegentur cu, vim placerat gubergren an. Mel ut sensibus repudiandae, ea nominavi expetenda periculis usu. An utroque tacimates nec, quot consetetur an quo. Decore electram id sea. Mutat dignissim ei has. Sonet equidem voluptua nam eu, principes laboramus signiferumque quo eu, no eius erant percipitur has. Singulis imperdiet argumentum est an, pro ad oblique dolorum nominati. Ei usu epicurei interesset. Praesent gubergren voluptatum pri ad. Ei fabulas democritum suscipiantur vis, per et invidunt conclusionemque. His ex posse mucius scripta, iudico partem ut mel, quem suscipiantur quo ad. Qui ex enim graece ornatus. Et eam tritani appellantur, vis nihil conclusionemque te. textshaping/inst/lorem/kana.txt0000644000176200001440000001132714737311031016362 0ustar liggesusers止ぎしび元関けとぐ変有うんーそ政商ツシ作辞変茨ト点好ホフテ面兵エマ力生れずよ異育賀クホ巨像席識ほ広消スケハヲ圧生テトセア理82度ほぱ禁予現千ドそぽ。意ラぴ賀際へずリゃ今組チクマツ分経かやリ件載うげ多産ずたぞつ債夢肌ネメセケ神分摂ム康市ムマチア拾済十にとレひ一永ヌ意合ネアルイ人年楽らレフイ物法じはゅだ更祝ミユモマ名誌横レなし。 様ぐほ日映ヨメエ読著を格熱ぽ枠古見ロ公7護月サフ少針よ処稿1言ノムアク真作フリエレ提経フす首3体墓ら。格にルを陸毎的けな国入オメルソ掲点おぶン人学スえ失政セ夫百ちがび専部根備クリょ発野ヘニホセ断照ニ前午タセ物思ケリ要法債休障フませわ。掲ワリトウ撲物ほスひ下写をけドば活能まげこみ好要レーみ守3書エ浜政べそけ紙暮メユソ定待41所ネケク点自いド書衛づ本仏ぽ。 問そち個点か蓄長ツフ交2終ムナソヤ気目ゆけイぞ態読てつご竹要応さらドも本事べが儀投マカ婦類リぴ成93姿対ツハ頭選辺げ朝感ざっさた真富携のせ。治再の台69衆企ーぽ後利ぴレ見最大キフイネ増勝68野ヲナ終加ナ京略感微酸ス。天ーの涙止タセ備夜カソ芸講ヌネ洋杯イ安今性ッ護携雪をゃま気単げく活蔵じま性戦ユテア問断ハノカセ方活ょもまそ済6未ニヒミハ我歳サヤ整気ひ。 新コ長宣2話トヨスリ議式きラ員丸協リ度6差よしト願図ぽぴく冷体みゆぴ提冷ヱ報金ツシリ電原6内だ電劣じ。地針コモ止厳てかげ週端モイ素持ホ稿6問シオネ首岐ト無近ニノユ書港もねこ演主ふわ無知ぽ社敏いつクド治町録庁クヘレリ文箱か戸女まきで半済わッ員町イドかと旅選御裕むだべゅ。 致をトろぼ過代ひ付参新てぼおぐ回自ム話軽9朝しば画長ケロ無品レ住経要ドあ真定梨ハ信界オフ量勲悔にぎ。攻だほンぞ人業47苦エ犠突ゅでしど費6生せ運8間クチム帯訳今べぼほ国図コ清転クノミ千信ソセウ量米とられ者鳥ほッぎ茶頭劇週辺ごろぼ。下ルリッフ室大てねたし制大レラ情辺ヤ央督政つまス機今んえね数確ワアヲヱ初近チヌニ刊写ルイヱ安拡趣るび患果ルネ炭容リぶわみ毎交彩ゆ。 57申にうおる則聞じ教権スヘ特越キツロコ流42状ぐあく刊校位むちーゅ最要都ノユ長射ーたち公村もーレ提料柴丁ゃ。検企ぴね載覧ヘミケ投21質すし少呼もぶどし定容ラユレ生束的ヱリヨヌ際宇ワ要記随ほ球際ワストツ聞雄類噴塗ぱ。金そよずへ話通マ問進んー応昇ど極評57盛うげか就級同ツタヒ改化イ設著ッ作結原能ノ原容けえば型業むぶ山数ヘエ問車べらむ。 惑ちかつら松相問モヱコヘ審起チ目込チワヤ之町ハヤケヱ堂強のンー発園セ典6国投城ま疑可42据灯琴4令告政よゅべ。済代かルへ地要ー携背見トぐへル社新ぎ締先フほ振溝訪ぴめスも者掲タムサラ早査たらる風校促縮れわて。計ヨノニ外音情業ヒメミ年情カサヒロ手換トリスサ疲公モヤミマ会力べ育永ロ政8点ッと闘治タノ権社ごくれド周死みぞ設保ば向集たくーり賞甲避賢彰ぐ。 残し魔松時けルほゃ京済じひ講幕質リメヲワ覚生たよス問治航試ずぼはル済更ツ用突けとる告蛎ユツ城題アキヌ馬基後エシフ紙記チヲ三援ユリラシ処4市ほ球副触これにと。力ク演交おた気会スヌ百左ヱ共止ぼゅ買武ワタリ敗8者ウ増的どぽてが力1海今断迎げたが最学わぼ経型フネ査村れぼつや。 花た告書サヘ女著ゅすこべ府9昨ルメキ本写リぴ背上うで川断点ぞ旅極ミワト通調ケテメホ社領ヱ集防木勇易げはべが。表賛むそ長元ろ権52傾ヤラワ読集ユタサ権督ど愛栄イモユ味46広次つげ茶上りんぶの草闘キミ兵打ヲヌレ真督積交示べにー。飛討おーっに著童択ウラ張北クぞぐ東断右ヘルサ稲術トチウ大誌んごとわ自入ヲワヒ輔場しはぽ報力ゅ破78傍剰哀32相ばむべ駆傑摯晋かどばな。 玲で点舟メコ竜見52鋭縫ふ聞娠ヒキム費報たりこゅ稿場てレぐ徳掲妙る勝案セケ講也たレこス。版ゅはり読存リよめー常権スメノ応26報止モロシイ者策ナ城権レ薬当ル北35忠1経トヌ機県機ソ伊勝設稿速つスての。覚ぐラる古念ちて第金シヤ公香モイ無以さをと夏数ね学前八験ルやすざ夕真つ緊3広じーし出森げわぼ読党ぜすへ追曲科オ両名残わン。 textshaping/inst/lorem/devanagari.txt0000644000176200001440000002115514737311031017551 0ustar liggesusersसदस्य लिये सारांश व्रुद्धि समूह संस्था असरकारक देखने करके आवश्यकत बेंगलूर अमितकुमार खरिदे विकास खरिदने समस्याओ सुना और्४५० परिभाषित अन्तरराष्ट्रीयकरन पहोच। लिए। सुचना करते जागरुक पहोचाना आपको बनाना वास्तव कार्यलय संस्क्रुति आंतरजाल सम्पर्क लाभान्वित वर्णन साधन लेने बाजार तरहथा। वर्तमान निर्माण प्रति भाति एसलिये गोपनीयता प्रोत्साहित होसके एवम् हमेहो। मर्यादित रचना मुक्त सहायता अनुवाद पहोचने एकत्रित बिन्दुओमे औषधिक रचना ७०है उपेक्ष व्याख्या विकासक्षमता बाजार हीकम संदेश एसलिये सभिसमज प्राथमिक प्राप्त प्रेरना समस्याए किया जानते दुनिया हुआआदी ध्वनि लोगो उपेक्ष किएलोग प्रौध्योगिकी चिदंश अथवा सुना तरीके गुजरना व्यवहार बीसबतेबोध भीयह करता। क्षमता अधिकार विकासक्षमता किएलोग सम्पर्क ढांचामात्रुभाषा उदेश हमारी कोहम क्षमता उपेक्ष आधुनिक चिदंश करता। उनके आवश्यकत अनुवादक उपलब्धता ढांचा उनको वेबजाल आधुनिक अमितकुमार सीमित दर्शाता प्रसारन अतित सभीकुछ सामूहिक बेंगलूर देखने ७हल होभर लक्ष्य पासपाई चिदंश स्थिति सोफ़तवेर हीकम सादगि ज्यादा करेसाथ प्राथमिक सक्षम करती आशाआपस विश्लेषण मर्यादित चिदंश गएआप अनुवादक केन्द्रिय सकते सीमित सहित बनाति गटकउसि संपुर्ण प्रव्रुति समाज तकरीबन कार्यसिधान्तो औषधिक दौरान लोगो ऎसाजीस उसीएक् सक्षम कीसे पहोचने वर्तमान बढाता हीकम कुशलता दुनिया ध्वनि उनका प्राप्त मुश्किले अन्तरराष्ट्रीयकरन बलवान द्वारा बीसबतेबोध करने दुनिया व्रुद्धि सुना उन्हे लेकिन वर्तमान पहोचाना सम्पर्क सकते मेमत प्रमान पुर्णता बनाकर जाएन कार्य तकनिकल वास्तविक प्रौध्योगिकी गएआप खरिदने यन्त्रालय विभाग बाजार लक्ष्य तरहथा। वेबजाल वर्ष समजते मानसिक निर्माण शारिरिक थातक उपेक्ष उसीएक् असरकारक जाता बनाति सीमित भारत अनुवादक निर्माता प्राण रहारुप असरकारक प्रोत्साहित लाभो सामूहिक बेंगलूर स्वतंत्रता होभर बनाने प्राण ७०है संस्था सिद्धांत लक्षण आवश्यक दर्शाता लेने नवंबर देकर ब्रौशर सुस्पश्ट विस्तरणक्षमता दस्तावेज विकेन्द्रित हिंदी जाएन प्रति संस्थान अधिकांश विकेन्द्रियकरण भारत बाधा माध्यम निर्माता मार्गदर्शन बनाना रखते दुनिया असक्षम पुर्णता विकास देखने होगा उसीएक् प्रतिबध्दता देने सक्षम गुजरना क्षमता। प्राथमिक कार्य अतित निरपेक्ष ढांचामात्रुभाषा आधुनिक स्वतंत्र बनाकर सेऔर आजपर हमारि पुस्तक संस्थान उन्हे भारतीय गोपनीयता सोफ़्टवेर नीचे बनाकर अधिक तकनीकी मुश्किल सुचनाचलचित्र गोपनीयता प्रोत्साहित कैसे वर्णन वर्णित दस्तावेज केन्द्रिय सकते सहयोग बिन्दुओमे अन्तरराष्ट्रीयकरन समाजो समाज लगती रहारुप आवश्यकत लेकिन सुचना तकरीबन व्रुद्धि उपयोगकर्ता एछित हुएआदि विकेन्द्रित काम हमारि बेंगलूर स्वतंत्र नीचे सहयोग ।क उन्हे जागरुक व्यवहार लेकिन लाभो उदेशीत संस्थान कराना यधपि माध्यम निर्देश बीसबतेबोध कीसे ढांचा मानसिक उसीएक् सोफ़तवेर बनाना वहहर सभिसमज अधिकांश सकता सहयोग गुजरना आंतरजाल लक्षण तरहथा। वास्तव जाने औषधिक सारांश स्वतंत्र व्याख्या भारत होने मुख्य अविरोधता प्राधिकरन विकेन्द्रियकरण प्रति पत्रिका प्राधिकरन अन्तरराष्ट्रीयकरन पहोच सुविधा वास्तव भाषाओ केवल संसाध हुआआदी विश्वव्यापि प्रतिबध्दता वर्ष उदेशीत तकरीबन चुनने गटको संस्था वेबजाल हमेहो। प्राण बनाए करने सदस्य पहोचाना जानकारी विश्व वर्ष शारिरिक व्रुद्धि बहुत जानते परिभाषित विकासक्षमता मुक्त पहोचाना मार्गदर्शन उनको हिंदी काम शारिरिक विश्लेषण खयालात उसीएक् विश्वास उन्हे करने होगा निर्देश मर्यादित सादगि पहोच प्रदान वार्तालाप व्याख्यान समाजो करके स्थिति सोफ़तवेर विशेष आंतरजाल केन्द्रिय पुष्टिकर्ता सुना बाजार किके कार्य हुएआदि दिनांक दर्शाता संस्थान पहोच प्राण विश्वास प्राथमिक व्याख्यान बदले औषधिक ध्येय लेने निरपेक्ष बीसबतेबोध बहुत बनाति स्वतंत्रता अमितकुमार मुखय उद्योग माहितीवानीज्य मुख्य textshaping/inst/lorem/armenian.txt0000644000176200001440000001433114737311031017240 0ustar liggesusersլոռեմ իպսում դոլոռ սիթ ամեթ, ադ ծոնսեքուաթ ելաբոռառեթ մեա. թեմպոռ ծոռպոռա նե եամ, քուոդսի պեռիծուլիս ծոնսեքուունթուռ քուո եու. ոռնաթուս մաիեսթաթիս պռի ծու, եում աթքուի ռեծթեքուե ծու. ան ամեթ վեռթեռեմ քուի. հինծ քուոդսի սիթ թե. ուսու թոլլիթ ութամուռ եի. պռի նեմոռե ծիվիբուս ֆուիսսեթ նե. իուսթո մաիոռում ան հիս. ութինամ պոսսիթ նեծեսսիթաթիբուս պեռ ութ, հաս եխպեթենդա ածծոմմոդառե ադ, եա քուաս նուլլա պեռ. անիմալ գռաեծիս եթ քուո. իմպեդիթ ասսուեվեռիթ քուի աթ. լաբիթուռ պեռիծուլա պեռսեքուեռիս եամ ութ, եի սեա գռաեծե աեթեռնո. եու աֆֆեռթ լաբոռամուս քուո, լոռեմ պութենթ ինթելլեգաթ վել ծու, իդ ֆածիլիսի սծռիբենթուռ եոս. թե հաս իմպեռդիեթ ռեպուդիառե. նեծ դեծոռե դոլոռեմ ծոթիդիեքուե եի. մեի ծու գռաեծե նոսթռում նեծեսսիթաթիբուս, եու հաս աեթեռնո ֆիեռենթ դելիծաթիսսիմի. եի նեծ վոծենթ ինեռմիս լուպթաթում, եսթ թալե ծասե ան. եամ թոթա ծիվիբուս ծոպիոսաե ան. աթ վիս լեգեռե ապպառեաթ, վել իդ ծոմպլեծթիթուռ ռեպռեհենդունթ. եվեռթի ոֆֆիծիիս վիս եա, իդ եոս քուոդ ծեթեռոս, ադ իուսթո պռոբաթուս ինիմիծուս եում. ծու հաս ալբուծիուս պաթռիոքուե, պռո եի գռաեծե սապեռեթ ռեֆեռռենթուռ. պեռ ծու պոռռո լեգենդոս, ասսում իռիուռե պաթռիոքուե իդ մեի. վել քուիս մանդամուս աբհոռռեանթ աթ, իուս ան սուաս ջուսթո վեռիթուս. եթ նոսթռուդ ֆածիլիսի ծոնսթիթուամ սեդ. եա պռո դիծաթ ֆիեռենթ, դուո եթ դեբիթիս ինծոռռուպթե. թոլլիթ մաիոռում քուո ին, եամ ադ ածծումսան պեռթինախ վիթուպեռաթոռիբուս. պռո ալիա սուսծիպիթ ռեֆեռռենթուռ ին, եու սծռիպթա ծոնսուլաթու վոլուպթաթիբուս մել. եի սումո գռաեծե մեդիոծռիթաթեմ սեդ, ինվիդունթ դելիծաթա սծռիպթոռեմ եթ սիթ, եի վիմ եռուդիթի թիբիքուե մաիեսթաթիս. սեա մաիոռում սադիպսծինգ ան, հոմեռո եուիսմոդ պեռիծուլիս իուս թե, թե ուսու եվեռթի պեռծիպիթուռ ինսթռուծթիոռ. դիծանթ ածծուսամուս ինթելլեգաթ դուո նո, սեդ թե թամքուամ ոմիթթամ ինծիդեռինթ. նամ եխ դիամ աեքուե պաուլո, եամ իլլուդ թանթաս սծաեվոլա եի, քուո նո դելենիթ ծիվիբուս վիթուպեռաթա. սեդ մոլլիս վոլումուս ադ, վել նո հառում ծհոռո ոմիթթանթուռ. նաթում հաբեմուս իռածունդիա հիս ութ, քուոդ նոլուիսսե պեռ ութ, վիս եա մոլեսթիե պեռֆեծթո պոսիդոնիում. եխ պռո պռիմա պոսսե դեֆինիթիոնեմ, եամ ոպոռթեաթ մոլեսթիաե ութ. դիսպութանդո վիթուպեռաթոռիբուս եում ծու, հիս եու եուիսմոդ նուսքուամ սուսծիպիթ. եի եռաթ վոծիբուս եսթ, իդ դեսեռունթ ինթելլեգամ ծոնծլուդաթուռքուե պռի, եթ վիխ ռեքուե սենսիբուս. եի ջուսթո մելիոռե քուո, իուս իդ դոմինգ նոսթռո. ռեքուե պոսիդոնիում ինթելլեգամ իդ պռո, դուո մունդի քուիդամ պլաթոնեմ ադ. մել վենիամ սծռիպսեռիթ եու, աբհոռռեանթ ծոնսեծթեթուեռ ութ եսթ. պռի եխ ագամ ֆածիլիսիս պեռթինածիա, աթ պռո մոլլիս քուաեքուե դեբիթիս, եամ սումո մոլեսթիե ծոթիդիեքուե եխ. քուի եթիամ եսսենթ պեռպեթուա եի. ենիմ ֆասթիդիի թոռքուաթոս քուո նե, մել նոլուիսսե ծոթիդիեքուե եթ. մեի ւիսի մազիմ դեթռախիթ նե. եոս քուիս աուդիռե ծու, հաս մոլեսթիե ռեպուդիառե եա. ուբիքուե դեթռախիթ սեդ ծու, եթ պեռ եսսենթ սանծթուս. եոս թե ուլլում մոլեսթիե դելիծաթա. ալիի լաթինե պռո ան, թե վիխ մոդուս նեմոռե, ին սուաս դեբեթ սիթ. թամքուամ պռոբաթուս ադիպիսծինգ ադ մել, հաբեո մաիեսթաթիս նե նեծ, հոմեռո պոսսիթ եխպեթենդիս պռո եի. ծու իուս ֆածեռ թռածթաթոս դեֆինիթիոնեմ, նուլլամ նումքուամ նեծեսսիթաթիբուս մել ադ. լաբիթուռ ինծոռռուպթե եսթ ին, եամ եխ սուաս մելիոռե ծոմպլեծթիթուռ. եթ պոռռո գռաեծո նեգլեգենթուռ մել, ծեթեռոս պեռսեծութի վիմ եխ, պռո նե անծիլլաե դիգնիսսիմ ռեպռեհենդունթ. ութ եիուս ֆածիլիսի նամ, եու մեա լեգեռե լաբոռամուս պեռթինածիա. ապեռիամ դելենիթ ծու նամ, ութ ենիմ իիսքուե մաիոռում սեա, վիդե եռռեմ ծոնվենիռե եա նեծ. եի ֆուգիթ պառթեմ ֆիեռենթ պեռ, եսսե ինանի դելիծաթա հիս եա, եսթ ութ սիմուլ քուիդամ. եխ եում վիթաե եիռմոդ, եամ եպիծուռեի մեդիոծռեմ ծոնսուլաթու եու. անթիոպամ ռեպուդիառե առգումենթում նե նեծ, ռեբում նիհիլ ոպթիոն եամ. textshaping/inst/lorem/cyrillic.txt0000644000176200001440000001237214737311031017263 0ustar liggesusersЛорем ипсум долор сит амет, атоморум интеллегам цотидиеяуе вел ут. Еи нонумес перфецто цонцептам иус, проприае вулпутате еу яуо, ин доценди десерунт сед. Вим долор молестиае улламцорпер ад, еу иллуд семпер вих, цетеро диссентиас нец но. Постулант яуаерендум еос ех, еа дуо продессет модератиус. Дицтас маиестатис ид нам, ид про утамур индоцтум инцидеринт, ет меа ферри иисяуе. Сеа дицат дицта ат, еос еа етиам опортере, нихил цоммодо персиус про еи. Еффициенди сусципиантур иус но, пер ад иллуд чоро платонем. Тота аццумсан ан сеа, витае оффендит ест ин. Лобортис цомплецтитур ан вих, мазим адиписци усу цу. Ностро деленит ех цум. Сеа саперет алияуандо но, виси луцилиус продессет ест цу. Еррем бландит опортеат нам ад, ин меа афферт цаусае апеириан. Про ех мунди цетеро, но еос хинц суммо нуллам. Нам ин вери улламцорпер, ет нец дицант адолесценс. Мел омнис оффициис форенсибус еу. Ипсум семпер ан цум, суас саперет елецтрам сит ид, усу инермис еррорибус ехпетендис не. Ех ерат цонвенире яуо. Пауло аппареат репрехендунт вел не, партем мелиоре нолуиссе еа при. Но мунди аццумсан про, инвидунт инимицус интеллегебат те нам. Те яуо елецтрам витуператорибус, идяуе децоре нонумы еа нец. Еум зрил риденс иудицабит не, дицо адхуц интерпретарис еи сит. Велит орнатус аппеллантур те меа. Ид сеа цонсул ессент ехпетендис, хис те дицит пхилосопхиа, еи лорем цаусае модератиус меа. Ин аппетере ассуеверит яуи, ет дицат яуидам про. Про ут путент оффендит, ат при нонумес луптатум луцилиус. Усу малис диссентиас еи, ет нам аццусамус персецути, нам еа сумо цивибус. Мел ад фалли нонумы хонестатис, утрояуе ратионибус цу ест. Иус аугуе торяуатос темпорибус не, ерос волуптатибус цум ет. Дуо ех виси мунди, еверти елецтрам ин ест, хас цибо натум те. При ут яуас поссим инцоррупте. Мел ат сумо иллуд. Мутат витуператорибус хас ех. Вел ин фугит нулла десерунт, не цум утамур пертинах омиттантур. Виде делицата опортере ут еум. Еа ест доцтус нострум, ех партем тхеопхрастус мел. Ех сит ерос аццусам, не ипсум яуалисяуе волуптатум сеа. Перпетуа салутанди адверсариум ат ест, елитр вивендум урбанитас ет нам, еам тота прима цетеро не. Цум хабео еяуидем те. Ад мунди ехпетенда сед, еос иллум тритани ратионибус цу. Ан вим мунди волумус. Еу интегре волуптуа луптатум вел, дицит яуаерендум цум ат, ат мел хабемус фуиссет. Еи усу иудицо инсоленс, омнес елигенди вис еи. Нец ут омниум салутанди, ан адмодум платонем цонсететур вел. Амет долоре еффициантур мел но, про солута аетерно диспутандо ид, молестие пхилосопхиа медиоцритатем яуи ех. Яуо солута репудиандае ет, еиус фиерент ет сеа. Ут вим модо епицури урбанитас, но усу утинам утрояуе, меа долоре алияуид салутатус ин. Аутем суавитате ест еа. Импедит инвенире нец ад, ад цум сумо децоре маиорум, иус вениам цонсул дицтас не. Цум ат мунди аудиам сплендиде. Иус легимус медиоцрем волуптатум еу. Вел те видиссе цонцептам. Дуо долоре орнатус но, елитр фацете епицури при ут. Ут еос еуисмод аццусам диссентиас, иллуд сенсибус вулпутате вис ад. textshaping/inst/lorem/georgian.txt0000644000176200001440000001517414737311031017247 0ustar liggesusersლორემ იფსუმ დოლორ სით ამეთ, ეუ ფრომფთა იმფერდიეთ ცუმ. ეთ ნეც ვოცენთ ფერფეთუა ფორენსიბუს. უთამურ თამყუამ დეთრახით ცუმ ნო. ყუო ან ყუოდ ფორრო დოლორე. იდ ფრო ალია ცონსეთეთურ, ეოს ორათიო ყუაერენდუმ სცრიბენთურ ნე. იდ ეუმ ვიდე ფიერენთ, იდ იუს მოდო დიცთას. სეა მალის ცონვენირე ვულფუთათე ეუ, სეა დიცანთ ვოცენთ გრაეცის ეი, ეხ ვერეარ სფლენდიდე ნამ. ყუოდ ფეთენთიუმ ცონსეყუუნთურ ველ ნო. ნობის ცორრუმფით ველ იდ, ფუთანთ დელენითი ნე იუს. ფოსსიმ ფაცილისი ფრი ად, უთ ესთ ერათ ინთელლეგათ რათიონიბუს. თოთა ეიუს ფოსიდონიუმ დუო ნე, თალე თანთას ნამ თე. ეოს იდ ფლაცერათ მოლესთიაე. ცონსულ ნოსთრუდ ან ვის, სით იისყუე ეუისმოდ ეი, ეოს ეუ ფათრიოყუე დიგნისსიმ ეხფეთენდის. ეუ ჰას ალიი რებუმ ეხფეთენდა, სით დემოცრითუმ აცცომმოდარე ეა. ცონსულ სანცთუს მელიორე ან ვიხ. ფერ თიბიყუე ცონსთითუამ ეთ. ეთ ადოლესცენს ცონსთითუთო ლიბერავისსე ეოს, ჰის ეუ ცჰორო ცომმუნე. ვიდით ფოსსე ათ იუს. ფოფულო თაციმათეს ყუო ეხ, ნო სეა მუნდი ლუდუს ინსთრუცთიორ. ეუ ერათ ეხფეთენდა ფრინციფეს ნამ, ველ ეა გრაეცე ერიფუით სიმილიყუე, ფერფეცთო ვითუფერათა სედ ან. ფრო დომინგ დეფინითიონემ ად. დოლორე ფოფულო ომითთანთურ მელ ად, ცომმოდო ინთეგრე ეფფიციენდი ეამ ცუ. ესთ ეა დიცათ ასსუმ დიცთას, ად მენანდრი ვოლუთფათ ნამ. სუმმო სცაევოლა მედიოცრემ ეუმ თე. ან სუმო აცცუმსან მეა, ფრო ეა ცორფორა აცცუსამუს. იდ დუო ორათიო ათომორუმ, ცუ აუთემ მუციუს მეი. ეხ უსუ ნუმყუამ ფაბელლას ფროდესსეთ, ან ლიბრის ეუისმოდ ეოს. ეუ დიცათ ვერთერემ ცონსეცთეთუერ ჰას, უთ ჰას დელენით ცონსეყუუნთურ. ვისი ერათ ომნეს მეი ნე. მეა თე ფერფეცთო სცრიფთორემ სუსციფიანთურ. ცუმ ალთერუმ რათიონიბუს ნეცესსითათიბუს ად, უთ დიცათ დიცთა ორათიო ცუმ. მელ თე ცასე დემოცრითუმ, ად ერანთ დოლორე ფჰაედრუმ მელ, ველ ინანი ცონცლუდათურყუე ნო. ინვენირე რეფუდიანდაე სედ ნე, ინ ნუმყუამ ჰონესთათის მეა. ეხ ჯუსთო გლორიათურ ცონსეთეთურ ვიმ. ჰის ნე ფოსთულანთ ფორენსიბუს თჰეოფჰრასთუს. ნათუმ გრაეცის ეხ ჰის, დოცენდი მაიესთათის ეა ეოს. მეის თემფორ ან ნამ, ინ ანიმალ ფჰილოსოფჰია ფერ. ველ ჰაბეო სინგულის ელეიფენდ იდ, ეი ეუმ ნიბჰ ვოლუმუს. ნო ნამ ეირმოდ ლეგიმუს დეთრაცთო, ლორემ ლაბითურ ვოლუმუს იუს იდ. ცომმუნე გუბერგრენ ფორენსიბუს ეთ ეოს, ვიდერერ ვულფუთათე ინთელლეგათ ეუ ფრო. ნო მალის აფფარეათ მაიესთათის ცუმ, უთ ესთ ლუფთათუმ აცცომმოდარე. სეა ეუ ვივენდუმ ინსოლენს ერრორიბუს, ვიხ ომნის უთამურ ეთ. ცაუსაე ოფთიონ დიცერეთ დუო ან. ფრო დიცათ ფეუგაით უთ. ეა ფალლი ეხერცი დისფუთათიონი იუს, ყუი ომნის ეფფიციანთურ ნე, ნათუმ სუავითათე სცრიფთორემ ფრო ცუ. იუს ათყუი მოლლის ეხ, ნე ყუო ნოსთრუმ ფერციფითურ. ყუი რებუმ აუგუე მედიოცრემ ეთ, ყუი დიცამ დოცთუს თე. ცონგუე დოლორუმ ეუმ ეთ. ეა მოდო დიცით იუს, ბრუთე ათყუი ადიფისცი ინ ვიხ. აუდირე ფლათონემ უთ მელ. ინსოლენს ფერიცულის უთ ცუმ, მეი აეთერნო ფაცილის ნე. ვის დომინგ ირაცუნდია ნე, მელ ეა ყუემ ვისი ელიგენდი. textshaping/inst/doc/0000755000176200001440000000000015072133301014325 5ustar liggesuserstextshaping/inst/doc/c_interface.R0000644000176200001440000000021715072133301016712 0ustar liggesusers## ----------------------------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) textshaping/inst/doc/c_interface.html0000644000176200001440000003622515072133301017465 0ustar liggesusers textshaping C interface

textshaping C interface

textshaping is predominantly intended to be used by other packages implementing graphic devices 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 successfully. 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.Rmd0000644000176200001440000001024415022221333017232 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 devices 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 successfully. 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/tools/0000755000176200001440000000000015002133362013743 5ustar liggesuserstextshaping/tools/winlibs.R0000644000176200001440000000162615002133362015542 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.md0000644000176200001440000000372315067011154014074 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/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/textshaping/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/r-lib/textshaping/graph/badge.svg)](https://app.codecov.io/gh/r-lib/textshaping) 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](https://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/build/0000755000176200001440000000000015072133301013702 5ustar liggesuserstextshaping/build/vignette.rds0000644000176200001440000000032615072133301016242 0ustar liggesusersb```b`aed`b2 1# 'O+I-JKLN MA/I()H,KWpV+ES&lJFIn" PKG_LIBS="-lfreetype -lharfbuzz -lfribidi -lpng" PKG_CFLAGS="-I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/glib-2.0 -I/usr/include/fribidi" # 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 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 " * brew: $PKG_BREW_NAME (OSX)" echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your" echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config" echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:" echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'" echo "-------------------------- [ERROR MESSAGE] ---------------------------" cat configure.log echo "--------------------------------------------------------------------" exit 1 fi # Write to Makevars sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars # Success exit 0 textshaping/man/0000755000176200001440000000000015022221333013354 5ustar liggesuserstextshaping/man/lorem_text.Rd0000644000176200001440000000276315022221333016035 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lorem_text.R \name{lorem_text} \alias{lorem_text} \alias{lorem_bidi} \title{Get gibberish text in various scripts} \usage{ lorem_text( script = c("latin", "chinese", "arabic", "devanagari", "cyrillic", "kana", "hangul", "greek", "hebrew", "armenian", "georgian"), n = 1 ) lorem_bidi( ltr = c("latin", "chinese", "devanagari", "cyrillic", "kana", "hangul", "greek", "armenian", "georgian"), rtl = c("arabic", "hebrew"), ltr_prop = 0.9, n = 1 ) } \arguments{ \item{script}{A string giving the script to fetch gibberish for} \item{n}{The number of paragraphs to fetch. Each paragraph will be its own element in the returned character vector.} \item{ltr, rtl}{scripts to use for left-to-right and right-to-left text} \item{ltr_prop}{The approximate proportion of left-to-right text in the final string} } \value{ a character vector of length \code{n} } \description{ Textshaping exists partly to allow all the various scripts that exists in the world to be used in R graphics. This function returns gibberish filler text (lorem ipsum text) in various scripts for testing purpose. Some of these are transliterations of the original lorem ipsum text while others are based an a distribution model. } \examples{ # Defaults to standard lorem ipsum lorem_text() # Get two paragraphs of hangul (Korean) lorem_text("hangul", 2) # Get gibberish bi-directional text lorem_bidi() } \references{ https://generator.lorem-ipsum.info } textshaping/man/get_font_features.Rd0000644000176200001440000000242115067011225017353 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 families to match} \item{italic}{logical indicating the font slant} \item{bold}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weight = "bold"} instead} \item{path, index}{path and 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/shape_text.Rd0000644000176200001440000001417514742410565016036 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, weight = "normal", width = "undefined", features = font_feature(), size = 12, res = 72, lineheight = 1, align = "auto", hjust = 0, vjust = 0, max_width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, direction = "auto", path = NULL, index = 0, bold = deprecated() ) } \arguments{ \item{strings}{A character vector of strings to shape} \item{id}{A vector grouping the strings together. If strings share an id the shaping will continue between strings} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{weight}{The weight to query for, either in numbers (\code{0}, \code{100}, \code{200}, \code{300}, \code{400}, \code{500}, \code{600}, \code{700}, \code{800}, or \code{900}) or strings (\code{"undefined"}, \code{"thin"}, \code{"ultralight"}, \code{"light"}, \code{"normal"}, \code{"medium"}, \code{"semibold"}, \code{"bold"}, \code{"ultrabold"}, or \code{"heavy"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{width}{The width to query for either in numbers (\code{0}, \code{1}, \code{2}, \code{3}, \code{4}, \code{5}, \code{6}, \code{7}, \code{8}, or \code{9}) or strings (\code{"undefined"}, \code{"ultracondensed"}, \code{"extracondensed"}, \code{"condensed"}, \code{"semicondensed"}, \code{"normal"}, \code{"semiexpanded"}, \code{"expanded"}, \code{"extraexpanded"}, or \code{"ultraexpanded"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{features}{A \code{\link[systemfonts:font_feature]{systemfonts::font_feature()}} object or a list of them, giving the OpenType font features to set} \item{size}{The size in points to use for the font} \item{res}{The resolution to use when doing the shaping. Should optimally match the resolution used when rendering the glyphs.} \item{lineheight}{A multiplier for the lineheight} \item{align}{Within text box alignment, either \code{'auto'}, \code{'left'}, \code{'center'}, \code{'right'}, \code{'justified'}, \code{'justified-left'}, \code{'justified-right'}, \code{'justified-center'}, or \code{'distributed'}. \code{'auto'} and \code{'justified'} will chose the left or right version depending on the direction of the text.} \item{hjust, vjust}{The justification of the textbox surrounding the text} \item{max_width}{The requested with of the string in inches. Setting this to something other than \code{NA} will turn on word wrapping.} \item{tracking}{Tracking of the glyphs (space adjustment) measured in 1/1000 em.} \item{indent}{The indent of the first line in a paragraph measured in inches.} \item{hanging}{The indent of the remaining lines in a paragraph measured in inches.} \item{space_before, space_after}{The spacing above and below a paragraph, measured in points} \item{direction}{The overall directional flow of the text. The default (\code{"auto"}) will guess the direction based on the content of the string. Use \code{"ltr"} (left-to-right) and \code{"rtl"} (right-to-left) to turn detection of and set it manually.} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} \item{bold}{logical indicating whether the font weight} } \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 placement of the the first character contributing to the glyph within the string} \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{font_path}{The path to the font file used during shaping of the glyph} \item{font_index}{The index of the font used to shape the glyph in the font file} \item{font_size}{The size of the font used during shaping} \item{advance}{The advancement amount to the next glyph} \item{ascender}{The ascend of the font used for the glyph. This does not measure the actual glyph} \item{descender}{The descend of the font used for the glyph. This does not measure the actual 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} \item{ltr}{The global direction of the string. If \code{TRUE} then it is left-to-right, otherwise it is right-to-left} } } \description{ Performs advanced text shaping of strings including font fallbacks, bidirectional script support, word wrapping and various character and paragraph level formatting settings. } \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/figures/0000755000176200001440000000000014672302615015035 5ustar liggesuserstextshaping/man/figures/lifecycle-questioning.svg0000644000176200001440000000244414672302657022072 0ustar liggesusers lifecycle: questioning lifecycle questioning textshaping/man/figures/lifecycle-stable.svg0000644000176200001440000000247214672302657021000 0ustar liggesusers lifecycle: stable lifecycle stable textshaping/man/figures/lifecycle-experimental.svg0000644000176200001440000000245014672302657022217 0ustar liggesusers lifecycle: experimental lifecycle experimental textshaping/man/figures/lifecycle-deprecated.svg0000644000176200001440000000244014672302657021621 0ustar liggesusers lifecycle: deprecated lifecycle deprecated textshaping/man/figures/lifecycle-superseded.svg0000644000176200001440000000244014672302657021664 0ustar liggesusers lifecycle: superseded lifecycle superseded textshaping/man/figures/lifecycle-archived.svg0000644000176200001440000000243014672302657021305 0ustar liggesusers lifecycle: archived lifecycle archived textshaping/man/figures/lifecycle-defunct.svg0000644000176200001440000000242414672302657021153 0ustar liggesusers lifecycle: defunct lifecycle defunct textshaping/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000246614672302657022602 0ustar liggesusers lifecycle: soft-deprecated lifecycle soft-deprecated textshaping/man/figures/lifecycle-maturing.svg0000644000176200001440000000243014672302657021346 0ustar liggesusers lifecycle: maturing lifecycle maturing textshaping/man/plot_shape.Rd0000644000176200001440000000157214737311031016015 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shape_text.R \name{plot_shape} \alias{plot_shape} \title{Preview shaped text and the metrics for the text box} \usage{ plot_shape(shape, id = 1) } \arguments{ \item{shape}{The output of a call to \code{\link[=shape_text]{shape_text()}}} \item{id}{The index of the text run to show in case \code{shape} contains multiples} } \value{ This function is called for its side effects } \description{ This function allows you to preview the layout that \code{\link[=shape_text]{shape_text()}} calculates. It is purely meant as a sanity check to make sure that the values calculated are sensible and shouldn't be used as a plotting function for rendering text on its own. } \examples{ arab_text <- lorem_text("arabic", 2) shape <- shape_text( arab_text, max_width = 5, indent = 0.2 ) try( plot_shape(shape) ) } textshaping/man/text_width.Rd0000644000176200001440000000516215022221333016032 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, weight = "normal", width = "undefined", features = font_feature(), size = 12, res = 72, include_bearing = TRUE, path = NULL, index = 0, bold = deprecated() ) } \arguments{ \item{strings}{A character vector of strings} \item{family}{The name of the font families to match} \item{italic}{logical indicating the font slant} \item{weight}{The weight to query for, either in numbers (\code{0}, \code{100}, \code{200}, \code{300}, \code{400}, \code{500}, \code{600}, \code{700}, \code{800}, or \code{900}) or strings (\code{"undefined"}, \code{"thin"}, \code{"ultralight"}, \code{"light"}, \code{"normal"}, \code{"medium"}, \code{"semibold"}, \code{"bold"}, \code{"ultrabold"}, or \code{"heavy"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{width}{The width to query for either in numbers (\code{0}, \code{1}, \code{2}, \code{3}, \code{4}, \code{5}, \code{6}, \code{7}, \code{8}, or \code{9}) or strings (\code{"undefined"}, \code{"ultracondensed"}, \code{"extracondensed"}, \code{"condensed"}, \code{"semicondensed"}, \code{"normal"}, \code{"semiexpanded"}, \code{"expanded"}, \code{"extraexpanded"}, or \code{"ultraexpanded"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} \item{features}{A \code{\link[systemfonts:font_feature]{systemfonts::font_feature()}} object or a list of them, giving the OpenType font features to set} \item{size}{The size in points to use for the font} \item{res}{The resolution to use when doing the shaping. Should optimally match the resolution used when rendering the glyphs.} \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} \item{bold}{logical indicating whether the font weight} } \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[systemfonts:shape_string]{systemfonts::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 have already been split 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/textshaping-package.Rd0000644000176200001440000000175115004613162017603 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 Software, PBC (03wc8by49) [copyright holder, funder] } } \keyword{internal} textshaping/DESCRIPTION0000644000176200001440000000306415072136402014321 0ustar liggesusersPackage: textshaping Title: Bindings to the 'HarfBuzz' and 'Fribidi' Libraries for Text Shaping Version: 1.0.4 Authors@R: c( person("Thomas Lin", "Pedersen", , "thomas.pedersen@posit.co", role = c("cre", "aut"), comment = c(ORCID = "0000-0002-5147-4711")), person("Posit Software, PBC", role = c("cph", "fnd"), comment = c(ROR = "03wc8by49")) ) 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: lifecycle, stats, stringi, systemfonts (>= 1.3.0), utils Suggests: covr, grDevices, grid, knitr, rmarkdown, testthat (>= 3.0.0) LinkingTo: cpp11 (>= 0.2.1), systemfonts (>= 1.0.0) VignetteBuilder: knitr Config/build/compilation-database: true Config/testthat/edition: 3 Config/usethis/last-upkeep: 2025-04-23 Encoding: UTF-8 RoxygenNote: 7.3.2 SystemRequirements: freetype2, harfbuzz, fribidi NeedsCompilation: yes Packaged: 2025-10-10 07:33:22 UTC; thomas Author: Thomas Lin Pedersen [cre, aut] (ORCID: ), Posit Software, PBC [cph, fnd] (ROR: ) Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2025-10-10 08:00:02 UTC