Skip to content

Commit

Permalink
py3.5 3.6 3.7 compatible
Browse files Browse the repository at this point in the history
resolve python 3.5 3.6 3.7 compatibility issues, all tested
remove long comment
  • Loading branch information
tomchen committed Dec 25, 2020
1 parent aa3dd9e commit 1b279e4
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 65 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"python.envFile": "${workspaceRoot}/vscode.env"
"python.envFile": "${workspaceRoot}/vscode.env",
"python.pythonPath": "C:\\Users\\Chen\\AppData\\Local\\Programs\\Python\\Python39\\python.exe"
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BDF Parser

[![PyPI package](https://img.shields.io/badge/pip%20install-bdfparser-brightgreen)](https://pypi.org/project/example-pypi-package/) [![Actions Status](https://github.com/tomchen/bdfparser/workflows/Test/badge.svg)](https://github.com/tomchen/bdfparser/actions) [![License](https://img.shields.io/github/license/tomchen/bdfparser)](https://github.com/tomchen/bdfparser/blob/master/LICENSE)
[![PyPI package](https://img.shields.io/badge/pip%20install-bdfparser-brightgreen)](https://pypi.org/project/bdfparser/) [![Actions Status](https://github.com/tomchen/bdfparser/workflows/Test/badge.svg)](https://github.com/tomchen/bdfparser/actions) [![License](https://img.shields.io/github/license/tomchen/bdfparser)](https://github.com/tomchen/bdfparser/blob/master/LICENSE)

BDF (Glyph Bitmap Distribution; [Wikipedia](https://en.wikipedia.org/wiki/Glyph_Bitmap_Distribution_Format); [Spec](https://font.tomchen.org/bdf_spec/)) format bitmap font file parser library in Python. It has [`Font`](https://font.tomchen.org/bdfparser_py/font), [`Glyph`](https://font.tomchen.org/bdfparser_py/glyph) and [`Bitmap`](https://font.tomchen.org/bdfparser_py/bitmap) classes providing more than 30 enriched API methods of parsing BDF fonts, getting their meta information, rendering text in any writing direction, adding special effects and manipulating bitmap images. It works seamlessly with [PIL / Pillow](https://pillow.readthedocs.io/en/stable/) and [NumPy](https://numpy.org/).

Expand Down
105 changes: 49 additions & 56 deletions src/bdfparser/bdfparser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# BDF (Glyph Bitmap Distribution Format) Bitmap Font File Parser in Python
# Copyright (c) 2017-2021 Tom CHEN (tomchen.org), MIT License
# https://font.tomchen.org/bdfparser_py/


import re
import io
import warnings
from sys import version_info as python_version


def format_warning(message, category, filename, lineno, file=None, line=None):
Expand Down Expand Up @@ -39,9 +41,16 @@ class Font(object):
]

def __init__(self, *argv):

if python_version < (3, 7, 0):
from collections import OrderedDict as ordered_dict
else:
ordered_dict = dict
self.glyphs = ordered_dict()

self.headers = {}
self.props = {}
self.glyphs = {}

self.__glyph_count_to_check = None
self.__curline_startchar = None
self.__curline_chars = None
Expand Down Expand Up @@ -291,7 +300,12 @@ def __prepare_glyphs_after(self):
"The glyph count next to 'CHARS' keyword does not exist")
else:
warnings.warn(
f"The glyph count next to 'CHARS' keyword is {str(self.__glyph_count_to_check)}, which does not match the actual glyph count {str(l)}")
"The glyph count next to 'CHARS' keyword is " +
str(self.__glyph_count_to_check) +
", which does not match the actual glyph count " + str(l)
)
# Use old style for Python 3.5 support. For 3.6+:
# f"The glyph count next to 'CHARS' keyword is {str(self.__glyph_count_to_check)}, which does not match the actual glyph count {str(l)}"

def length(self):
return len(self.glyphs)
Expand All @@ -300,13 +314,7 @@ def __len__(self):
return self.length()

def itercps(self, order=1, r=None):
# order: -1: reverse order in the BDF font file; 0: order in the BDF font file;
# 1: ascending codepoint order; 2: descending codepoint order
# r: codepoint range, accepts:
# * integer (examples: `128` (Basic Latin / ASCII), `0x100` (Basic Latin and Latin-1 Supplement / cp1250 / cp1251 / cp1252))
# * tuple of integers (examples: `(0, 127)` (same as `128`), `(0, 0xff)` (same as `0x100`), `(48, 57)` (all numbers 0-9), `(65, 90)` (all uppercase basic latin letters A-Z), `(97, 122)` (all lowercase basic latin letters a-z), `(1328, 0x1032F)`)
# * list of tuples of integers (example: `[(65, 90), (97, 122)]` (all basic latin letters A-Za-z), `[(0x2E80, 0x9FFF), (0xA960, 0xA97F), (0xAC00, 0xD7FF), (0xF900, 0xFAFF), (0xFE30, 0xFE4F), (0xFF00, 0xFFEF), (0x20000, 0x3134F)]` (this is roughly all CJK characters in the Unicode))
# see also https://en.wikipedia.org/wiki/Unicode_block
# https://font.tomchen.org/bdfparser_py/font/#iterglyphs

ks = self.glyphs.keys()
if order == 1:
Expand All @@ -316,7 +324,10 @@ def itercps(self, order=1, r=None):
elif order == 2:
retiterator = iter(sorted(ks, reverse=True))
elif order == -1:
retiterator = reversed(ks)
try:
retiterator = reversed(ks)
except TypeError:
retiterator = reversed(list(ks)) # Python <=3.7
if r is not None:
def f(cp):
if isinstance(r, int):
Expand All @@ -338,25 +349,18 @@ def iterglyphs(self, order=1, r=None):
def glyphbycp(self, codepoint):
if codepoint not in self.glyphs:
raise Exception(
f"Glyph \"{chr(codepoint)}\" (codepoint {str(codepoint)}) does not exist in the font")
"Glyph \"" + chr(codepoint) + "\" (codepoint " +
str(codepoint) + ") does not exist in the font"
)
# Use old style for Python 3.5 support. For 3.6+:
# f"Glyph \"{chr(codepoint)}\" (codepoint {str(codepoint)}) does not exist in the font"
return Glyph(dict(zip(self.__META_TITLES, self.glyphs[codepoint])), self)

def glyph(self, character):
return self.glyphbycp(ord(character))

def drawcps(self, cps, linelimit=512, mode=1, direction='lrtb', usecurrentglyphspacing=False):
# `mode`: 0: ffb; 1: dwidth horizontally, dwidth1 vertically
# `direction`:
# * 'lrtb' or 'lr': left to right, lines from top to bottom (most common direction)
# * 'lrbt' : left to right, lines from bottom to top
# * 'rltb' or 'rl': right to left, lines from top to bottom (Arabic, Hebrew, Persian, Urdu)
# * 'rlbt' : right to left, lines from bottom to top
# * 'tbrl' or 'tb': top to bottom, lines from right to left (Chinese traditionally)
# * 'tblr' : top to bottom, lines from left to right
# * 'btrl' or 'bt': bottom to top, lines from right to left
# * 'btlr' : bottom to top, lines from left to right
# `usecurrentglyphspacing` is useful when direction='rl', example: see the difference between
# `font.draw('U的', direction='rl')` and `font.draw('U的', direction='rl', usecurrentglyphspacing=True)`
# https://font.tomchen.org/bdfparser_py/font#draw

dire_shortcut_dict = {
'lr': 'lrtb',
Expand Down Expand Up @@ -409,7 +413,9 @@ def drawcps(self, cps, linelimit=512, mode=1, direction='lrtb', usecurrentglyphs
interglyph_global = self.headers[interglyph_str2]
else:
interglyph_global = None
# warnings.warn(f"The font do not have `{interglyph_keyword}`, glyph spacing adjustment could be skipped unless present in individual glyphs")
# warnings.warn("The font do not have `" + interglyph_keyword + "`, glyph spacing adjustment could be skipped unless present in individual glyphs")
# # Use old style for Python 3.5 support. For 3.6+:
# # warnings.warn(f"The font do not have `{interglyph_keyword}`, glyph spacing adjustment could be skipped unless present in individual glyphs")

list_of_bitmaplist = []
bitmaplist = []
Expand Down Expand Up @@ -454,7 +460,11 @@ def append_bitmaplist_and_offsetlist():
else:
if len(bitmaplist) == 0:
raise Exception(
f"`linelimit` ({linelimit}) is too small the line can't even contain one glyph: \"{glyph.chr()}\" (codepoint {cp}, width: {w})")
"`linelimit` (" + linelimit + ") is too small the line can't even contain one glyph: \"" +
glyph.chr() + "\" (codepoint " + cp + ", width: " + w + ")"
)
# Use old style for Python 3.5 support. For 3.6+:
# f"`linelimit` ({linelimit}) is too small the line can't even contain one glyph: \"{glyph.chr()}\" (codepoint {cp}, width: {w})"
append_bitmaplist_and_offsetlist()
size = 0
bitmaplist = []
Expand Down Expand Up @@ -494,6 +504,8 @@ def chr(self):
return chr(self.cp())

def draw(self, mode=0, bb=None):
# https://font.tomchen.org/bdfparser_py/glyph#draw

if mode == 0:
retbitmap = self.__draw_fbb()
elif mode == 1:
Expand All @@ -507,11 +519,6 @@ def draw(self, mode=0, bb=None):
'Parameter bb in draw() method must be set when mode=-1')
return retbitmap

# 0 (default): area represented by the bitmap hex data, positioned and resized (cropped) (`fbbx` × `fbby`) according to `FONTBOUNDINGBOX` (the font's global bounding box)
# 1: area represented by the bitmap hex data, resized (cropped) according to `BBX` (`bbw` × `bbh`), which is the individual glyph bounding box, without unnecessary blank margin (but still possible to have blank margin)
# 2: area represented by the bitmap hex data, original, without resizing
# -1: user specified area. if this mode is chosen, you must specify `fbb`, which is a tuple (fbbx, fbby, fbbxoff, fbbyoff) representing your customized font bounding box. Similar to `FONTBOUNDINGBOX`, fbbx and fbby represent the size, fbbxoff and fbbyoff represent the relative position of the starting point to the origin

def __draw_user_specified(self, fbb):
bbxoff = self.meta.get('bbxoff')
bbyoff = self.meta.get('bbyoff')
Expand All @@ -530,16 +537,21 @@ def __draw_bb(self):
l = len(bindata)
if l != bbh:
raise Exception(
f"Glyph \"{str(self.meta.get('glyphname'))}\" (codepoint {str(self.meta.get('codepoint'))})'s bbh, {str(bbh)}, does not match its hexdata line count, {str(l)}")
"Glyph \"" + str(self.meta.get('glyphname')) + "\" (codepoint " + str(self.meta.get(
'codepoint')) + ")'s bbh, " + str(bbh) + ", does not match its hexdata line count, " + str(l)
)
# Use old style for Python 3.5 support. For 3.6+:
# f"Glyph \"{str(self.meta.get('glyphname'))}\" (codepoint {str(self.meta.get('codepoint'))})'s bbh, {str(bbh)}, does not match its hexdata line count, {str(l)}"
bitmap.bindata = [b[0:bbw] for b in bindata]
return bitmap

def __draw_fbb(self):
fh = self.font.headers
return self.__draw_user_specified((fh['fbbx'], fh['fbby'], fh['fbbxoff'], fh['fbbyoff']))

# get the relative position (displacement) of the origin from the left bottom corner of the bitmap drawn by method `draw()`, or vice versa (i.e. displacement of the left bottom corner of the bitmap from the origin)
def origin(self, mode=0, fromorigin=False, xoff=None, yoff=None):
# https://font.tomchen.org/bdfparser_py/glyph#origin

bbxoff = self.meta.get('bbxoff')
bbyoff = self.meta.get('bbyoff')
if mode == 0:
Expand Down Expand Up @@ -661,10 +673,7 @@ def overlay(self, bitmap):

@classmethod
def concatall(cls, bitmaplist, direction=1, align=1, offsetlist=None):
# offsetlist is spacing offset between two glyphs, len(offsetlist) should be len(bitmaplist)-1
# direction: 1: right, 0: down, 2: left, -1: up
# if horizontal (1 right or 2 left): align: 1: bottom; 0: top
# if vertical (0 down or -1 up) : align: 1: left; 0: right
# https://font.tomchen.org/bdfparser_py/bitmap#bitmapconcatall

if direction > 0: # horizontal

Expand Down Expand Up @@ -809,11 +818,8 @@ def glow(self):
return self

def bytepad(self, bits=8):
# Do this before using the bitmap for a glyph in a BDF font.
# Per BDF spec, if the bit/pixel count in a line is not multiple of 8,
# the line needs to be padded on the right with 0s to the nearest byte (that is, multiple of 8)
# `bits` is 8 by default because 1 byte = 8 bits,
# you can change it if you want to use other unconventional, off-spec values, such as 4 (half a byte)
# https://font.tomchen.org/bdfparser_py/bitmap#bytepad

w = self.width()
h = self.height()
mod = w % bits
Expand All @@ -822,14 +828,7 @@ def bytepad(self, bits=8):
return self.crop(w + bits - mod, h)

def todata(self, datatype=1):
# `datatype`: output data type
# * 0 : binary-encoded multi-line string e.g. '00010\n11010\n00201'
# * 1 : list of binary-encoded strings e.g. ['00010','11010','00201']
# * 2 : list of lists of integers 0 or 1 (or 2 sometimes) e.g. [[0,0,0,1,0],[1,1,0,1,0],[0,0,2,0,1]]
# * 3 : list of integers 0 or 1 (or 2 sometimes) (to be used with .width() and .height()) e.g. [0,0,0,1,0,1,1,0,1,0,0,0,2,0,1]
# * 4 : list of lowercase hexadecimal-encoded strings (without '0x', padded with leading '0's according to width) e.g. ['02','1a']
# * 5 : list of integers e.g. [2,26]
# NOTE: you can't have '2's when using datatypes 4 and 5
# https://font.tomchen.org/bdfparser_py/bitmap#todata

if datatype == 0:
return '\n'.join(self.bindata)
Expand All @@ -847,13 +846,7 @@ def todata(self, datatype=1):
return [int(l, 2) for l in self.bindata]

def tobytes(self, mode='RGB', bytesdict=None):
# output bytes to be used to create PIL / Pillow library image with `Image.frombytes()`
# `mode`: PIL image mode
# (see also https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes , but only the followings are supported)
# * '1' : 1-bit pixels, black and white, stored with one pixel per byte
# * 'L' : 8-bit pixels, black and white
# * 'RGB' : 3x8-bit pixels, true color
# * 'RGBA': 4x8-bit pixels, true color with transparency mask
# https://font.tomchen.org/bdfparser_py/bitmap#tobytes

if mode == '1':

Expand Down
2 changes: 0 additions & 2 deletions tests/test_bitmap.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import unittest

from bdfparser import Font, Bitmap

from .info import specfont_path, bitmap_qr2_bindata, bitmap_qr3_bindata


Expand Down
5 changes: 2 additions & 3 deletions tests/test_font.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import unittest

from bdfparser import Font, Glyph

from .info import unifont_path, glyph_a_meta


Expand All @@ -18,7 +16,8 @@ def test_load_file_path(self):
self.assertIsInstance(self.font.load_file_path(unifont_path), Font)

def test_load_file_obj(self):
self.assertIsInstance(self.font.load_file_obj(open(unifont_path)), Font)
self.assertIsInstance(
self.font.load_file_obj(open(unifont_path)), Font)


class TestFont(unittest.TestCase):
Expand Down
2 changes: 0 additions & 2 deletions tests/test_glyph.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import unittest

from bdfparser import Font, Glyph

from .info import unifont_path, glyph_a_meta, bitmap_a_bindata, specfont_path


Expand Down

0 comments on commit 1b279e4

Please sign in to comment.