Giter Club home page Giter Club logo

Comments (9)

AllanCameron avatar AllanCameron commented on June 26, 2024

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.

AllanCameron avatar AllanCameron commented on June 26, 2024

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.

teunbrand avatar teunbrand commented on June 26, 2024

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:

image

I'm slowly becoming this person over the issue:

image

from geomtextpath.

AllanCameron avatar AllanCameron commented on June 26, 2024

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.

teunbrand avatar teunbrand commented on June 26, 2024

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.

teunbrand avatar teunbrand commented on June 26, 2024

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.

AllanCameron avatar AllanCameron commented on June 26, 2024

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:

  1. 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)
  2. 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.
  3. 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.

teunbrand avatar teunbrand commented on June 26, 2024

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.

AllanCameron avatar AllanCameron commented on June 26, 2024

OK. Let's leave it - for now.

from geomtextpath.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.