If you didn't know, I made a Gemini client called Moonlander back when I first got into Gemini. After 3 days of development, it essentially got abandoned.Moonlander
It looks pretty and has some styling options my current client, Kristall doesn't have¹, mostly related to the spacing of text. (Maximum width, line/paragraph spacing, etc.)Kristall
Moonlander is also one of the first² (and one of the few) Gemini clients to do something special: It renders text directly into a blank canvas instead of using any OS text widgets.
This decision has the main advantage of not being bound by any of the limitations the OS text widgets have on positioning and styling the text.
However, it comes with some major disadvantages:
- A lot more complicated and requires extra handling for the things the OS widgets give you for granted, like culling scrolled lines to not give too much to the text renderer.
- Accessibility goes out the window.
- Text is _really_ hard to work with.
I'd like to focus on the latter for this specific article.
Text hates you in general
If you haven't read the following two articles, please do, you'll understand why you probably shouldn't attempt what I tried with Moonlander:Text Rendering Hates You - Alexis Beingessner
Text Editing Hates You Too - lord.io
The majority of the content in these articles actually aren't that relevant to Moonlander, as I offload all the dirty rendering work to the Pango library, and Moonlander doesn't contain any text editing functions.Pango
The specific part I want to focus on instead, is text selection.
Text isn't consistent
Let's begin with the simplest part of it all. How do you select a single line plain English text?
If you use a monospaced font, your job is easy. You can find which letter the cursor is on by dividing the cursor's X position with the letter width to get which letter the cursor is on.
If you're using a "normal" (not monospaced) font however, you'll immediately find an issue with that method: How wide is a letter?
You cannot just get the average width and use that, as even an inaccuracy of 5 to 6 pixels (depending of the font size of course) can cause your selection to go off. Or it might just break entirely on lines containing mostly thin or thick letters.
Looping over each letter and measuring them might be enough, but you'll probably have to do a lot of tricks to make that perform well.
Let's say you managed to make a single line work. Now, how do you do multiple lines?
This probably isn't that big of a deal as a single line, since you can mark each line as "completely selected" and it would probably work by itself.
Now you get a new issue in your issue tracker. The issue says the user cannot select Arabic text like they are used to on other programs.
Arabic is written left-to-right, so you have to handle that somehow. You might think of just reversing the entire algorithm, but then you'll need to make sure you deal with left-to-right text in the middle of right-to-left text. I also heard Japanese can be written top-to-bottom too, so good luck with that if you want to make that work as well!
Emoji: Two Children in a Trench Coat
There are some emoji that are actually two (or more?) characters that render as one. Some examples being flags and basically all emoji that also take a skin tone or some other modifier.Why Do Flag Emoji Count As Two Characters? - Tom Scott
A list of emoji with zero-width-joiner support.
What would be the best way to handle them within a selection? I don't know. Do you?
Tying this back to Moonlander
What was all that about text selection and why did I talk about it for half the page?
Well, Moonlander currently doesn't have any support for text selection. Which primarily means you cannot copy text out. The pages are essentially glorified images.
Links do work in text/gemini, as there are no in-line links in the format, so just the height of a line is enough to figure out if a link should be acted upon, and that is already required for various other functionality in the renderer, so it's trivial to find out.
When I started working on Moonlander, I was somewhat aware of all this, but I thought Pango would have the necessary tools to deal with it for me. But it does not. Or I couldn't find them in my research.
This, combined with the fact that even if it did, I would have to reimplement the selection controls like CTRL-A, CTRL-SHIFT-<arrow key> just killed my interest in continuing to work on Moonlander.
So, It's Dead?
Maybe. Maybe not.
I am not entirely sure if I will continue working on Moonlander, or just contribute to an already existing client.
If I decide to work on Moonlander though, I will most likely switch to using OS widgets like everyone else. The canvas renderer approach is too much work for little gain, and I am pretty sure I could pull off the fancy-ness of Moonrender³ with some hacks on top of OS widgets.
1: I should probably add them myself some time.
2: Actually, I am not sure. I think there are couple more clients that do this, but which one came the earliest, I have no clue.
3: Moonrender is the name of the rendering component used in Moonlander. It's totally separate from the Moonlander code, so you should be able to yank it off and continue working on it if you wish. The code is available on the same repository as Moonlander.