Comments (9)
Good idea to have this issue separate Teun.
I thought I would confirm some of our assumptions. First of all, ensure that the output of shape_text
gives results in pixels, and that we convert the result to inches by dividing by 96. If this is the case, the first two xs in the picture should be aligned:
library(grid)
# Measure 40-point Arial text with vjust of 0.5
txt <- textshaping::shape_text(
strings = "x",
id = 1,
family = "Arial",
vjust = 0.5,
size = 40,
lineheight = 1.2,
res = 72)
# Assume the y_offset is in pixels as stated in the docs.
# Convert it to inches, which we know
# from empirical measurements are 96 pixels
pixel_offset <- txt$shape$y_offset
inch_offset <- pixel_offset / 96
# Create a textgrob from the above measurements. If we set the vjust to 0,
# but add the inch offset, we should obtain the same result as setting the
# vjust to 0.5
tg_adjusted <- textGrob("x", x = unit(2, "in"), y = unit(3 + inch_offset, "in"),
vjust = 0, gp = gpar(family = "Arial", fontsize = 40))
# Create a textgrob that should match the adjusted version
tg <- textGrob("x", x = unit(3, "in"), y = unit(3, "in"),
vjust = 0.5, gp = gpar(family = "Arial", fontsize = 40))
# Create a textgrob with a vjust of 0 for comparison
tg0 <- textGrob("x", x = unit(4, "in"), y = unit(3, "in"),
vjust = 0, gp = gpar(family = "Arial", fontsize = 40))
# Create the vjust's reference line and the baselines of the letters
tg_baseline_measured <- linesGrob(y = unit(c(3, 3), "in"),
gp = gpar(col = "red"))
tg_midline <- linesGrob(y = unit(rep(3 + inch_offset, 2), "in"),
gp = gpar(col = "green"))
grid.newpage()
grid.draw(tg_midline)
grid.draw(tg_baseline_measured)
grid.draw(tg)
grid.draw(tg_adjusted)
grid.draw(tg0)
Created on 2022-01-19 by the reprex package (v2.0.1)
from geomtextpath.
Secondly, check that line spacing works as expected. I think if that is the case, the first and second "columns" should match here:
library(grid)
# Measure 40-point Arial text with vjust of 0.5
txt <- textshaping::shape_text(
strings = "x\nx",
id = 1,
family = "Arial",
vjust = 0.5,
size = 40,
lineheight = 1.2,
res = 72)
# Assume the y_offset is in pixels as stated in the docs.
# Convert it to inches, which we know
# from empirical measurements are 96 pixels
pixel_offset <- txt$shape$y_offset
inch_offset <- pixel_offset / 96
# Create a textgrob from the above measurements. If we set the vjust to 0,
# but add the inch offset, we should obtain the same result as setting the
# vjust to 0.5
tg_adjusted <- textGrob("x", x = unit(2, "in"), y = unit(3 + inch_offset[1], "in"),
vjust = 0, gp = gpar(family = "Arial", fontsize = 40))
tg_adjusted2 <- textGrob("x", x = unit(2, "in"), y = unit(3 + inch_offset[3], "in"),
vjust = 0, gp = gpar(family = "Arial", fontsize = 40))
# Create a textgrob that should match the adjusted version
tg <- textGrob("x\nx", x = unit(3, "in"), y = unit(3, "in"),
vjust = 0.5, gp = gpar(family = "Arial", fontsize = 40))
# Create a textgrob with a vjust of 0 for comparison
tg0 <- textGrob("x\nx", x = unit(4, "in"), y = unit(3, "in"),
vjust = 0, gp = gpar(family = "Arial", fontsize = 40))
# Create the vjust's reference line and the baselines of the letters
tg_baseline_measured <- linesGrob(y = unit(c(3, 3), "in"),
gp = gpar(col = "red"))
tg_midline <- linesGrob(y = unit(rep(3 + inch_offset[1], 2), "in"),
gp = gpar(col = "green"))
tg_midline2 <- linesGrob(y = unit(rep(3 + inch_offset[3], 2), "in"),
gp = gpar(col = "green"))
grid.newpage()
grid.draw(tg_midline)
grid.draw(tg_midline2)
grid.draw(tg_baseline_measured)
grid.draw(tg)
grid.draw(tg_adjusted)
grid.draw(tg_adjusted2)
grid.draw(tg0)
Clearly, this doesn't match. We seem to need a line spacing in shape_text of about 1.72 to get them to match, which is a multiplier of 1.43. I don't know where this comes from, but at least it appears constant even if we change the font family:
library(grid)
# Measure 40-point Arial text with vjust of 0.5
txt <- textshaping::shape_text(
strings = "x\nx",
id = 1,
family = "mono",
vjust = 0.5,
size = 60,
lineheight = 1.72,
res = 96)
# Assume the y_offset is in pixels as stated in the docs.
# Convert it to inches, which we know
# from empirical measurements are 96 pixels
pixel_offset <- txt$shape$y_offset
inch_offset <- pixel_offset / 96
# Create a textgrob from the above measurements. If we set the vjust to 0,
# but add the inch offset, we should obtain the same result as setting the
# vjust to 0.5
tg_adjusted <- textGrob("x", x = unit(2, "in"), y = unit(3 + inch_offset[1], "in"),
vjust = 0, gp = gpar(fontfamily = "mono", fontsize = 60))
tg_adjusted2 <- textGrob("x", x = unit(2, "in"), y = unit(3 + inch_offset[3], "in"),
vjust = 0, gp = gpar(fontfamily = "mono", fontsize = 60))
# Create a textgrob that should match the adjusted version
tg <- textGrob("x\nx", x = unit(3, "in"), y = unit(3, "in"),
vjust = 0.5, gp = gpar(fontfamily = "mono", fontsize = 60))
# Create a textgrob with a vjust of 0 for comparison
tg0 <- textGrob("x\nx", x = unit(4, "in"), y = unit(3, "in"),
vjust = 0, gp = gpar(fontfamily = "mono", fontsize = 60))
# Create the vjust's reference line and the baselines of the letters
tg_baseline_measured <- linesGrob(y = unit(c(3, 3), "in"),
gp = gpar(col = "red"))
tg_midline <- linesGrob(y = unit(rep(3 + inch_offset[1], 2), "in"),
gp = gpar(col = "green"))
tg_midline2 <- linesGrob(y = unit(rep(3 + inch_offset[3], 2), "in"),
gp = gpar(col = "green"))
grid.newpage()
grid.draw(tg_midline)
grid.draw(tg_midline2)
grid.draw(tg_baseline_measured)
grid.draw(tg)
grid.draw(tg_adjusted)
grid.draw(tg_adjusted2)
grid.draw(tg0)
Created on 2022-01-19 by the reprex package (v2.0.1)
from geomtextpath.
I'm still a bit hesitant about the 96 pixels per inch thing, particularly because they normally depends on the resolution of a screen. It also doesn't make a lot of sense to me that the x-measurements are apparently at 72 ppi and the y-offsets at 96 ppi. However, it is still a clear improvement on the current measurement. I'm still mildly annoyed that there is an apparent 1 or 2 pixel difference, but that can be entirely due to rounding errors beyond our control (and I haven't managed to find a better value).
With regards to the lineheight, I don't think we've exactly nailed it yet. For example, here is what it looks like with the impact font, which I tested because it has a large x-height relative to the lineheight:
I'm slowly becoming this person over the issue:
from geomtextpath.
Haha! You might be right though. I think this function should help to show that this is the case:
draw <- function(string = "x",
family = "Arial",
vjust = 0.5,
size = 20,
lineheight = 1.2,
dpi = 96) {
# Measure 40-point Arial text with vjust of 0.5
txt <- textshaping::shape_text(
strings = string,
id = 1,
family = family,
vjust = vjust,
size = size,
lineheight = lineheight,
res = 72)
# Assume the y_offset is in pixels as stated in the docs.
# Convert it to inches, which we know
# from empirical measurements are 96 pixels
pixel_offset <- txt$shape$y_offset
inch_offset <- pixel_offset / dpi
# Create a textgrob from the above measurements. If we set the vjust to 0,
# but add the inch offset, we should obtain the same result as setting the
# vjust to 0.5
gp <- gpar(fontfamily = family, fontsize = size, lineheight = lineheight)
strings <- unlist(strsplit(string, "\n"))
tg_adjusted <- textGrob(strings[1],
x = unit(2, "in"),
y = unit(3 + inch_offset[1], "in"),
vjust = 0,
gp = gp)
tg_adjusted2 <- if(grepl("\n", string)) {
textGrob(strings[2],
x = unit(2, "in"),
y = unit(3 + inch_offset[3], "in"),
vjust = 0,
gp = gp)
} else nullGrob()
# Create a textgrob that should match the adjusted version
tg <- textGrob(string,
x = unit(4, "in"),
y = unit(3, "in"),
vjust = vjust,
gp = gp)
# Create the vjust's reference line and the baselines of the letters
tg_baseline_measured <- linesGrob(y = unit(c(3, 3), "in"),
gp = gpar(col = "red"))
tg_midline <- linesGrob(y = unit(rep(3 + inch_offset[1], 2), "in"),
gp = gpar(col = "green"))
tg_midline2 <- if(grepl("\n", string))
linesGrob(y = unit(rep(3 + inch_offset[3], 2), "in"),
gp = gpar(col = "green")) else nullGrob()
grid.newpage()
grid.draw(tg_midline)
grid.draw(tg_midline2)
grid.draw(tg_baseline_measured)
grid.draw(tg)
grid.draw(tg_adjusted)
grid.draw(tg_adjusted2)
}
We can see that this is not giving consistent results:
draw(family = "Arial", dpi = 96, size = 150, vjust = 1.5)
draw(family = "mono", dpi = 96, size = 150, vjust = 1.5)
Close, but not close enough to all be rounding errors.
Created on 2022-01-19 by the reprex package (v2.0.1)
from geomtextpath.
I'm having a bit of trouble grasping the relevant changes relative to your previous code. Is it that vjust is now != 0.5?
from geomtextpath.
At this point I'd be happy to patch up the most glaring discrepancies with magic constants, without patching up the hole in my soul from not understanding the reasons.
We could change the text_shape()
function as below, but replacing the magic constants with our best guesses.
text_shape <- function(text, id, gp, res = 72, vjust = 0.5, hjust = 0.5,
align = "center", unit = "inch") {
magic_vjust <- 0.75
magic_lheight <- 1
lineheight <- gp$lineheight %||% 1.2
lineheight <- lineheight * magic_lheight
vjust <- (vjust - 0.5) * magic_vjust + 0.5
# Remedy for https://github.com/r-lib/systemfonts/issues/85
vjust[vjust == 1] <- 1 + .Machine$double.eps
txt <- shape_text(
strings = text,
family = gp$fontfamily %||% "",
size = gp$fontsize %||% 12,
italic = (gp$font %||% 1) %in% c(3, 4),
bold = (gp$font %||% 1) %in% c(2, 4),
lineheight = lineheight,
tracking = gp$tracking %||% 0,
id = id,
res = res, vjust = vjust, hjust = hjust, align = align
)
adj <- resolution_to_unit(res = res, unit = unit)
shape_vars <- c("x_offset", "y_offset", "x_midpoint")
metric_vars <- c("width", "height", "left_bearing", "right_bearing",
"top_bearing", "left_border", "top_border", "pen_x", "pen_y")
txt$shape[, shape_vars] <- txt$shape[, shape_vars] * adj
txt$metrics[, metric_vars] <- txt$metrics[, metric_vars] * adj
txt
}
from geomtextpath.
This function just allows all the parameters to be adjusted independently. Basically, the measurements don't seem to work exactly and are inconsistent between fonts, plus the line spacing seems inaccurate. Incidentally, it gives slightly different measurements from systemfonts::shape_string
. I am prepared to declare myself a Principal Skinner at this point :)
I think that the whole string shaping mechanism is doomed to give us inconsistent results that won't match the textGrob exactly. This is a shame, but still produces internally consistent results, and perhaps we shouldn't worry too much about it.
The problem with magic constants is that they seem to be font specific. We could add in magic constants that work for the default font on our own systems, but this won't hold across all systems.
So as I see it, we have 3 options:
- Leave it as it is. It doesn't seem to have bothered anyone else yet despite there being quite a bit of community use and other (more obscure) issues being raised. If anyone raises the issue, we point them to this discussion, and possibly raise the issue with
textshaping
(though I'm not sure how actively it is maintained) - Add magic constants as you suggest. My guess is that this will lead to more consistent results for most users most of the time but has the potential to cause other issues down the line.
- Try to fix the problem to our satisfaction using other methods - I'm sure we can make adjustments based on empirical measurements every time
textpathGrob
is called. This might be complex and slow (but probably not), and a method doesn't spring immediately to mind, but my hunch is that it's possible.
I don't have a strong preference between 1) and 2). Number 3) would be best, but might be a project for a future release rather than the first release?
from geomtextpath.
I'm mostly leaning towards option 1 at the moment: we've provided plenty of control over finetuning through the vjust scales. Also we've done what is within reason to try to figure out what is going on, but we haven't gotten there yet and I think the ROI tradeoff is starting to swing towards leaving things as-is. We can always make a note in the aesthetics vignette that vjust is not expected to follow geom_text()
to the pixel in a side-by-side comparison. Also, we can always reopen the topic should some new insights come to us.
The problem with magic constants is that they seem to be font specific
Yeah this bummed me out as well. There are so many different metrics to a font and I experimented with some ratios between e.g. the max. ascent and lineheight, or the dimensions of the bouding box and the lineheight, or the height metric minus the top bearing etc, but none of them seemed to be spot-on. At this point, I don't even know how fontsize
corresponds to these metrics anymore.
from geomtextpath.
OK. Let's leave it - for now.
from geomtextpath.
Related Issues (20)
- geom_textsegment() with arrow() draws line to the top left of the plot HOT 4
- Denisty fill HOT 6
- label border? HOT 2
- geom_textsmooth() doesn't choose method if not explicitly set HOT 2
- Request something like "dodge" along path HOT 3
- Chinese character support HOT 3
- geom_labelsf() not recognizing aesthetics HOT 5
- While working with ragg, minuses aren't drawn HOT 2
- geom_textpath() always draw a empty box when label is ""
- Ignoring unknown parameters: text_smoothing HOT 10
- Feature request - multiple labels per line HOT 7
- geom_textsmooth computation fails if method argument is not specified HOT 3
- ggplot2 is separating size and linewidth HOT 2
- Negative values in geom_textcontour not appearing HOT 1
- geom_textlinerange HOT 1
- Question on angle
- text_only in geom_labelsegment
- Feature request - avoid text overlapping
- two labels on same curve HOT 5
- straight argument unknown in geom_textsf() HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from geomtextpath.