Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formatted string origin with different alignment #106

Closed
djrrb opened this issue May 30, 2017 · 22 comments
Closed

Formatted string origin with different alignment #106

djrrb opened this issue May 30, 2017 · 22 comments

Comments

@djrrb
Copy link
Sponsor

djrrb commented May 30, 2017

I am using FormattedString() with "right" and "center" alignments for multiline strings, and noticed that text() will draw it the origin as the bottom left corner of the final line of the text. This means that for right and centered text, I cannot easily predict the position of the text relative to the origin point, because it is dependent on the length of the content in the last line.

A demo:

for alignment in ['center', 'right']:
    for myString in ['AA\nAAAA', 'AAAA\nAA']:
        newPage(1000, 1000)
        translate(250, 250)
        oval(-10, -10, 20, 20)
        fs = FormattedString(myString, align=alignment, fontSize=200)
        b = BezierPath()
        b.text(fs)
        drawPath(b)

I'm not sure if there's actually an issue to be solved here, but I am curious if there a way to get them placed consistently?

Of course this is not an issue when using textBox() instead of text(), but unfortunately I am trying to use this in conjunction BezierPath() which does not have a textBox() method.

Have you considered making the origin for center-aligned text at the bottom center, and for right-aligned at the bottom right? (this is similar to what happens to alignments of basic text fields in Illustrator). I understand that a FormattedString can have more than one alignment, so maybe that’s not a perfect solution either.

I think I might be able to work around this by calculating the final line of the string, making a separate FormattedString object, getting the textSize()[0] of that, and then offsetting the text object by that amount. Just wondering if there’s a more straightforward way before I go that route.

As always, many many thanks!

@justvanrossum
Copy link
Collaborator

Yeah, that behavior makes little sense for multi-line strings. We should reconsider in favor of AI's point-text behavior.

@typemytype
Copy link
Owner

typemytype commented May 31, 2017

there is idd something strange...

Did some small test and this should work (have to commit the changes):

the blue is a bezierPath.text(..)
the black is normal text(..)

and maybe bezierPath.text(..) should also get a align argument, so it has the same arguments as text(...). It cannot get the alignment from the formattedString as the formattedString can contain multiple different alignments...

screen shot 2017-05-31 at 10 16 08

and there is already box support in a bezierPath.text see http://www.drawbot.com/content/shapes/bezierPath.html#drawBot.context.baseContext.BezierPath.text

@justvanrossum
Copy link
Collaborator

The other problem is that the origin passed to text() is seen as the origin of the last line. So if you add a line, the first line moves up. That feels wrong and unintuitive, and is also different from what AI does.
db_multiline

@typemytype
Copy link
Owner

Should it be based on the first line?

@typemytype
Copy link
Owner

typemytype commented May 31, 2017

So the H in your example should be on the given point?
this could break some existing scripts... (and this was also how it was implemented in the original DrawBot :)

@justvanrossum
Copy link
Collaborator

Yes. I don't think the original DrawBot intentionally supported multiple lines to begin with... You couldn't even set line distance.

@typemytype
Copy link
Owner

typemytype commented May 31, 2017

this should do it: 2234409

the result is now the same for text() as for bezierPath.text() and this makes sense :)

screen shot 2017-05-31 at 11 34 00

for alignment in ['left', 'center', 'right']:
    for myString in ['AA\nAAAA', 'AAAA\nAA']:
        newPage(1000, 1000)
        translate(500, 500)
        stroke(1, 0, 0)
        line((0, -500), (0, 500))
        stroke(None)
                
        fs = FormattedString(myString, align=alignment, fontSize=160, fill=(0, .5))
        
        b = BezierPath()
        b.text(fs, align=alignment)
        
        translate(0, 200)
        fill(0, 1, 0)
        oval(-10, -10, 20, 20)
        fill(0, 0, 1)        
        translate(0, 0)
        drawPath(b)
        
        translate(0, -450)
        fill(0, 1, 0)
        oval(-10, -10, 20, 20)
        text(fs, (0, 0), align=alignment)

@justvanrossum
Copy link
Collaborator

It's very close now. There's some confustion between FormattedStrings's align and text() align, it seems:

fs = FormattedString()

fs.align("right")
fs.fontSize(80)
fs += "Hello\nHelllooo"

text(fs, (500, 500))  # no align, but the "block" is still aligned left  of the point

@typemytype
Copy link
Owner

It could raise an error when both align is provided and the text is an formattedString object

A formattedString could have multiple alignments for different lines of text

@djrrb
Copy link
Sponsor Author

djrrb commented May 31, 2017

Can I just take this opportunity to say how awesome you both are!

In case this is helpful, one thought for multiple alignments within a FormattedString is to let text(align="?") control the position of the origin relative to the bounding box of the FormattedString, and then let the internal alignments of the FormattedString stand (however those are calculated).

Here is a quick example of what i mean made using 3.88 (before your recent commit). The green box shows a related behavior with textSize() that you may have already changed):

screen shot 2017-05-31 at 8 50 36 am


fontSize(30)

fs = FormattedString('AA\nAAAA\n', align='right', fontSize=60)
fs.append('AA\nAAAA\n', align='center')
fs.append('AA\nAAAA', align='left')

translate(250, 250)
fill(1, 0, 0)

text('text(align="left")', (0, -60))
save()
fill(1, 1, 0)
rect(0, 0, textSize(fs)[0]*2, textSize(fs)[1])
fill(0, 1, 0)
rect(0, 0, textSize(fs)[0], textSize(fs)[1])
restore()

oval(-10, -10, 20, 20)
text(fs, (0, 0))

translate(500)

text('text(align="center")', (0, -60))
save()
fill(1, 1, 0)
rect(0, 0, textSize(fs)[0]*2, textSize(fs)[1])
fill(0, 1, 0)
rect(0, 0, textSize(fs)[0], textSize(fs)[1])
restore()


oval(-10+textSize(fs)[0], -10, 20, 20)
text(fs, (0, 0))

translate(500)

text('text(align="right")', (0, -60))
save()
fill(1, 1, 0)
rect(0, 0, textSize(fs)[0]*2, textSize(fs)[1])
fill(0, 1, 0)
rect(0, 0, textSize(fs)[0], textSize(fs)[1])
restore()
oval(-10+textSize(fs)[0]*2, -10, 20, 20)
text(fs, (0, 0))

@typemytype
Copy link
Owner

in that case it would be beter to add an argument: position and let align just do the alignment and not both...

@justvanrossum
Copy link
Collaborator

I think that text()'s align argument should just be ignored if the FormattedString has it, just like eg. fontSize() is simply overruled by fs.fontSize().

@justvanrossum
Copy link
Collaborator

I don't see the concept of a bounding box making much sense in the context of text().

@djrrb
Copy link
Sponsor Author

djrrb commented May 31, 2017

Yeah that makes sense.

@typemytype
Copy link
Owner

when a formattedString is provided the alignment is doing only the positioning relative to the given point, it does not overrule the alignment in the formattedString.

I don't see the concept of a bounding box making much sense in the context of text().

text(..) has no box or anything related, the text just flows in a single link unless a hard enter is somewhere in the given string

bezierPath.text(...) has a box attribute as it's nice to be able to draw text in the bezierPath in a given "text box"

@justvanrossum
Copy link
Collaborator

Perhaps text() should grow a box kw arg and textBox() could be deprecated? Or BezierPath could get a textBox() method?

@typemytype
Copy link
Owner

text() draws all text while textBox() draws inside the box and returns the clipped text

BezierPath.textBox() will be the winner ;)

@typemytype
Copy link
Owner

done see 94a182e

@justvanrossum
Copy link
Collaborator

Super, thanks! @djrrb, can you verify it and close the issue? Note the behavior of text() with multiple lines has changed in an incompatible way.

@djrrb
Copy link
Sponsor Author

djrrb commented Jun 3, 2017

Sorry for the delay...having trouble building from source with sudo python setup.py py2app (it says I it cannot find fontParts even though import fontParts works for me in python.) Don’t want to trouble you with this, but if you have a suggestion feel free to let me know.

Otherwise I will continue to troubleshoot on my end and you can feel free to close the issue (I will reopen if I have anything else to say).

Thanks again...this is very cool!

@typemytype
Copy link
Owner

py2app is not always precise with their tracebacks. Do you have all required packages installed? See https://github.com/typemytype/drawbot/blob/master/README.md

It works for me, so I guess I can push a release later on the weekend

@typemytype
Copy link
Owner

typemytype commented Jul 14, 2017

This issue should be fixed, I'm closing the issue. Reopen if this is still problematic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants