October 25

On Thursday, 24 October 2024 at 11:26:14 UTC, IchorDev wrote:

>

On Monday, 21 October 2024 at 02:31:19 UTC, cookiewitch wrote:

>

Fluid requires DMD≥2.098.1 or LDC≥1.28.1. The tour requires a newer compiler than that (meanining Fluid itself might still work) but I did not pinpoint the exact version. GDC is not supported.

Is it going to be supported?

Can't say for sure, because I haven't used it myself, but it's a matter of adding it to CI and doing some fix-ups where needed.

October 25

On Thursday, 24 October 2024 at 12:02:00 UTC, IchorDev wrote:

>

On Thursday, 3 October 2024 at 11:15:16 UTC, cookiewitch wrote:

>

After 9 months of development and 252 commits later, finally 0.7.0 gets to see the sunlight!

This project certainly seems promising, although I’m rather worried about how many classes there are with almost no data—so many allocations for what could just be a struct with function pointers, no? That’s how FreeType does ‘inheritance’ in pure C.

Fluid nodes aren't just pointers to functions, so no. I think switching to struct based code would intensely increase Fluid's complexity, both for me and anyone trying to start using it. I don't think it's worth it.

>

Anyway, at this stage, how difficult is it to make a custom Fluid backend? (e.g. SDL2 for input or a different renderer)

The backend API still isn't very polished. It's not difficult, but it takes some time to prepare. I also regret choosing a Raylib-like API rather than an event-based on. I want to change that in a later update, probably 0.8.0 or 0.9.0.

>

Also does this project have proper text layout support? I noticed there’s a dependency on FreeType; but FreeType doesn’t do layout, only rendering. I’m about to release BindBC-Pango if you need a text layout engine. They’re absolutely imperative for acceptable internationalisation support.

That is true, Fluid can only do the basic left-to-right text layout right now. To be frank, it isn't even able to center or right align text. So far I've opted for Freetype, because I'm more familiar with APIs of its kind and I wanted to save some of my time, but it might be about time I tried Pango. Thank you for your work on the bindings, I'll check them out!

October 25

On Friday, 25 October 2024 at 09:44:18 UTC, cookiewitch wrote:

>

On Thursday, 24 October 2024 at 12:02:00 UTC, IchorDev wrote:

>

Anyway, at this stage, how difficult is it to make a custom Fluid backend? (e.g. SDL2 for input or a different renderer)

The backend API still isn't very polished. It's not difficult, but it takes some time to prepare. I also regret choosing a Raylib-like API rather than an event-based on. I want to change that in a later update, probably 0.8.0 or 0.9.0.

I will check in again once that's done. :)

> >

Also does this project have proper text layout support? I noticed there’s a dependency on FreeType; but FreeType doesn’t do layout, only rendering. I’m about to release BindBC-Pango if you need a text layout engine. They’re absolutely imperative for acceptable internationalisation support.

That is true, Fluid can only do the basic left-to-right text layout right now. To be frank, it isn't even able to center or right align text. So far I've opted for Freetype, because I'm more familiar with APIs of its kind and I wanted to save some of my time, but it might be about time I tried Pango. Thank you for your work on the bindings, I'll check them out!

No problem! Only thing holding me from putting it on dub is that its dependency BindBC-GLib has no README yet.
BindBC-Pango comes with an example that should adequately demonstrate the basics of how to make use of it with FreeType's renderer. If you want to make your own renderer, I've translated enough of GObject's macros into string mixin generators that you can sub-class Pango's base render class like you would in C without any extra hassle:

FT_Face[PangoFont*] faces;

///A simplified version of `pango_ft2_font_get_face` from PangoFT, which is not a public method.
FT_Face pango_ft2_font_get_face(PangoFont* font) nothrow{
	assert(font);
	if(auto face = font in faces)
		return *face;
	
	FcPattern* pattern = pango_fc_font_get_pattern(cast(PangoFcFont*)font);
	FT_Error err;
	
	char* filename;
	if(FcPatternGetString(pattern, FC_FILE, 0, &filename) != FcResultMatch){
		//ERROR!
	}
	int id;
	if(FcPatternGetInteger(pattern, FC_INDEX, 0, &id) != FcResultMatch){
		//ERROR!
	}
	FT_Face face;
	err = FT_New_Face(ftLib, filename, id, &face);
	if(err){
		//ERROR!
	}
	double size;
	if(FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch){
		//ERROR!
	}
	err = FT_Set_Char_Size(
		face,
		cast(FT_F26Dot6)(size * (1<<6)),
		cast(FT_F26Dot6)(size * (1<<6)),
		0, 0,
	);
	if(err){
		//ERROR!
	}
	
	faces[font] = face;
	return face;
}

