Fork me on GitHub
Richie19:03:28 line-height (.getHeight metrics) gives "SUBMIT2" and "help2" whereas line-height (.getAscent metrics) would give "SUBMIT" and "help" which look nicer.


I don't have any prior experience with this stuff and it's confusing for me. I that the font starts drawing at a on the baseline. The rectangle's 0,0 is top left. We have to shift the text but idk by how much. I think I want to treat the ascent plus descent as the height and center that. That ignores the leading which would be relevant for multiple lines of text. My understanding is that the Height is the Ascent+Descent+Leading where the Ascent is the hump of an 'h' to the top (or the top of an 'h' to the top of an 'o') and the Descent is the bottom of a 'p' to the baseline. (or bottom of a 'p' to the bottom of an 'o')


Maybe the rectangle should be Ascent+Descent in height instead of Height. i.e. Height is the result of .getHeight() which is the line height. I'm interested to hear what you think. I started looking into it since my button looked bad. I understand what's going on well enough that I can get something that's not bad.


membrane.ui/label's API isn't a good fit. It both does too much and too little. For backwards compatibility reasons, I think it's too late to "fix" membrane.ui/label's behavior, but I definitely want to create a new builtin text element that is more sane and deprecate membrane.ui/label. There's some related features that I would want to consider as part of the design of a better text element: • alignment • justification • text direction • emoji • styling • wrapping • line height • multi-font


My experience so far is that I don’t like ui/label but I haven’t figured out yet exactly what I don’t like. says it provides justification and text direction. I haven’t tried to use it for anything though so I don’t know in what way it provides those things. Probably not in a way that’s composable with membrane.


What do you mean by “wrapping”?


word wrap, text wrap


Ah, right.


basically, you provide some bounds and have the text wrap to the next line


TextLayout makes a lot of sense for Java2d, but can't be used for any of the other options.


Oh right. I wasn't even thinking about the other backends.


It does look like a good reference though and it wasn't on my radar. Thanks for the pointer!


Did you see this? some font rendering thoughts.


@U09D96P9B, I have. It's good food for thought. It's still unclear to me the best way to incorporate all the following: • alignment • justification • text direction • emoji • styling • wrapping • line height • multi-font


Something else, why decrement the line height? This makes the lines one pixel closer together on the y axis, right?


I'm not totally sure, but I think the issue is that .getHeight returns an integer instead of a float which is subtly wrong. I think what happened is I didn't realize why the text layout was wrong and just picked an option that looked reasonable. Later, I found out that there's a separate API that does return the ascent, descent, and leading as float values (see, but didn't revisit the text rendering.


Ooh, I was wondering about that commented code block.


So the bottom line is that I'm not totally sure why line height is decremented. Another guess is that it made text rendering better match skia's, but I don't know off the top of my head.


Ok, thanks.


Sorry I don't have a better answer 😕


Haha, no trouble. I was trying to avoid touching the source so that I could more easily contribute changes but I decided to just start mucking around however I want and worry about it later. e.g. I'm going to change getHeight to getAscent instead of trying to work around it. I'll also delete that decrement and see if anything breaks, lol.


Makes sense. You may also consider just creating a new Element that works the way you want.


I guess that doesn't work for changing all the elements that use label already.


Yea, I wanted to edit text-bounds.


Idk. I'm not sure how to not create a problem for my future self.


That does highlight an interesting point with respect to components like textareas. Specifying a font isn't enough.


I'm not sure what you mean. Do you mean that we could have a protocol for font that provides ascent, descent, advance, etc instead of trying to get it off of the font? What I'm thinking seems clearer to me now. The origin of text in the java2d backend is a point on the baseline at the far left end. text-bounds needs to shift the origin up to be at the top left of the text so that you can draw some text and draw a box that bounds the text and have the text looks like it's compactly nested inside the box. That distance is the ascent. The height would include the descent which isn't relevant for this. Recognizing that we need to translate the origin of the font, maybe we make that configurable. The caller can decide how to translate the origin and can use height or ascent or ascent+leading. Just pass a function. I might not want to make the origin the top left; maybe I pick a good y-coordinate for the baseline of the font in my button. Or maybe I use the strikethrough as the center... If I consider the origin as the top left of the em block then height is right... It's just not what I want at the moment.


What do you think about unit tests that compare backends? For stuff like this^ we might want a test...


Never mind, I should just translate it.


Oh, right. Height would not give you the top of the em block. I'm talking in circles. I'm going to make some examples.


