-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The vjust
issue
#62
Comments
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 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) |
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) |
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: |
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) |
I'm having a bit of trouble grasping the relevant changes relative to your previous code. Is it that vjust is now != 0.5? |
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(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
} |
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 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:
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? |
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
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 |
OK. Let's leave it - for now. |
I thought to separate this discussion from the discussion in #50 where we expanded the issue identified in #55 which hopefully makes it easier to distinguish roadmap things from vjust things. (Also, it would be very satisfying to close this issue instead of having to wait until we're done with all roadmap things)
What we know so far summarized:
vjust
behaves in {geomtextpath} differs somewhat from {ggplot2}max_ascent
/lineheight
ratio is about 0.787 (for Arial I'll presume)textshaping::shape_text()
might be in points rather than pixels?The text was updated successfully, but these errors were encountered: