Requirements for dependent shapes framework
Closed, ResolvedPublic

Description

We have multiple discussions on IRC about dependent shapes system in Krita. I feel really uncomfortable discussing that basing on a mere SVG standard, without real-world applications to that. So I decided to start a list of requirements that we want to fulfill by implementing such dependencies.

Requirements

  1. Text on path feature
    1. The user should be able to edit the path using the standard shape-editing tool
    2. The user should be able to edit custom handles provided by the text shape itself:
      • anchors
      • text offset from start of path (startOffset)
      • offset from the curve (dy on <text>)
      • change text position: below/above the path (side)
      • [TODO] what else
  1. Text in shape feature
    1. The user should be able to resize the parent shape. When resizing happens, the internal text shape undergoes a relayout
    2. The user should be able to transform the parent shape. In this case the internal text of the shape is not relayouted, but just scaled/transformed (like all the shape decorations, like stroke/pattern).
    3. The user should be able to edit custom handles provided by the text shape itself:
      • anchors
      • offset from the sides of the shape (shape-padding)
      • [TODO] what else
  1. SVG2 specification supports "referenced objects" feature, which behaves effectively like "cloned" shapes.
    • [TODO] Do we actually want to support this feature on-the-fly? Will painters have enough usefulness in it? Right now our policy is bake/detach all the lazy links on loading.
  1. "Enter group" feature
    1. The user should be able to edit the shapes inside a group without ungrouping them
      1. Select "Enter group" mode
      2. All shapes except of the ones belonging to a group are grayed-out
      3. The user can select and edit shapes inside the group
      4. Clicking on any (grayed-out) shape outside the group exits the "enter group" mode.

Questions to answer

  1. Are there any other usecases of dependent shapes framework?
  2. Should we support SVG2's "referenced object" feature? Personally, I believe that we shouldn't (and, afair, DOM-model doesn't actually keep runtime links for such objects, it detaches all references on loading, as per specification; though my info might be outdated)

The user should be able to edit custom handles provided by the text shape itself:

Can this include changing the text orientation from inside the path to outside the path (for example text outside the circle and text inside the circle path)

dkazakov updated the task description. (Show Details)Aug 29 2023, 7:53 AM

Hi, @kamathraghavendra!

Can this include changing the text orientation from inside the path to outside the path (for example text outside the circle and text inside the circle path)

I've added this point to the text-on-path feature

woltherav added a comment.EditedAug 30 2023, 3:42 PM

I think technically speaking the clip-paths are also a situation where there might be shape-dependency, though that can be resolved quite easily. Beyond that it's stuff like patterns and markers, but there it is also a lot more self-evident that we should just resolve, as they're resources and therefore suitably contained.

The user should be able to transform the parent shape. In this case the internal text of the shape is not relayouted, but just scaled/transformed (like all the shape decorations, like stroke/pattern).

FWIW:

Right now, for text path we are required to only apply 'local transforms' on the textpath: https://svgwg.org/svg2-draft/text.html#TextPathElementHrefAttribute

I've been interpreting the text-in-shape in the same way, and I suspect Inkscape works similarly, based off this discussion: https://github.com/w3c/svgwg/issues/877 (That is, original shape is not transformed, but the text is, so only the text rotates.

Similarly, right now, if you transform a shape and set it as the shape-inside of a text, it will layout in the transformed shape. If you want to rotate both at once, you'd need to rotate a group that contains both.

BTW:

  1. Deevad uses inkscape to typeset pepper and carrot, so he prolly has the most real-world experience with SVG 2 text-in-shape.
  2. We were at one point considering to have a special KoShape for comic balloons, that would keep track of the text shape, balloon shape and the balloon tail, such a KoShape could be a container for both that keeps track of the transforms if this turns out to be that important.
alvinhochun updated the task description. (Show Details)Aug 30 2023, 7:06 PM

Also need to consider flowing text in multiple shapes, and shape-subtract.

Right now, for text path we are required to only apply 'local transforms' on the textpath

Well, that is how our tools currently work. They have two modes of transformation, so, theoretically, we should implement (and test) them somehow. How we bake that into SVG is a separate topic :)

Btw, a new set of weird technical questions:

  1. Can a text shape belong to multiple (ungrouped) vector shapes? Or they should always be grouped first?
  2. Can a text shape have both, text-on-path and text-in-shape at the same time?
  3. Can a single "balloon-like" shape has multiple text chunks manipulated separately? E.g. one in the top-left corner, another in the bottom-right corner. SVG allows to have it in a single <text> block, but our tools do not support that natively afair. This question is necessary to evaluate design decision that "the contour shape belongs to a text shape, hence there can be only one text shape "inside" the balloon".

Btw, a new set of weird technical questions:

  1. Can a text shape belong to multiple (ungrouped) vector shapes? Or they should always be grouped first?

A text shape can flow into multiple shapes, within SVG these are just linked, so both grouped and ungrouped is possible within SVG.

  1. Can a text shape have both, text-on-path and text-in-shape at the same time?

In terms of the data, yes, but in the layout, the text-in-shape or inline-size prevents text-in-path from being interpreted.

So...

<svg width="460px" height="270px"
    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="2.0">
<defs>
<path id="bubble" d="M60 0C90 0 120 30 120 60C120 90 90 120 60 120C30 120 0 90 0 60C0 30 30 0 60 0Z" />
<path id="figure-of-eight" d="M50 0C75 0 100 25 100 50C100 100 0 100 0 150C0 175 25 200 50 200C75 200 100 175 100 150C100 100 0 100 0 50C0 25 25 0 50 0Z" />
</defs>

<text style="shape-inside: url(#bubble);" transform="translate(20, 20)">
    <textPath xlink:href="#figure-of-eight">The quick brown fox jumps over the lazy dog</textPath>
</text>
</svg>

Krita will load the above as only a text-in-shape, Firefox will load only the textPath, Inkscape is bugged because it loads both, the official pseudo algorithm is pretty clear about ignoring textPath when using shape-inside or inline-size.

  1. Can a single "balloon-like" shape has multiple text chunks manipulated separately? E.g. one in the top-left corner, another in the bottom-right corner. SVG allows to have it in a single <text> block, but our tools do not support that natively afair. This question is necessary to evaluate design decision that "the contour shape belongs to a text shape, hence there can be only one text shape "inside" the balloon".

No, wrapped text has a single anchor/alignment and does not allow for x, y, dx, dy and rotate to have effect (Basically, if text is soft-wrapping, it skips the unique SVG 1.1 features).

A text shape can flow into multiple shapes, within SVG these are just linked, so both grouped and ungrouped is possible within SVG.

Then the design of the shapes being owned by the text shape may become a bit weird in some corner cases. E.g. we might end up with a configuration like that:

  • shapeA
  • groupShapeB
    • shapeC
    • shapeD
  • textShapeE -> flows inside shapeA and shapeD

As far as I can tell this configuration is not possible to implement within the current design.

In terms of the data, yes, but in the layout, the text-in-shape or inline-size prevents text-in-path from being interpreted.

That makes things a bit easier got the text-on-path, because text-on-path can actually use the embed-the-path design.

No, wrapped text has a single anchor/alignment and does not allow for x, y, dx, dy and rotate to have effect (Basically, if text is soft-wrapping, it skips the unique SVG 1.1 features).

It means that we have a strict one-to-many relationship. One "text object" is related to one or more "contour objects". Parent-children/ownership is one example of such relationships, though it doesn't seem to fit properly, because it conflicts with the existing grouping relationship.

We need to think about that a bit more.

A text shape can flow into multiple shapes, within SVG these are just linked, so both grouped and ungrouped is possible within SVG.

Then the design of the shapes being owned by the text shape may become a bit weird in some corner cases. E.g. we might end up with a configuration like that:

  • shapeA
  • groupShapeB
    • shapeC
    • shapeD
  • textShapeE -> flows inside shapeA and shapeD

    As far as I can tell this configuration is not possible to implement within the current design.

So, basically, I had originally based the design on how the old artistic text shape 'owned' paths: https://invent.kde.org/graphics/krita/-/blob/f851b98745100a17ce2f3f680a8f11a15d46bacf/plugins/flake/artistictextshape/ArtisticTextShape.cpp#L577