Right now, if someone uses textarea, they can provide a font which provides a name, size, and weight. If someone wanted to reuse textarea with different styling they would probably want more options. I don't know what the right answer is off the top of my head. It would require some design work.


> What do you think about unit tests that compare backends? For stuff like this^ we might want a test... Being able to compare backends makes sense, but having different backends exactly match is a non-goal.


There are many reasons why different backends might have different output.


For some backends (like the terminal backend), there's not even a good way to compare it with other backends.


Another point is that all the current backends are based on 2d graphics, but all the membrane concepts of UI can be applied to any output (eg. 3d, audio, haptic, smell-o-vision, etc).


Constructing and drawing a TextLayout and its bounding rectangle:

   Graphics2D g = ...;
   Point2D loc = ...;
   Font font = Font.getFont("Helvetica-bold-italic");
   FontRenderContext frc = g.getFontRenderContext();
   TextLayout layout = new TextLayout("This is a string", font, frc);
   layout.draw(g, (float)loc.getX(), (float)loc.getY());

   Rectangle2D bounds = layout.getBounds();
The TextLayout docs has an example on how to get the bounds. That might be easier/nicer than folding Font.getStringBounds against max. I'm having trouble getting it to work for me though so I don't know that I want to promote it. Just fyi I guess.
(defn line-metrics
  [{:keys [font text]}]
  (let [jfont (backend/get-java-font font)
        frc (backend/get-font-render-context)
        line-metrics (.getLineMetrics jfont
        string-bounds (.getStringBounds jfont text frc)
        text-layout (TextLayout. text jfont frc)]
    {:line-metrics {:height (.getHeight line-metrics)
                    :ascent (.getAscent line-metrics)
                    :descent (.getDescent line-metrics)
                    :leading (.getLeading line-metrics)
                    :strikethrough-offset (.getStrikethroughOffset line-metrics)
                    :strikethrough-thickness (.getStrikethroughThickness line-metrics)
                    :underline-offset (.getUnderlineOffset line-metrics)
                    :underline-thickness (.getUnderlineThickness line-metrics)}
     :string-bounds {:width (.getWidth string-bounds)
                     :height (.getHeight string-bounds)}
     :text-layout {:origin-x (.getX (.getBounds text-layout))
                   :origin-y (.getY (.getBounds text-layout))
                   :width (.getWidth (.getBounds text-layout))
                   :height (.getHeight (.getBounds text-layout))}}))

(line-metrics (ui/label "SUBMIT" (ui/font nil 40)))

{:line-metrics {:height 50.3125
                :ascent 40.214844
                :descent 8.7890625
                :leading 1.3085938
                :strikethrough-offset -10.3515625
                :strikethrough-thickness 1.9921875
                :underline-offset 4.2382812
                :underline-thickness 2.9296875}
 :string-bounds {:width 151.11328125
                 :height 50.3125}
 :text-layout {:origin-x 1.796875
               :origin-y -29.125
               :height 29.609375}}
(let [lbl (ui/label "SUBMIT" (ui/font nil 40))
      jfont (backend/get-java-font (:font lbl))
      text (:text lbl)]
  (backend/text-bounds jfont text))

[151.11328125 50.3125]

(line-metrics (ui/label "SUBMIT\nBUTTON" (ui/font nil 40)))

{:line-metrics {:height 50.3125
                :ascent 40.214844
                :descent 8.7890625
                :leading 1.3085938
                :strikethrough-offset -10.3515625
                :strikethrough-thickness 1.9921875
                :underline-offset 4.2382812
                :underline-thickness 2.9296875}
 :string-bounds {:width 315.546875
                 :height 50.3125}
 :text-layout {:origin-x 1.796875
               :origin-y -29.140625
               :width 310.47265625
               :height 29.625}}

(let [lbl (ui/label "SUBMIT\nBUTTON" (ui/font nil 40))
      jfont (backend/get-java-font (:font lbl))
      text (:text lbl)]
  (backend/text-bounds jfont text))

[164.43359375 100.625]
I'm still trying to understand these numbers.


Actually, that doesn't look much better.


Not sure what your goal is with this experiment, but if you're trying to find a better API, I've been thinking that figma’s data model might provide some good inspiration. Additionally, one of the primary use cases is aligning text with other elements. You should be able to align with baseline, ascent, or in rare circumstances, other features.


Sorry, yea. I never followed up with what I thought about these numbers. e.g. I don't know how to interpret [:text-layout :height] Yea, I was hoping it would be a better api. Where can I read about Figma's data model?

phronmophobic21:03:00 It's a little wonky, but I think it has proven that it can represent the types of things UI designers care about.