Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: correct small rendering #761

Merged
merged 4 commits into from
Aug 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/font-patcher.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ jobs:
sudo ninja
sudo ninja install

- name: Compile showttf
run: |
cd "$GITHUB_WORKSPACE/fontforge-${{matrix.FontForgeRelease.version}}/contrib/fonttools"
mkdir build
cd build
cmake -Wno-dev -GNinja ..
ninja showttf
echo "SHOWTTF=$(realpath showttf)" >> $GITHUB_ENV
cd ../../..

- name: Setup additional dependencies
run: |
pip install fonttools --quiet
Expand Down Expand Up @@ -83,6 +93,14 @@ jobs:
echo "FONT_FAMILY was ${{ env.FONT_FAMILY }}"
[[ "${{ env.FONT_FAMILY }}" == "Hack Nerd Font" ]] && echo "Font Family matches expected" || exit 1

- name: Spot check font properties
run: |
${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf" | grep 'File Checksum.*diff=0\s*$' && echo "TTF checksum ok" || exit 1
ORIG_MINPPEM=$(${{ env.SHOWTTF }} -c "src/unpatched-fonts/Hack/Regular/Hack-Regular.ttf" | grep 'lowestppem=' )
PATCH_MINPPEM=$(${{ env.SHOWTTF }} -c "$GITHUB_WORKSPACE/temp/Hack Regular Nerd Font Complete.ttf" | grep 'lowestppem=' )
echo "${ORIG_MINPPEM} == ${PATCH_MINPPEM}"
[[ ${ORIG_MINPPEM} == ${PATCH_MINPPEM} ]] && echo "lowestRecPPEM matches" || exit 1

- name: Patcher monospaced
run: |
mkdir -p $GITHUB_WORKSPACE/temp/
Expand Down
145 changes: 139 additions & 6 deletions font-patcher
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,114 @@ except ImportError:
)


class TableHEADWriter:
""" Access to the HEAD table without external dependencies """
def getlong(self, pos = None):
""" Get four bytes from the font file as integer number """
if pos:
self.goto(pos)
return (ord(self.f.read(1)) << 24) + (ord(self.f.read(1)) << 16) + (ord(self.f.read(1)) << 8) + ord(self.f.read(1))

def getshort(self, pos = None):
""" Get two bytes from the font file as integer number """
if pos:
self.goto(pos)
return (ord(self.f.read(1)) << 8) + ord(self.f.read(1))

def putlong(self, num, pos = None):
""" Put number as four bytes into font file """
if pos:
self.goto(pos)
self.f.write(bytearray([(num >> 24) & 0xFF, (num >> 16) & 0xFF ,(num >> 8) & 0xFF, num & 0xFF]))
self.modified = True

def putshort(self, num, pos = None):
""" Put number as two bytes into font file """
if pos:
self.goto(pos)
self.f.write(bytearray([(num >> 8) & 0xFF, num & 0xFF]))
self.modified = True

def calc_checksum(self, start, end, checksum = 0):
""" Calculate a font table checksum, optionally ignoring another embedded checksum value (for table 'head') """
self.f.seek(start)
for i in range(start, end - 4, 4):
checksum += self.getlong()
checksum &= 0xFFFFFFFF
i += 4
extra = 0
for j in range(4):
if i + j <= end:
extra += ord(self.f.read(1))
extra = extra << 8
checksum = (checksum + extra) & 0xFFFFFFFF
return checksum

def find_head_table(self):
""" Search all tables for the HEAD table and store its metadata """
self.f.seek(4)
numtables = self.getshort()
self.f.seek(3*2, 1)

for i in range(numtables):
tab_name = self.f.read(4)
self.tab_check_offset = self.f.tell()
self.tab_check = self.getlong()
self.tab_offset = self.getlong()
self.tab_length = self.getlong()
if tab_name == b'head':
return
raise Exception('No HEAD table found')

def goto(self, where):
""" Go to a named location in the file or to the specified index """
if type(where) is str:
positions = {'checksumAdjustment': 2+2+4,
'flags': 2+2+4+4+4,
'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2,
}
where = self.tab_offset + positions[where]
self.f.seek(where)


def calc_full_checksum(self, check = False):
""" Calculate the whole file's checksum """
self.f.seek(0, 2)
self.end = self.f.tell()
full_check = self.calc_checksum(0, self.end, (-self.checksum_adj) & 0xFFFFFFFF)
if check and (0xB1B0AFBA - full_check) & 0xFFFFFFFF != self.checksum_adj:
sys.exit("Checksum of whole font is bad")
return full_check

def calc_table_checksum(self, check = False):
tab_check_new = self.calc_checksum(self.tab_offset, self.tab_offset + self.tab_length - 1, (-self.checksum_adj) & 0xFFFFFFFF)
if check and tab_check_new != self.tab_check:
sys.exit("Checksum of 'head' in font is bad")
return tab_check_new

def reset_table_checksum(self):
new_check = self.calc_table_checksum()
self.putlong(new_check, self.tab_check_offset)

def reset_full_checksum(self):
new_adj = (0xB1B0AFBA - self.calc_full_checksum()) & 0xFFFFFFFF
self.putlong(new_adj, 'checksumAdjustment')

def close(self):
self.f.close()


def __init__(self, filename):
self.modified = False
self.f = open(filename, 'r+b')

self.find_head_table()

self.flags = self.getshort('flags')
self.lowppem = self.getshort('lowestRecPPEM')
self.checksum_adj = self.getlong('checksumAdjustment')


class font_patcher:
def __init__(self):
self.args = None # class 'argparse.Namespace'
Expand Down Expand Up @@ -123,15 +231,40 @@ class font_patcher:

# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
if self.sourceFont.fullname != None:
self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.fullname + self.extension, flags=(str('opentype'), str('PfEd-comments')))
print("\nGenerated: {}".format(self.sourceFont.fontname))
outfile = self.args.outputdir + "/" + self.sourceFont.fullname + self.extension
self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fontname, outfile)
else:
self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension, flags=(str('opentype'), str('PfEd-comments')))
print("\nGenerated: {}".format(self.sourceFont.fullname))
outfile = self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension
self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile)

# Adjust flags that can not be changed via fontforge
try:
source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
if source_font.lowppem != dest_font.lowppem:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified:
dest_font.reset_table_checksum()
dest_font.reset_full_checksum()
except Exception as error:
print("Can not handle font flags ({})".format(repr(error)))
finally:
try:
source_font.close()
dest_font.close()
except:
pass
print(message)

if self.args.postprocess:
subprocess.call([self.args.postprocess, self.args.outputdir + "/" + self.sourceFont.fullname + self.extension])
print("\nPost Processed: {}".format(self.sourceFont.fullname))
subprocess.call([self.args.postprocess, outfile])
print("\nPost Processed: {}".format(outfile))


def setup_arguments(self):
Expand Down