Python, PIL, ImageFont, Line Break, aalib and text module [duplicate] - python

I have a python script that is writing text to images using the PIL. Everything this is working fine except for when I encounter strings with carriage returns in them. I need to preserve the carriage returns in the text. Instead of writing the carriage return to the image, I get a little box character where the return should be. Here is the code that is writing the text:
<code>
draw = ImageDraw.Draw(blankTemplate)
draw.text((35 + attSpacing, 570),str(attText),fill=0,font=attFont)
</code>
attText is the variable that I am having trouble with. I'm casting it to a string before writing it because in some cases it is a number.
Thanks for you help.

Let's think for a moment. What does a "return" signify? It means go to the left some distance, and down some distance and resume displaying characters.
You've got to do something like the following.
y, x = 35, 570
for line in attText.splitlines():
draw.text( (x,y), line, ... )
y = y + attSpacing

You could try the the following code which works perfectly good for my needs:
# Place Text on background
lineCnt = 0
for line in str(attText):
draw = ImageDraw.Draw(blankTemplate)
draw.text((35 + attSpacing,570 + 80 * lineCnt), line, font=attFont)
lineCnt = lineCnt +1
The expression "y+80*lineCnt" moves the text down y position depending on the line no. (the factor "80" for the shift must be adapted according the font).

Related

Making a text box function for Manim, but Tex objects insert automatic line breaks

I wrote the following code, which attempts to take a list of strings, and fit them inside of a bounding box of a specified width and height. It does this by inserting line breaks in appropriate places, and if the height is still exceeded, then it scales the text down by half the size and then attempts to fit it again -- and so on, until eventually the text fits.
def tbox(slst, width, height):
if width < 0.01 or height < 0.01: raise (" Error: Box too small ")
if slst == []: raise(" Error: empty string list ")
nlst = [] # Word width list
t_height = 0 # Max word height
for s in slst:
t = Tex(s+" ")
nlst.append(t.width)
t_height = max(t_height, t.height)
t_ind = 0
size = 1
cur_line = ""
line_len = 0
out = []
while t_ind < len(slst): # While there are strings to process
# If a single string is too large or the height outside the max
if nlst[t_ind]*size > width or len(out)*t_height*size > height:
# then reset with a smaller text size.
t_ind, cur_line, line_len, out = 0, "", 0, []
size *= 0.5
continue
# If the current string fits, add it
if (line_len + nlst[t_ind])*size <= width:
cur_line += slst[t_ind]+" "
line_len += nlst[t_ind]
# otherwise log the line and start a fresh line
else:
out.append(cur_line)
cur_line = ""
line_len = 0
continue
t_ind += 1
# Log any line that remains at the end.
if line_len > 0:
out.append(cur_line)
# Fit the resulting strings into a scaled VGroup
vg = VGroup()
for s in out:
t = Tex(s).scale(size)
vg.add(t)
vg.arrange(DOWN)
return vg
Now when I try to use this on a long string, and allow it to have a wide bounding box, it seems that Manim somehow automatically adds line breaks for strings that are too long. (At least I think so -- I don't know what else could be causing the occurrence of line breaks that don't show up before passing the strings into the Tex constructor.)
Is there a way to turn this off? I tried parsing through the source code for Tex and its superclass, but can't identify a part of the code that would be inserting these line breaks.
You could not find the piece of code responsible for these line breaks because Manim does not insert them either: when Tex or MathTex is rendered, Manim basically compiles a LaTeX document, converts it to an SVG, and imports the result.
Just as every other LaTeX document, the one that Manim compiles also has a page width -- and if your string is too long, LaTeX breaks the line accordingly.
Take a look at these lines: https://github.com/behackl/manim-content/blob/dad382e6843700cf9afa64675d2be1d3cdb10bca/2022-06_four-gf-problems.py#L9-L13 -- I've modified the default LaTeX page width there to make some text break automatically in my video, and you can likely adapt that approach.

What causes this return() to create a SyntaxError?

I need this program to create a sheet as a list of strings of ' ' chars and distribute text strings (from a list) into it. I have already coded return statements in python 3 but this one keeps giving
return(riplns)
^
SyntaxError: invalid syntax
It's the return(riplns) on line 39. I want the function to create a number of random numbers (randint) inside a range built around another randint, coming from the function ripimg() that calls this one.
I see clearly where the program declares the list I want this return() to give me. I know its type. I see where I feed variables (of the int type) to it, through .append(). I know from internet research that SyntaxErrors on python's return() functions usually come from mistype but it doesn't seem the case.
#loads the asciified image ("/home/userX/Documents/Programmazione/Python projects/imgascii/myascify/ascimg4")
#creates a sheet "foglio1", same number of lines as the asciified image, and distributes text on it on a randomised line
#create the sheet foglio1
def create():
ref = open("/home/userX/Documents/Programmazione/Python projects/imgascii/myascify/ascimg4")
charcount = ""
field = []
for line in ref:
for c in line:
if c != '\n':
charcount += ' '
if c == '\n':
charcount += '*' #<--- YOU GONNA NEED TO MAKE THIS A SPACE IN A FOLLOWING FUNCTION IN THE WRITER.PY PROGRAM
for i in range(50):#<------- VALUE ADJUSTMENT FROM WRITER.PY GOES HERE(default : 50):
charcount += ' '
charcount += '\n'
break
for line in ref:
field.append(charcount)
return(field)
#turn text in a list of lines and trasforms the lines in a list of strings
def poemln():
txt = open("/home/gcg/Documents/Programmazione/Python projects/imgascii/writer/poem")
arrays = []
for line in txt:
arrays.append(line)
txt.close()
return(arrays)
#rander is to be called in ripimg()
def rander(rando, fldepth):
riplns = []
for i in range(fldepth):
riplns.append(randint((rando)-1,(rando)+1)
return(riplns) #<---- THIS RETURN GIVES SyntaxError upon execution
#opens a rip on the side of the image.
def ripimg():
upmost = randint(160, 168)
positions = []
fldepth = 52 #<-----value is manually input as in DISTRIB function.
positions = rander(upmost,fldepth)
return(positions)
I omitted the rest of the program, I believe these functions are enough to get the idea, please tell me if I need to add more.
You have incomplete set of previous line's parenthesis .
In this line:-
riplns.append(randint((rando)-1,(rando)+1)
You have to add one more brace at the end. This was causing error because python was reading things continuously and thought return statement to be a part of previous uncompleted line.

Python String Wrap

I want to wrap the string at 30,700 in this script. What is the best way of doing this, I have tried using textWrap but it does not seem to work. This is my code:
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
canvas = canvas.Canvas("Forensic Report.pdf", pagesize=letter)
canvas.setLineWidth(.3)
canvas.setFont('Helvetica', 12)
canvas.drawString(30,750,'LYIT MOBILE FORENSICS DIVISION')
canvas.drawString(500,750,"Date: 12/02/2018")
canvas.line(500,747,595,747)
canvas.drawString(500,725,'Case Number:')
canvas.drawString(580,725,"10")
canvas.line(500,723,595,723)
canvas.drawString(30, 700, 'This forensic report has been compiled by the forensic examiner in conclusion to the investigation into the RTA case which occured on 23/01/2018')
canvas.save()
print("Forensic Report Generated")
Perhaps you want to use the drawText?
Doing so, your code will be
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
canvas = canvas.Canvas("Forensic Report.pdf", pagesize=letter)
canvas.setLineWidth(.3)
canvas.setFont('Helvetica', 12)
canvas.drawString(30,750,'LYIT MOBILE FORENSICS DIVISION')
canvas.drawString(500,750,"Date: 12/02/2018")
canvas.line(500,747,595,747)
canvas.drawString(500,725,'Case Number:')
canvas.drawString(580,725,"10")
canvas.line(500,723,595,723)
line1 = 'This forensic report has been compiled by the forensic'
line2 = 'examiner in conclusion to the investigation into the RTA'
line3 = 'case which occured on 23/01/2018'
textobject = canvas.beginText(30, 700)
lines = [line1, line2, line3]
for line in lines:
textobject.textLine(line)
canvas.drawText(textobject)
canvas.save()
This is also the solution suggested here. Unfortunately, I do not see it as a valid solution for automatic text wrapping in new lines, i.e. you should manage how to split the string yourself.
You should wrap the string itself using a function and draw what's returned. To get a properly wrapped string you could specify a line length like so:
def wrap(string, length):
if len(string) < length:
return string
return string[:length:] + '\n' + wrap(string[length::], length)
The wrap function first checks if the string length is less than the specified length and immediately returns it if so. If it's longer then it appends a newline character to the end of a substring up to length, plus sends the remainder of the string to the wrap() function to check the rest.
Running:
string = "This is a really super duper long string from some forensic report and should take up a lot of space..."
length = 20
print(wrap(string, length))
Will print out:
This is a really sup
er duper long string
from some forensic
report and should ta
ke up a lot of space
...
Because you PROBABLY don't want every single line to be truncated at the line width, we can fix this by adding another recursive function to check for the most recent whitespace character like so:
def seek(string, index):
if string[index-1] == ' ':
return index
return seek(string, index-1)
The seek() will return the index of the last whitespace character of any string (substring in this case).
Note: seek() HAS to check the previous character string[index-1] otherwise will give you the index of the space character and wrap() will append it to each new line.
You can then modify the wrap() function like so:
def wrap(string, length):
if len(string) < length:
return string
pos = seek(string, length)
return string[:pos:] + '\n' + wrap(string[pos::], length)
Running:
string = "This is a really super duper long string from some forensic report and should take up a lot of space..."
length = 20
print(wrap(string, length))
Prints out:
This is a really
super duper long
string from some
forensic report and
should take up a
lot of space...

Wit's end with file to dict

Python: 2.7.9
I erased all of my code because I'm going nuts.
Here's the gist (its for Rosalind challenge thingy):
I want to take a file that looks like this (no quotes on carets)
">"Rosalind_0304
actgatcgtcgctgtactcg
actcgactacgtagctacgtacgctgcatagt
">"Rosalind_2480
gctatcggtactgcgctgctacgtg
ccccccgaagaatagatag
">"Rosalind_2452
cgtacgatctagc
aaattcgcctcgaactcg
etc...
What I can't figure out how to do is basically everything at this point, my mind is so muddled. I'll just show kind of what I was doing, but failing to do.
1st. I want to search the file for '>'
Then assign the rest of that line into the dictionary as a key.
read the next lines up until the next '>' and do some calculations and return
findings into the value for that key.
go through the file and do it for every string.
then compare all values and return the key of whichever one is highest.
Can anyone help?
It might help if I just take a break. I've been coding all day and i think I smell colors.
def func(dna_str):
bla
return gcp #gc count percentage returned to the value in dict
With my_function somewhere that returns that percentage value:
with open('rosalind.txt', 'r') as ros:
rosa = {line[1:].split(' ')[0]:my_function(line.split(' ')[1].strip()) for line in ros if line.strip()}
top_key = max(rosa, key=rosa.get)
print(top_key, rosa.get(top_key))
For each line in the file, that will first check if there's anything left of the line after stripping trailing whitespace, then discard the blank lines. Next, it adds each non-blank line as an entry to a dictionary, with the key being everything to the left of the space except for the unneeded >, and the value being the result of sending everything to the right of the space to your function.
Then it saves the key corresponding to the highest value, then prints that key along with its corresponding value. You're left with a dictionary rosa that you can process however you like.
Complete code of the module:
def my_function(dna):
return 100 * len(dna.replace('A','').replace('T',''))/len(dna)
with open('rosalind.txt', 'r') as ros:
with open('rosalind_clean.txt', 'w') as output:
for line in ros:
if line.startswith('>'):
output.write('\n'+line.strip())
elif line.strip():
output.write(line.strip())
with open('rosalind_clean.txt', 'r') as ros:
rosa = {line[1:].split(' ')[0]:my_function(line.split(' ')[1].strip()) for line in ros if line.strip()}
top_key = max(rosa, key=rosa.get)
print(top_key, rosa.get(top_key))
Complete content of rosalind.txt:
>Rosalind_6404 CCTGCGGAAGATCGGCACTAGAATAGCCAGAACCG
TTTCTCTGAGGCTTCCGGCCTTCCCTCCCACTAATAATTCTGAGG
>Rosalind_5959 CCATCGGTAGCGCATCCTTAGTCCAATTAAGTCCCTATCCA
GGCGCTCCGCCGAAGGTCTATATCCA
TTTGTCAGCAGACACGC
>Rosalind_0808 CCACCCTCGTGGT
ATGGCTAGGCATTCAGGAACCGGAGAACGCTTCAGACCAGCCCGGACTGGGAACCTGCGGGCAGTAGGTGGAAT
Result when running the module:
Rosalind_0808 60.91954022988506
This should properly handle an input file that doesn't necessarily have one entry per line.
See SO's formatting guide to learn how to make inline or block code tags to get past things like ">". If you want it to appear as regular text rather than code, escape the > with a backslash:
Type:
\>Rosalind
Result:
>Rosalind
I think I got that part down now. Thanks so much. BUUUUT. Its throwing an error about it.
rosa = {line[1:].split(' ')[0]:calc(line.split(' ')[1].strip()) for line in ros if line.strip()}
IndexError: list index out of range
this is my func btw.
def calc(dna_str):
for x in dna_str:
if x == 'G':
gc += 1
divc += 1
elif x == 'C':
gc += 1
divc += 1
else:
divc += 1
gcp = float(gc/divc)
return gcp
Exact test file. no blank lines before or after.
>Rosalind_6404
CCTGCGGAAGATCGGCACTAGAATAGCCAGAACCGTTTCTCTGAGGCTTCCGGCCTTCCC
TCCCACTAATAATTCTGAGG
>Rosalind_5959
CCATCGGTAGCGCATCCTTAGTCCAATTAAGTCCCTATCCAGGCGCTCCGCCGAAGGTCT
ATATCCATTTGTCAGCAGACACGC
>Rosalind_0808
CCACCCTCGTGGTATGGCTAGGCATTCAGGAACCGGAGAACGCTTCAGACCAGCCCGGAC
TGGGAACCTGCGGGCAGTAGGTGGAAT

Python Textwrap - forcing 'hard' breaks

I am trying to use textwrap to format an import file that is quite particular in how it is formatted. Basically, it is as follows (line length shortened for simplicity):
abcdef <- Ok line
abcdef
ghijk <- Note leading space to indicate wrapped line
lm
Now, I have got code to work as follows:
wrapper = TextWrapper(width=80, subsequent_indent=' ', break_long_words=True, break_on_hyphens=False)
for l in lines:
wrapline=wrapper.wrap(l)
This works nearly perfectly, however, the text wrapping code doesn't do a hard break at the 80 character mark, it tries to be smart and break on a space (at approx 20 chars in).
I have got round this by replacing all spaces in the string list with a unique character (#), wrapping them and then removing the character, but surely there must be a cleaner way?
N.B Any possible answers need to work on Python 2.4 - sorry!
A generator-based version might be a better solution for you, since it wouldn't need to load the entire string in memory at once:
def hard_wrap(input, width, indent=' '):
for line in input:
indent_width = width - len(indent)
yield line[:width]
line = line[width:]
while line:
yield '\n' + indent + line[:indent_width]
line = line[indent_width:]
Use it like this:
from StringIO import StringIO # Makes strings look like files
s = """abcdefg
abcdefghijklmnopqrstuvwxyz"""
for line in hard_wrap(StringIO(s), 12):
print line,
Which prints:
abcdefg
abcdefghijkl
mnopqrstuvw
xyz
It sounds like you are disabling most of the functionality of TextWrapper, and then trying to add a little of your own. I think you'd be better off writing your own function or class. If I understand you right, you're simply looking for lines longer than 80 chars, and breaking them at the 80-char mark, and indenting the remainder by one space.
For example, this:
s = """\
This line is fine.
This line is very long and should wrap, It'll end up on a few lines.
A short line.
"""
def hard_wrap(s, n, indent):
wrapped = ""
n_next = n - len(indent)
for l in s.split('\n'):
first, rest = l[:n], l[n:]
wrapped += first + "\n"
while rest:
next, rest = rest[:n_next], rest[n_next:]
wrapped += indent + next + "\n"
return wrapped
print hard_wrap(s, 20, " ")
produces:
This line is fine.
This line is very lo
ng and should wrap,
It'll end up on a
few lines.
A short line.

Categories