Extend the text tool.
Open, Needs TriagePublic

Description

A continuation of T1004

In Krita 4.0, we released a text tool that can...

  • Handle richtext styling like fonts, bold, italic.
  • Handle Unicode goodness such as Middle-eastern and Asian scripts, as well as Emoji's (When you have an emoji font installed), and weird freaky unicode stuff like Zalgo: https://eeemo.net/
  • Left to Right and Right to Left are automatically recongised!
  • Copy-paste/find/replace/syntax highlighting, and more editor functions.

Original Requirements:

  • paragraphs (but not pages)
  • Multi language support:
    • right-to-left
    • left-to-right
    • top-to-bottom script layout
    • support for western scripts, semitic scripts and chinese/japanese. (Unicode support)
    • Mongolian, probably not so much. (Top to bottom, left to right, as opposed to CJK top to bottom, right to left)
  • fill irregular shapes (balloons and boxes)
  • styles: bold, italic, fonts
  • compatible with inkscape (svg)
  • can be put on a path
  • easily translatable using scripts that work on .kra files (Wolthera made a POT extractor for the comics manager in python, so this is easily possible.)
  • can be baked/rendered from a .kra file -- What does this mean?

What SVG features are still open:

SVG 1.1

  • Rotation attribute
  • Handling multiple inputs into dx/dy/x/y properly.
  • Text on path (this basically takes the previous two to be fixed and then have an arbitrary path used as input for this.
  • Text on path, stretched. This is a different beast from above as it requires deforming the path itself, instead of just rotating and positioning things.
  • XML white-space handling (Check svg2 too)
  • TextLength and lengthAdjust
  • Font stretch
  • Small caps (Font-variant, Check svg2 too)
  • Dominant baseline (Check svg2 too)
  • Alignment baseline (Check svg2 too)
  • Writing mode and glyph orientation. (Check svg2 too)

SVG 2

  • CSS whitespace: Pre/Preline/Normal/Prewrap/Nowrap.
  • Text-decoration line/text decoration-style/text-decoration-fill/text-decoration-stroke
  • Font-variant for all sorts of open type features. (This will require modifying QT itself to get access to Harfbuzz)
  • CSS writing mode values instead of svg 1.1 writing mode values.
  • Dominant baseline: Reset size/use-script/no change
  • Alignment baseline: 'auto', 'before-edge', and 'after-edge' removed.
  • Baseline-shift: 'baseline' property should be converted to '0' or just not defined at all.
  • Letterspacing: "SVG 2 removes percentage values from the letter-spacing property. "
  • Wordspacing: " In SVG 1.1, percentages define additional spacing as a percentage of the SVG viewport size. In SVG 2, following CSS Text Level 3, percentages define additional spacing as a percentage of the affected character's width. "
  • Kerning: "This property is replaced in SVG 2 by the CSS font-kerning property which solely controls turning on/off the use of the font kerning tables. "

Wrapping text:

  • Inline size: A given width(or height in case of vertical text) at which text wraps around.
  • Shape inside/shape subtract/shape padding/shape margin/shape image threshold: A crazy christmas tree way of defining text-layout areas. Will require...
    • A layout algorithm that can take several arbitrary shapes and lay out the text accross that. And handle justfied/center/right/left align
    • A way to define several arbitrary shapes.
    • See how we would want to use shape subtract.
    • An algorithm that can take arbitrary shapes and add/remove padding/margin to them.
    • A moment of wonderin whether we want to support shape image threshold at all... (And if so, how!)
  • Text-align: Left, Right, Justify, Center
  • Text-align last: The alignment of the last bit of text.
  • Line-break
  • Word-break
  • Hyphens
  • Word-wrap
  • Overflow wrap.
  • Text-overflow
  • Line-height

GUI

  • Wordspacing/LetterSpacing/Kerning all work and the gui for it should be merged to master.

Other:

  • There's still a weird thing where QT doesn't report the size of fonts properly. This is super-evident with things like Zalgo, but less so with things like italics on text. There's a hack for the latter in the code, so we need to be mindful of that.
  • We will need to sit down for things regarding Asian specific styling methods, like emphasis marks and ruby. SVG has nothing about this in its specs.
  • Think about text-styling and css stylesheets and when we want to use these.
hellozee claimed this task.Mar 13 2018, 2:49 PM
hellozee added subscribers: rempt, hellozee.

Working on the GUI widgets, strangely the gui reports the letter spacing as 100 whereas the value should be 0, either it is giving the value in percentage or I am doing something wrong.

Working on the GUI widgets, strangely the gui reports the letter spacing as 100 whereas the value should be 0, either it is giving the value in percentage or I am doing something wrong.

It is probably defaulting to percentual letterspacing, so check the font letterspacing type first: https://doc.qt.io/qt-5/qtextcharformat.html#fontLetterSpacingType

Hi :)
I'd like to thank you all for the work done on supporting SVG and RTL text. This is really appreciated.

I was testing one of the last nightly (or beta) builds and found out that the direction of Arabic text is LTR. The special SVG "direction" property is needed for the direction to be applied.

Another small issue: If you typed a LTR sentence and then hit Enter then typed a RTL sentence, (and you applied the direction property to the RTL one), you'll get this: :)

Font-variant for all sorts of open type features. (This will require modifying QT itself to get access to Harfbuzz)

This will be really beneficial for many software, but I'm not sure Qt will expose that stuff so easily...
As you know, Scribus went the long way and added support by using HarfBuzz directly.

That's all. Thanks!

woltherav added a comment.EditedMar 19 2018, 11:31 AM

Hi :)
I'd like to thank you all for the work done on supporting SVG and RTL text. This is really appreciated.

I was testing one of the last nightly (or beta) builds and found out that the direction of Arabic text is LTR. The special SVG "direction" property is needed for the direction to be applied.

Another small issue: If you typed a LTR sentence and then hit Enter then typed a RTL sentence, (and you applied the direction property to the RTL one), you'll get this: :)

Font-variant for all sorts of open type features. (This will require modifying QT itself to get access to Harfbuzz)

This will be really beneficial for many software, but I'm not sure Qt will expose that stuff so easily...
As you know, Scribus went the long way and added support by using HarfBuzz directly.

That's all. Thanks!

Could you please report a bug for these at bugs.kde.org? Phabricator comments are too difficult to track bugs in.

(Also, add a screenshot of what it looks like in the editor then)

Hi :)
I'd like to thank you all for the work done on supporting SVG and RTL text. This is really appreciated.

I was testing one of the last nightly (or beta) builds and found out that the direction of Arabic text is LTR. The special SVG "direction" property is needed for the direction to be applied.

Another small issue: If you typed a LTR sentence and then hit Enter then typed a RTL sentence, (and you applied the direction property to the RTL one), you'll get this: :)

Font-variant for all sorts of open type features. (This will require modifying QT itself to get access to Harfbuzz)

This will be really beneficial for many software, but I'm not sure Qt will expose that stuff so easily...
As you know, Scribus went the long way and added support by using HarfBuzz directly.

That's all. Thanks!

Could you please report a bug for these at bugs.kde.org? Phabricator comments are too difficult to track bugs in.

(Also, add a screenshot of what it looks like in the editor then)

Done in 392064 and 392065

hellozee removed hellozee as the assignee of this task.Apr 6 2018, 10:32 AM

resurrecting this old beast...

Text shape refactor

We have several problems with the text tool. This particular plan for refactoring was inspired by a study of how we draw vector shapes onto the canvas, in particular for improving the color management situation there.

All shape drawing code in Krita is currently done with QPainter. In particular, all non-image objects call the KoShapeBackground, which in turn draws their qpainterpath for them. Our text objects are an exception, they use the drawing function of QTextLayout. If we can refactor them to use the KoShapeBackgrounds, bug 430774 will be resolved. The majority of this refactor will consist of getting the text shape to generate qpainterpaths to draw.

Anatomy of the text shape

Before we discuss how to approach that, here's a summary of the structure of text in Krita right now:

The main class is KoSvgTextChunkShape. These are objects that hold text, positioning information and markup, but also can hold other KoSvgTextChunkShapes, as 'subchunks'. KoSvgTextShape is a specialization of this class with the specific function of being a root node.

Outside of those two, there's several utility classes:

  • KoSVGText -- File with wrapper and utility classes.
  • KoSvgTextShapeMarkupConverter -- Utility class for all the svg and html conversions.
  • KoSvgTextProperties -- special class to abstract away the differences between flake's interpretation of the properties and svg's.
  • KoSvgTextChunkShapeLayoutInterface -- Interface for TextChunk's positioning and other useful layout info.

All of our problems are in KoSVGTextShape: The drawing is done in paintComponent, and it uses QTextLayout. It also has a number of tiny classes and wrappers to work around oddities in QTextLayout's behaviour. The last bit of interest is textOutline, which creates a qpainterpath for the given text, but does not support color differences within the text itself.

Approach

Having laid out how text currently works. This is what I want to do:

  • KoTextChunkShapes are the ones who know their styling information. I propose that KoTextChunkShapes therefore become responsible for drawing themselves.
  • I also think they should be responsible for getting the glyph paths from the shaper (for now, we should only focus on QRawFont here), as they have all the information for that. (Even in the case that there's a wraparound break inside a ligature, it would be simpler to create two separate textchunks and let those get the glyphs and draw).
  • All of the layout code needs to go into its own layout class. It currently overcomplicates what is happening in KoSVGTextShape, and makes it hard to figure out where the SVG ends and the QTextLayout starts, and thus makes it hard to imagine where to start with integrating a different layout engine.

So what would happen is that the TextShape is told to relayout. TextShape calls the layout. The layout uses the layoutInterface to get all the necessary information to do layout. When done, the layout returns positioning information back to the textChunks, which use that to draw the glyph correctly.

I have been wondering, 'should the the layout engine do the drawing?' But I worry it might end up with a lot of duplicate data for caching the layout calculation results (This is currently the case, but that's also because it's necessary to convert our SVG data into QText data, it may not be the case with any other layout engine).

The nature of the transform information being returned from the layout to the textchunk is also difficult. We have three options: SVG positioning, QTransform and transformed QPainterPath. The first two would be per glyph.

QTransform would be the easiest in terms of seperating concerns, because then the TextChunk just needs to apply that to the glyphs and draw them. The layout would not need to do anything with the glyphs.

SVG positioning is harder, because then the TextChunk will need to do some laying out itself, but at the same time, it may be useful to have this information in the TextChunk so that making an SVG 1.1 fallback for SVG 2 wrapped text would be easier.

Finally, the QPainterPath makes the layout engine handle the positioning of each glyph by transforming their painterpath. This has as an advantage that the TextChunk doesn't need to do any layout at all. It is also helpful with the 'stretched' text for text paths. However, we would need to have SVG 1.1 fallback for wrapped text be handled by the wrapped text layout engine, and I am wary of complicating the job of the layout engine, given it's pretty complex already (this is also related to svg text chunks and our text chunks being slightly different, we might need to do some specialized loading where positioning info in wrapped text is ignored so that we don't have a recipe for an endless amount of chunks when going through a serialization cycle, but this may be necessary regardless).

My gut says that the QPainterPath will be the best solution, though I am unsure how to integrate saving and loading of fallbacks. Perhaps I am over-engineering.

Hi, @woltherav!

In particular, all non-image objects call the KoShapeBackground, which in turn draws their qpainterpath for them

That is not really true, I'm afraid. We use quite a lot of QPainter's features. Namely:

  • QBrush for doing textured fills
  • QPen for doing textured outlines
  • transformation
  • setClipPath() for clipping SVG feature (see KoClipMaskPainter)

Basically, we just cannot render shapes without reimplementing (very) complicated algorithms of QPainter.

If we can refactor them to use the KoShapeBackgrounds, bug 430774 will be resolved

Well, for this particular purpose, we could just convert the text into QPainterPath, couldn't we? Though in general it is not possible to convert text to path and paint the path because there are a lot pixel-precise hints in the internals of text rendering libraries.

KoTextChunkShapes are the ones who know their styling information. I propose that KoTextChunkShapes therefore become responsible for drawing themselves.

I might be wrong, but I have a feeling it is not possible in general. KoTextChunkShape represents a single <tspan> element. And, unless <tspan> has its own x, y, dx or dy attribute, the contained text belongs to the parent "svg text chunk" (and by SVG's definition) and therefore interacts with surrounding <tspan> objects in weird ways. Basically, a single KoTextChunkShape is not a self-sufficient entity. It is just a representation of <tspan>. Therefore it cannot paint itself.

All of the layout code needs to go into its own layout class.

Well, doing layout in a separate class is fine. Though right now layout is just done in one function in KoSvgTextShape. The rest stuff around is just a machinery to fetch/parse all the SVG attribute inheritance mess. I would recommend to keep KoSvgTextChunkShape what it does now, that is "represents a tspan object", and do the layout and painting in some other place (KoSvgTextShape now or any other class if you want to change that).

My gut says that the QPainterPath will be the best solution, though I am unsure how to integrate saving and loading of fallbacks. Perhaps I am over-engineering.

I might be misunderstanding something, but I feel that you cannot just represent a text as a QPainterPath and then paint. You need a special library for that.

woltherav added a comment.EditedOct 14 2021, 1:04 PM

Ah, I think what you're missing is that QRawFont can give QPainterPaths for glyphs (we use it in textOutline), and I suggest that we always go through that, and use those paths to paint with the KoShapeBackgrounds, instead of making QTextLayout do it, as that one does not know about our KoShapeBackgrounds. I also want to have it not happen in KoTextShape, because it doesn't know the fills and strokes and whatever of the textchunk. It makes more sense to me to have the textchunks, which do know this info to handle that.

If the textchunk cannot be responsible for painting paths, then the layout will need to produce a group of KoPathShapes that can then be drawn. Only the layout knows where the final position will be. If we ever create a text-on-canvas tool, the layout will then also become responsible for showing the selection, handling text-editing and editing the relevant textchunk whenever the properties change. That seems like a whole lot of responsibility for what is already a complex class.

I dunno, I was thinking that if I am going to refactor it, might as well try to think of future applications?

That is not really true, I'm afraid. We use quite a lot of QPainter's features. -snip-

Yes, KoShapeBackground uses QPainter. There's functions in KisPaintDevice which also use QPainter for this purpose. But, regardless of whether they use QPainter or not, the text tool is currently using a different rendering path than all the other vector shapes, and I want it to have the same rendering path, as it causes bugs and headaches.

Though in general it is not possible to convert text to path and paint the path because there are a lot pixel-precise hints in the internals of text rendering libraries.

I would strongly recommend you discuss this particular need with artists. My understand of text in Krita is that it's there to help people avoid the tedious task of handwriting text. The artist has a lot of control over how the text will be finally represented in Krita, while pixel-hinting and the like is important for situations where the type-setter does not have full control over the final result, like web-browsers and UI elements, and thus the engine needs to get it at it's most legible in one go. There's even a text-rendering hint property in SVG 2 precisely because it isn't absolutely necessary in all contexts.

(And, if you look at what it says, it doesn't even require pixel-hinting, just anti-aliasing)

EDIT: Technically, it's possible to get a bitmap that has font-hinting from something like freetype and that we then implement a function in the ShapeBackgrounds, but not all text things can handle this, and font-hinting is different on all OS, so I would like to have a path based solution first.

woltherav added a comment.EditedOct 14 2021, 2:30 PM

Text rendering in Krita on a 72dpi document, with left the text shape, and right the text-shapes converted to paths.

The difference between the two.


The difference on a 300dpi document, complete with my layer setup.

EDIT: This refactor is being blocked on the basis that "right now we have exactly one place where we do the layout and paint, so it should be easy to switch to the "new" engine". I give up.

woltherav added a comment.EditedOct 21 2021, 12:15 PM

https://people.gnome.org/~federico/blog/text-in-librsvg.html

Some interesting observations about unittests here.

Text layout discussion by Ralph Levien, has a lot of useful links:

https://raphlinus.github.io/text/2020/10/26/text-layout.html

woltherav added a comment.EditedDec 2 2021, 8:51 PM

Discussion on using Harfbuzz in javascript:

https://increment.com/programming-languages/unplain-text-primer-on-non-latin/

pango's wrapmode and bidi support.

https://docs.gtk.org/Pango/pango_bidi.html
https://docs.gtk.org/Pango/enum.WrapMode.html

Found this after digging around and finding that apparantly libraqm isn't too great at linebreaking:

https://github.com/adah1972/libunibreak
https://github.com/adah1972/breaktext (example of libunibreak in use)

I asked about Ruby layout in SVG text:
https://github.com/w3c/svgwg/issues/870

Of note is that none of the libraries and links we're been looking at handle ruby-layout, so we'll have to do something there ourselves either-how.

Web-platform tests for SVG:
https://github.com/web-platform-tests/wpt/tree/master/svg

includes many svg2 text tests too...

woltherav added a comment.EditedDec 8 2021, 6:18 PM

Turns out Unicode's ICU also has a text-layout component that integrates with harfbuzz and is apparently used by firefox and libreoffice. (I might be wrong, and these two might only be using the bidi and text-segmentation components)
https://unicode-org.github.io/icu/userguide/layoutengine/paragraph.html
https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1ParagraphLayout.html

We should ask libreoffice folks that occasionally show up in irc how their experience is with it. Word spacing and letter spacing are also not build in, but it seems pango only supports the latter, and text layout engines don't think that's their job. pango and icu do have the line/wordbreak algorithms which are a prerequisite for letter spacing and word spacing.

This also doesn't have cursor handling, which raqm and pango do have.

Spacing-by-using-linebreaks dox in mozilla: thebes, wordspacing space, word and letterspacing in actual html layout
rest of firefox layout(mind, this is web): https://searchfox.org/mozilla-central/source/layout/generic

Ok, I've decided to write a report for everything I've learned. I hope we can use this as a basis for a proper meeting at some point:

Re colour font, we already have a related bug report (sort of): https://bugs.kde.org/show_bug.cgi?id=439908

knowzero added a subscriber: knowzero.EditedFeb 6 2022, 11:56 PM

Yey, finally!

@woltherav - While I understand the need for rewriting the backend and moving away from QTextLayout. From the sound of things the on-canvas editing will take a while correct?

If so, would it make sense for me to maybe port the effort I did for the LazyTextTool rewrite into C++? (Not the QGraphicscene based, but it is based on painting of QPlainTextEdit). It still uses QTextLayout and everything. But depending on how much time we got, I may be able to get it ready even for 5.1 short of any unexpected situations. At the very least it would be better than what we have now, and during the backend can be overhauled and it can be replaced later with a more proper one.

Thoughts?

woltherav added a comment.EditedFeb 7 2022, 9:21 PM

@knowzero : No clue. The problem is that we haven't had a post-5.0 meeting yet, and as far as I know only you and Alvin have read my report. I have literally no clue what is going to happen.

@woltherav I see... is there any plans on when that meeting will happen?

Also, if we look at things another way, there won't be a text tool for 5.1 either way right? And since it wouldn't touch the rendering portion much if any and use the current backed, it shouldn't interfere with the refactoring effort, correct?

Otherwise, if you think it is best to wait for the meeting, then I guess I can pause it and work on other things.

rempt added a comment.Feb 8 2022, 8:33 AM

I've read it...

For flowing text, I did have a small proposal couple years back. Unfortunately got busy with my own stuff, couldn't even participate in GSoC.

My terrible take on this is probably not consider text as part of SVG and instead have it as a separate layer type. No more headache of parsing XML back and forth. We can always borrow the spec in a different format.

Another example though not practical as of now is using Skia instead of cairo, like enve.

Though when it comes to on-canvas editing, I am a bit confused since it looked a hell lot of work the last I even tried. I really like this approach where we embed the current dialog into the Tool Options docker.