membrane

Richie 2022-03-21T19:17:28.556509Z

https://github.com/phronmophobic/membrane/blob/e547cdcec33239b3920428a47b9966b93d300be8/src/membrane/java2d.clj#L271 line-height (.getHeight metrics) gives "SUBMIT2" and "help2" whereas line-height (.getAscent metrics) would give "SUBMIT" and "help" which look nicer.

Richie 2022-03-22T11:56:45.700039Z

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.

Richie 2022-03-22T12:00:34.846889Z

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

Richie 2022-03-22T12:50:10.388909Z

Never mind, I should just translate it.

Richie 2022-03-22T12:54:20.638279Z

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.

phronmophobic 2022-03-22T19:45:56.161789Z

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.

phronmophobic 2022-03-22T19:47:14.812429Z

> 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.

phronmophobic 2022-03-22T19:48:00.832359Z

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

phronmophobic 2022-03-22T19:48:58.056909Z

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

phronmophobic 2022-03-22T19:51:14.149309Z

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).

Richie 2022-03-24T23:38:41.893329Z

https://docs.oracle.com/en/java/javase/16/docs/api/java.desktop/java/awt/font/TextLayout.html

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();
   bounds.setRect(bounds.getX()+loc.getX(),
                  bounds.getY()+loc.getY(),
                  bounds.getWidth(),
                  bounds.getHeight());
   g.draw(bounds);
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
                                 text
                                 frc)
        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
               :width 
               :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.

Richie 2022-03-24T23:40:52.980909Z

With syntax highlighting... https://gist.github.com/rgkirch/5242a58ca7d5ff4ff32f42a9f84247d2

Richie 2022-03-24T23:41:43.887309Z

Actually, that doesn't look much better.

phronmophobic 2022-03-25T02:55:47.045579Z

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.

Richie 2022-03-25T03:51:17.177229Z

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?

phronmophobic 2022-03-25T21:22:00.464159Z

https://www.figma.com/developers/api#node-types It's a little wonky, but I think it has proven that it can represent the types of things UI designers care about.

Richie 2022-03-21T19:25:58.646359Z

I don't have any prior experience with this stuff and it's confusing for me. I https://docs.oracle.com/javase/8/docs/api/java/awt/FontMetrics.html that the font starts drawing at a https://docs.oracle.com/javase/8/docs/api/java/awt/doc-files/FontMetrics-1.gif 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')

Richie 2022-03-21T19:39:57.404349Z

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.

phronmophobic 2022-03-21T20:07:37.300069Z

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

Richie 2022-03-21T20:15:02.122099Z

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. https://docs.oracle.com/en/java/javase/16/docs/api/java.desktop/java/awt/font/TextLayout.html 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.

Richie 2022-03-21T20:16:20.597659Z

What do you mean by “wrapping”?

phronmophobic 2022-03-21T20:16:58.350459Z

word wrap, text wrap

Richie 2022-03-21T20:17:09.691659Z

Ah, right.

phronmophobic 2022-03-21T20:17:22.229519Z

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

phronmophobic 2022-03-21T20:18:46.177669Z

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

Richie 2022-03-21T20:19:21.485859Z

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

phronmophobic 2022-03-21T20:20:00.308649Z

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

chromalchemy 2022-03-21T20:41:52.117779Z

Did you see this? some font rendering thoughts. https://tonsky.me/blog/font-size/

phronmophobic 2022-03-21T20:45:58.414609Z

@chromalchemy, 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

Richie 2022-03-22T00:25:46.254019Z

Something else, why decrement the line height? https://github.com/phronmophobic/membrane/blob/e547cdcec33239b3920428a47b9966b93d300be8/src/membrane/java2d.clj#L251 This makes the lines one pixel closer together on the y axis, right?

phronmophobic 2022-03-22T00:39:34.965729Z

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 https://github.com/phronmophobic/membrane/blob/e547cdcec33239b3920428a47b9966b93d300be8/src/membrane/java2d.clj#L107), but didn't revisit the text rendering.

phronmophobic 2022-03-22T00:39:38.676109Z

good catch!

phronmophobic 2022-03-22T00:43:42.262529Z

Created an issue here, https://github.com/phronmophobic/membrane/issues/40

Richie 2022-03-22T01:01:24.217149Z

Ooh, I was wondering about that commented code block.

phronmophobic 2022-03-22T01:43:38.794049Z

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.

Richie 2022-03-22T01:44:12.592249Z

Ok, thanks.

phronmophobic 2022-03-22T01:48:43.901509Z

Sorry I don't have a better answer 😕

Richie 2022-03-22T01:53:08.513149Z

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.

phronmophobic 2022-03-22T01:54:35.601879Z

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

phronmophobic 2022-03-22T01:54:52.285719Z

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

Richie 2022-03-22T01:55:22.122039Z

Yea, I wanted to edit text-bounds.

Richie 2022-03-22T01:55:51.597879Z

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

phronmophobic 2022-03-22T02:12:40.743419Z

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