library(grDevices) library(grid) HersheyLabel <- function(x, y=unit(.5, "npc")) { lines <- strsplit(x, "\n")[[1]] if (!is.unit(y)) y <- unit(y, "npc") n <- length(lines) if (n > 1) { y <- y + unit(rev(seq(n)) - mean(seq(n)), "lines") } grid.text(lines, y=y, gp=gpar(fontfamily="HersheySans")) } ## NOTE that we make use of a font that has a free licence so ## that we can distribute the font along with 'grid' to standardize testing ## Values found from textshaping::shape_text(), but stored manually ## to avoid dependency on 'textshaping' ## ## textshaping::shape_text("glyphs", ## path="Fonts/Montserrat/static/Montserrat-Medium.ttf") ## Do NOT use normalizePath() because it generates ## a path /home/staff/paul that ghostscript does NOT like RobotoFont <- glyphFont(system.file("fonts", "Roboto", "Roboto-Medium.ttf", package="grDevices"), 0, "Roboto Medium", 400, "normal") RobotoInfo <- list(id = c(75, 80, 93, 84, 76, 87), x = c(0, 6.796875, 9.859375, 15.703125, 22.453125, 29.109375), y = rep(0, 6), font = 1, size = rep(12, 6), fontList = glyphFontList(RobotoFont), width = 35.29688, height = 25.21875/2, ## divide by 2 cos of 'textshaping' bug hAnchor = glyphAnchor(0, "left"), vAnchor = glyphAnchor(-(25.21875/2 - 11.14062), "bottom")) Roboto <- do.call(glyphInfo, RobotoInfo) MontserratFont <- glyphFont(system.file("fonts", "Montserrat", "static", "Montserrat-Medium.ttf", package="grDevices"), 0, "Montserrat Medium", 400, "normal") MontserratInfo <- list(id = c(461, 499, 620, 556, 469, 567), x = c(0.00000, 8.28125, 11.62500, 18.32812, 26.51562, 34.68750), y = rep(0, 6), font = 1, size = rep(12, 6), fontList = glyphFontList(MontserratFont), width = glyphWidth(c(40.70312, 40.70312 - 0.5 - 0.359375), label=c("width", "tight"), left=c("left", "leftBearing")), height = glyphHeight(c(26.23438/2, 26.23438/2 - 2.703125 - 0.609375), label=c("height", "tight"), bottom=c("bottom", "bottomBearing")), hAnchor = glyphAnchor(c(0, 0.5), label=c("left", "leftBearing")), vAnchor = glyphAnchor(c(0, -(26.23438/2 - 11.60938), -(26.23438/2 - 11.60938 - 0.609375)), label=c("baseline", "bottom", "bottomBearing"))) Montserrat <- do.call(glyphInfo, MontserratInfo) ## Set up "global" 'testGlyphInfo' so that pdfEmbeddedRecording() device ## can use it in call to embedGlyphs() testGlyphInfo <- list() testGlyph <- function(info, ...) { testGlyphInfo[[length(testGlyphInfo) + 1]] <<- info if (!inherits(info, "RGlyphInfo")) { ## List of infos n <- length(info) for (i in 1:n) { vp <- viewport(y=i/(n+1)) pushViewport(vp) grid.glyph(info[[i]], ...) popViewport() } } else { grid.glyph(info, ...) } } testGlyphGrob <- function(info, ...) { testGlyphInfo[[length(testGlyphInfo) + 1]] <<- info if (!inherits(info, "RGlyphInfo")) { ## List of infos n <- length(info) vps <- lapply(1:n, function(i) viewport(y=i/(n+1))) do.call(grobTree, mapply(function(x, vp) { glyphGrob(x, ..., vp=vp) }, info, vps)) } else { glyphGrob(info, ...) } } ## glyphs grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(Montserrat) HersheyLabel("Montserrat glyphs", y=.2) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(Roboto) HersheyLabel("Roboto glyphs", y=.2) ## glyphs with NA/non-finite values missingIDinfo <- MontserratInfo missingIDinfo$id[1] <- NA missingID <- do.call(glyphInfo, missingIDinfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(missingID) HersheyLabel("Missing glyph id ('g' missing)", y=.2) missingXinfo <- MontserratInfo missingXinfo$x[2] <- NA missingX <- do.call(glyphInfo, missingXinfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(missingX) HersheyLabel("Missing glyph x ('l' missing)", y=.2) missingYinfo <- MontserratInfo missingYinfo$y[3] <- NA missingY <- do.call(glyphInfo, missingYinfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(missingY) HersheyLabel("Missing glyph y ('y' missing)", y=.2) ## glyphs with font file non-existent (should produce warning) ## (output is unpredictable, but likely to be weird because the glyph ids ## are unlikely to match the glyph ids in the substituted font) nofile <- Roboto nofile$fonts[[1]]$file <- "road/to/nowhere" grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(nofile) HersheyLabel("Font file not found - output will be weird", y=.2) ## Manual hack of "RGlyphInfo" object missingfile <- Roboto missingfile$fonts[[1]]$file <- as.character(NA) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(missingfile) HersheyLabel("Font file not found - output will be weird", y=.2) ## glyphs with colour colourInfo <- MontserratInfo colourInfo$col <- rep("red", 6) colour <- do.call(glyphInfo, colourInfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(colour) HersheyLabel("glyphs with colour (red)", y=.2) ## Missing colour is OK missingColourInfo <- colourInfo missingColourInfo$col[4] <- NA missingColour <- do.call(glyphInfo, missingColourInfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(missingColour) HersheyLabel("glyphs with one colour missing (red -> black)", y=.2) ## glyphs with alignment grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, hjust="left", vjust="bottom") HersheyLabel("left bottom justification", y=.2) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, hjust="left", vjust="baseline") HersheyLabel("(left) baseline justification", y=.2) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, hjust=glyphJust(0, "tight"), vjust="baseline") HersheyLabel("tight left (baseline) justification\n(tiny bit further left)", y=.2) ## rotated glyphs: ### viewport rotation grid.newpage() pushViewport(viewport(angle=30)) grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat) popViewport() HersheyLabel("rotated glyphs", y=.2) grid.newpage() pushViewport(viewport(angle=30)) grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, hjust="left", vjust="baseline") popViewport() HersheyLabel("rotated (left baseline justified) glyphs", y=.2) ### glyph rotation rotationInfo <- MontserratInfo rotationInfo$rot <- rep(180, 6) rotation <- do.call(glyphInfo, rotationInfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(rotation) HersheyLabel("glyphs with rotation (180 degrees)", y=.2) # Different rotation for each glyph works as well: sepRotInfo <- MontserratInfo sepRotInfo$id <- rep(499, 6) sepRotInfo$rot <- seq(from = 0, to = 90, length = 6) sepRot <- do.call(glyphInfo, sepRotInfo) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(sepRot) HersheyLabel("glyphs with different rotations", y=.2) # Rotation of glyphs inside a rotated viewport works: grid.newpage() pushViewport(viewport(angle=30)) grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(sepRot) popViewport() HersheyLabel("rotated glyphs inside rotated viewport", y=.2) ## glyph x/y/width/height grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, name="glyph") grid.segments(0, .5, grobX("glyph", 180), .5, gp=gpar(col="red")) grid.segments(1, 1, grobX("glyph", 45), grobY("glyph", 45), gp=gpar(col="red")) grid.rect(width=grobWidth("glyph"), height=grobHeight("glyph"), gp=gpar(fill=NA)) HersheyLabel("glyph x/y/width/height", y=.2) grid.newpage() pushViewport(viewport(angle=30)) grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.segments(.5,0, .5,1, gp=gpar(col="grey")) testGlyph(Montserrat, hjust="left", vjust="baseline", name="glyph") grid.segments(0, .5, grobX("glyph", 180), .5, gp=gpar(col="red")) grid.segments(1, 1, grobX("glyph", 45), grobY("glyph", 45), gp=gpar(col="red")) popViewport() HersheyLabel("(rotated left baseline justified) glyph x/y/width/height", y=.2) ## glyphs in tiling pattern grid.newpage() pat <- pattern(gTree(children=gList(rectGrob(width=unit(2, "cm"), height=unit(1, "cm"), gp=gpar(fill="grey")), testGlyphGrob(Montserrat))), width=unit(2, "cm"), height=unit(1, "cm"), extend="repeat") grid.circle(r=.3, gp=gpar(fill=pat)) HersheyLabel("glyphs as tiling pattern", y=.1) ## glyphs as clipping path grid.newpage() pushViewport(viewport(clip=testGlyphGrob(Montserrat))) grid.segments(0, unit(.5, "npc") + unit(seq(-5, 5), "mm"), 1, unit(.5, "npc") + unit(seq(-5, 5), "mm"), gp=gpar(col=c("red", "blue"), lwd=2)) popViewport() HersheyLabel("glyphs as clipping path", y=.2) ## glyphs as mask grid.newpage() grid.segments(gp=gpar(col="red", lwd=20)) pushViewport(viewport(mask=testGlyphGrob(Montserrat, gp=gpar(col=rgb(0,0,0,.5))))) grid.rect(gp=gpar(fill="black")) popViewport() HersheyLabel("glyphs as mask", y=.2) ## glyphs in group grid.newpage() grid.group(testGlyphGrob(Montserrat), "xor", segmentsGrob(gp=gpar(lwd=20))) HersheyLabel("glyphs in group (xor line)", y=.2) ## glyphs in (transformed) group grid.newpage() grid.define(testGlyphGrob(Montserrat), name="glyphs") pushViewport(viewport(width=2, height=4)) grid.use("glyphs") popViewport() HersheyLabel("glyphs in transformed group", y=.2) ## glyphs as path grid.newpage() grid.stroke(testGlyphGrob(Montserrat), gp=gpar(lwd=.5)) HersheyLabel("glyphs as (stroked) path", y=.2) ## multiple fonts ## printVals <- function(x) { ## cat(paste0("c(", paste(temp$shape[[x]], collapse=", "), ")\n")) ## } ## ## library(textshaping) ## temp <- ## shape_text(c("hello ", "world!"), ## id=1, ## bold=c(FALSE, TRUE), ## italic=c(FALSE, TRUE), ## path=c("Fonts/Montserrat/static/Montserrat-Medium.ttf", ## "Fonts/Montserrat/static/Montserrat-BoldItalic.ttf")) ## printVals("index") Montserrat2 <- glyphFontList(glyphFont(system.file("fonts", "Montserrat", "static", "Montserrat-Medium.ttf", package="grDevices"), 0, "Montserrat-Medium", 400, "normal"), glyphFont(system.file("fonts", "Montserrat", "static", "Montserrat-BoldItalic.ttf", package="grDevices"), 0, "Montserrat-BoldItalic", 700, "italic")) MontserratInfo2 <- list(id = c(469, 434, 499, 499, 521, 1642, 614, 521, 559, 499, 427, 1606), x = c(0, 8.171875, 15.515625, 18.859375, 22.203125, 29.828125, 33.0625, 44.171875, 52.015625, 57.125, 60.734375, 69.03125), y = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), font = rep(1:2, each=6), fontList = Montserrat2, size = rep(12, 12), width = glyphWidth(c(72.5, 72.5 - 1.09375 - -0.421875), label=c("width", "tight"), left=c("left", "leftBearing")), height = glyphHeight(c(26.23438/2, 26.23438/2 - 2.703125 - 2.921875), label=c("height", "tight"), bottom=c("bottom", "bottomBearing")), hAnchor = glyphAnchor(c(0, 1.09375), label=c("left", "leftBearing")), vAnchor = glyphAnchor(c(0, -(26.23438/2 - 11.60938), -(26.23438/2 - 11.60938 - 2.921875)), label=c("baseline", "bottom", "bottomBearing"))) Montserrat2 <- do.call(glyphInfo, MontserratInfo2) grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) testGlyph(Montserrat2) HersheyLabel("Montserrat glyphs (mixed style)", y=.2) ## Normal text plus glyphs grid.newpage() grid.segments(0,.5,1,.5, gp=gpar(col="grey")) grid.text("test", y=3/4) testGlyph(Montserrat) HersheyLabel("Montserrat glyphs plus normal text", y=.2) ## Two glyphInfo's in same image ## (particularly relevant for embedding fonts in pdf() output) grid.newpage() testGlyph(list(Montserrat, Roboto)) HersheyLabel("Montserrat glyphs plus Roboto glyphs in separate glyph grobs")