bytes.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import sys
  2. import struct
  3. from math import floor
  4. def generateOffsets(list, length, offsetStart, usingClasses=True):
  5. """
  6. Takes a list of classes that have a .toBytes() function (or a list of bytes objects),
  7. converts it to a large blob of connected bytes, with a matching list of offsets.
  8. inputs:
  9. - array: the array of classes that has a .toBytes() function.
  10. - length: length of each offset: 32 (4 bytes/UInt32) or 16 (2 bytes/UInt16).
  11. - offsetStart: the number of bytes you want the offsets to begin at. (normally a negative number)
  12. - usingClasses: (default: true) whether you're inputting classes with a toBytes() function or bytes objects.
  13. Returns a dict with:
  14. - ["offsetBytes"] - the offsets as a list of bytes objects, encoded by the given length
  15. - ["offsetInts"] - the offsets as a list of ints
  16. - ["bytes"] - the compiled blob of bytes.
  17. - https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
  18. """
  19. # check the input first
  20. if length not in [16, 32]:
  21. raise ValueError(f"generateOffsets requires a bit length of either '16' or '32'. You gave '{length}'.")
  22. if usingClasses:
  23. for num, x in enumerate(list):
  24. try:
  25. temp = x.toBytes()
  26. except ValueError as e:
  27. raise ValueError(f"The list given to generateOffsets must be classes that all have a toBytes() function. Item {num} in this list doesn't.")
  28. if offsetStart < 0:
  29. raise ValueError(f"The offsetStart given was a negative number ({offsetStart}). It can't be a negative number.")
  30. # now do the conversion
  31. offsetBytes = b'' # each offset number as bytes, cumulatively calculated
  32. bytesBlob = b'' # the entire compacted blob of bytes
  33. offsetInts = [] # each offset as ints, to temporarily run totals on
  34. for x in range(0, len(list)):
  35. # convert this object into bytes, add it to The Blob.
  36. if usingClasses:
  37. objectInBytes = list[x].toBytes()
  38. else:
  39. objectInBytes = list[x]
  40. bytesBlob += objectInBytes
  41. # cumulatively add the offset position for this particular section of The Blob.
  42. if x == 0:
  43. offsetInt = offsetStart
  44. elif x > 0:
  45. if usingClasses:
  46. prevBytesLen = len(list[x-1].toBytes())
  47. else:
  48. prevBytesLen = len(list[x-1])
  49. offsetInt = offsetInts[-1] + prevBytesLen
  50. offsetInts.append(offsetInt)
  51. # represent the offset position as bytes, ready for output into a neat list
  52. offset = b''
  53. if length == 16:
  54. offset = struct.pack( ">H", offsetInt) # Offset16 (UInt16)
  55. elif length == 32:
  56. offset = struct.pack( ">I", offsetInt) # Offset32 (UInt32)
  57. offsetBytes += offset
  58. return {"offsetBytes": offsetBytes, "offsetInts": offsetInts, "bytes": bytesBlob}
  59. def calculateTableChecksum(data):
  60. """
  61. Calculates checksums for tables.
  62. If the data length is not a multiple of 4, it assumes it
  63. should be padded with null bytes to make it so.
  64. Should not be used on anything but the bytes output of a whole table.
  65. - https://docs.microsoft.com/en-us/typography/opentype/spec/otff#calculating-checksums
  66. (code being used from fonttools - https://github.com/fonttools/fonttools/blob/master/Lib/fontTools/ttLib/sfnt.py)
  67. (I don't have to credit fonttools, I just wanted to~)
  68. """
  69. remainder = len(data) % 4
  70. if remainder:
  71. data += b"\0" * (4 - remainder)
  72. value = 0
  73. blockSize = 4096
  74. assert blockSize % 4 == 0
  75. for i in range(0, len(data), blockSize):
  76. block = data[i:i+blockSize]
  77. longs = struct.unpack(">%dL" % (len(block) // 4), block)
  78. value = (value + sum(longs)) & 0xffffffff
  79. return value
  80. def outputTableBytes(data):
  81. """
  82. Outputs table bytes in a specific way that makes them ready to be composed into a font file.
  83. It returns a tuple containing:
  84. [0] The bytes output of the table, but padded so it's 32-bit aligned (is a multiple of 4 bytes).
  85. [1] The length of the unpadded bytes output of the table.
  86. The original length is necessary for TableRecord entries. (https://docs.microsoft.com/en-us/typography/opentype/spec/otff#calculating-checksums)
  87. This function should only be used on the output of each table as a whole.
  88. - https://docs.microsoft.com/en-us/typography/opentype/spec/otff#font-tables
  89. """
  90. remainder = len(data) % 4
  91. if remainder:
  92. return (data + b"\0" * (4 - remainder), len(data)) # pad with zeroes
  93. else:
  94. return (data, len(data))