struct PangoCustomRenderer{
	PangoRenderer parentInstance;
	//custom class instance data goes here
}
struct PangoCustomRendererClass{
	PangoRendererClass parentClass;
}

mixin(G_DEFINE_TYPE("PangoCustomRenderer", "pango_Custom_renderer", "PANGO_TYPE_RENDERER"));

extern(C) nothrow{
	void pango_Custom_renderer_init(PangoCustomRenderer* self){
		//per-instance initialisation
	}
	
	void pango_Custom_renderer_class_init(PangoCustomRendererClass* self){
		auto rendererClass = &self.parentClass; //also written as `cast(PangoRendererClass*)`
		
		rendererClass.drawGlyph = &pangoCustomRendererDrawGlyph; //override drawGlyph
	}
	
	struct DrawState{
		FT_Vector pos;
	}
	
	///Move the pen position
	int moveTo(const(FT_Vector)* to, void* user) => 0;
	///Draw a line
	int lineTo(const(FT_Vector)* to, void* user) => 0;
	///Draw a quadratic bézier
	int conicTo(const(FT_Vector)* ctrl, const(FT_Vector)* to, void* user) => 0;
	///Draw a cubic bézier
	int cubicTo(const(FT_Vector)* ctrl1, const(FT_Vector)* ctrl2, const(FT_Vector)* to, void* user) => 0;
	
	void pangoCustomRendererDrawGlyph(PangoRenderer* pangoRenderer, PangoFont* font, PangoGlyph glyph, double x, double y){
		if(FT_Face face = pango_ft2_font_get_face(font)){
			FT_Load_Glyph(face, glyph, 0);
			
			//NOTE: glyph outline direction may be reversed
			
			immutable fns = FT_Outline_Funcs(
				&moveTo, &lineTo, &conicTo, &cubicTo, 0, 0,
			);
			auto state = DrawState();
			FT_Outline_Decompose(&face.glyph.outline, &fns, &state);
		}
	}
}

//using the class:
auto pangoRenderer = cast(PangoRenderer*)g_object_new(pango_Custom_renderer_get_type(), null);
//...
pango_renderer_draw_layout(pangoRenderer, layout, 0,0);
October 25

On Friday, 25 October 2024 at 12:21:14 UTC, IchorDev wrote:

>

On Friday, 25 October 2024 at 09:44:18 UTC, cookiewitch wrote:

>

On Thursday, 24 October 2024 at 12:02:00 UTC, IchorDev wrote:

>

Anyway, at this stage, how difficult is it to make a custom Fluid backend? (e.g. SDL2 for input or a different renderer)

The backend API still isn't very polished. It's not difficult, but it takes some time to prepare. I also regret choosing a Raylib-like API rather than an event-based on. I want to change that in a later update, probably 0.8.0 or 0.9.0.

I will check in again once that's done. :)

> >

Also does this project have proper text layout support? I noticed there’s a dependency on FreeType; but FreeType doesn’t do layout, only rendering. I’m about to release BindBC-Pango if you need a text layout engine. They’re absolutely imperative for acceptable internationalisation support.

That is true, Fluid can only do the basic left-to-right text layout right now. To be frank, it isn't even able to center or right align text. So far I've opted for Freetype, because I'm more familiar with APIs of its kind and I wanted to save some of my time, but it might be about time I tried Pango. Thank you for your work on the bindings, I'll check them out!

No problem! Only thing holding me from putting it on dub is that its dependency BindBC-GLib has no README yet.
BindBC-Pango comes with an example that should adequately demonstrate the basics of how to make use of it with FreeType's renderer. If you want to make your own renderer, I've translated enough of GObject's macros into string mixin generators that you can sub-class Pango's base render class like you would in C without any extra hassle [...]

Looking through Pango's documentation and code, integrating it with Fluid might be tricky. This topic is also complex and there are things I don't understand about Pango's approach that I can't find an explanation of. This is probably something I'll have to spend a lot of time on to get a proper grasp of.

