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

Control text leading trim #193

Open
souporserious opened this issue Mar 7, 2022 · 12 comments
Open

Control text leading trim #193

souporserious opened this issue Mar 7, 2022 · 12 comments

Comments

@souporserious
Copy link

Hello, thank you for this amazing library! I'm currently using the Text component in @react-three/drei and wondering if there's a way to achieve control over leading trim? I was looking to use Capsize to possibly do it, but then came across #82 which says blockBounds and visibleBounds were added which seems like could be helpful here. I would love a first-class way to control this, but interested if you have a suggestion to hack a solution for now? Happy to help look into a PR for support if you're interested 🙂

@lojjic
Copy link
Collaborator

lojjic commented Mar 7, 2022

That's an interesting suggestion! I wasn't aware of the text-edge/leading-trim CSS proposals, they seem very useful.

I'm not sure if I'd want to implement those as currently defined in the working draft, since they pretty clearly say "do not implement (yet)" and I'd like to avoid future compat issues. But, I do think it would be fairly trivial to do the following:

  • Add relevant metrics to the textRenderInfo response: cap height, ex height, half leading... (ascender and descender are already there)
  • Allow some more of those metrics as keywords for anchorY: e.g. top-cap, top-ascent, bottom-descent etc.

Do those sound like they'd enable what you want?

In the meantime you should be able to infer some of those from what's currently provided ... for example in textRenderInfo you get the ascender/descender/lineHeight properties and you can calculate the half-leading from those as (lineHeight - (ascender - descender)) / 2 which you can apply as an offset.

Note to self: this will become more complicated with the multiple fonts per line work, I'll need to give that more thought.

@souporserious
Copy link
Author

That makes sense not to preemptively add anything until the spec is finalized! Love the idea of being able to use anchorY to set it automatically and might offer a small perf bump once implemented since I don't have to sync myself.

I think your suggestion of using textRenderInfo should work fine for now! I wasn't sure how to go about that, but now realize I can use drei's Text onSync callback to get that information.

I appreciate the quick reply! Again, thanks for such a cool text implementation! I was working on my own with things like text wrapping and very happy to see all of the wonderful work done here with things like text kerning and even selection! 🙏

@souporserious
Copy link
Author

Made some progress this week on getting all of the proper values, thanks for the half-leading tip!

image

Curious if you have any ideas for how I can achieve lineGap functionality in the current system like the following?

image

I think I have all of the info I need now, just not sure how I should go about counteracting your line height calculation. Is this a feature you'd be open to supporting as well?

Having more control of how line height is applied will make creating designs like this easier:

image

@lojjic
Copy link
Collaborator

lojjic commented Mar 13, 2022

Is this a feature you'd be open to supporting as well?

Absolutely!

If I'm understanding correctly, if the whole text block was just shifted upward by halfLeading + (ascender - capHeight), then the lineHeight would still be sufficient to control the spacing? Or is a new line gap property needed? (I don't see one in the CSS working draft, fwiw.)

@souporserious
Copy link
Author

Awesome! 🎉 That equation for offsetting the text block to align the top to the cap height works great! But yeah, for a lineGap property I think it needs to be done in the library code. I'm probably just tired from a long week, but having trouble calculating this offset to lineHeight to adjust for it 😅. There aren't any working drafts right now that I know of and it is more of a nice to have since designers sometimes rely on precise spacing.

Using a simple example, I'd like to pass capHeight and lineGap to a custom Text component:

<Text capHeight={1} lineGap={0.5} />

I want this to size the text so the cap size is exactly 1 world unit and the space between the baseline and next cap height is 0.5 world units.

To get the proper fontSize adjustment I do the following taken from capsize:

const capHeightScale = fontMetrics.capHeight / fontMetrics.unitsPerEm
const fontSize = capHeight / capHeightScale

This works great and sizes the cap height appropriately to one world unit, but then I run into trouble trying to calculate the offset for the lineHeight option. Since the capsize utility expects it's for CSS I think it's getting this calculation wrong.

Happy to help with PRs here btw 😁 I can start something once I get some time here probably later in the month.

@lojjic
Copy link
Collaborator

lojjic commented Mar 14, 2022

Thank you for the examples! I'm starting to understand better now. 😁

I've added a couple things to get closer:

  • textRenderInfo now includes capHeight and xHeight values
  • anchorY now accepts top-cap and top-ex keywords

I think the big missing piece now is that the font sizing (and therefore line height) is solely based on the "em height" as defined in CSS, whereas you want to have it use the cap height as the basis, does that sound accurate? I'll ponder this some more.

@souporserious
Copy link
Author

Woot, appreciate you getting those in! 🙌

whereas you want to have it use the cap height as the basis, does that sound accurate?

Yeah, exactly! Ideally, being able to control spacing between the baseline and next cap height or the spacing between the descender and next cap height would be amazing. This is how traditional graphic designers controlled the spacing between lines which unfortunately never made its way into CSS.

@souporserious
Copy link
Author

souporserious commented Mar 14, 2022

Giving some more thought to what an API could look like, maybe allowing control of start/end of the lineHeight would be an elegant solution? So in my scenario I could do something like this:

{
  fontSize: 1.35,
  lineHeight: 1.125,
  lineHeightStart: 'descender', // or baseline
  lineHeightEnd: 'cap'
}

Not sure how this would translate to the current functionality to prevent any breaking changes though.

@lojjic
Copy link
Collaborator

lojjic commented Mar 17, 2022

I've been toying with the idea of a fontSizeBasis prop that defines what metric the fontSize should be interpreted as corresponding to. By default it would be "em" for the CSS behavior, but you could set it to others: "cap" would be your baseline-to-cap box for example. Maybe allow two keywords to define a bottom/top like "descender cap".

It may not hit all use cases, but I feel like that could make a lot of the things you're trying to do easier ... once you can control the exact height of the "cap" using fontSize, for instance, then since lineHeight is always relative to the fontSize then you can control the gap between "cap"s using simple math.

lineHeight = (fontSize + "line gap") / fontSize

@souporserious
Copy link
Author

That sounds great! 😍 I've been trying to hack around it this week with no luck so a more first-class way to do it would be wonderful!

Related, I was wondering how line height relates to the browser's line height? I've been thinking of world units as rems, but not sure if that's a correct way to think about it. I made an example here to illustrate what I'm doing in web with rem units and then when trying to do it with trokia it seems the line height is much more dramatic with the same values.

@lojjic
Copy link
Collaborator

lojjic commented Mar 17, 2022

Cool, I'll give that a shot then.

Related, I was wondering how line height relates to the browser's line height? I've been thinking of world units as rems

I guess you could think of world units that way? I tend to think of them like real-world meters, though.

But for lineHeight specifically, remember that a numeric lineHeight is always relative to the fontSize. So if your calculated line height is in rems or px or some other absolute length, you probably need to divide it by the fontSize.

@souporserious
Copy link
Author

Ah, ok that makes way more sense now! Dividing the line height by the font size is exactly what I was looking for and looks like it works with Capsize as expected now 😎. I was definitely overcomplicating that lol.

Thank you so much for all of your help on this so far and for adding the extra needed info and features! Happy to help test the fontSizeBasis feature once it's ready if you go that route 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants