Initial commit
This commit is contained in:
297
devtools/create_bladerunner/subtitles/fontCreator/fonFileLib.py
Normal file
297
devtools/create_bladerunner/subtitles/fontCreator/fonFileLib.py
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
osLibFound = False
|
||||
sysLibFound = False
|
||||
shutilLibFound = False
|
||||
structLibFound = False
|
||||
imagePilLibFound = False
|
||||
|
||||
try:
|
||||
import os
|
||||
except ImportError:
|
||||
print ("[Error] os python library is required to be installed!")
|
||||
else:
|
||||
osLibFound = True
|
||||
|
||||
try:
|
||||
import sys
|
||||
except ImportError:
|
||||
print ("[Error] sys python library is required to be installed!")
|
||||
else:
|
||||
sysLibFound = True
|
||||
|
||||
try:
|
||||
import struct
|
||||
except ImportError:
|
||||
print ("[Error] struct python library is required to be installed!")
|
||||
else:
|
||||
structLibFound = True
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
print ("[Error] Image python library (PIL) is required to be installed!")
|
||||
else:
|
||||
imagePilLibFound = True
|
||||
|
||||
if (not osLibFound) \
|
||||
or (not sysLibFound) \
|
||||
or (not structLibFound) \
|
||||
or (not imagePilLibFound):
|
||||
sys.stdout.write("[Error] Errors were found when trying to import required python libraries\n")
|
||||
sys.exit(1)
|
||||
|
||||
from struct import *
|
||||
|
||||
MY_MODULE_VERSION = "0.90"
|
||||
MY_MODULE_NAME = "fonFileLib"
|
||||
|
||||
class FonHeader(object):
|
||||
maxEntriesInTableOfDetails = -1 # this is probably always the number of entries in table of details, but it won't work for the corrupted TAHOMA18.FON file
|
||||
maxGlyphWidth = -1 # in pixels
|
||||
maxGlyphHeight = -1 # in pixels
|
||||
graphicSegmentByteSize = -1 # Graphic segment byte size
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
|
||||
class fonFile(object):
|
||||
m_header = FonHeader()
|
||||
|
||||
simpleFontFileName = 'GENERIC.FON'
|
||||
realNumOfCharactersInImageSegment = 0 # this is used for the workaround for the corrupted TAHOME18.FON
|
||||
nonEmptyCharacters = 0
|
||||
|
||||
glyphDetailEntriesLst = [] # list of 5-value tuples. Tuple values are (X-offset, Y-offset, Width, Height, Offset in Graphics segment)
|
||||
glyphPixelData = None # buffer of pixel data for glyphs
|
||||
|
||||
m_traceModeEnabled = False
|
||||
|
||||
# traceModeEnabled is bool to enable more printed debug messages
|
||||
def __init__(self, traceModeEnabled = True):
|
||||
del self.glyphDetailEntriesLst[:]
|
||||
self.glyphPixelData = None # buffer of pixel data for glyphs
|
||||
self.simpleFontFileName = 'GENERIC.FON'
|
||||
self.realNumOfCharactersInImageSegment = 0 # this is used for the workaround for the corrupted TAHOME18.FON
|
||||
self.nonEmptyCharacters = 0
|
||||
self.m_traceModeEnabled = traceModeEnabled
|
||||
|
||||
return
|
||||
|
||||
def loadFonFile(self, fonBytesBuff, maxLength, fonFileName):
|
||||
self.simpleFontFileName = fonFileName
|
||||
|
||||
offsInFonFile = 0
|
||||
localLstOfDataOffsets = []
|
||||
del localLstOfDataOffsets[:]
|
||||
#
|
||||
# parse FON file fields for header
|
||||
#
|
||||
try:
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
self.header().maxEntriesInTableOfDetails = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
if self.simpleFontFileName == 'TAHOMA18.FON': # deal with corrupted original 'TAHOMA18.FON' file
|
||||
self.realNumOfCharactersInImageSegment = 176
|
||||
if self.m_traceModeEnabled:
|
||||
print ("[Debug] SPECIAL CASE. WORKAROUND FOR CORRUPTED %s FILE. Only %d characters supported!" % (self.simpleFontFileName, self.realNumOfCharactersInImageSegment))
|
||||
else:
|
||||
self.realNumOfCharactersInImageSegment = self.header().maxEntriesInTableOfDetails
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
self.header().maxGlyphWidth = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
self.header().maxGlyphHeight = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
self.header().graphicSegmentByteSize = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
if self.m_traceModeEnabled:
|
||||
print ("[Debug] Font file (FON) Header Info: ")
|
||||
print ("[Debug] Number of entries: %d, Glyph max-Width: %d, Glyph max-Height: %d, Graphic Segment size: %d" % (self.header().maxEntriesInTableOfDetails, self.header().maxGlyphWidth, self.header().maxGlyphHeight, self.header().graphicSegmentByteSize))
|
||||
#
|
||||
# Glyph details table (each entry is 5 unsigned integers == 5*4 = 20 bytes)
|
||||
# For most characters, their ASCII value + 1 is the index of their glyph's entry in the details table. The 0 entry of this table is reserved
|
||||
#
|
||||
#tmpXOffset, tmpYOffset, tmpWidth, tmpHeight, tmpDataOffset
|
||||
if self.m_traceModeEnabled:
|
||||
print ("[Debug] Font file (FON) glyph details table: ")
|
||||
for idx in range(0, self.realNumOfCharactersInImageSegment):
|
||||
tmpTuple = struct.unpack_from('i', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
tmpXOffset = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
tmpYOffset = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
tmpWidth = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
tmpHeight = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
tmpTuple = struct.unpack_from('I', fonBytesBuff, offsInFonFile) # unsigned integer 4 bytes
|
||||
tmpDataOffset = tmpTuple[0]
|
||||
offsInFonFile += 4
|
||||
|
||||
if tmpWidth == 0 or tmpHeight == 0:
|
||||
if self.m_traceModeEnabled:
|
||||
print ("Index: %d\t UNUSED *****************************************************************" % (idx))
|
||||
else:
|
||||
self.nonEmptyCharacters += 1
|
||||
if self.m_traceModeEnabled:
|
||||
print ("Index: %d\txOffs: %d\tyOffs: %d\twidth: %d\theight: %d\tdataOffs: %d" % (idx, tmpXOffset, tmpYOffset, tmpWidth, tmpHeight, tmpDataOffset))
|
||||
if tmpDataOffset not in localLstOfDataOffsets:
|
||||
localLstOfDataOffsets.append(tmpDataOffset)
|
||||
else:
|
||||
# This never happens in the original files. Offsets are "re-used" but not really because it happens only for empty (height = 0) characters which all seem to point to the next non-empty character
|
||||
if self.m_traceModeEnabled:
|
||||
print ("Index: %d\t RE-USING ANOTHER GLYPH *****************************************************************" % (idx))
|
||||
|
||||
self.glyphDetailEntriesLst.append( ( tmpXOffset, tmpYOffset, tmpWidth, tmpHeight, tmpDataOffset) )
|
||||
|
||||
offsInFonFile = (4 * 4) + (self.header().maxEntriesInTableOfDetails * 5 * 4) # we need the total self.header().maxEntriesInTableOfDetails here and not self.realNumOfCharactersInImageSegment
|
||||
self.glyphPixelData = fonBytesBuff[offsInFonFile:]
|
||||
return True
|
||||
except:
|
||||
print ("[Error] Loading Font file (FON) %s failed!" % (self.simpleFontFileName))
|
||||
raise
|
||||
return False
|
||||
|
||||
def outputFonToPNG(self):
|
||||
print ("[Info] Exporting font file (FON) to PNG: %s" % (self.simpleFontFileName + ".PNG"))
|
||||
|
||||
targWidth = 0
|
||||
targHeight = 0
|
||||
paddingFromTopY = 2
|
||||
paddingBetweenGlyphsX = 10
|
||||
|
||||
if len(self.glyphDetailEntriesLst) == 0 or (len(self.glyphDetailEntriesLst) != self.realNumOfCharactersInImageSegment and len(self.glyphDetailEntriesLst) != self.header().maxEntriesInTableOfDetails) :
|
||||
print ("[Error] Font file (FON) loading process did not complete correctly. Missing important data in structures. Cannot output image!")
|
||||
return
|
||||
|
||||
# TODO asdf refine this code here. the dimensions calculation is very crude for now
|
||||
if self.header().maxGlyphWidth > 0 :
|
||||
targWidth = (self.header().maxGlyphWidth + paddingBetweenGlyphsX) * (self.realNumOfCharactersInImageSegment + 1)
|
||||
else:
|
||||
targWidth = 1080
|
||||
|
||||
# TODO asdf refine this code here. the dimensions calculation is very crude for now
|
||||
if self.header().maxGlyphHeight > 0 :
|
||||
targHeight = self.header().maxGlyphHeight * 2
|
||||
else:
|
||||
targHeight = 480
|
||||
|
||||
imTargetGameFont = Image.new("RGBA",(targWidth, targHeight), (0,0,0,0))
|
||||
#print (imTargetGameFont.getbands())
|
||||
#
|
||||
# Now fill in the image segment
|
||||
# Fonts in image segment are stored in pixel colors from TOP to Bottom, Left to Right per GLYPH.
|
||||
# Each pixel is 16 bit (2 bytes). Highest bit seems to determine transparency (on/off flag).
|
||||
# There seem to be 5 bits per RGB channel and the value is the corresponding 8bit value (from the 24 bit pixel color) shifting out (right) the 3 LSBs
|
||||
# First font image is the special character (border of top row and left column) - color of font pixels should be "0x7FFF" for filled and "0x8000" for transparent
|
||||
drawIdx = 0
|
||||
drawIdxDeductAmount = 0
|
||||
for idx in range(0, self.realNumOfCharactersInImageSegment):
|
||||
# TODO check for size > 0 for self.glyphPixelData
|
||||
# TODO mark glyph OUTLINES? (optional by switch)
|
||||
(glyphXoffs, glyphYoffs, glyphWidth, glyphHeight, glyphDataOffs) = self.glyphDetailEntriesLst[idx]
|
||||
glyphDataOffs = glyphDataOffs * 2
|
||||
#print (idx, glyphDataOffs)
|
||||
currX = 0
|
||||
currY = 0
|
||||
if (glyphWidth == 0 or glyphHeight == 0):
|
||||
drawIdxDeductAmount += 1
|
||||
drawIdx = idx - drawIdxDeductAmount
|
||||
|
||||
for colorIdx in range(0, glyphWidth*glyphHeight):
|
||||
tmpTuple = struct.unpack_from('H', self.glyphPixelData, glyphDataOffs) # unsigned short 2 bytes
|
||||
pixelColor = tmpTuple[0]
|
||||
glyphDataOffs += 2
|
||||
|
||||
# if pixelColor > 0x8000:
|
||||
# print ("[Debug] WEIRD CASE" # NEVER HAPPENS - TRANSPARENCY IS ON/OFF. There's no grades of transparency)
|
||||
rgbacolour = (0,0,0,0)
|
||||
if pixelColor == 0x8000:
|
||||
rgbacolour = (0,0,0,0) # alpha: 0.0 fully transparent
|
||||
else:
|
||||
tmp8bitR1 = ( (pixelColor >> 10) ) << 3
|
||||
tmp8bitG1 = ( (pixelColor & 0x3ff) >> 5 ) << 3
|
||||
tmp8bitB1 = ( (pixelColor & 0x1f) ) << 3
|
||||
rgbacolour = (tmp8bitR1,tmp8bitG1,tmp8bitB1, 255) # alpha: 1.0 fully opaque
|
||||
#rgbacolour = (255,255,255, 255) # alpha: 1.0 fully opaque
|
||||
|
||||
if currX == glyphWidth:
|
||||
currX = 0
|
||||
currY += 1
|
||||
|
||||
imTargetGameFont.putpixel(( (drawIdx + 1) * (self.header().maxGlyphWidth + paddingBetweenGlyphsX ) + currX, paddingFromTopY + glyphYoffs + currY), rgbacolour)
|
||||
currX += 1
|
||||
try:
|
||||
imTargetGameFont.save(os.path.join(u'.', self.simpleFontFileName + ".PNG"), "PNG")
|
||||
except Exception as e:
|
||||
print ("[Error] Unable to write to output PNG file. " + str(e))
|
||||
|
||||
def header(self):
|
||||
return self.m_header
|
||||
#
|
||||
#
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
# main()
|
||||
errorFound = False
|
||||
# By default assumes a file of name SUBTLS_E.FON in same directory
|
||||
# otherwise tries to use the first command line argument as input file
|
||||
# 'TAHOMA24.FON' # USED IN CREDIT END-TITLES and SCORERS BOARD AT POLICE STATION
|
||||
# 'TAHOMA18.FON' # USED IN CREDIT END-TITLES
|
||||
# '10PT.FON' # BLADE RUNNER UNUSED FONT - Probably font for reporting system errors
|
||||
# 'KIA6PT.FON' # BLADE RUNNER MAIN FONT
|
||||
# 'SUBTLS_E.FON' # OUR EXTRA FONT USED FOR SUBTITLES
|
||||
inFONFile = None
|
||||
inFONFileName = 'SUBTLS_E.FON' # Subtitles font custom
|
||||
|
||||
if len(sys.argv[1:]) > 0 \
|
||||
and os.path.isfile(os.path.join(u'.', sys.argv[1])) \
|
||||
and len(sys.argv[1]) >= 5 \
|
||||
and sys.argv[1][-3:].upper() == 'FON':
|
||||
inFONFileName = sys.argv[1]
|
||||
print ("[Info] Attempting to use %s as input FON file..." % (inFONFileName))
|
||||
elif os.path.isfile(os.path.join(u'.', inFONFileName)):
|
||||
print ("[Info] Using default %s as input FON file..." % (inFONFileName))
|
||||
else:
|
||||
print ("[Error] No valid input file argument was specified and default input file %s is missing." % (inFONFileName))
|
||||
errorFound = True
|
||||
|
||||
if not errorFound:
|
||||
try:
|
||||
print ("[Info] Opening %s" % (inFONFileName))
|
||||
inFONFile = open(os.path.join(u'.', inFONFileName), 'rb')
|
||||
except:
|
||||
errorFound = True
|
||||
print ("[Error] Unexpected event:", sys.exc_info()[0])
|
||||
raise
|
||||
if not errorFound:
|
||||
allOfFonFileInBuffer = inFONFile.read()
|
||||
fonFileInstance = fonFile(True)
|
||||
if fonFileInstance.m_traceModeEnabled:
|
||||
print ("[Debug] Running %s (%s) as main module" % (MY_MODULE_NAME, MY_MODULE_VERSION))
|
||||
if (fonFileInstance.loadFonFile(allOfFonFileInBuffer, len(allOfFonFileInBuffer), inFONFileName)):
|
||||
print ("[Info] Font file (FON) was loaded successfully!")
|
||||
fonFileInstance.outputFonToPNG()
|
||||
else:
|
||||
print ("[Error] Error while loading Font file (FON)!")
|
||||
inFONFile.close()
|
||||
else:
|
||||
#debug
|
||||
#print ("[Debug] Running %s (%s) imported from another module" % (MY_MODULE_NAME, MY_MODULE_VERSION))
|
||||
pass
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
sysLibFound = False
|
||||
try:
|
||||
import sys
|
||||
except ImportError:
|
||||
print ("[Error] sys python library is required to be installed!")
|
||||
else:
|
||||
sysLibFound = True
|
||||
|
||||
if (not sysLibFound):
|
||||
sys.stdout.write("[Error] Errors were found when trying to import required python libraries\n")
|
||||
sys.exit(1)
|
||||
|
||||
import grabberFromPNG17BR
|
||||
if __name__ == "__main__":
|
||||
grabberFromPNG17BR.main(sys.argv[0:])
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
python2.7 fontCreator.py -ip /d/Westwood/BladeRunnerENG
|
||||
@@ -0,0 +1 @@
|
||||
python2.7 fontCreator.py -im ./samples/KIA6PT.FON-Ext012TranspZeroThresh0002.png -oe ./samples/overrideEncodingKIA6PT.txt -om KIA6PT.FON -pxLL 14 -pxTT 15 -pxKn 1 -pxWS 4 -pxYo 1 --noAutoTabCalculation -ld EN_ANY --trace
|
||||
@@ -0,0 +1 @@
|
||||
python2.7 fontCreator.py -im ./samples/Tahoma_18Shdw-G3NewMrgd.png -oe ./samples/overrideEncodingSUBLTS.txt -om SUBTLS_E.FON -pxLL 42 -pxTT 30 -pxKn 1 -pxWS 7 -ld EN_ANY --trace
|
||||
@@ -0,0 +1 @@
|
||||
python2.7 fontCreator.py -im ./samples/Tahoma_18ShdwSimpleN4TransOnBlack.png -oe ./samples/overrideEncodingTahoma18.txt -om TAHOMA18.FON -pxLL 42 -pxTT 30 -pxKn 1 -pxWS 5 -pxYo 3 -ld EN_ANY --trace
|
||||
@@ -0,0 +1 @@
|
||||
python2.7 fontCreator.py -im ./samples/Tahoma_24ShdwSimpleN3BlackMrgd.png -oe ./samples/overrideEncodingTahoma24.txt -om TAHOMA24.FON -pxLL 45 -pxTT 40 -pxKn 1 -pxWS 8 -pxYo 2 -ld EN_ANY --trace
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1 @@
|
||||
! ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ … í Ά ñ â é Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ
|
||||
@@ -0,0 +1,7 @@
|
||||
targetEncoding=windows-1253
|
||||
asciiCharList=!!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~–ƒΆΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ
|
||||
explicitKerningList=!:1,;:1,`:1,{:1,|:1,}:1
|
||||
explicitWidthIncrement=i:0,j:1,l:1
|
||||
originalFontName=KIA6PT
|
||||
specialOutOfOrderGlyphsUTF8ToAsciiTargetEncoding=é:ƒ,ü:–
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
targetEncoding=windows-1252
|
||||
asciiCharList=!!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—™š›œžŸ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
|
||||
explicitKerningList=Ì:1,Í:2,Î:1,Ï:1,ì:1,í:2,î:1,ï:1
|
||||
explicitWidthIncrement=1:2,Ì:1,Í:1
|
||||
originalFontName=SUBLTS
|
||||
specialOutOfOrderGlyphsUTF8ToAsciiTargetEncoding=ÿ:€
|
||||
@@ -0,0 +1,7 @@
|
||||
targetEncoding=windows-1253
|
||||
asciiCharList=!!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~–ƒΆΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ
|
||||
explicitKerningList=i:-1
|
||||
explicitWidthIncrement=i:0,j:1,l:1
|
||||
originalFontName=TAHOMA
|
||||
specialOutOfOrderGlyphsUTF8ToAsciiTargetEncoding=é:ƒ,ü:–
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Reference in New Issue
Block a user