It uses KoShape::addDependee to listen to changes on the linked shape. We could then, upon saving the textshape, check if the shape has a parent, and if not, save the shape to the <defs/>, and otherwise let that parent handle the saving (though, the text shape should still be told which ID it needs to save).

However, the big wrinkle with that is that the document cloning for background saving was implemented after the artistic text shape got removed, so the addDependee system is not taken into account (leading this link to break). This why @alvinhochun was originally looking into the ID system (and was also able to fix some bugs there earlier this month), because he was hoping to find a way to keep this link intact upon cloning.

I agree with the rest that you mentioned. For clarification we have 4 of these inform/ownership mechanisms:

  1. Parent-child.
  2. TextOnShapeContainer
  3. ShapeChangeListener.
  4. AddDependee (basically a shape change listener uniquely for KoShapes).
dkazakov added a comment.EditedJul 22 2025, 1:40 PM

Hi, @woltherav!

I tried to rethink the problems from the GUI/UIX point of view and came to the following idea:

We have two kinds of user interactions:

  1. "Enter Group" interaction.
    1. Applies to Group Shape, Clip Mask and (disputable) Clip Path features.
    2. To edit the subordinate shape, the user should right-click and press "Enter Group" action. Then the internal (or clip-) shapes become visible and editable (and all other are grayed-out)
  1. "Dependent shapes" interaction
      1. Applies to Text on Path and Text in Shapes
      2. When the contour shape is selected (with any tool)
        • the text shape is highlighted with some dash lines
        • clicking on this dashed text shape selects the text shape and activates the text tool
        • double-clicking/enter on the dashed text shape toggles between text vs default tool for the text shape
      3. When the text shape is selected (with any tool)
        • the contour shape is highlighted with some dash lines
        • clicking on this dashed contour shape selects the contour shape and activates the path editing tool
        • double-clicking/enter on the dashed contour shape toggles between path editing vs default tool for the contour shape
      4. Text tool:
        • can select text only
        • controls text properties only, no editing of the contour (except of the dash-highlighting)
      5. Path editing tool:
        • can only select the contour (when it is visible by user's choice or when dash-highlighted)
      6. Default Tool selects text:
        • the only transformation available is the rotation of the text (TODO: might be best to move to the text tool?)
        • other transformations do nothing
    1. Default Tool selects contour:
      • when "Scale Styles" option is active, then both, text and contour are transformed synchronously as a single group (on SVG-level, the transformations are just duplicated)
      • when "Scale Styles" option is off:
        • scale transformation: relayouts the text
        • translation transformation: translates the contour and relayouts the text
        • rotate transformation: rotates the contour and relayouts the text
        • shear transformation: shears the contour and relayouts the text
woltherav added a comment.EditedJul 23 2025, 9:38 AM

Hi, @woltherav!

I tried to rethink the problems from the GUI/UIX point of view and came to the following idea:

We have two kinds of user interactions:

  1. "Enter Group" interaction.
    1. Applies to Group Shape, Clip Mask and (disputable) Clip Path features.
    2. To edit the subordinate shape, the user should right-click and press "Enter Group" action. Then the internal (or clip-) shapes become visible and editable (and all other are grayed-out)

Inkscape allows entering group by double clicking as well.

We shouldn't forget to give group shapes a unique look and feel, btw.

  1. "Dependent shapes" interaction
    1. Applies to Text on Path and Text in Shapes
    2. When the contour shape is selected (with any tool)
      • the text shape is highlighted with some dash lines
      • clicking on this dashed text shape selects the text shape and activates the text tool
      • double-clicking/enter on the dashed text shape toggles between text vs default tool for the text shape

Nitpicks:

  • You can't expect enter/double click inside the text tool to let it switch back to default tool, because selected in text tool means editing, and during editing enter is new line and double click is 'select word'.
  • Currently, only the text tool has a concept of "hover".
  1. Default Tool selects text:
    • the only transformation available is the rotation of the text (TODO: might be best to move to the text tool?)
    • other transformations do nothing
  2. Default Tool selects contour:
    • when "Scale Styles" option is active, then both, text and contour are transformed synchronously as a single group (on SVG-level, the transformations are just duplicated)
    • when "Scale Styles" option is off:
      • scale transformation: relayouts the text
      • translation transformation: translates both text and the contour like in "Scale Styles" mode
      • rotate transformation: rotates both text and the contour like in "Scale Styles" mode (TODO: shouldn't it rotate the contour separately?)
      • shear transformation: shears both text and the contour like in "Scale Styles" mode

I think this latter model is not going to work that well. If you have text inside a rect and want both to, ex, rotate in tandem, they *must* be inside the same group. Because svg spec says we *must* take local transforms of the shapes into account for layout, if you rotate the group, the transform is on the group and not local anymore.

So arguably we could have a special group that always keeps outline shapes and their text together (and for a special comic-balloon shape, this would be the correct solution), but I have to admit I'm slightly worried about forcing all shapes into that straight jacket (esp if we want to have people use this in conjunction with inkscape).

I think this latter model is not going to work that well. If you have text inside a rect and want both to, ex, rotate in tandem, they *must* be inside the same group. Because svg spec says we *must* take local transforms of the shapes into account

SVG does not define anything in the area of user interactions. We should provide users a sane interface and is useful for work, not a well-defined and portable state automata for rendering images, like SVG tries to do.

We don't need any groups for that. The UIX can (and, I guess, should) be implemented at the level of the tool. The only tricky thing that might happen is that we might need to declare some kind of "dependency type", i.e.

enum DependencyType {
    DependentOfClipPath,
    DependentOfContour,
    DependentOfTextOnLine,
};

And change the signature of addDependee() as:

bool addDependee(KoShape *shape, DependencyType type);
woltherav added a comment.EditedJul 24 2025, 9:35 AM

While SVG doesn't indicate UX in this case (it actually *does* for example, for selections), this particular thing is not going to be possible without a group containing both, unless we break with the spec.


(Inkscape: red is both shape and text untransformed. green is shape transformed, blue is shape *and* text transformed)

Let's focus on getting 'addDependee' working, as right now it breaks upon cloning the document. Then afterwards, when you can actually create text in shapes and manipulate them, you'll be able to see my point.

EDIT: here's the document:

dkazakov added a comment.EditedJul 24 2025, 10:01 AM

this particular thing is not going to be possible without a group containing both, unless we break with the spec.

Can't we just assign a transform tag to the <text> element?

UPD:

If it is not possible, then we could reconsider if we want to really support dynamically-linked objects. We could just explicitly detach and (at least logically) create a group on loading...

If we declare this limitation, then we can actually keep the current approach, without implementing the addDependee() stuff.

ok let's try this again:


For embedded flow-shapes.

This is the default behaviour when creating a text-area in inkscape or illustrator/photoshop. We can do the following:

transform flow shape:

<svg>
<defs>
<rect id="flowShape" transform="rotate(30)" />
</defs>
<text style="shape-inside="url(#flowShape)" />
</svg>

for rotating the text and the flow shape together:

<svg>
<defs>
<rect id="flowShape" />
</defs>
<text transform="rotate(30)" style="shape-inside="url(#flowShape)" />
</svg>

For linked shapes

Linked shapes happen in inkscape when you tell it to flow a text into a given shape. I cannot verify how exactly illustrator behaves when you transform the linked shape(s). I know in CSP's case, the shape and the text can be transformed individually.

rotating flow shape:

<svg>
<rect id="flowShape" transform="rotate(30)" />
<text style="shape-inside="url(#flowShape)" />
</svg>

OR

<svg>
 <g krita::balloonShape="true">
   <rect transform="rotate(30)" id="flowShape"/>
   <text style="shape-inside="url(#flowShape)" />
 </g>
</svg>

rotating both together:

<svg>
 <g transform="rotate(30)" krita::balloonShape="true">
   <rect id="flowShape"/>
   <text style="shape-inside="url(#flowShape)" />
 </g>
</svg>

Personally, I'm more inclined to only do embedded shapes for 5.3, as I worry we might be running out of time, and it is much easier to reason about.