TextNode.SetWordwrap


#1

Hi all,

What are the units for the value that SetWordwrap accepts?

Thanks.


#2

i found it to be a bit random … i think its the width of a normal char.


#3

Does anyone have a more concrete answer? A couple of places in the manual mention “screen units” in relation to text, but how do screen units relate to, for instance, the scale used by aspect2d?

I would like to set explicit top, left, and right margins for my text… and setting the right margin with wordwrap is turning into a guessing game.

Thanks again.


#4

To put my question another way if it helps: is there a way to convert between the units used by TextNode and pixels?


#5

If only wordwrap, it doesn’t matter.
Try this quick & dirty approach :

from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
import sys

def changeTextScale(OST,inc):
    textScale=OST['scale'][0]+inc
    temp=OnscreenText(text='M',
           font=f,
           scale=textScale)
    bounds=temp.getTightBounds()
    bx=(bounds[1][0]-bounds[0][0])*fontXmistery
    temp.destroy()
    OST['scale']=textScale
    OST['wordwrap']=textWrapX/bx

#######################################################
# f=loader.loadFont('Transmetals.ttf')
# fontXmistery=1.02

f=TextNode.getDefaultFont()
fontXmistery=1.5 # for default font

textWrapX=.5 # text wrap width, relative to aspect2d
#######################################################

# create a background to see the defined wrap range
twx=textWrapX*.5
CM=CardMaker('')
CM.setFrame(-twx,twx,-1,1)
card=aspect2d.attachNewNode(CM.generate())

OT = OnscreenText(text='MMMM ISDJFSS DFSJKH FDJF SDK skdh fkjsdhk fjshdkfjh sdkjf hskdjfh ksjdhfSDFLSD SDFJRI0FJDK FFI O FJF wrfkjhsdf4 4u50 08u48 u3948u9834u 89u395u3 4985u439 8u59348uy 954y39yu 3948 OIJD 4UI0 084u 0r3i34j 03 JIEFJ 304R JJWRJOWI 84 JR 3JR0J',
        font=f,
        scale=.06, mayChange=1, pos=(0,.7),
        wordwrap=0)
changeTextScale(OT,0)


inc=.0018
DO=DirectObject()
DO.accept('arrow_up',changeTextScale,extraArgs=[OT,inc])
DO.accept('arrow_up-repeat',changeTextScale,extraArgs=[OT,inc])
DO.accept('arrow_down',changeTextScale,extraArgs=[OT,-inc])
DO.accept('arrow_down-repeat',changeTextScale,extraArgs=[OT,-inc])
DO.accept('escape',sys.exit)
OnscreenText("hold [ARROW UP/DOWN] : change text scale",pos=(0,.9))
run()

There is a varying “something” (I don’t know what exactly, and I guess it’s not published to python level too) for each font, I call it “fontXmistery”.


#6

Great stuff, ynjh_jo. Using getTightBounds is exactly what I was looking for. I’m wondering if David or someone else who knows more about the internals can comment on the mystery factor here. I was hoping to set things up to be agnostic of the font used, so experimenting with a particular font to get its factor isn’t an option.


#7

The wordwrap width is given in “screen units”. These are the same units that you setPos() on the text node, unless you have scaled the text node (which you usually do).

In general, a typical line of text is about 1.0 units high. Since aspect2d is only 2.0 units high, we typically scale text by a factor of 0.1 or smaller. This means you also scale the visible wordwrap width by the same factor.

To answer more concretely, the text size is related to its point size. From http://panda3d.org/apiref.php?page=DynamicTextFont#setPointSize:

So if you set the point size higher, the font will be larger than 1 screen unit. But since you can also achieve this with setScale(), most people don’t bother with the point size, and just keep all their text at 1.0 units high.

The document says that a 10 point font is “about” 1 unit high instead of exactly 1 unit high because, frankly, font height is only approximate anyway. Every letter has a different height. In fact, it is the font cell that is exactly 1 unit high, but most fonts don’t fill out their cell completely.

David


#8

Thanks!


#9

It means, the correct way would be like this :

from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
from string import ascii_letters,digits,punctuation
import sys

def changeTextScale(OST,inc):
    font=OST.getFont()
    font.clear()
    font.setPointSize(font.getPointSize()+inc)
    font.setLineHeight(font.getLineHeight()+inc*.1)
    OST['wordwrap']=textWrapX/OST['scale'][0]

def changeWrapRange(inc):
    if textWrapX+inc<.1: return
    global textWrapX
    textWrapX+=inc
    card.setSx(textWrapX*.5)
    OT['wordwrap']=textWrapX/OT['scale'][0]


myFonts = [TextNode.getDefaultFont()] + [ loader.loadFont(ff) for ff in (
'arial.ttf',
'Transmetals.ttf',
'bluehigh.ttf',
'Shift.ttf',
'911 Porscha.ttf',
)]
#######################################################
choice=0 # choose font !!
f=myFonts[choice]

textWrapX=.5 # text wrap width, relative to aspect2d
#######################################################

# create a background to see the defined wrap range
CM=CardMaker('')
CM.setFrameFullscreenQuad()
card=aspect2d.attachNewNode(CM.generate(),sort=-100)
card.setSx(textWrapX*.5)

OT = OnscreenText(text='MMMM ISDJFSS DFSJKH FDJF SDK skdh fkjsdhk fjshdkfjh sdkjf hskdjfh ksjdhfSDFLSD SDFJRI0FJDK FFI O FJF wrfkjhsdf4 4u50 08u48 u3948u9834u 89u395u3 4985u439 8u59348uy 954y39yu 3948 OIJD 4UI0 084u 0r3i34j 03 JIEFJ 304R JJWRJOWI 84 JR 3JR0J',
        font=f,
        mayChange=1, pos=(0,.7),
        wordwrap=0)
changeTextScale(OT,0)

# text's wireframe quads
wire=OT.instanceUnderNode(aspect2d,'0',sort=-1)
wire.setRenderModeWireframe(1)
wire.setTextureOff(1)
wire.setColor(0,0,1,1)

inc=.5
DO=DirectObject()
DO.accept('arrow_up',changeTextScale,extraArgs=[OT,inc])
DO.accept('arrow_up-repeat',changeTextScale,extraArgs=[OT,inc])
DO.accept('arrow_down',changeTextScale,extraArgs=[OT,-inc])
DO.accept('arrow_down-repeat',changeTextScale,extraArgs=[OT,-inc])
DO.accept('arrow_left',changeWrapRange,extraArgs=[-.05])
DO.accept('arrow_left-repeat',changeWrapRange,extraArgs=[-.05])
DO.accept('arrow_right',changeWrapRange,extraArgs=[.05])
DO.accept('arrow_right-repeat',changeWrapRange,extraArgs=[.05])
DO.accept('s',wire.show)
DO.accept('h',wire.hide)
DO.accept('escape',sys.exit)
OnscreenText("hold [ARROW UP/DOWN] : change text scale",pos=(0,.95),scale=.05)
OnscreenText("hold [ARROW LEFT/RIGHT] : change wrap range",pos=(0,.9),scale=.05)
run()

so the texture size depends on text size onscreen, to give the optimum quality ?


#10

Right, that’s the idea. If you use setPointSize(), to scale the font, the letters of the font will use up more pixels, keeping a consistent quality as the text gets bigger. If you use node.setScale() instead, the pixels will be stretched further, potentially causing blurry text.

Still, managing several different fonts in different point sizes can be confusing (and can waste texture memory). It’s usually easiest to make the font be always 1.0 units high, and scale it accordingly. If you want more detail, you can use setPixelsPerUnit() to add more pixels without changing the size of the font.

But there might be unusual applications where it’s easiest to manage the font in terms of its point size, and that’s why setPointSize() exists.

David


#11

That’s the more reasonable choice for using the same font in different sizes.

def changeTextScale(OST,inc):  # by changing pixels per unit
    font=OST.getFont()
    font.clear()
    font.setPixelsPerUnit(font.getPixelsPerUnit()+inc)
    OST['scale']=OST['scale'][0]+inc*.004
    OST['wordwrap']=textWrapX/OST['scale'][0]

Thanks again. :smiley: