Creating PDF with Python using FPDF char by char - python

I'm trying to create a pdf with python and I want to put a text in pdf char by char.
I can't find out how to do it and when it saves output pdf all of the characters are on each other.
this is my code snippet:
from fpdf import FPDF
pdf = FPDF('P', 'mm', (100,100))
# Add a page
pdf.add_page()
# set style and size of font
# that you want in the pdf
pdf.add_font('ariblk', '', "ArialBlack.ttf", uni=True)
pdf.set_font("ariblk",size = int(50*0.8))
text = [['a','b','c','d','e','w','q'],['f','g','h','i','j','k','l']]
print("creating pdf...")
line = 0
for w in range(0,len(text)):
for h in range(0,len(text[w])):
# create a cell
r = int (50)
g = int (100)
b = int (10)
pdf.set_text_color(r, g, b)
text_out = text[w][h]
pdf.cell(0,line, txt = text_out, ln = 2)
# save the pdf with name .pdf
pdf.output(name = "img/output.pdf", dest='F')
print("pdf created!")
and this is what my code output is:
(this is copy-paste from the output pdf): iljfbeqaghdckw
(this is a screenshot of the output):

I don't know fpdf module but I think that your problem only comes from the fact that you don't change the X, Y coordinates of printing of each character.
You have to use 'pdf.set_xy()` to set the X and Y coordinates of each of your characters
I made small changes to the font and colors for my tests.
from fpdf import FPDF
import random
pdf = FPDF('P', 'mm', (100,100))
# Add a page
pdf.add_page()
# set style and size of font
# that you want in the pdf
#pdf.add_font('ariblk', '', "ArialBlack.ttf", uni=True)
pdf.set_font("Arial",size = int(24))
text = [['a','b','c','d','e','w','q'],['f','g','h','i','j','k','l']]
print("creating pdf...")
line = 10
for w in range(len(text)):
for h in range(len(text[w])):
# create a cell
r = random.randint(1, 255)
g = random.randint(1, 255)
b = random.randint(1, 255)
pdf.set_text_color(r, g, b)
text_out = text[w][h]
pdf.set_xy(10*w, 10*h)
pdf.cell(10, 10, txt=text_out, ln=0, align='C')
# save the pdf with name .pdf
pdf.output(name = "output.pdf", dest='F')
print("pdf created!")
Then, you have to adapt the offset of X and/or Y according to the display you want to obtain in print.
Remark: As you don't change the values of r, g, b in your for loops, the best is to go up the assignment of variables r, g and b before the for loops
Output in the PDF:
a f
b g
c h
d i
e j
w k
q l

Related

Dynamically positioning of elements in pyFPDF

I am using pyFPDF to automate reports (trying at least), and I am having problems dynamically adjusting the positions of multi_cells and graphs after page 01.
I tried two things:
I was using fixed distances between each graph and text, using something like:
pdf = FPDF()
pdf.add_page()
pdf.set_font('Arial', style = 'B', size = 12)
position_y = 100
position_x = (A4_size_x)/2
text = 80
var = [0,1,2,3,4,5]
for i in var:
pdf.image(f'1. Gráficos/{cpf}-{fator}.png', #Graphs that were dynamically generated
x = position_x - 25,
y = position_y + (text * var),
w = 50,
h = 50)
pdf.set_xy(position_x - 50, ((position_y + (text * var)) - 45))
pdf.multi_cell(w = 100,
h = 5,
txt = 'Some Dynamic Text',
align='C')
pdf.output(f'2. Reports/{cpf}.pdf', 'F')
The problem here is that when it reaches a page break, the position values calculate from the new page and they break the whole document.
I also tried disabling page break:
pdf.set_auto_page_break(auto = False, margin = 0.0)
But then there is only one page in the document.
The question here is, how to keep adding objects in the same intervals across multiple pages?
Thank you!

Search/Update Graphics in a PDF

Is there any way to search for a graphic in a PDF page?
For example, I have used the PyFPDF library to generate a PDF with few rectangles and circles (code below). Now I would like to
get a list of ellipses that are printed in each page and their location.
and update the color of all the circles to blue
Is there any library which allows this?
PDF generation with few rectangles and circles:
Sample PDF generated using code below: Download the PDF
from fpdf import FPDF
# Prepare PDF generator
pdf = FPDF(orientation = 'L', unit = 'in', format = 'A4')
pdf.add_page()
pdf.set_fill_color(0, 0, 0)
# Draw the rectangle
pdf.rect(x = 1, y = 1, w = 2, h = 2, style = 'S')
pdf.rect(x = 1.5, y = 1.5, w = 5, h = 1, style = 'S')
pdf.ellipse(x = 1, y = 4, w = 1, h = 1, style = 'S')
pdf.ellipse(x = 4, y = 5, w = 1, h = 1, style = 'S')
pdf.ellipse(x = 9, y = 2, w = 1, h = 1, style = 'F')
pdf.rect(x = 6, y = 5, w = 2, h = 1, style = 'F')
# Write to file
pdf.output("test.pdf")
NOT YOUR ANSWER just a visual comment
Its not been easy to dissect and rebuild by hand so used some shortcuts. but the pdf supplied has all vectors as only one black encoded stream object that looks something like this
4 0 obj
<</Filter /FlateDecode /Length 251>>
stream
xœU’»q!„sª 3舜ºÆ¿#'Nܾ-.‚ïЮVßCN¥Åß¹äœãWh<7±°$¶Hª‹Þ°þ|Æ#ÙÖ^­­
©yW¸Ì¼˜­‚ïcÂÕHÜ’M]Þr˜F0KÄÝ:€›ÄäBIstK¹,A# á9E>£ŽóPïÌÔWQ¹¼„õÑI²^9ØìR‚à;ÂáÖW(©Êåš¾Þõœ"!”¼k‚Æû‹Rñ ÏTëóöÒ²'®¶;F¨uç8§j—òaZ°ô²r#)­_¾ ù·¼+‰ |æñTøº×óÿ_Ø+ü)„Ÿ
endstream
endobj
Thus at a simple level everything at once could possibly be prefixed as blue. but if we decode the stream, it is easier to inject colours per part of stroked stream here I marked the stack targets in green and have injected 0 0 1 rg for blue (end of square rect & 1 group of the chords and 0 g for stop Graphics change between times
Thus as with many a PDF question:-
Can I reverse engineer PDF to
"other the and that ,this Do" the answer is do it at source
$pdf->SetDrawColor(0,0,0);
$pdf->Circle(100,50,30,'D');
$pdf->SetFillColor(0,0,255);
$pdf->Circle(110,47,7,'F');

Splitting a BigTiff using tifffile

I am having trouble splitting a BigTiff. I am orginally using #Ivan code from this question. I had to modify it a bit since I am using a .tif that was larger than 4GB. Pillow does not support BigTiffs. So I ended up using tifffile. The code is running, though the image is not being clipped to the box parameter. I am thinking it has to do that tifffile reads the image in as a numpy array and is not actually being clipped by anything???
I've also noticed that when this code is running my memory is just about maxing out. I tried assigning the data type to a unassigned 8-bit and that drastically helped. Should I compress it as well? I don't want to change the data too much since I will be classifying them and do not want to lose/change data.
import os
from itertools import product
import tifffile
def tile(filename, dir_in, dir_out, d):
name, ext = os.path.splitext(filename)
img = tifffile.imread(os.path.join(dir_in, filename))
print(type(img))
w = img.shape[0]
h = img.shape[1]
grid = list(product(range(0, h - h % d, d), range(0, w - w % d, d)))
for i, j in grid:
box = (j, i, j + d, i + d)
out = os.path.join(dir_out, f'{name}_{i}_{j}{ext}')
img = img.clip(box)
img = img.astype('uint8')
tifffile.imsave(out, img)
tile('Orthomosaic_export_MonFeb01193821460106.tif',
r'D:\ortho',
r'D:\model_images',
1000)
Ended up merging this answer into my code and finally got it to run.
import os
import tifffile
def tile(filename, dir_in, dir_out):
name, ext = os.path.splitext(filename)
img = tifffile.imread(os.path.join(dir_in, filename))
windowsize_r = 10000
windowsize_c = 10000
i = 0
for r in range(0, img.shape[0] - windowsize_r, windowsize_r):
for c in range(0, img.shape[1] - windowsize_c, windowsize_c):
window = img[r:r + windowsize_r, c:c + windowsize_c]
out = os.path.join(dir_out, f'{name}_{i}_{ext}')
tifffile.imsave(out, window)
i = i + 1
tile('Shopfield_Orthomosaic_export_MonFeb01193821460106.tif',
r'D:\ortho',
r'D:\model_images')

How to use my own bitmap font in PIL.ImageFont?

I created a bitmap font, basically a 256x256 png image where each character occupies 8x8 tile. I want to use it with Pillow as ImageFont but there's no info on this in Pillow docs. It says I can load bitmap fonts like this
font = ImageFont.load("arial.pil")
but "PIL uses its own font file format to store bitmap fonts." so I guess png file won't work. How can I tell PIL to use said bitmap and where each character is on it?
Not a complete answer, but too much for a comment, and it may be useful or spur someone else to work out the other 60% :-)
I may delete it if anyone else comes up with something better...
You can go to the Pillow repository on Github and download a ZIP file of the code.
If you go in there and nose around you will find two things that appear to work hand-in-hand, namely a .PIL file and a .PBM file.
In Tests/fonts there is a file called 10x20.pbm which is actually a PNG file if you look inside it. So, if you change its name to 10x20.png you can view it and it looks like this:
By the way, if you want to split that into 10x20 size chunks with one letter in each, you can use ImageMagick in Terminal like this:
convert 10x20.pbm -crop 10x20 char_%d.png
and you will get a bunch of files called char_0.png, char_1.png etc. The first 4 look like this:
If you look in src/PIL/FontFile.py there is this code that seems to know how to access/generate the metrics for a font:
#
# The Python Imaging Library
# $Id$
#
# base class for raster font file parsers
#
# history:
# 1997-06-05 fl created
# 1997-08-19 fl restrict image width
#
# Copyright (c) 1997-1998 by Secret Labs AB
# Copyright (c) 1997-1998 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from __future__ import print_function
import os
from . import Image, _binary
WIDTH = 800
def puti16(fp, values):
# write network order (big-endian) 16-bit sequence
for v in values:
if v < 0:
v += 65536
fp.write(_binary.o16be(v))
##
# Base class for raster font file handlers.
class FontFile(object):
bitmap = None
def __init__(self):
self.info = {}
self.glyph = [None] * 256
def __getitem__(self, ix):
return self.glyph[ix]
def compile(self):
"Create metrics and bitmap"
if self.bitmap:
return
# create bitmap large enough to hold all data
h = w = maxwidth = 0
lines = 1
for glyph in self:
if glyph:
d, dst, src, im = glyph
h = max(h, src[3] - src[1])
w = w + (src[2] - src[0])
if w > WIDTH:
lines += 1
w = (src[2] - src[0])
maxwidth = max(maxwidth, w)
xsize = maxwidth
ysize = lines * h
if xsize == 0 and ysize == 0:
return ""
self.ysize = h
# paste glyphs into bitmap
self.bitmap = Image.new("1", (xsize, ysize))
self.metrics = [None] * 256
x = y = 0
for i in range(256):
glyph = self[i]
if glyph:
d, dst, src, im = glyph
xx = src[2] - src[0]
# yy = src[3] - src[1]
x0, y0 = x, y
x = x + xx
if x > WIDTH:
x, y = 0, y + h
x0, y0 = x, y
x = xx
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
self.bitmap.paste(im.crop(src), s)
self.metrics[i] = d, dst, s
def save(self, filename):
"Save font"
self.compile()
# font data
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
# font metrics
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
fp.write(b"PILfont\n")
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
fp.write(b"DATA\n")
for id in range(256):
m = self.metrics[id]
if not m:
puti16(fp, [0] * 10)
else:
puti16(fp, m[0] + m[1] + m[2])
So hopefully someone has time/knowledge of how to put those two together to enable you to generate the metrics file for your PNG. I think you just need something that does the last 10 lines of that code for your PNG.
There appear to be 23 bytes of header which you can simply replicate, and then there are 256 "entries", i.e. 1 for each of 256 glyphs. Each entry has 10 numbers in it, and each number is 16-bit big endian.
Let's look at the header:
dd if=10x20.pil bs=23 count=1| xxd -c23 | more
00000000: 5049 4c66 6f6e 740a 3b3b 3b3b 3b3b 3230 3b0a 4441 5441 0a PILfont.;;;;;;20;.DATA.
Then you can see the entries using the command below to skip the header and group nicely:
dd if=10x20.pil bs=23 iseek=1| xxd -g2 -c20
which gives:
Column 1 appears to be the width of the glyph.
Column 7 is the x-offset of the left edge of the glyph in the image and column 9 is the x-offset of the right edge of the glyph in the image. So you will see that column 7 on each line is the same as column 9 on the previous line, i.e. that the glyphs abutt each other going across the image.
If you look at this extract from further down the file, you can see it starts a new row of glyphs in the output image in the middle of the extract (marked in red). That tells us that the bitmap should be no more than 800 pixels wide and that column 8 is the y-offset of the top of the glyph in the bitmap file and column 10 is the y-offset of the bottom of the glyph in the bitmap. You should see that when a new line row of glyphs starts in the bitmap file that x goes to zero and column 8 takes the previous value from column 10.

Convert png back to wav

I've modified some code that takes a .wav file and turns it into a .png.
The original source for the .wav to .png conversion is from:
https://github.com/bobvanluijt/audio-convolutional-neural-network/blob/master/convertWavToPng.py
I've edited it so that instead the colors are sorted in a gradient fashion so that it looks like:
https://imgur.com/a/TSwEOdt
Here:
from PIL import Image
import wave, struct, sys, math
##
# Collect input
##
if sys.argv[1][-4:] != '.wav':
sys.exit("First argument should be a .wav file")
if sys.argv[2][-4:] != '.png':
sys.exit("Second argument should be a .png file")
##
# Conversion:
##
# Wave file needs to be 16 bit mono
waveFile = wave.open(sys.argv[1], 'r')
if waveFile.getnchannels() != 1:
sys.exit("ERROR: The wave file should be single channel (mono)")
imageRgbArray = list()
waveLength = waveFile.getnframes()
# Create the image size (based on the length)
imageSize = math.ceil(math.sqrt(waveLength))
# Loop through the wave file
for i in range(waveLength):
# Try to read frame, if not possible fill with 0x0
try:
waveData = waveFile.readframes(1)
data = struct.unpack("<h", waveData) # This loads the wave bit
convertedData = int(data[0]) + 32768
except:
convertedData = 0
pass
bits = 5
rgbData = tuple([(convertedData>>bits*i)&(2**bits-1) for i in range(3)])
rgbData = tuple(map(lambda x: x<<3, rgbData))
# Add the RGB value to the image array
imageRgbArray.append(rgbData)
# Create new image
im = Image.new('RGB', (int(imageSize), int(imageSize)))
# Add image data
im.putdata(list(sorted(imageRgbArray)))
# Save image
im.save(sys.argv[2])
But now I need to be able to convert the sorted .png back into a .wav file.
Luckily, I already have this to work with:
https://github.com/bobvanluijt/audio-convolutional-neural-network/blob/master/convertPngToWav.py
from PIL import Image
import wave, struct, sys, soundfile as Sndfile, numpy as np, math
##
# Collect input
##
if sys.argv[1][-4:] != '.png':
sys.exit("First argument should be a .png file")
if sys.argv[2][-4:] != '.wav':
sys.exit("Second argument should be a .wav file")
##
# Conversion:
##
# Open image
with Image.open(sys.argv[1]) as pngFile:
# Load image
pngAllPixels = pngFile.load()
# Set the counters that create the image
countX = 0
countY = 0
count = pngFile.size[0] * pngFile.size[1]
# Create the array which will contain all the bits
bitArray = list()
# Loop through the individual pixels
while count > 0:
# Set the location of the pixel that should be loaded
singlePixel = pngAllPixels[countX, countY]
# Get RGB vals and convert them to hex
singlePixelToHexString = '%02x%02x%02x' % (singlePixel[0], singlePixel[1], singlePixel[2])
# Break if end of file (0x0)
if singlePixelToHexString == "000000":
break # break because audio is < 44100 bit
# Convert hex string into actual hex
singlePixelToHex = hex(int("0x" + singlePixelToHexString.lstrip("0"), 16) + int("0x0", 16))
# This adds 16bit/2 (=32768) to the data and converts hex into a bit
singleBit = int(singlePixelToHex, 16) - 32768
# Append the single bit to the array
bitArray.append(singleBit)
# Run through the image and set x and y vals (goes to next row when ready)
if countX == (pngFile.size[0] - 1):
countX = 0
countY += 1
else:
countX += 1
count -= 1
# Convert the array into a Numpy array
bitArrayNp = np.array(bitArray, dtype=np.int16)
# Output the file
Sndfile.write(sys.argv[2], bitArrayNp, 44100, 'PCM_16')
I've been told I need a way to convert each 3-byte pixel color into a two byte number to turn it back into the original wav file.
I think that means changing
rgbData = tuple(map(lambda x: x<<3, rgbData)) back to
rgbData = tuple(map(lambda x: x<<2, rgbData))
But I'm not entirely sure how to implement that in the pngtowav.py file.
I'm new at this, so anything helps.

Categories

Resources