Reputation: 193
I am having some trouble saving my pdf properly. I am trying to plot a barcode label and subsequently save it as a pdf, as in the following code. I have installed the code128.ttf font on my windows. Also, I have tried setting the .savefig dpi argument to fig.dpi, as argued in this post.
import os
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
def draw_label(label, label_dimensions_x=3.8189, label_dimensions_y=1.41732):
# import barcode code128 font
fpath = os.path.join("path", "to", "font", "code128.ttf")
prop = fm.FontProperties(fname=fpath, size=58)
fig, ax = plt.subplots(1, figsize=(label_dimensions_x,
label_dimensions_y))
plt.axis('off')
plt.xticks([], [])
plt.yticks([], [])
plt.tight_layout()
plt.xlim(0, label_dimensions_x)
plt.ylim(0, label_dimensions_y)
# plot barcode
plt.text(label_dimensions_x / 2, label_dimensions_y / 2, label,
ha='center', va='bottom',
fontproperties=prop)
plt.show()
try:
plt.savefig(os.path.join("path", "to", "output", label + '.pdf'),
dpi=plt.gcf().dpi)
except PermissionError:
logging.warning("Close the current label pdf's before running this script.")
plt.close()
return
draw_label('123456789')
This is what is output in the plot window.
This is what is output in the .pdf saved file, and this happens for all kinds of labels - it's not as if the numbers 1 to 9 except 8 are not printable. EDIT: If I substitute a normal text font (in this case Frutiger Roman) for the code128.ttf, and set plt.axis('on') the text is not clipped, see this. Admitted, it's not pretty and doesn't fit too well, but it should be readable still.
Upvotes: 2
Views: 598
Reputation: 308120
You're over-complicating things by using matplotlib and a font. Generating an image directly and saving it to a PDF file is not much more complicated, and far more reliable.
As noted by Brian Anderson, it's not enough to encode the characters in your string. You need to add a start code, a checksum, and a stop code to make a complete barcode. The function code128_codes
below does this, leaving the conversion to an image as a separate step.
from PIL import Image
def list_join(seq):
''' Join a sequence of lists into a single list, much like str.join
will join a sequence of strings into a single string.
'''
return [x for sub in seq for x in sub]
_code128B_mapping = dict((chr(c), [98, c+64] if c < 32 else [c-32]) for c in range(128))
_code128C_mapping = dict([(u'%02d' % i, [i]) for i in range(100)] + [(u'%d' % i, [100, 16+i]) for i in range(10)])
def code128_codes(s):
''' Code 128 conversion to a list of raw integer codes.
Only encodes ASCII characters, does not take advantage of
FNC4 for bytes with the upper bit set. Control characters
are not optimized and expand to 2 characters each.
Coded for https://stackoverflow.com/q/52710760/5987
'''
if s.isdigit() and len(s) >= 2:
# use Code 128C, pairs of digits
codes = [105] + list_join(_code128C_mapping[s[i:i+2]] for i in range(0, len(s), 2))
else:
# use Code 128B and shift for Code 128A
codes = [104] + list_join(_code128B_mapping[c] for c in s)
check_digit = (codes[0] + sum(i * x for i,x in enumerate(codes))) % 103
codes.append(check_digit)
codes.append(106) # stop code
return codes
_code128_patterns = '''
11011001100 11001101100 11001100110 10010011000 10010001100 10001001100
10011001000 10011000100 10001100100 11001001000 11001000100 11000100100
10110011100 10011011100 10011001110 10111001100 10011101100 10011100110
11001110010 11001011100 11001001110 11011100100 11001110100 11101101110
11101001100 11100101100 11100100110 11101100100 11100110100 11100110010
11011011000 11011000110 11000110110 10100011000 10001011000 10001000110
10110001000 10001101000 10001100010 11010001000 11000101000 11000100010
10110111000 10110001110 10001101110 10111011000 10111000110 10001110110
11101110110 11010001110 11000101110 11011101000 11011100010 11011101110
11101011000 11101000110 11100010110 11101101000 11101100010 11100011010
11101111010 11001000010 11110001010 10100110000 10100001100 10010110000
10010000110 10000101100 10000100110 10110010000 10110000100 10011010000
10011000010 10000110100 10000110010 11000010010 11001010000 11110111010
11000010100 10001111010 10100111100 10010111100 10010011110 10111100100
10011110100 10011110010 11110100100 11110010100 11110010010 11011011110
11011110110 11110110110 10101111000 10100011110 10001011110 10111101000
10111100010 11110101000 11110100010 10111011110 10111101110 11101011110
11110101110 11010000100 11010010000 11010011100 1100011101011'''.split()
def code128_img(s, height=100, bar_width=1):
''' Generate a Code 128 barcode image.
Coded for https://stackoverflow.com/q/52968042/5987
'''
codes = code128_codes(s)
pattern = ''.join(_code128_patterns[c] for c in codes)
pattern = '00000000000' + pattern + '00000000000'
width = bar_width * len(pattern)
color, bg = (0, 0, 0), (255, 255, 255)
im = Image.new('RGB', (width, height), bg)
ld = im.load()
for i, bar in enumerate(pattern):
if bar == '1':
for y in range(height):
for x in range(i * bar_width, (i + 1) * bar_width):
ld[x, y] = color
return im
>>> im = code128_img('AM-H-10-01-1')
>>> im.save(r'c:\temp\temp.pdf')
Upvotes: 0
Reputation: 1766
Sam,
First, your barcode won't scan, as is. The string requires a start character, a checksum and a stop character to be added for Code128B. So, there's that.
I recommend changing to Code 39 font (which, doesn't require checksum, and start and stop characters are the same: "*") or writing the code to produce the checksum and learning a little more about Code 128 at Code 128 Wiki.
Second, I suspect there are issues with the bounding box for the graphic during the conversion to PDF. That small section of barcode being converted looks more like a piece of the number nine in the string. I suspect there is some image clipping going on.
Try substituting a regular text font to make sure the barcode image isn't being lost in the conversion.
Edited answer to include suggestion to use PNG instead of PDF.
I managed to get the software to work if you output to PNG format. I know, now the problem becomes how to convert PNG to PDF. You can start by investigating some of the libraries mentioned here: Create PDF from a list of images
In short I recommend you create graphics files and then embed them in document files.
I also added the code you need to build the barcode with the start, checksum and stop characters:
import os import matplotlib.pyplot as plt from matplotlib import font_manager as fm def draw_label(label, label_dimensions_x=3.8189, label_dimensions_y=1.41732): # import barcode code128 font fpath = os.path.join("./", "code128.ttf") prop = fm.FontProperties(fname=fpath, size=32) fig, ax = plt.subplots(1, figsize=(label_dimensions_x, label_dimensions_y)) plt.axis('off') plt.xticks([], []) plt.yticks([], []) plt.tight_layout() plt.xlim(0, label_dimensions_x) plt.ylim(0, label_dimensions_y) # calc checksum THEN plot barcode weight = 1 chksum = 104 for x in label: chksum = chksum + weight*(ord(x)-32) weight = weight + 1 chksum = chksum % 103 chkchar = chr(chksum+32) label128 = "%s%s%s%s" % ('Ñ', label, chkchar, 'Ó') plt.text(label_dimensions_x / 2, label_dimensions_y / 2, label128, ha='center', va='bottom', fontproperties=prop) try: plt.savefig(os.path.join("./", label + '.png')) except PermissionError: logging.warning("Close the current label pdf's before running this script.") return draw_label('123456789') draw_label('987654321') draw_label('Test&Show')
Upvotes: 1