I'd really appreciate it if there were some other, more comprehensive learning resources. Do you know any?

October 31

On Friday, 25 October 2024 at 12:49:36 UTC, cookiewitch wrote:

>

Looking through Pango's documentation and code, integrating it with Fluid might be tricky. This topic is also complex and there are things I don't understand about Pango's approach that I can't find an explanation of. This is probably something I'll have to spend a lot of time on to get a proper grasp of.

I'd really appreciate it if there were some other, more comprehensive learning resources. Do you know any?

Sorry for my late reply! Anyway, here we go…

Easy compromises

I was able to figure out how to use it for static text pretty easily from just the official docs. I found it best to look at what you want at the end (e.g. rendering the text), find how you do that, and then work backwards from there.

This might not help you that much, but here's a step-by-step outline on getting started using Pango, with links to the relevant documentation, which provide a decent amount of insight into what each class is/does. This boils down to about 20 lines of code from my example.

  • First you need a PangoFontMap. You can, for instance, get a PangoFontMap implemented using FreeType with pango_ft2_font_map_new.
  • Then, create a PangoContext from the PangoFontMap.
  • Create a PangoFontDescription (e.g. using pango_font_description_new) and use whichever setter functions on it to provide info about what font you want it to use, how large it should be, et cetera. Pango automatically uses fallback fonts when needed, which is why you don't just give it which font to use. This way, when users write glyphs that are not included in your preferred font, the overall appearance of the text can remain more-or-less consistent. In my example I only set the size of a PangoFontDescription, so Pango will basically default to using the system's preferred default fonts.
  • Set the PangoContext's PangoFontDescription to the one you just created/populated.
  • Create a PangoLayout from the PangoContext. This layout object handles a lot of things for you that you can mostly otherwise do yourself with components of the Pango library like .
  • You can set the layout's contents with:

From there, you'd mostly be interacting with the layout.

A few things to note:

  • Pango uses reference counting for basically everything, so make sure to read up on how you're supposed to unref objects you've created so that they get deallocated. Often this is done with g_object_unref instead of a function from Pango.
  • The API & API reference use the term 'character' confusingly. Mostly it seems to refer to code-points, but occasionally I believe it is used to refer to individual bytes (i.e. code-units).

So many problems

I really don't think the stock PangoLayout implementation provides a way to make using ropes work in a fast manner without just capitulating and using strings. However, you could re-implement most (but not all) of PangoLayout's layout functionality from functions that Pango exposes. This page should give you an idea.

Pango is far from perfect, and in some ways using it is an inherent compromise between what you want, and what's possible using libraries out there today. Even TextKit is not without its problems.

The baby and the bathwater

There is one way around all of this, which is building a new text layout engine…

Some consideration should be given to where to start. For instance, one could build up from HarfBuzz. However, HarfBuzz is another GLib-based FSF C library, just like Pango, so perhaps this would just shift the compromises further down the line. Say we want no compromises; then we have to do everything 100% in D. Certainly neither of these options are trivial, and it would be a fool's errand to embark upon them alone.
Having a full D text layout engine is a pet dream of mine of course, so if you're interested in contributing to a project like that then I might be able to dedicate some of my spare time to it.

November 01

On Thursday, 31 October 2024 at 13:08:55 UTC, IchorDev wrote:

>

On Friday, 25 October 2024 at 12:49:36 UTC, cookiewitch wrote:

>

[...]

I'd really appreciate it if there were some other, more comprehensive learning resources. Do you know any?

Sorry for my late reply! Anyway, here we go…

[...]

Some consideration should be given to where to start. For instance, one could build up from HarfBuzz. However, HarfBuzz is another GLib-based FSF C library, just like Pango, so perhaps this would just shift the compromises further down the line. Say we want no compromises; then we have to do everything 100% in D. Certainly neither of these options are trivial, and it would be a fool's errand to embark upon them alone.

Thank you for dedicating your time to this, and for the very elaborate response! Reimplementing PangoLayout does sound like an option, but as far as I've seen, Pango operates on strings all the way through. I suppose it is possible to assemble parts of the rope into a string on the stack, and only operate on a single part at once, but I don't think this is going to be very bug-proof. Alternatively, Fluid's text engine does distinguish between static and dynamic text with an "edit mode" flag, so it could be possible to apply Pango on stringified static text, but that's never going to be enough.

Oh, and also, since Pango is LGPLv2 licensed, I think a port of PangoLayout could run into licensing problems the way D programs are usually built [with DUB].

>

Having a full D text layout engine is a pet dream of mine of course, so if you're interested in contributing to a project like that then I might be able to dedicate some of my spare time to it.

I've been thinking about this last week and I'm under the impression this is what is going to happen anyway. For what it's worth, I'm currently working on another large PR which upscales Fluid's text engine, splitting it into a separate fluid.text package and optimizing it on the way through. It is mostly standalone, so if you find it appropriate, I could create a new Git & DUB repository for it once I'm done. I would like to have your input on it.

November 02

On Friday, 1 November 2024 at 12:14:25 UTC, cookiewitch wrote:

>

On Thursday, 31 October 2024 at 13:08:55 UTC, IchorDev wrote:

>

Sorry for my late reply! Anyway, here we go…

[...]

Some consideration should be given to where to start. For instance, one could build up from HarfBuzz. However, HarfBuzz is another GLib-based FSF C library, just like Pango, so perhaps this would just shift the compromises further down the line. Say we want no compromises; then we have to do everything 100% in D. Certainly neither of these options are trivial, and it would be a fool's errand to embark upon them alone.

Thank you for dedicating your time to this, and for the very elaborate response! Reimplementing PangoLayout does sound like an option, but as far as I've seen, Pango operates on strings all the way through. I suppose it is possible to assemble parts of the rope into a string on the stack, and only operate on a single part at once, but I don't think this is going to be very bug-proof. Alternatively, Fluid's text engine does distinguish between static and dynamic text with an "edit mode" flag, so it could be possible to apply Pango on stringified static text, but that's never going to be enough.

Compromises!

>

Oh, and also, since Pango is LGPLv2 licensed, I think a port of PangoLayout could run into licensing problems the way D programs are usually built [with DUB].

If the modified source is available then I don't think that's a problem?

> >

Having a full D text layout engine is a pet dream of mine of course, so if you're interested in contributing to a project like that then I might be able to dedicate some of my spare time to it.

I've been thinking about this last week and I'm under the impression this is what is going to happen anyway. For what it's worth, I'm currently working on another large PR which upscales Fluid's text engine, splitting it into a separate fluid.text package and optimizing it on the way through. It is mostly standalone, so if you find it appropriate, I could create a new Git & DUB repository for it once I'm done. I would like to have your input on it.

Sure, that sounds like a great idea!

Here are my thoughts about what could be changed based on how fluid.text is currently set up:

  • StyledText only stores its strings as ropes, making it less ideal for static text, and not very flexible (e.g. can't use a custom type with fast insertion). Ideally there'd be a layout option for static text that uses finite forward ranges of const(char) internally for static text, and also one that uses a mutable string templated interface internally for user-editable text.
  • StyledText only performs rendering to a texture, preventing it from working with renderers that use modern text rendering techniques, and not giving the user any control over the rendering pipeline. Any kind of rendering functionality should be separated out, and StyledText should provide a renderer-agnostic way to get all of the necessary information for rendering (e.g. each font glyph ID, its transformation, and style) as a forward range interface or similar.
  • Different methods of cutting off overflowing text should be provided, like placing ellipses at the end of the visible text.
  • You will probably want to use FontConfig (see: BindBC-FontConfig) by default (but ideally with an option for a custom implementation) to figure out what fonts each system prefers as fallbacks. And same story with using FreeType by default to load & process font data.
  • TextStyleSlice uses a palette right now, which is pointlessly limited to 256 different items. It might be worth attempting to use a SumType containing each individual styling option, since then the defaults don't have to be re-specified with 1 exception every time someone uses bold or italic. Otherwise, TextStyleSlice should probably use a size_t for style indices. Also, things like colour that do not affect text layout should not be part of the Style used when making layouts in the first place. A user's renderer can worry about that.
  • The Typeface interface expects implementations to have way too much state.
  • Everything that goes into text-shaping should be accessible in isolation so that people who need to perform more fine-grained tasks can use them. For example, the bi-directional algorithm.
  • The styling needs to be known when generating a layout, and it might be desirable for the layout to be lazily re-computed.

So all in all, a bit like this example:

alias LayoutStyle = SumType!(
	FontFace, Padding, TextDirection,
	Alignment, Justify, /+et cetera+/
);
struct LayoutStyleRange{
	size_t from, to;
	Style style;
}

struct Caret{ Vec2!float pos; float height; }

mixin template SharedTextLayoutStuff(Text){
	Text text;
	RedBlackTree!(LayoutStyleRange, ".from", true) styleRanges; //styles are sorted by `.from` for efficiency
	uint textSerialNumber; //to see if text was updated
	
	///calculates the layout. Called when `textSerialNumber` is no longer current by anything that uses the layout (e.g. getRenderData)
	void calculate();
	
	///gets data used for rendering the glyphs
	SomeForwardRange!GlyphRenderData getRenderData();
	
	//more functions...
	
	///the position & height of a caret at a certain string index
	Caret getCaret(size_t ind) const;
	///the string index of a caret at a certain visual position
	size_t getIndex(Vec2!float pos) const;
}

///rarely changes, uses string-like forward ranges
struct TextLayout(Text)
if(IsSomeFiniteCharRange!InternalText){
	mixin SharedTextLayoutStuff!(InternalText);
	
	//static-specific stuff
}

///changes with random insertion and deletion, uses an abstract interface
struct DynamicTextLayout(Text)
if(
	isDynamicLayoutText!Text
	//`Text` needs at least:
	//a forward range interface (empty, popFront, front)
	//opIndex
	//opIndexAssign with overloads for slicing
	//insertion & deletion
	//a number that is incremented every time it is updated
){
	mixin SharedTextLayoutStuff!(Text);
	
	//dynamic-specific stuff
}
November 03

On Saturday, 2 November 2024 at 17:33:19 UTC, IchorDev wrote:

>

[...]
Sure, that sounds like a great idea!

Here are my thoughts about what could be changed based on how fluid.text is currently set up:
[...]

  • TextStyleSlice uses a palette right now, which is pointlessly limited to 256 different items. It might be worth attempting to use a SumType containing each individual styling option, since then the defaults don't have to be re-specified with 1 exception every time someone uses bold or italic. Otherwise, TextStyleSlice should probably use a size_t for style indices. Also, things like colour that do not affect text layout should not be part of the Style used when making layouts in the first place. A user's renderer can worry about that.

Inline styling of Text was just a dirty hack to have syntax highlighting working instead of offering a proper, full solution, so it's far from being the final design. Some of my concerns:

  • How would you approach coloring text from the user's perspective?
  • What about handling text mixed with other content (like images, icons, UI components), something akin to CSS display: inline-block?
>
  • The Typeface interface expects implementations to have way too much state.

Typeface is something I'm not very happy about either.

>

So all in all, a bit like this example: [...]

Looks good to me. Having a separate structure for static and dynamic text is reasonable, and as you've seen Fluid currently only distinguishes between this at runtime. I'd opt for a regular struct instead of mixin, though.

I think it's worth noting that I also care about maintaining performance for large files (100K–10MB), which is what I'm attempting to fix on the text-input-ranges branch, but I'm still struggling. Fluid will only draw the text that is visible on the screen (which is handled by CompositeTexture).

I've introduced a TextRulerCache structure which maps characters (by index) to position in text in a way that also preserves layout data, so a piece of text can be edited without recalculating the layout for the rest of it. I'm not very proud of it, it's apparently very fragile, so I'm curious what is your idea of handling this, too.

November 08

On Sunday, 3 November 2024 at 12:13:07 UTC, cookiewitch wrote:

>

On Saturday, 2 November 2024 at 17:33:19 UTC, IchorDev wrote:

>

[...]
Sure, that sounds like a great idea!

Here are my thoughts about what could be changed based on how fluid.text is currently set up:
[...]

  • TextStyleSlice uses a palette right now, which is pointlessly limited to 256 different items. It might be worth attempting to use a SumType containing each individual styling option, since then the defaults don't have to be re-specified with 1 exception every time someone uses bold or italic. Otherwise, TextStyleSlice should probably use a size_t for style indices. Also, things like colour that do not affect text layout should not be part of the Style used when making layouts in the first place. A user's renderer can worry about that.

Inline styling of Text was just a dirty hack to have syntax highlighting working instead of offering a proper, full solution, so it's far from being the final design. Some of my concerns:

  • How would you approach coloring text from the user's perspective?

I think we should give users the tools to do rendering themselves, which lets them implement non-layout features like colour on top however they please. For instance, when colour is updated the layout remains the same, so colour can be denoted by a structure on the user’s side that the renderer reads from.

>
  • What about handling text mixed with other content (like images, icons, UI components), something akin to CSS display: inline-block?

That’s a tricky one!
I was thinking that an API to add shapes that the text ‘goes around’. The shape type would be a sumtype of different shapes, probably stored in an optimisation structure like a tree.
But what if you want an inline rectangle that will move depending on the text layout? For that case we could ask that the user just get the position that the text ends, place their element manually, and then use a second layout object, with the first line set to start after the end of the element. I acknowledge that’s a lot of work on the user end, but I’d argue that it’s an obscure (and frankly absurd) use-case, and requires a lot of implementation-specific decisions that make it impossible to account for everyone’s needs with our own implementation. Can the rectangle overflow the margins at the end of a line, or does it wrap? How is the line with the inline rectangle on it vertically aligned? How and when can shapes intersect with text if at all?
Placing icons inline could probably work the same way as image-based emoji, and we could provide a function to add surrogate image glyphs (either to a font or to a layout? Not sure) for people who don’t want to make a font just to write their custom icons. There’s a whole section of Unicode code points set aside for ‘private use’ like this.

>

Looks good to me. Having a separate structure for static and dynamic text is reasonable, and as you've seen Fluid currently only distinguishes between this at runtime. I'd opt for a regular struct instead of mixin, though.

The mixin template there is just for shared code and wouldn’t have any meaningful functionality on its own. It’s also the kind of thing that might be better as a string mixin due to sections of 100% duplicated code needing to call non-duplicated functions.

>

I've introduced a TextRulerCache structure which maps characters (by index) to position in text in a way that also preserves layout data, so a piece of text can be edited without recalculating the layout for the rest of it. I'm not very proud of it, it's apparently very fragile, so I'm curious what is your idea of handling this, too.

I think (but I could be wrong) that text can’t transcend line-breaks. If I’m right then perhaps text could be segmented at line-breaks, and then requesting to lay out and render it could be done by requesting an index range. And then the dynamic Text type needs an interface to report whether a certain range of lines is ‘clean’ (unmodified since last calculation) and an interface to mark lines as clean. Note that this requires special user-side handling for top-to-bottom text, making it more of a pain to use.

3 days ago

On Friday, 8 November 2024 at 21:13:19 UTC, IchorDev wrote:

>

That’s a tricky one!
I was thinking that an API to add shapes that the text ‘goes around’. The shape type would be a sumtype of different shapes, probably stored in an optimisation structure like a tree.
But what if you want an inline rectangle that will move depending on the text layout? For that case we could ask that the user just get the position that the text ends, place their element manually, and then use a second layout object, with the first line set to start after the end of the element. I acknowledge that’s a lot of work on the user end, but I’d argue that it’s an obscure (and frankly absurd) use-case, and requires a lot of implementation-specific decisions that make it impossible to account for everyone’s needs with our own implementation. Can the rectangle overflow the margins at the end of a line, or does it wrap? How is the line with the inline rectangle on it vertically aligned? How and when can shapes intersect with text if at all?

Measurement and rendering uses the TextRuler struct (frankly more important than the Fluid-centric StyledText one we keep discussing), which I hope to keep super simple so it can be easily manipulated by the user. It keeps track of the pen position and text size.

The idea is TextRuler is fed pieces of unbreakable content ("words") which may be text, but it can be anything of known width. A textual word would be first measured and then placed in the ruler to place it in the correct spot in the text.

Whatever manipulates the ruler, like the Text struct, could handle other pieces through a hook that would run between words. I imagine it would have a signature like void delegate(ref TextRuler, WordSize). inline-block-like content (images, buttons, etc.) would increase line height and add a single "word" both matching the image's size. inline elements could add multiple words. float content could be simulated by inserting a word on every line at its supposed location, and thus would even support non-rectangular elements anywhere within the line.

I'm not sure yet when to move the engine into its own project, but I would like to have this ability in Fluid soon; I thought I would let you know about this idea.

As for TextRulerCache I mentioned earlier, I'm almost sure I've ironed out all the quirks and bugs. I think it's OK now, but certainly coding a pretty weird and complex data structure while having a fever wasn't my brightest idea.

1 2
Next ›   Last »