Skip to content
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

textAlign(CENTER, CENTER) does not vertically center user-loaded fonts #6593

Closed
1 of 17 tasks
micuat opened this issue Nov 28, 2023 · 15 comments
Closed
1 of 17 tasks

textAlign(CENTER, CENTER) does not vertically center user-loaded fonts #6593

micuat opened this issue Nov 28, 2023 · 15 comments

Comments

@micuat
Copy link
Member

micuat commented Nov 28, 2023

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build Process
  • Unit Testing
  • Internalization
  • Friendly Errors
  • Other (specify if possible)

p5.js version

1.8.0

Web browser and version

any

Operating System

any

Steps to reproduce this

below is copied from a topic on discourse by @quinton-ashley : https://discourse.processing.org/t/why-is-text-not-centered-properly-with-textalign-center-center/


textAlign(CENTER, CENTER) does not seem to work when used with user loaded fonts.

Here is a demonstration I made in p5.js:
editor.p5js.org

Here’s the same code run with q5.js showing how the text is supposed to be displayed in the center of the canvas.
editor.p5js.org

I think the problem is caused by the use of OpenType instead of the native canvas text drawing. Text quality is also noticeably worse than native canvas rendering at lower resolutions as shown in the demonstrations.

@micuat
Copy link
Member Author

micuat commented Nov 28, 2023

After his initial post, I (Naoto) noticed that q5.js simply uses textBaseline under the hood. I don't exactly know what p5.js is doing, but skimming other issues, I'm guessing it calculates the vertical position on its own to support multiline.

Also, I found that by using textBounds, same alignment as ctx.textBaseline = "middle" can be achieved:
https://editor.p5js.org/micuat/sketches/jWiM9dMcu

@diyaayay
Copy link
Contributor

Hey,
I would like to work on this issue.
Thanks.

@micuat
Copy link
Member Author

micuat commented Nov 28, 2023

@diyaayay feel free to, but can you suggest what steps have to be taken?

@davepagurek
Copy link
Contributor

@micuat I noticed on the Discourse thread you mentioned:

(while researching, I noticed that q5.js (thus textBaseline) won’t center multiline properly, and this is why p5.js is doing some additional calculation)

Do you think we need to adjust this to treat single lines differently?

if (this._textBaseline === constants.CENTER) {
finalMaxHeight = originalY + maxHeight - ascent / 2;

@diyaayay
Copy link
Contributor

diyaayay commented Dec 5, 2023

Thanks to @perminder-17 for helping me with this issue. This is what we came up with, which solves the issue. There may be better ways to achieve this still. Let me know if there are any changes that could be a better approach, Thanks.

let offset = 0;
if (this._textBaseline === constants.CENTER) {
  if (lines.length === 1) {
    offset = 6 * (p.textAscent() - (0.9084) * this._textSize);
  }
  else {
    offset = (lines.length - 1) * p.textLeading() / 2;
  }
}

@dhowe
Copy link
Contributor

dhowe commented Dec 5, 2023

Thanks for this @diyaayay -- can you clarify what those hard-coded numbers (6 and 0.9084) represent ?

@diyaayay
Copy link
Contributor

diyaayay commented Dec 6, 2023

@dhowe
0.9084: This is because the p.textAscent() is 0.9084 times textsize when we don't use textFont. When we use textfont we adjust the offset and when we don't get the offset with value 0.

This value is used to calculate the offset for vertically centering single lines of text when a text font is not used. It represents the ratio of the text ascent to the text size when the default font is used. This value is hardcoded because it is an empirical observation based on the default font metrics in various browsers. It might not be accurate for all fonts, but it provides a reasonable approximation for cases.

6: It is used to compensate for the difference between the actual text ascent and the value obtained using the 0.9084 ratio. This difference arises due to the variations in font metrics across different browsers and systems. Like we are adjusting the offset. Let's say, we are drawing textFont with an offset of 12 pixels below the original. And the value which we were getting with (p.textAscent() -0. 9084(textSize) =-2) then we multiplied with 6 to get the original value and drew it to the center by aligning vertically.

There is another approach that I came across which does not involve hardcoded values:

let offset = 0;
if (this._textBaseline === constants.CENTER) {
  if (lines.length === 1) {
    const textWidth = this.textWidth(lines[0]);
    const canvasWidth = this.canvas.width;
    offset = (canvasWidth - textWidth) / 2;
  } else {
    offset = (lines.length - 1) * p.textLeading() / 2;
  }
}

The screenshots below are the outputs and accuracy achieved from the hardcoded values approach:

1a

1b

The screenshots below are the outputs of the second approach that I mentioned above:

1
2
3

@dhowe
Copy link
Contributor

dhowe commented Dec 8, 2023

The outputs look correct for the 2nd method, but I'm not sure I understand the code: we are talking about vertically centering a single line of text, but, unless I'm misreading, the calculation uses textWidth and canvasWidth only ?

Also, there are two outputs we could potentially match: a) how we handle centered vertical alignment for browser fonts, and b) how Processing handles this case

