Question

I am looking to achieve several features to sweeten up the output of my program. I want to set the background of a column of text to a certain color so that it is more clear that columns belong together.

A picture is worth a thousand words: i want to convert this:

Pos.  :123456789012345
Name. :ABCDEFGHIJKLMNO  
Str.  :SOMESTRINGSOMET  <---- indented by half a row, Column number superscripted
Str.  :SOM       SOMET
Str.  :SOMESTRIN    ET

to this:

example

How can i do that using python ? Is there a framework that alows me to do this? I converted this into latex, however i cannot get the superscripted numbers to work. They need to be above the text, but not acting as a character, since there is a relation between the columns of the picure.

Example: A is at position 1. It has as Properties S and O, thats why it is on top of those two and that is why there is an indent of half a character. S has the superscript 170, T has the superscript 185.

particularly hard is also the part where the Str part is shifted by half a character.I would like to have an example for this to integrate into my program.

Was it helpful?

Solution

It is possible to come up with tricky solutions, considering mono spaced fonts, that may solve your problem. But they are just waiting to fail. The gray columns in your post can be added exactly as in your figure, but I've chosen to not make it change the colors in the letters (simply because I find it more readable this way).

Here is something possible to come up with PIL:

enter image description here

Slightly changing the fonts:

enter image description here

More "severe" font changes next, all produced with the code as is.

enter image description here    enter image description here

And next is the code for generating these figures. I didn't really make it nice, and it can be improved in many ways. Consider this as an example of what you will need to do to, maybe, achieve a solution for your problem. To work with any kind of font you will need an actual typesetting system, such as LaTeX.

import sys
import Image, ImageDraw, ImageFont


# Assumption: some other piece of code separates the data in the following
# format.
line1 = [("Pos.  :", 0), ("123456789012345", 0)]
line2 = [("Name. :", 0), ("ABCDEFGHIJKLMNO", 0)]
line3 = [("Str.  ", 0), (":", -0.5), ("SOMESTRINGSOMEST", -0.5)]
line4 = [("Wave 1:", 0), ("_XXXX_X____X_X_", 0)]
line5 = [("Wave 2:", 0), ("__XX_XXX_X__X_X", 0)]
line_data = [line1, line2, line3, line4, line5]
# Texts to draw over the last element, in specific positions,
# of lines present in line_data.
subscript = {
        2: { # Meaning: third item in line_data
            0: "170",  # Meaning: draw "170" over the first char
            len(line3[-1][0]) - 1: "185", # Draw "185" over the last char
            7: "180",   # Meaning: draw "180" over the eight char
           },
        4: {5: "hi"},
        3: {6: "ops"}
        }

# If the following fonts are not mono spaced, you are going to suffer.
#
# Normal font.
font = ImageFont.truetype('FreeMono.ttf', 40)
# Font for subscript.
font_tiny = ImageFont.truetype('FreeMono.ttf', 20)


im = Image.new("RGBA", (1000, 1000), 'white')
draw = ImageDraw.Draw(im)
line_offset = 4
start_y = 6

width_A, height_A = font.getsize('A')
_, height_tiny_A = font_tiny.getsize('A')

# Collect even columns from the last item of list line1.
even_columns = []
x = 0
for i, (text, _) in enumerate(line1):
    for j, letter in enumerate(text):
        if i == len(line1) - 1 and not j % 2:
            even_columns.append(x)
        x += width_A

# Write all lines.
width = 0
l_start_y = start_y
for di, data in enumerate(line_data):
    x = 0
    for i, (text, xoff) in enumerate(data):
        for j, letter in enumerate(text):
            # Apply x offset.
            extra = width_A * xoff
            draw.text((x + extra, l_start_y), letter, font=font, fill='black')
            x += width_A
    width = max(x, width)
    l_start_y += height_A + line_offset

# Collect letter positions from the lines that will have subscripts.
letter_pos = {}
for k in subscript:
    letter_pos[k] = {}
    x = sum(len(text) for text, _ in line_data[k][:-1]) * width_A
    text, xoff = line_data[k][-1]
    for i in range(len(text)):
        extra = width_A * xoff
        letter_pos[k][i] = x + extra
        x += width_A
# Write all subscripts.
for k, v in subscript.items():
    line = line_data[k]
    for pos, text in v.items():
        x = letter_pos[k][pos]
        y = start_y + (line_offset + height_A) * k
        y -= height_tiny_A * 0.4 # XXX A poor heuristic that worked here.
        draw.text((x, y), text, font=font_tiny, fill='black')
        width = max(width, int(x + font_tiny.getsize(text)[0]))

# Draw grey columns.
columns = Image.new(im.mode, im.size, 'white')
mask = Image.new("L", im.size, 'white')
for x in even_columns:
    columns.paste((128, 128, 128), (x, line_offset, x + width_A, l_start_y))
    mask.paste(164, (x, line_offset, x + width_A, l_start_y), )
im = Image.composite(im, columns, mask)

# Crop and save the resulting image.
im.crop((0, 0, width, l_start_y + 2)).save(sys.argv[1])

OTHER TIPS

This is really more of a LaTeX question than a Python question -- i.e., if you determine the markup that you want your Python script to generate, then actually generating it is fairly easy.

In this case, I think you could probably get the effects you want with a fairly complex table (\begin{table}...\end{table}), where each of your cells spans two columns, and one column is used as the offset to get the "half character" you want. It wouldn't surprise me if there's an easier way to do this in LaTeX (which has way more libraries than I can keep in my head), but that's the one that comes to mind. So your table would look something like this:

+------+--+--+--+--+--+--+--+
| pos. |  |  1  |  2  |  3  |      ... 
+------+--+--+--+--+--+--+--+
| name.|  |  A  |  B  |  C  |      ... 
+------+--+--+--+--+--+--+--+--+
|      | 170 |     |     |     |   ... 
+------+--+--+--+--+--+--+--+--+
| Str. |  S  |  O  |  M  |  E  |   ... 
+------+--+--+--+--+--+--+--+--+

(Where +s indicate where the actual columns are, and most cells span two columns. The row with "170" on it would use \tiny or some such command.)

My suggestion would be to play around with generating a LaTeX file by hand first that does what you want (or some other formatting language of your choice file); and then worry about writing some python code to generate it.

While PIL works ok for simple stuff, you have a lot more options if you use SVG. You can use pySVG (handy pySVG tutorial) or svgwrite to create your SVG programmatically, or just print it as text. Then use ImageMagick (either on the commandline as convert or from python using pythonmagicwand) to render the SVG to any raster type you want. While fine tuning the SVG you can edit it with a text editor and look at it in a web browser :)

# make some text in any font, any size, any effects
import pysvg
s = svg()
myStyle = StyleBuilder()
myStyle.setFontFamily(fontfamily="Verdana")
myStyle.setFontSize("5em")
myStyle.setFilling("blue")
t1 = text("Hello World", 0, 100)
t1.set_style(myStyle.getStyle())
s.addElement(t1)
s.save('test.svg')

from pythonmagickwand.image import Image
img = Image('test.svg')
img.format = 'PNG'
img.save('test.png')
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top