SVGKit / SVGKit

Display and interact with SVG Images on iOS / OS X, using native rendering (CoreAnimation)
Other
4.47k stars 1.08k forks source link

Tspan element support #320

Open michaelfrain opened 9 years ago

michaelfrain commented 9 years ago

I've forked the current project and am attempting to add some basic Tspan element support. I've added the tspan tag to the parser, and a class as well, but am wondering which direction you would suggest I go in to make it parse/render properly. Any thoughts would definitely be welcome.

adamgit commented 9 years ago

My expereince:

In brief ... CT's layout system is that you create rectangular (or any CGPath, if you get fancy) layout-regions, that CT then fills with text. These regions can be pre/post transformed to meet the SVG expectations.

It often gets tricky when you don't know how big an area you want to layout into. Apple has settings for "infinitiely wide" OR "infinitely high" (if you look carefully in the API ref, you pass in magic constants for either of those when delcaring the layout area). Off the top of my head, I can't remember what SVG says about layout regions on non-specific shapes/bounds. You'd have to look into that.

w.r.t. parsing - the best thing is to make a new class that implements/extends SVGKParserExtension, and declares itself as supporting TSPAN etc. That way, your parsing code is very modular and isolated from the rest of SVGKit. Moving forwards, we're trying to migrate all tag-specific parsing code into those subclasses.

(with the main one, that implements default/core SVG parsing, getting smaller and smaller over time)

This makes it much easier for us to maintain BUT ALSO much easier for people to extend / replace / upgrade bits of the parser for their own needs.

greghe commented 9 years ago

I think one of the challenges you'll face in rendering sub-text elements is the amount of state that needs to be shared among them. Currently SVGImage newLayerWithElement essentially walks the DOM and asks each element to create a CALayer if necessary.

I think it's likely you'll want to short circuit this loop at the level of a text element container and have the text element create a CALayer for itself and have its sub text elements also create CALayers as part of the same process.

adamgit commented 9 years ago

What's the benefit of doing this up-front?

You'll still need to jump down the tree to find out the transforms and metadata for each blob of text.

On 2 May 2015 at 23:59, Greg Herlihy notifications@github.com wrote:

I think one of the challenges you'll face in rendering sub-text elements is the amount of state that needs to be shared among them. Currently SVGImage newLayerWithElement essentially walks the DOM and asks each element to create a CALayer if necessary.

I think it's likely you'll want to short circuit this loop at the level of a text element container and have the text element create a CALayer for itself and have its sub text elements also create CALayers as part of the same process.

— Reply to this email directly or view it on GitHub https://github.com/SVGKit/SVGKit/issues/320#issuecomment-98415273.

greghe commented 9 years ago

Yes, the parent can call down to the sub text elements and ask them to draw their text and so forth recursively.

After all, the parent text element has to start drawing some text until it runs into a sub text element which then has to start drawing where the parent left off, and when it's done, pop back up to the parent where the parent resumes drawing where the child left off. This process can be repeated any number of times and to any level of depth, so the tighter the integration between the parent and child text elements, the easier it will be to implement.

adamgit commented 9 years ago

Off the top of my head ... I thought that's not how SVG text works ... I thought that each text element was defined to be positioned without rendering the surrounding elements?

CoreText works this way too: everything is positioned in absolute space, you can render in any order you like. The only bit that's dynamic is overflow text from one text-region to another (e.g. columns in a newspaper), which is a cool feature but IME rarely gets used with CT.

On 4 May 2015 at 04:53, Greg Herlihy notifications@github.com wrote:

Yes, the parent can call down to the sub text elements and ask them to draw their text and so forth recursively.

After all, the parent text element has to start drawing some text until it runs into a sub text element which then has to start drawing where the parent left off, and when it's done, pop back up to the parent where the parent resumes drawing where the child left off. This process can be repeated any number of times and to any level of depth, so the tighter the integration between the parent and child text elements, the easier it will be to implement.

— Reply to this email directly or view it on GitHub https://github.com/SVGKit/SVGKit/issues/320#issuecomment-98582983.

greghe commented 9 years ago

I'm afraid that is exactly how SVG text works. Consider this simple example from the SVG standard:

<g font-family="Verdana" font-size="45" >
    <text x="200" y="150" fill="blue" >
      You are
        <tspan font-weight="bold" fill="red" >not</tspan>
      a banana.
    </text>
  </g>

Here the renderer has to draw the "You are", drop into the tspan and draw the red "not" and then return to draw the rest of the sentence.

adamgit commented 9 years ago

IIRC ... because of the specific way XML parsers handle text inside XML tags ... that actually parses as:

<text ... > (text)You are(/text) <tspan ... > (text)not(/text) (text)a banana(/text)

IIRC in Apple's CoreText, you set up the whole text area, and give it teh text:

"You are not a banana"

...and you apply a character-level style to "the characters at indexes 8 to 11"

...and don't do anything else. Apple handles the rest for you.

I'm not sure how that would be easiest to implement, but .. it strikes me that:

  1. A text can construct itself without caring about child elements
  2. A tspan by definition has to have a parent text. So long as that text has a public / protected variable allowing you to directly apply Apple Styling attributes ... the child tspan could wait until its turn to be parsed, apply its change to its parent text, and all is well
  3. When generating CALayers, IIRC there are CT methods that allow you to find out "what's the final layout rect for the characters at indexes 8 to 11?" ... so tspan could generate an articial CALayer for itself, making hit detection work on styled text, which would be very nice.

?????? - so many ifs and maybes in all this :)

On 4 May 2015 at 16:11, Greg Herlihy notifications@github.com wrote:

I'm afraid that is exactly how SVG text works. Consider this simple example http://www.w3.org/TR/SVG11/text.html#ExampleTSpan01 from the SVG standard:

You are not a banana.

Here the renderer has to draw the "You are", drop into the tspan and draw the red "not" and then return to draw the rest of the sentence.

— Reply to this email directly or view it on GitHub https://github.com/SVGKit/SVGKit/issues/320#issuecomment-98744282.

greghe commented 9 years ago

I think we're in basic agreement that the top-level text element should render its child text elements as part of its own rendering operation. Since text elements can be nested (a tspan inside a tspan) some way to handle recursion will also likely be required.

adamgit commented 9 years ago

I'm saying: I don't think top-level needs to be aware of lower level at all.

Top-level generates its rendering.

Lower-level generates its rendering ... by post-processing the top-level.

On 6 May 2015 at 00:33, Greg Herlihy notifications@github.com wrote:

I think we're in basic agreement that the top-level text element should render its child text elements as part of its own rendering operation. Since text elements can be nested (a tspan inside a tspan) some way to handle recursion will also likely be required.

— Reply to this email directly or view it on GitHub https://github.com/SVGKit/SVGKit/issues/320#issuecomment-99261070.

alaija commented 9 years ago

Hi do you have any progress here? My problem is: I have SVG string

<text fill="#000" text-anchor="start" font-family="Arial" stroke="none" font-size="12" stroke-width="0" x="151" y="352" >
<tspan>Hello,</tspan>
<tspan dy="14" x="151">World!</tspan>
</text>

So on my screen I will have:

Hello,World!

But it must be:

Hello,
World!

May be you can help me with it?

santu1990 commented 5 years ago

@adamgit Hey, Any fix for tspan tag for text?

adamgit commented 5 years ago

Feel free to write it. My suggested approach from 4 years ago still stands. Maybe there's an easier way now, with newer CoreText libraries - but I haven't used them recently, so I'm not the person to ask.