Below images show that a) and b) are not the same. And, if I remember correctly, we decided, rather early on, to deviate here as Processing's approach appeared to be incorrect. Further, since we don't mess with existing sketches, it would seem we should aim for the first output below as our target.

@diyaayay perhaps you can show the results of one or both of your methods, overlayed on the first output, for a few different fonts/sizes ?

p5.js browser font:

function setup() {
  createCanvas(400, 400);
  textFont("Georgia", 48);
  textAlign(CENTER, CENTER);
  line(200, 0, 200, height);
  line(0, 200, width, 200);
  text("Align", 200, 200);
}
image


 

processing .vlw font:

size(400, 400);
textFont(loadFont("Georgia-48.vlw"));
textAlign(CENTER, CENTER);
line(200, 0, 200, height);
line(0, 200, width, 200);
text("Align", 200, 200);
image


 

And p5.js user-font (the problem, for reference):

function setup() {
  createCanvas(400, 400);
  loadFont("Georgia.ttf", f => {
    textFont(f, 48);
    textAlign(CENTER, CENTER);
    line(200, 0, 200, height);
    line(0, 200, width, 200);
    text("Align", 200, 200);
  });
}
Screenshot 2023-12-08 at 6 18 05 PM

@diyaayay
Copy link
Contributor

diyaayay commented Dec 26, 2023

@dhowe Sorry for replying so late, I was busy with university exams.
After going through the calculations a little bit, I am confused. textLeading is defined as spacing between lines of text in units of pixels. It should exist between multiple lines of text, that's what I can see on the reference page.
So for a single line of text, I'm assuming there should not be a value I mean zero or not defined.
I console logged the textLeading value for single lines that we're dealing with here, for a textSize of 100, it is 125, for textSize 200, it is 250, and for 20 it is 25, respectively all for user loaded fonts. the offset calculation here in the code is shown below:

// Offset to account for vertically centering multiple lines of text - no
 // need to adjust anything for vertical align top or baseline
      let offset = 0;
      if (this._textBaseline === constants.CENTER) {
        offset = (lines.length - 1) * p.textLeading() / 2;
      } else if (this._textBaseline === constants.BOTTOM) {
        offset = (lines.length - 1) * p.textLeading();
      }

this is what p5.js is currently using for vertically centering multiple lines of text.

Should we be handling textLeading for single lines or calculating offset with including textLeading for single lines?

Or am I in the wrong direction?

@dhowe
Copy link
Contributor

dhowe commented Dec 27, 2023

The issue appears to be with the the y-position adjustment in this line, which is attempting to vertically center the text (without this adjustment, the text is baseline-aligned.) So this._textAscent(fontSize) / 2 appears to be an incorrect value for this case. For fonts NOT loaded by the user, which don't exhibit this problem, alignment is handled automatically via the canvas' drawingContext.

image

function setup() {
  createCanvas(400, 400);
  textFont('Georgia', 48);
  doText(100);

  loadFont("Georgia.ttf", f => {
    textFont(f, 48);
    doText(300);
  });
}

function doText(x) {
  textAlign(CENTER, CENTER);
  line(x, 0, x, height);
  line(0, 200, width, 200);
  text("Align", x, 200);
}

@diyaayay
Copy link
Contributor

diyaayay commented Dec 28, 2023

@dhowe

This is the Bottom alignment:
image
This is the Top alignment:
image
This is Center Alignment:
image

So I thought I need to incorporate the full height and tried using these calculations:

switch (renderer._textBaseline) {
      case constants.TOP:
        y += this._textAscent(fontSize);
        break;
      case constants.CENTER:
        y += (this._textAscent(fontSize) - this._textDescent(fontSize)) / 2;
        break;
      case constants.BOTTOM:
        y -= this._textDescent(fontSize);
        break;
    }

I feel this also isn't the most correct approach because for top , the current approach of p5.js should have worked. However ,these were the results.

Center:

image

@diyaayay
Copy link
Contributor

Idk what is wrong with the calculation of the ascent because the top alignment should have worked right? or is there an extra offset at the ascent because of the nature of user-loaded fonts.

@limzykenneth
Copy link
Member

@diyaayay Looking at your screenshots, could it be that the top and center align text is using the font's bounding box top instead of ascent to calculate its position, accounting for the extra space at the top?

@diyaayay
Copy link
Contributor

@limzykenneth Oh! maybe. I will look into this again. I still have the branch where I was working on this issue. Thanks for mentioning another potential cause of why this might be happening.

@dhowe
Copy link
Contributor

dhowe commented Nov 12, 2024

closing pending further information

@dhowe dhowe closed this as completed Nov 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants