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:
Slightly changing the fonts:
More "severe" font changes next, all produced with the code as is.
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])