author | rptlab |
Tue, 30 Apr 2013 14:28:14 +0100 | |
branch | py33 |
changeset 3723 | 99aa837b6703 |
parent 3721 | 0c93dd8ff567 |
child 3728 | 8559eca73c9c |
permissions | -rw-r--r-- |
3617 | 1 |
#Copyright ReportLab Europe Ltd. 2000-2012 |
1637 | 2 |
#see license.txt for license details |
3032 | 3 |
__version__ = '$Id$' |
4 |
__doc__="""TrueType font support |
|
1637 | 5 |
|
6 |
This defines classes to represent TrueType fonts. They know how to calculate |
|
7 |
their own width and how to write themselves into PDF files. They support |
|
8 |
subsetting and embedding and can represent all 16-bit Unicode characters. |
|
9 |
||
10 |
Note on dynamic fonts |
|
11 |
--------------------- |
|
12 |
||
13 |
Usually a Font in ReportLab corresponds to a fixed set of PDF objects (Font, |
|
14 |
FontDescriptor, Encoding). But with dynamic font subsetting a single TTFont |
|
15 |
will result in a number of Font/FontDescriptor/Encoding object sets, and the |
|
16 |
contents of those will depend on the actual characters used for printing. |
|
17 |
||
18 |
To support dynamic font subsetting a concept of "dynamic font" was introduced. |
|
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
19 |
Dynamic Fonts have a _dynamicFont attribute set to 1. |
1637 | 20 |
|
3032 | 21 |
Dynamic fonts have the following additional functions:: |
1637 | 22 |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
23 |
def splitString(self, text, doc): |
1637 | 24 |
'''Splits text into a number of chunks, each of which belongs to a |
25 |
single subset. Returns a list of tuples (subset, string). Use |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
26 |
subset numbers with getSubsetInternalName. Doc is used to identify |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
27 |
a document so that different documents may have different dynamically |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
28 |
constructed subsets.''' |
1637 | 29 |
|
30 |
def getSubsetInternalName(self, subset, doc): |
|
31 |
'''Returns the name of a PDF Font object corresponding to a given |
|
32 |
subset of this dynamic font. Use this function instead of |
|
33 |
PDFDocument.getInternalFontName.''' |
|
34 |
||
35 |
You must never call PDFDocument.getInternalFontName for dynamic fonts. |
|
36 |
||
37 |
If you have a traditional static font, mapping to PDF text output operators |
|
3032 | 38 |
is simple:: |
1637 | 39 |
|
40 |
'%s 14 Tf (%s) Tj' % (getInternalFontName(psfontname), text) |
|
41 |
||
3032 | 42 |
If you have a dynamic font, use this instead:: |
1637 | 43 |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
44 |
for subset, chunk in font.splitString(text, doc): |
1637 | 45 |
'%s 14 Tf (%s) Tj' % (font.getSubsetInternalName(subset, doc), chunk) |
46 |
||
47 |
(Tf is a font setting operator and Tj is a text ouput operator. You should |
|
48 |
also escape invalid characters in Tj argument, see TextObject._formatText. |
|
49 |
Oh, and that 14 up there is font size.) |
|
50 |
||
51 |
Canvas and TextObject have special support for dynamic fonts. |
|
52 |
""" |
|
53 |
||
54 |
import string |
|
3275
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
55 |
from struct import pack, unpack, error as structError |
3723
99aa837b6703
second stage of port to Python 3.3; working hello world
rptlab
parents:
3721
diff
changeset
|
56 |
from reportlab.lib.utils import getBytesIO |
1637 | 57 |
from reportlab.pdfbase import pdfmetrics, pdfdoc |
3340
7dce483dd143
ttfonts.py: use rl_config for standard asciiReadable
rgbecker
parents:
3326
diff
changeset
|
58 |
from reportlab import rl_config |
1637 | 59 |
|
60 |
class TTFError(pdfdoc.PDFError): |
|
61 |
"TrueType font exception" |
|
62 |
pass |
|
63 |
||
2509 | 64 |
|
65 |
def SUBSETN(n,table=string.maketrans('0123456789','ABCDEFGHIJ')): |
|
66 |
return ('%6.6d'%n).translate(table) |
|
1637 | 67 |
# |
68 |
# Helpers |
|
69 |
# |
|
70 |
||
2217
18ff4aed511d
Truetype fonts now convert latin-1 to utf8 if fed non-utf8
andy_robinson
parents:
2216
diff
changeset
|
71 |
from codecs import utf_8_encode, utf_8_decode, latin_1_decode |
3721 | 72 |
parse_utf8=lambda x, decode=utf_8_decode: list(map(ord,decode(x)[0])) |
73 |
parse_latin1 = lambda x, decode=latin_1_decode: list(map(ord,decode(x)[0])) |
|
2217
18ff4aed511d
Truetype fonts now convert latin-1 to utf8 if fed non-utf8
andy_robinson
parents:
2216
diff
changeset
|
74 |
def latin1_to_utf8(text): |
18ff4aed511d
Truetype fonts now convert latin-1 to utf8 if fed non-utf8
andy_robinson
parents:
2216
diff
changeset
|
75 |
"helper to convert when needed from latin input" |
18ff4aed511d
Truetype fonts now convert latin-1 to utf8 if fed non-utf8
andy_robinson
parents:
2216
diff
changeset
|
76 |
return utf_8_encode(latin_1_decode(text)[0])[0] |
1637 | 77 |
|
78 |
def makeToUnicodeCMap(fontname, subset): |
|
1683 | 79 |
"""Creates a ToUnicode CMap for a given subset. See Adobe |
1637 | 80 |
_PDF_Reference (ISBN 0-201-75839-3) for more information.""" |
81 |
cmap = [ |
|
82 |
"/CIDInit /ProcSet findresource begin", |
|
83 |
"12 dict begin", |
|
84 |
"begincmap", |
|
85 |
"/CIDSystemInfo", |
|
86 |
"<< /Registry (%s)" % fontname, |
|
87 |
"/Ordering (%s)" % fontname, |
|
88 |
"/Supplement 0", |
|
89 |
">> def", |
|
90 |
"/CMapName /%s def" % fontname, |
|
91 |
"/CMapType 2 def", |
|
92 |
"1 begincodespacerange", |
|
93 |
"<00> <%02X>" % (len(subset) - 1), |
|
94 |
"endcodespacerange", |
|
95 |
"%d beginbfchar" % len(subset) |
|
2645 | 96 |
] + ["<%02X> <%04X>" % (i,v) for i,v in enumerate(subset)] + [ |
1637 | 97 |
"endbfchar", |
98 |
"endcmap", |
|
99 |
"CMapName currentdict /CMap defineresource pop", |
|
100 |
"end", |
|
101 |
"end" |
|
2645 | 102 |
] |
1637 | 103 |
return string.join(cmap, "\n") |
104 |
||
105 |
def splice(stream, offset, value): |
|
106 |
"""Splices the given value into stream at the given offset and |
|
107 |
returns the resulting stream (the original is unchanged)""" |
|
108 |
return stream[:offset] + value + stream[offset + len(value):] |
|
109 |
||
110 |
def _set_ushort(stream, offset, value): |
|
111 |
"""Writes the given unsigned short value into stream at the given |
|
112 |
offset and returns the resulting stream (the original is unchanged)""" |
|
113 |
return splice(stream, offset, pack(">H", value)) |
|
114 |
||
2107 | 115 |
try: |
116 |
import _rl_accel |
|
117 |
except ImportError: |
|
118 |
try: |
|
119 |
from reportlab.lib import _rl_accel |
|
120 |
except ImportError: |
|
121 |
_rl_accel = None |
|
1637 | 122 |
|
2201 | 123 |
try: |
124 |
hex32 = _rl_accel.hex32 |
|
125 |
except: |
|
126 |
def hex32(i): |
|
3721 | 127 |
return '0X%8.8X' % (int(i)&0xFFFFFFFF) |
2283 | 128 |
try: |
2711
3d307d53dff7
fix up to use Martin Loewis' better checksum approach
rgbecker
parents:
2709
diff
changeset
|
129 |
add32 = _rl_accel.add32L |
3d307d53dff7
fix up to use Martin Loewis' better checksum approach
rgbecker
parents:
2709
diff
changeset
|
130 |
calcChecksum = _rl_accel.calcChecksumL |
1758 | 131 |
except: |
2709 | 132 |
def add32(x, y): |
133 |
"Calculate (x + y) modulo 2**32" |
|
3721 | 134 |
return (x+y) & 0xFFFFFFFF |
2709 | 135 |
|
2107 | 136 |
def calcChecksum(data): |
2709 | 137 |
"""Calculates TTF-style checksums""" |
2107 | 138 |
if len(data)&3: data = data + (4-(len(data)&3))*"\0" |
3721 | 139 |
return sum(unpack(">%dl" % (len(data)>>2), data)) & 0xFFFFFFFF |
2709 | 140 |
del _rl_accel |
1637 | 141 |
# |
142 |
# TrueType font handling |
|
143 |
# |
|
144 |
||
145 |
GF_ARG_1_AND_2_ARE_WORDS = 1 << 0 |
|
146 |
GF_ARGS_ARE_XY_VALUES = 1 << 1 |
|
147 |
GF_ROUND_XY_TO_GRID = 1 << 2 |
|
148 |
GF_WE_HAVE_A_SCALE = 1 << 3 |
|
149 |
GF_RESERVED = 1 << 4 |
|
150 |
GF_MORE_COMPONENTS = 1 << 5 |
|
151 |
GF_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6 |
|
152 |
GF_WE_HAVE_A_TWO_BY_TWO = 1 << 7 |
|
153 |
GF_WE_HAVE_INSTRUCTIONS = 1 << 8 |
|
154 |
GF_USE_MY_METRICS = 1 << 9 |
|
155 |
GF_OVERLAP_COMPOUND = 1 << 10 |
|
156 |
GF_SCALED_COMPONENT_OFFSET = 1 << 11 |
|
157 |
GF_UNSCALED_COMPONENT_OFFSET = 1 << 12 |
|
158 |
||
159 |
def TTFOpenFile(fn): |
|
160 |
'''Opens a TTF file possibly after searching TTFSearchPath |
|
161 |
returns (filename,file) |
|
162 |
''' |
|
2254 | 163 |
from reportlab.lib.utils import rl_isfile, open_for_read |
1637 | 164 |
try: |
2254 | 165 |
f = open_for_read(fn,'rb') |
1637 | 166 |
return fn, f |
167 |
except IOError: |
|
168 |
import os |
|
169 |
if not os.path.isabs(fn): |
|
170 |
for D in rl_config.TTFSearchPath: |
|
171 |
tfn = os.path.join(D,fn) |
|
2254 | 172 |
if rl_isfile(tfn): |
173 |
f = open_for_read(tfn,'rb') |
|
1637 | 174 |
return tfn, f |
175 |
raise TTFError('Can\'t open file "%s"' % fn) |
|
176 |
||
177 |
class TTFontParser: |
|
178 |
"Basic TTF file parser" |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
179 |
ttfVersions = (0x00010000,0x74727565,0x74746366) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
180 |
ttcVersions = (0x00010000,0x00020000) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
181 |
fileKind='TTF' |
1637 | 182 |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
183 |
def __init__(self, file, validate=0,subfontIndex=0): |
1637 | 184 |
"""Loads and parses a TrueType font file. file can be a filename or a |
1748
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
185 |
file object. If validate is set to a false values, skips checksum |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
186 |
validation. This can save time, especially if the font is large. |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
187 |
""" |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
188 |
self.validate = validate |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
189 |
self.readFile(file) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
190 |
isCollection = self.readHeader() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
191 |
if isCollection: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
192 |
self.readTTCHeader() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
193 |
self.getSubfont(subfontIndex) |
1637 | 194 |
else: |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
195 |
if self.validate: self.checksumFile() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
196 |
self.readTableDirectory() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
197 |
self.subfontNameX = '' |
1637 | 198 |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
199 |
def readTTCHeader(self): |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
200 |
self.ttcVersion = self.read_ulong() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
201 |
self.fileKind = 'TTC' |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
202 |
self.ttfVersions = self.ttfVersions[:-1] |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
203 |
if self.ttcVersion not in self.ttcVersions: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
204 |
raise TTFError('"%s" is not a %s file: can\'t read version 0x%8.8x' %(self.filename,self.fileKind,self.ttcVersion)) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
205 |
self.numSubfonts = self.read_ulong() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
206 |
self.subfontOffsets = [] |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
207 |
a = self.subfontOffsets.append |
3721 | 208 |
for i in range(self.numSubfonts): |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
209 |
a(self.read_ulong()) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
210 |
|
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
211 |
def getSubfont(self,subfontIndex): |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
212 |
if self.fileKind!='TTC': |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
213 |
raise TTFError('"%s" is not a TTC file: use this method' % (self.filename,self.fileKind)) |
1637 | 214 |
try: |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
215 |
pos = self.subfontOffsets[subfontIndex] |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
216 |
except IndexError: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
217 |
raise TTFError('TTC file "%s": bad subfontIndex %s not in [0,%d]' % (self.filename,subfontIndex,self.numSubfonts-1)) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
218 |
self.seek(pos) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
219 |
self.readHeader() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
220 |
self.readTableDirectory() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
221 |
self.subfontNameX = '-'+str(subfontIndex) |
1637 | 222 |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
223 |
def readTableDirectory(self): |
1637 | 224 |
try: |
225 |
self.numTables = self.read_ushort() |
|
226 |
self.searchRange = self.read_ushort() |
|
227 |
self.entrySelector = self.read_ushort() |
|
228 |
self.rangeShift = self.read_ushort() |
|
229 |
||
230 |
# Read table directory |
|
231 |
self.table = {} |
|
232 |
self.tables = [] |
|
3721 | 233 |
for n in range(self.numTables): |
1637 | 234 |
record = {} |
235 |
record['tag'] = self.read_tag() |
|
236 |
record['checksum'] = self.read_ulong() |
|
237 |
record['offset'] = self.read_ulong() |
|
238 |
record['length'] = self.read_ulong() |
|
239 |
self.tables.append(record) |
|
240 |
self.table[record['tag']] = record |
|
241 |
except: |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
242 |
raise TTFError('Corrupt %s file "%s" cannot read Table Directory' % (self.fileKind, self.filename)) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
243 |
if self.validate: self.checksumTables() |
1637 | 244 |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
245 |
def readHeader(self): |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
246 |
'''read the sfnt header at the current position''' |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
247 |
try: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
248 |
self.version = version = self.read_ulong() |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
249 |
except: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
250 |
raise TTFError('"%s" is not a %s file: can\'t read version' %(self.filename,self.fileKind)) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
251 |
|
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
252 |
if version==0x4F54544F: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
253 |
raise TTFError('%s file "%s": postscript outlines are not supported'%(self.fileKind,self.filename)) |
1637 | 254 |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
255 |
if version not in self.ttfVersions: |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
256 |
raise TTFError('Not a TrueType font: version=0x%8.8X' % version) |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
257 |
return version==self.ttfVersions[-1] |
1637 | 258 |
|
3132
f87241ffab1c
ttfonts.py: allow unicode filenames (suggestion from Yoann Roman <yroman-reportlab@altalang.com>)
rgbecker
parents:
3127
diff
changeset
|
259 |
def readFile(self,f): |
f87241ffab1c
ttfonts.py: allow unicode filenames (suggestion from Yoann Roman <yroman-reportlab@altalang.com>)
rgbecker
parents:
3127
diff
changeset
|
260 |
if hasattr(f,'read'): |
f87241ffab1c
ttfonts.py: allow unicode filenames (suggestion from Yoann Roman <yroman-reportlab@altalang.com>)
rgbecker
parents:
3127
diff
changeset
|
261 |
self.filename = '(ttf)' |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
262 |
else: |
3132
f87241ffab1c
ttfonts.py: allow unicode filenames (suggestion from Yoann Roman <yroman-reportlab@altalang.com>)
rgbecker
parents:
3127
diff
changeset
|
263 |
self.filename, f = TTFOpenFile(f) |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
264 |
|
3132
f87241ffab1c
ttfonts.py: allow unicode filenames (suggestion from Yoann Roman <yroman-reportlab@altalang.com>)
rgbecker
parents:
3127
diff
changeset
|
265 |
self._ttf_data = f.read() |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
266 |
self._pos = 0 |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
267 |
|
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
268 |
def checksumTables(self): |
1637 | 269 |
# Check the checksums for all tables |
270 |
for t in self.tables: |
|
271 |
table = self.get_chunk(t['offset'], t['length']) |
|
2709 | 272 |
checksum = calcChecksum(table) |
1637 | 273 |
if t['tag'] == 'head': |
274 |
adjustment = unpack('>l', table[8:8+4])[0] |
|
2709 | 275 |
checksum = add32(checksum, -adjustment) |
276 |
xchecksum = t['checksum'] |
|
277 |
if xchecksum != checksum: |
|
278 |
raise TTFError('TTF file "%s": invalid checksum %s table: %s (expected %s)' % (self.filename,hex32(checksum),t['tag'],hex32(xchecksum))) |
|
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
279 |
|
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
280 |
def checksumFile(self): |
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
281 |
# Check the checksums for the whole file |
2709 | 282 |
checksum = calcChecksum(self._ttf_data) |
3721 | 283 |
if 0xB1B0AFBA!=checksum: |
2709 | 284 |
raise TTFError('TTF file "%s": invalid checksum %s (expected 0xB1B0AFBA) len: %d &3: %d' % (self.filename,hex32(checksum),len(self._ttf_data),(len(self._ttf_data)&3))) |
1637 | 285 |
|
286 |
def get_table_pos(self, tag): |
|
287 |
"Returns the offset and size of a given TTF table." |
|
288 |
offset = self.table[tag]['offset'] |
|
289 |
length = self.table[tag]['length'] |
|
290 |
return (offset, length) |
|
291 |
||
292 |
def seek(self, pos): |
|
293 |
"Moves read pointer to a given offset in file." |
|
294 |
self._pos = pos |
|
295 |
||
296 |
def skip(self, delta): |
|
297 |
"Skip the given number of bytes." |
|
298 |
self._pos = self._pos + delta |
|
299 |
||
300 |
def seek_table(self, tag, offset_in_table = 0): |
|
301 |
"""Moves read pointer to the given offset within a given table and |
|
302 |
returns absolute offset of that position in the file.""" |
|
303 |
self._pos = self.get_table_pos(tag)[0] + offset_in_table |
|
304 |
return self._pos |
|
305 |
||
306 |
def read_tag(self): |
|
307 |
"Read a 4-character tag" |
|
2581 | 308 |
self._pos += 4 |
2123 | 309 |
return self._ttf_data[self._pos - 4:self._pos] |
1637 | 310 |
|
311 |
def read_ushort(self): |
|
312 |
"Reads an unsigned short" |
|
2581 | 313 |
self._pos += 2 |
314 |
return unpack('>H',self._ttf_data[self._pos-2:self._pos])[0] |
|
1637 | 315 |
|
316 |
def read_ulong(self): |
|
317 |
"Reads an unsigned long" |
|
2581 | 318 |
self._pos += 4 |
2709 | 319 |
return unpack('>L',self._ttf_data[self._pos - 4:self._pos])[0] |
1637 | 320 |
|
321 |
def read_short(self): |
|
322 |
"Reads a signed short" |
|
2581 | 323 |
self._pos += 2 |
3275
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
324 |
try: |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
325 |
return unpack('>h',self._ttf_data[self._pos-2:self._pos])[0] |
3721 | 326 |
except structError as error: |
327 |
raise TTFError(error) |
|
1637 | 328 |
|
329 |
def get_ushort(self, pos): |
|
330 |
"Return an unsigned short at given position" |
|
2581 | 331 |
return unpack('>H',self._ttf_data[pos:pos+2])[0] |
1637 | 332 |
|
333 |
def get_ulong(self, pos): |
|
334 |
"Return an unsigned long at given position" |
|
2709 | 335 |
return unpack('>L',self._ttf_data[pos:pos+4])[0] |
1637 | 336 |
|
337 |
def get_chunk(self, pos, length): |
|
338 |
"Return a chunk of raw data at given position" |
|
2123 | 339 |
return self._ttf_data[pos:pos+length] |
1637 | 340 |
|
341 |
def get_table(self, tag): |
|
342 |
"Return the given TTF table" |
|
343 |
pos, length = self.get_table_pos(tag) |
|
2123 | 344 |
return self._ttf_data[pos:pos+length] |
1637 | 345 |
|
346 |
class TTFontMaker: |
|
347 |
"Basic TTF file generator" |
|
348 |
||
349 |
def __init__(self): |
|
350 |
"Initializes the generator." |
|
351 |
self.tables = {} |
|
352 |
||
353 |
def add(self, tag, data): |
|
354 |
"Adds a table to the TTF file." |
|
355 |
if tag == 'head': |
|
356 |
data = splice(data, 8, '\0\0\0\0') |
|
357 |
self.tables[tag] = data |
|
358 |
||
359 |
def makeStream(self): |
|
360 |
"Finishes the generation and returns the TTF file as a string" |
|
3723
99aa837b6703
second stage of port to Python 3.3; working hello world
rptlab
parents:
3721
diff
changeset
|
361 |
stm = getBytesIO() |
3127
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
362 |
write = stm.write |
1637 | 363 |
|
364 |
numTables = len(self.tables) |
|
365 |
searchRange = 1 |
|
366 |
entrySelector = 0 |
|
367 |
while searchRange * 2 <= numTables: |
|
368 |
searchRange = searchRange * 2 |
|
369 |
entrySelector = entrySelector + 1 |
|
370 |
searchRange = searchRange * 16 |
|
371 |
rangeShift = numTables * 16 - searchRange |
|
372 |
||
373 |
# Header |
|
3127
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
374 |
write(pack(">lHHHH", 0x00010000, numTables, searchRange, |
1637 | 375 |
entrySelector, rangeShift)) |
376 |
||
377 |
# Table directory |
|
3721 | 378 |
tables = list(self.tables.items()) |
1637 | 379 |
tables.sort() # XXX is this the correct order? |
380 |
offset = 12 + numTables * 16 |
|
381 |
for tag, data in tables: |
|
382 |
if tag == 'head': |
|
383 |
head_start = offset |
|
384 |
checksum = calcChecksum(data) |
|
3127
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
385 |
write(tag) |
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
386 |
write(pack(">LLL", checksum, offset, len(data))) |
1637 | 387 |
paddedLength = (len(data)+3)&~3 |
388 |
offset = offset + paddedLength |
|
389 |
||
390 |
# Table data |
|
391 |
for tag, data in tables: |
|
3127
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
392 |
data += "\0\0\0" |
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
393 |
write(data[:len(data)&~3]) |
1637 | 394 |
|
395 |
checksum = calcChecksum(stm.getvalue()) |
|
3721 | 396 |
checksum = add32(0xB1B0AFBA, -checksum) |
1637 | 397 |
stm.seek(head_start + 8) |
3127
953069f62709
ttfonts.py: small improvements copied from jython development
rgbecker
parents:
3032
diff
changeset
|
398 |
write(pack('>L', checksum)) |
1637 | 399 |
|
400 |
return stm.getvalue() |
|
401 |
||
402 |
class TTFontFile(TTFontParser): |
|
403 |
"TTF file parser and generator" |
|
404 |
||
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
405 |
def __init__(self, file, charInfo=1, validate=0,subfontIndex=0): |
1748
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
406 |
"""Loads and parses a TrueType font file. |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
407 |
|
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
408 |
file can be a filename or a file object. If validate is set to a false |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
409 |
values, skips checksum validation. This can save time, especially if |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
410 |
the font is large. See TTFontFile.extractInfo for more information. |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
411 |
""" |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
412 |
TTFontParser.__init__(self, file, validate=validate,subfontIndex=subfontIndex) |
1637 | 413 |
self.extractInfo(charInfo) |
414 |
||
415 |
def extractInfo(self, charInfo=1): |
|
3031 | 416 |
""" |
417 |
Extract typographic information from the loaded font file. |
|
1637 | 418 |
|
3031 | 419 |
The following attributes will be set:: |
420 |
||
421 |
name PostScript font name |
|
422 |
flags Font flags |
|
423 |
ascent Typographic ascender in 1/1000ths of a point |
|
424 |
descent Typographic descender in 1/1000ths of a point |
|
425 |
capHeight Cap height in 1/1000ths of a point (0 if not available) |
|
426 |
bbox Glyph bounding box [l,t,r,b] in 1/1000ths of a point |
|
427 |
_bbox Glyph bounding box [l,t,r,b] in unitsPerEm |
|
428 |
unitsPerEm Glyph units per em |
|
429 |
italicAngle Italic angle in degrees ccw |
|
430 |
stemV stem weight in 1/1000ths of a point (approximate) |
|
431 |
||
432 |
If charInfo is true, the following will also be set:: |
|
433 |
||
434 |
defaultWidth default glyph width in 1/1000ths of a point |
|
435 |
charWidths dictionary of character widths for every supported UCS character |
|
436 |
code |
|
437 |
||
1748
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
438 |
This will only work if the font has a Unicode cmap (platform 3, |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
439 |
encoding 1, format 4 or platform 0 any encoding format 4). Setting |
3031 | 440 |
charInfo to false avoids this requirement |
441 |
||
1637 | 442 |
""" |
443 |
# name - Naming table |
|
444 |
name_offset = self.seek_table("name") |
|
445 |
format = self.read_ushort() |
|
446 |
if format != 0: |
|
3721 | 447 |
raise TTFError("Unknown name table format (%d)" % format) |
1637 | 448 |
numRecords = self.read_ushort() |
449 |
string_data_offset = name_offset + self.read_ushort() |
|
2201 | 450 |
names = {1:None,2:None,3:None,4:None,6:None} |
3721 | 451 |
K = list(names.keys()) |
2201 | 452 |
nameCount = len(names) |
3721 | 453 |
for i in range(numRecords): |
1637 | 454 |
platformId = self.read_ushort() |
455 |
encodingId = self.read_ushort() |
|
456 |
languageId = self.read_ushort() |
|
457 |
nameId = self.read_ushort() |
|
458 |
length = self.read_ushort() |
|
459 |
offset = self.read_ushort() |
|
2201 | 460 |
if nameId not in K: continue |
461 |
N = None |
|
462 |
if platformId == 3 and encodingId == 1 and languageId == 0x409: # Microsoft, Unicode, US English, PS Name |
|
2566 | 463 |
opos = self._pos |
464 |
try: |
|
465 |
self.seek(string_data_offset + offset) |
|
466 |
if length % 2 != 0: |
|
3721 | 467 |
raise TTFError("PostScript name is UTF-16BE string of odd length") |
2566 | 468 |
length /= 2 |
469 |
N = [] |
|
470 |
A = N.append |
|
471 |
while length > 0: |
|
472 |
char = self.read_ushort() |
|
473 |
A(chr(char)) |
|
474 |
length -= 1 |
|
475 |
N = ''.join(N) |
|
476 |
finally: |
|
477 |
self._pos = opos |
|
2201 | 478 |
elif platformId == 1 and encodingId == 0 and languageId == 0: # Macintosh, Roman, English, PS Name |
1664
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
479 |
# According to OpenType spec, if PS name exists, it must exist |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
480 |
# both in MS Unicode and Macintosh Roman formats. Apparently, |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
481 |
# you can find live TTF fonts which only have Macintosh format. |
2201 | 482 |
N = self.get_chunk(string_data_offset + offset, length) |
483 |
if N and names[nameId]==None: |
|
484 |
names[nameId] = N |
|
485 |
nameCount -= 1 |
|
486 |
if nameCount==0: break |
|
3275
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
487 |
if names[6] is not None: |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
488 |
psName = names[6].replace(" ", "-") #Dinu Gherman's fix for font names with spaces |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
489 |
elif names[4] is not None: |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
490 |
psName = names[4].replace(" ", "-") |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
491 |
# Fine, one last try before we bail. |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
492 |
elif names[1] is not None: |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
493 |
psName = names[1].replace(" ", "-") |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
494 |
else: |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
495 |
psName = None |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
496 |
|
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
497 |
# Don't just assume, check for None since some shoddy fonts cause crashes here... |
1637 | 498 |
if not psName: |
3721 | 499 |
raise TTFError("Could not find PostScript font name") |
2201 | 500 |
for c in psName: |
501 |
oc = ord(c) |
|
2768
7af1018f9411
ttfonts.py: small change inspired by Dinu Gherman
rgbecker
parents:
2711
diff
changeset
|
502 |
if oc>126 or c in ' [](){}<>/%': |
3721 | 503 |
raise TTFError("psName=%r contains invalid character '%s' ie U+%04X" % (psName,c,ord(c))) |
1637 | 504 |
self.name = psName |
2201 | 505 |
self.familyName = names[1] or psName |
506 |
self.styleName = names[2] or 'Regular' |
|
507 |
self.fullName = names[4] or psName |
|
508 |
self.uniqueFontID = names[3] or psName |
|
1637 | 509 |
|
510 |
# head - Font header table |
|
511 |
self.seek_table("head") |
|
512 |
ver_maj, ver_min = self.read_ushort(), self.read_ushort() |
|
513 |
if ver_maj != 1: |
|
3721 | 514 |
raise TTFError('Unknown head table version %d.%04x' % (ver_maj, ver_min)) |
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
515 |
self.fontRevision = self.read_ushort(), self.read_ushort() |
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
516 |
|
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
517 |
self.skip(4) |
1637 | 518 |
magic = self.read_ulong() |
519 |
if magic != 0x5F0F3CF5: |
|
3721 | 520 |
raise TTFError('Invalid head table magic %04x' % magic) |
1637 | 521 |
self.skip(2) |
2774
ad67eff48c23
ttfonts.py: add _bbox (bbox in orig units) and unitsPerEm to TTFontFace
rgbecker
parents:
2768
diff
changeset
|
522 |
self.unitsPerEm = unitsPerEm = self.read_ushort() |
2782
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
523 |
scale = lambda x, unitsPerEm=unitsPerEm: x * 1000. / unitsPerEm |
1637 | 524 |
self.skip(16) |
525 |
xMin = self.read_short() |
|
526 |
yMin = self.read_short() |
|
527 |
xMax = self.read_short() |
|
528 |
yMax = self.read_short() |
|
3721 | 529 |
self.bbox = list(map(scale, [xMin, yMin, xMax, yMax])) |
1637 | 530 |
self.skip(3*2) |
531 |
indexToLocFormat = self.read_ushort() |
|
532 |
glyphDataFormat = self.read_ushort() |
|
533 |
||
534 |
# OS/2 - OS/2 and Windows metrics table |
|
535 |
# (needs data from head table) |
|
3326 | 536 |
if "OS/2" in self.table: |
1718
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
537 |
self.seek_table("OS/2") |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
538 |
version = self.read_ushort() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
539 |
self.skip(2) |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
540 |
usWeightClass = self.read_ushort() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
541 |
self.skip(2) |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
542 |
fsType = self.read_ushort() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
543 |
if fsType == 0x0002 or (fsType & 0x0300) != 0: |
3721 | 544 |
raise TTFError('Font does not allow subsetting/embedding (%04X)' % fsType) |
2581 | 545 |
self.skip(58) #11*2 + 10 + 4*4 + 4 + 3*2 |
1718
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
546 |
sTypoAscender = self.read_short() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
547 |
sTypoDescender = self.read_short() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
548 |
self.ascent = scale(sTypoAscender) # XXX: for some reason it needs to be multiplied by 1.24--1.28 |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
549 |
self.descent = scale(sTypoDescender) |
1637 | 550 |
|
1718
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
551 |
if version > 1: |
2581 | 552 |
self.skip(16) #3*2 + 2*4 + 2 |
1718
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
553 |
sCapHeight = self.read_short() |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
554 |
self.capHeight = scale(sCapHeight) |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
555 |
else: |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
556 |
self.capHeight = self.ascent |
1637 | 557 |
else: |
1718
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
558 |
# Microsoft TTFs require an OS/2 table; Apple ones do not. Try to |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
559 |
# cope. The data is not very important anyway. |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
560 |
usWeightClass = 500 |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
561 |
self.ascent = scale(yMax) |
d9df24d9a50e
Support Apple TTFs: different signature and optional OS/2 table
mgedmin
parents:
1690
diff
changeset
|
562 |
self.descent = scale(yMin) |
1637 | 563 |
self.capHeight = self.ascent |
564 |
||
565 |
# There's no way to get stemV from a TTF file short of analyzing actual outline data |
|
566 |
# This fuzzy formula is taken from pdflib sources, but we could just use 0 here |
|
567 |
self.stemV = 50 + int((usWeightClass / 65.0) ** 2) |
|
568 |
||
569 |
# post - PostScript table |
|
570 |
# (needs data from OS/2 table) |
|
571 |
self.seek_table("post") |
|
572 |
ver_maj, ver_min = self.read_ushort(), self.read_ushort() |
|
1721
2cace59f0f50
Support POST table version 4 as found in some Apple TTFs.
mgedmin
parents:
1719
diff
changeset
|
573 |
if ver_maj not in (1, 2, 3, 4): |
2cace59f0f50
Support POST table version 4 as found in some Apple TTFs.
mgedmin
parents:
1719
diff
changeset
|
574 |
# Adobe/MS documents 1, 2, 2.5, 3; Apple also has 4. |
2cace59f0f50
Support POST table version 4 as found in some Apple TTFs.
mgedmin
parents:
1719
diff
changeset
|
575 |
# From Apple docs it seems that we do not need to care |
2cace59f0f50
Support POST table version 4 as found in some Apple TTFs.
mgedmin
parents:
1719
diff
changeset
|
576 |
# about the exact version, so if you get this error, you can |
2cace59f0f50
Support POST table version 4 as found in some Apple TTFs.
mgedmin
parents:
1719
diff
changeset
|
577 |
# try to remove this check altogether. |
3721 | 578 |
raise TTFError('Unknown post table version %d.%04x' % (ver_maj, ver_min)) |
1637 | 579 |
self.italicAngle = self.read_short() + self.read_ushort() / 65536.0 |
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
580 |
self.underlinePosition = self.read_short() |
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
581 |
self.underlineThickness = self.read_short() |
1637 | 582 |
isFixedPitch = self.read_ulong() |
583 |
||
584 |
self.flags = FF_SYMBOLIC # All fonts that contain characters |
|
585 |
# outside the original Adobe character |
|
586 |
# set are considered "symbolic". |
|
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
587 |
if self.italicAngle!= 0: |
1637 | 588 |
self.flags = self.flags | FF_ITALIC |
589 |
if usWeightClass >= 600: # FW_REGULAR == 500, FW_SEMIBOLD == 600 |
|
590 |
self.flags = self.flags | FF_FORCEBOLD |
|
591 |
if isFixedPitch: |
|
592 |
self.flags = self.flags | FF_FIXED |
|
593 |
# XXX: FF_SERIF? FF_SCRIPT? FF_ALLCAP? FF_SMALLCAP? |
|
594 |
||
595 |
# hhea - Horizontal header table |
|
596 |
self.seek_table("hhea") |
|
597 |
ver_maj, ver_min = self.read_ushort(), self.read_ushort() |
|
598 |
if ver_maj != 1: |
|
3721 | 599 |
raise TTFError('Unknown hhea table version %d.%04x' % (ver_maj, ver_min)) |
1637 | 600 |
self.skip(28) |
601 |
metricDataFormat = self.read_ushort() |
|
602 |
if metricDataFormat != 0: |
|
3721 | 603 |
raise TTFError('Unknown horizontal metric data format (%d)' % metricDataFormat) |
1637 | 604 |
numberOfHMetrics = self.read_ushort() |
605 |
if numberOfHMetrics == 0: |
|
3721 | 606 |
raise TTFError('Number of horizontal metrics is 0') |
1637 | 607 |
|
608 |
# maxp - Maximum profile table |
|
609 |
self.seek_table("maxp") |
|
610 |
ver_maj, ver_min = self.read_ushort(), self.read_ushort() |
|
611 |
if ver_maj != 1: |
|
3721 | 612 |
raise TTFError('Unknown maxp table version %d.%04x' % (ver_maj, ver_min)) |
1637 | 613 |
numGlyphs = self.read_ushort() |
614 |
||
615 |
if not charInfo: |
|
616 |
self.charToGlyph = None |
|
617 |
self.defaultWidth = None |
|
618 |
self.charWidths = None |
|
619 |
return |
|
620 |
||
621 |
if glyphDataFormat != 0: |
|
3721 | 622 |
raise TTFError('Unknown glyph data format (%d)' % glyphDataFormat) |
1637 | 623 |
|
624 |
# cmap - Character to glyph index mapping table |
|
625 |
cmap_offset = self.seek_table("cmap") |
|
626 |
self.skip(2) |
|
627 |
cmapTableCount = self.read_ushort() |
|
628 |
unicode_cmap_offset = None |
|
3721 | 629 |
for n in range(cmapTableCount): |
1637 | 630 |
platformID = self.read_ushort() |
631 |
encodingID = self.read_ushort() |
|
632 |
offset = self.read_ulong() |
|
633 |
if platformID == 3 and encodingID == 1: # Microsoft, Unicode |
|
634 |
format = self.get_ushort(cmap_offset + offset) |
|
635 |
if format == 4: |
|
636 |
unicode_cmap_offset = cmap_offset + offset |
|
1664
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
637 |
break |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
638 |
elif platformID == 0: # Unicode -- assume all encodings are compatible |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
639 |
format = self.get_ushort(cmap_offset + offset) |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
640 |
if format == 4: |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
641 |
unicode_cmap_offset = cmap_offset + offset |
09dcb3294a0d
Applied Marius' patch for TrueType fonts converted from OS X .dfont files.
dinu_gherman
parents:
1637
diff
changeset
|
642 |
break |
1637 | 643 |
if unicode_cmap_offset is None: |
3721 | 644 |
raise TTFError('Font does not have cmap for Unicode (platform 3, encoding 1, format 4 or platform 0 any encoding format 4)') |
1749
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
645 |
self.seek(unicode_cmap_offset + 2) |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
646 |
length = self.read_ushort() |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
647 |
limit = unicode_cmap_offset + length |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
648 |
self.skip(2) |
3326 | 649 |
segCount = int(self.read_ushort() / 2.0) |
1637 | 650 |
self.skip(6) |
3721 | 651 |
endCount = list(map(lambda x, self=self: self.read_ushort(), range(segCount))) |
1637 | 652 |
self.skip(2) |
3721 | 653 |
startCount = list(map(lambda x, self=self: self.read_ushort(), range(segCount))) |
654 |
idDelta = list(map(lambda x, self=self: self.read_short(), range(segCount))) |
|
1637 | 655 |
idRangeOffset_start = self._pos |
3721 | 656 |
idRangeOffset = list(map(lambda x, self=self: self.read_ushort(), range(segCount))) |
1637 | 657 |
|
658 |
# Now it gets tricky. |
|
659 |
glyphToChar = {} |
|
660 |
charToGlyph = {} |
|
3721 | 661 |
for n in range(segCount): |
662 |
for unichar in range(startCount[n], endCount[n] + 1): |
|
1637 | 663 |
if idRangeOffset[n] == 0: |
664 |
glyph = (unichar + idDelta[n]) & 0xFFFF |
|
665 |
else: |
|
666 |
offset = (unichar - startCount[n]) * 2 + idRangeOffset[n] |
|
667 |
offset = idRangeOffset_start + 2 * n + offset |
|
1749
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
668 |
if offset >= limit: |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
669 |
# workaround for broken fonts (like Thryomanes) |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
670 |
glyph = 0 |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
671 |
else: |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
672 |
glyph = self.get_ushort(offset) |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
673 |
if glyph != 0: |
3e11c6e8efa6
Added a workaround for some broken TTFs (like Thyromanes)
mgedmin
parents:
1748
diff
changeset
|
674 |
glyph = (glyph + idDelta[n]) & 0xFFFF |
1637 | 675 |
charToGlyph[unichar] = glyph |
3326 | 676 |
if glyph in glyphToChar: |
1637 | 677 |
glyphToChar[glyph].append(unichar) |
678 |
else: |
|
679 |
glyphToChar[glyph] = [unichar] |
|
680 |
self.charToGlyph = charToGlyph |
|
681 |
||
682 |
# hmtx - Horizontal metrics table |
|
683 |
# (needs data from hhea, maxp, and cmap tables) |
|
684 |
self.seek_table("hmtx") |
|
685 |
aw = None |
|
686 |
self.charWidths = {} |
|
687 |
self.hmetrics = [] |
|
3721 | 688 |
for glyph in range(numberOfHMetrics): |
1637 | 689 |
# advance width and left side bearing. lsb is actually signed |
690 |
# short, but we don't need it anyway (except for subsetting) |
|
691 |
aw, lsb = self.read_ushort(), self.read_ushort() |
|
692 |
self.hmetrics.append((aw, lsb)) |
|
693 |
aw = scale(aw) |
|
694 |
if glyph == 0: |
|
695 |
self.defaultWidth = aw |
|
3326 | 696 |
if glyph in glyphToChar: |
1637 | 697 |
for char in glyphToChar[glyph]: |
698 |
self.charWidths[char] = aw |
|
3721 | 699 |
for glyph in range(numberOfHMetrics, numGlyphs): |
1637 | 700 |
# the rest of the table only lists advance left side bearings. |
701 |
# so we reuse aw set by the last iteration of the previous loop |
|
702 |
lsb = self.read_ushort() |
|
703 |
self.hmetrics.append((aw, lsb)) |
|
3326 | 704 |
if glyph in glyphToChar: |
1637 | 705 |
for char in glyphToChar[glyph]: |
706 |
self.charWidths[char] = aw |
|
707 |
||
708 |
# loca - Index to location |
|
709 |
self.seek_table('loca') |
|
710 |
self.glyphPos = [] |
|
711 |
if indexToLocFormat == 0: |
|
3721 | 712 |
for n in range(numGlyphs + 1): |
1637 | 713 |
self.glyphPos.append(self.read_ushort() << 1) |
714 |
elif indexToLocFormat == 1: |
|
3721 | 715 |
for n in range(numGlyphs + 1): |
1637 | 716 |
self.glyphPos.append(self.read_ulong()) |
717 |
else: |
|
3721 | 718 |
raise TTFError('Unknown location table format (%d)' % indexToLocFormat) |
1637 | 719 |
|
720 |
# Subsetting |
|
721 |
||
722 |
def makeSubset(self, subset): |
|
723 |
"""Create a subset of a TrueType font""" |
|
724 |
output = TTFontMaker() |
|
725 |
||
726 |
# Build a mapping of glyphs in the subset to glyph numbers in |
|
727 |
# the original font. Also build a mapping of UCS codes to |
|
728 |
# glyph values in the new font. |
|
729 |
||
1683 | 730 |
# Start with 0 -> 0: "missing character" |
1637 | 731 |
glyphMap = [0] # new glyph index -> old glyph index |
732 |
glyphSet = {0:0} # old glyph index -> new glyph index |
|
733 |
codeToGlyph = {} # unicode -> new glyph index |
|
734 |
for code in subset: |
|
3326 | 735 |
if code in self.charToGlyph: |
1637 | 736 |
originalGlyphIdx = self.charToGlyph[code] |
737 |
else: |
|
738 |
originalGlyphIdx = 0 |
|
3326 | 739 |
if originalGlyphIdx not in glyphSet: |
1637 | 740 |
glyphSet[originalGlyphIdx] = len(glyphMap) |
741 |
glyphMap.append(originalGlyphIdx) |
|
742 |
codeToGlyph[code] = glyphSet[originalGlyphIdx] |
|
743 |
||
744 |
# Also include glyphs that are parts of composite glyphs |
|
745 |
start = self.get_table_pos('glyf')[0] |
|
746 |
n = 0 |
|
747 |
while n < len(glyphMap): |
|
748 |
originalGlyphIdx = glyphMap[n] |
|
749 |
glyphPos = self.glyphPos[originalGlyphIdx] |
|
750 |
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos |
|
3275
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
751 |
n += 1 |
c53e22ea435f
ttfonts.py: fix problems parsing 6000 ttfs contributed by Jerry Casiano
rgbecker
parents:
3132
diff
changeset
|
752 |
if not glyphLen: continue |
1637 | 753 |
self.seek(start + glyphPos) |
754 |
numberOfContours = self.read_short() |
|
755 |
if numberOfContours < 0: |
|
756 |
# composite glyph |
|
757 |
self.skip(8) |
|
758 |
flags = GF_MORE_COMPONENTS |
|
759 |
while flags & GF_MORE_COMPONENTS: |
|
760 |
flags = self.read_ushort() |
|
761 |
glyphIdx = self.read_ushort() |
|
3326 | 762 |
if glyphIdx not in glyphSet: |
1637 | 763 |
glyphSet[glyphIdx] = len(glyphMap) |
764 |
glyphMap.append(glyphIdx) |
|
765 |
if flags & GF_ARG_1_AND_2_ARE_WORDS: |
|
766 |
self.skip(4) |
|
767 |
else: |
|
768 |
self.skip(2) |
|
769 |
if flags & GF_WE_HAVE_A_SCALE: |
|
770 |
self.skip(2) |
|
771 |
elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE: |
|
772 |
self.skip(4) |
|
773 |
elif flags & GF_WE_HAVE_A_TWO_BY_TWO: |
|
774 |
self.skip(8) |
|
775 |
||
776 |
numGlyphs = n = len(glyphMap) |
|
777 |
while n > 1 and self.hmetrics[n][0] == self.hmetrics[n - 1][0]: |
|
2645 | 778 |
n -= 1 |
1637 | 779 |
numberOfHMetrics = n |
780 |
||
781 |
# The following tables are simply copied from the original |
|
782 |
for tag in ('name', 'OS/2', 'cvt ', 'fpgm', 'prep'): |
|
1682
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
783 |
try: |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
784 |
output.add(tag, self.get_table(tag)) |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
785 |
except KeyError: |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
786 |
# Apparently some of the tables are optional (cvt, fpgm, prep). |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
787 |
# The lack of the required ones (name, OS/2) would have already |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
788 |
# been caught before. |
e2639301c342
Marius' path for non-standard TrueType files
andy_robinson
parents:
1664
diff
changeset
|
789 |
pass |
1637 | 790 |
|
791 |
# post - PostScript |
|
792 |
post = "\x00\x03\x00\x00" + self.get_table('post')[4:16] + "\x00" * 16 |
|
793 |
output.add('post', post) |
|
794 |
||
795 |
# hhea - Horizontal Header |
|
796 |
hhea = self.get_table('hhea') |
|
797 |
hhea = _set_ushort(hhea, 34, numberOfHMetrics) |
|
798 |
output.add('hhea', hhea) |
|
799 |
||
800 |
# maxp - Maximum Profile |
|
801 |
maxp = self.get_table('maxp') |
|
802 |
maxp = _set_ushort(maxp, 4, numGlyphs) |
|
803 |
output.add('maxp', maxp) |
|
804 |
||
805 |
# cmap - Character to glyph mapping |
|
806 |
# XXX maybe use format 0 if possible, not 6? |
|
807 |
entryCount = len(subset) |
|
808 |
length = 10 + entryCount * 2 |
|
809 |
cmap = [0, 1, # version, number of tables |
|
810 |
1, 0, 0,12, # platform, encoding, offset (hi,lo) |
|
811 |
6, length, 0, # format, length, language |
|
812 |
0, |
|
813 |
entryCount] + \ |
|
3721 | 814 |
list(map(codeToGlyph.get, subset)) |
3326 | 815 |
cmap = pack(*([">%dH" % len(cmap)] + cmap)) |
1637 | 816 |
output.add('cmap', cmap) |
817 |
||
1683 | 818 |
# hmtx - Horizontal Metrics |
1637 | 819 |
hmtx = [] |
3721 | 820 |
for n in range(numGlyphs): |
1637 | 821 |
originalGlyphIdx = glyphMap[n] |
822 |
aw, lsb = self.hmetrics[originalGlyphIdx] |
|
823 |
if n < numberOfHMetrics: |
|
2831 | 824 |
hmtx.append(int(aw)) |
825 |
hmtx.append(int(lsb)) |
|
3326 | 826 |
hmtx = pack(*([">%dH" % len(hmtx)] + hmtx)) |
1637 | 827 |
output.add('hmtx', hmtx) |
828 |
||
829 |
# glyf - Glyph data |
|
830 |
glyphData = self.get_table('glyf') |
|
831 |
offsets = [] |
|
832 |
glyf = [] |
|
833 |
pos = 0 |
|
3721 | 834 |
for n in range(numGlyphs): |
1637 | 835 |
offsets.append(pos) |
836 |
originalGlyphIdx = glyphMap[n] |
|
837 |
glyphPos = self.glyphPos[originalGlyphIdx] |
|
838 |
glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos |
|
839 |
data = glyphData[glyphPos:glyphPos+glyphLen] |
|
840 |
# Fix references in composite glyphs |
|
841 |
if glyphLen > 2 and unpack(">h", data[:2])[0] < 0: |
|
842 |
# composite glyph |
|
843 |
pos_in_glyph = 10 |
|
844 |
flags = GF_MORE_COMPONENTS |
|
845 |
while flags & GF_MORE_COMPONENTS: |
|
846 |
flags = unpack(">H", data[pos_in_glyph:pos_in_glyph+2])[0] |
|
847 |
glyphIdx = unpack(">H", data[pos_in_glyph+2:pos_in_glyph+4])[0] |
|
848 |
data = _set_ushort(data, pos_in_glyph + 2, glyphSet[glyphIdx]) |
|
849 |
pos_in_glyph = pos_in_glyph + 4 |
|
850 |
if flags & GF_ARG_1_AND_2_ARE_WORDS: |
|
851 |
pos_in_glyph = pos_in_glyph + 4 |
|
852 |
else: |
|
853 |
pos_in_glyph = pos_in_glyph + 2 |
|
854 |
if flags & GF_WE_HAVE_A_SCALE: |
|
855 |
pos_in_glyph = pos_in_glyph + 2 |
|
856 |
elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE: |
|
857 |
pos_in_glyph = pos_in_glyph + 4 |
|
858 |
elif flags & GF_WE_HAVE_A_TWO_BY_TWO: |
|
859 |
pos_in_glyph = pos_in_glyph + 8 |
|
860 |
glyf.append(data) |
|
861 |
pos = pos + glyphLen |
|
1745
1915be8a52df
Align glyphs at dword boundaries as recommended by the TTF spec. (also fixes a bug)
mgedmin
parents:
1721
diff
changeset
|
862 |
if pos % 4 != 0: |
1915be8a52df
Align glyphs at dword boundaries as recommended by the TTF spec. (also fixes a bug)
mgedmin
parents:
1721
diff
changeset
|
863 |
padding = 4 - pos % 4 |
1915be8a52df
Align glyphs at dword boundaries as recommended by the TTF spec. (also fixes a bug)
mgedmin
parents:
1721
diff
changeset
|
864 |
glyf.append('\0' * padding) |
1915be8a52df
Align glyphs at dword boundaries as recommended by the TTF spec. (also fixes a bug)
mgedmin
parents:
1721
diff
changeset
|
865 |
pos = pos + padding |
1637 | 866 |
offsets.append(pos) |
867 |
output.add('glyf', string.join(glyf, "")) |
|
868 |
||
869 |
# loca - Index to location |
|
870 |
loca = [] |
|
871 |
if (pos + 1) >> 1 > 0xFFFF: |
|
872 |
indexToLocFormat = 1 # long format |
|
873 |
for offset in offsets: |
|
874 |
loca.append(offset) |
|
3326 | 875 |
loca = pack(*([">%dL" % len(loca)] + loca)) |
1637 | 876 |
else: |
877 |
indexToLocFormat = 0 # short format |
|
878 |
for offset in offsets: |
|
879 |
loca.append(offset >> 1) |
|
3326 | 880 |
loca = pack(*([">%dH" % len(loca)] + loca)) |
1637 | 881 |
output.add('loca', loca) |
882 |
||
883 |
# head - Font header |
|
884 |
head = self.get_table('head') |
|
885 |
head = _set_ushort(head, 50, indexToLocFormat) |
|
886 |
output.add('head', head) |
|
887 |
||
888 |
return output.makeStream() |
|
889 |
||
890 |
||
891 |
# |
|
892 |
# TrueType font embedding |
|
893 |
# |
|
894 |
||
895 |
# PDF font flags (see PDF Reference Guide table 5.19) |
|
896 |
FF_FIXED = 1 << 1-1 |
|
897 |
FF_SERIF = 1 << 2-1 |
|
898 |
FF_SYMBOLIC = 1 << 3-1 |
|
899 |
FF_SCRIPT = 1 << 4-1 |
|
900 |
FF_NONSYMBOLIC = 1 << 6-1 |
|
901 |
FF_ITALIC = 1 << 7-1 |
|
902 |
FF_ALLCAP = 1 << 17-1 |
|
903 |
FF_SMALLCAP = 1 << 18-1 |
|
904 |
FF_FORCEBOLD = 1 << 19-1 |
|
905 |
||
2216 | 906 |
class TTFontFace(TTFontFile, pdfmetrics.TypeFace): |
1637 | 907 |
"""TrueType typeface. |
908 |
||
909 |
Conceptually similar to a single byte typeface, but the glyphs are |
|
910 |
identified by UCS character codes instead of glyph names.""" |
|
911 |
||
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
912 |
def __init__(self, filename, validate=0, subfontIndex=0): |
1637 | 913 |
"Loads a TrueType font from filename." |
2216 | 914 |
pdfmetrics.TypeFace.__init__(self, None) |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
915 |
TTFontFile.__init__(self, filename, validate=validate, subfontIndex=subfontIndex) |
1637 | 916 |
|
917 |
def getCharWidth(self, code): |
|
918 |
"Returns the width of character U+<code>" |
|
919 |
return self.charWidths.get(code, self.defaultWidth) |
|
920 |
||
921 |
def addSubsetObjects(self, doc, fontname, subset): |
|
922 |
"""Generate a TrueType font subset and add it to the PDF document. |
|
923 |
Returns a PDFReference to the new FontDescriptor object.""" |
|
924 |
||
925 |
fontFile = pdfdoc.PDFStream() |
|
926 |
fontFile.content = self.makeSubset(subset) |
|
927 |
fontFile.dictionary['Length1'] = len(fontFile.content) |
|
928 |
if doc.compression: |
|
929 |
fontFile.filters = [pdfdoc.PDFZCompress] |
|
930 |
fontFileRef = doc.Reference(fontFile, 'fontFile:%s(%s)' % (self.filename, fontname)) |
|
931 |
||
932 |
flags = self.flags & ~ FF_NONSYMBOLIC |
|
933 |
flags = flags | FF_SYMBOLIC |
|
934 |
||
935 |
fontDescriptor = pdfdoc.PDFDictionary({ |
|
936 |
'Type': '/FontDescriptor', |
|
937 |
'Ascent': self.ascent, |
|
938 |
'CapHeight': self.capHeight, |
|
939 |
'Descent': self.descent, |
|
940 |
'Flags': flags, |
|
941 |
'FontBBox': pdfdoc.PDFArray(self.bbox), |
|
942 |
'FontName': pdfdoc.PDFName(fontname), |
|
943 |
'ItalicAngle': self.italicAngle, |
|
944 |
'StemV': self.stemV, |
|
945 |
'FontFile2': fontFileRef, |
|
946 |
}) |
|
947 |
return doc.Reference(fontDescriptor, 'fontDescriptor:' + fontname) |
|
948 |
||
949 |
class TTEncoding: |
|
950 |
"""Encoding for TrueType fonts (always UTF-8). |
|
951 |
||
952 |
TTEncoding does not directly participate in PDF object creation, since |
|
953 |
we need a number of different 8-bit encodings for every generated font |
|
954 |
subset. TTFont itself cares about that.""" |
|
955 |
||
956 |
def __init__(self): |
|
957 |
self.name = "UTF-8" |
|
958 |
||
959 |
class TTFont: |
|
960 |
"""Represents a TrueType font. |
|
961 |
||
962 |
Its encoding is always UTF-8. |
|
963 |
||
964 |
Note: you cannot use the same TTFont object for different documents |
|
965 |
at the same time. |
|
966 |
||
967 |
Example of usage: |
|
968 |
||
969 |
font = ttfonts.TTFont('PostScriptFontName', '/path/to/font.ttf') |
|
970 |
pdfmetrics.registerFont(font) |
|
971 |
||
972 |
canvas.setFont('PostScriptFontName', size) |
|
973 |
canvas.drawString(x, y, "Some text encoded in UTF-8") |
|
974 |
""" |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
975 |
class State: |
2785
5e9233938058
ttfonts.py: allow for different subset name prefix
rgbecker
parents:
2782
diff
changeset
|
976 |
namePrefix = 'F' |
3340
7dce483dd143
ttfonts.py: use rl_config for standard asciiReadable
rgbecker
parents:
3326
diff
changeset
|
977 |
def __init__(self,asciiReadable=None): |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
978 |
self.assignments = {} |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
979 |
self.nextCode = 0 |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
980 |
self.internalName = None |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
981 |
self.frozen = 0 |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
982 |
|
3340
7dce483dd143
ttfonts.py: use rl_config for standard asciiReadable
rgbecker
parents:
3326
diff
changeset
|
983 |
if asciiReadable is None: |
7dce483dd143
ttfonts.py: use rl_config for standard asciiReadable
rgbecker
parents:
3326
diff
changeset
|
984 |
asciiReadable = rl_config.ttfAsciiReadable |
7dce483dd143
ttfonts.py: use rl_config for standard asciiReadable
rgbecker
parents:
3326
diff
changeset
|
985 |
|
2645 | 986 |
if asciiReadable: |
987 |
# Let's add the first 128 unicodes to the 0th subset, so ' ' |
|
988 |
# always has code 32 (for word spacing to work) and the ASCII |
|
989 |
# output is readable |
|
3721 | 990 |
subset0 = list(range(128)) |
2645 | 991 |
self.subsets = [subset0] |
992 |
for n in subset0: |
|
993 |
self.assignments[n] = n |
|
994 |
self.nextCode = 128 |
|
995 |
else: |
|
996 |
self.subsets = [[32]*33] |
|
997 |
self.assignments[32] = 32 |
|
2501
98a53df9b49e
ttfonts.py: apply Albertas Agejevas' <alga@pov.lt> spaces patch to fix para splitting
rgbecker
parents:
2341
diff
changeset
|
998 |
|
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
999 |
_multiByte = 1 # We want our own stringwidth |
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
1000 |
_dynamicFont = 1 # We want dynamic subsetting |
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
1001 |
|
3368
afa025c34493
reportlab: new base font mechanism more fully applied
rgbecker
parents:
3340
diff
changeset
|
1002 |
def __init__(self, name, filename, validate=0, subfontIndex=0,asciiReadable=None): |
1748
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
1003 |
"""Loads a TrueType font from filename. |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
1004 |
|
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
1005 |
If validate is set to a false values, skips checksum validation. This |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
1006 |
can save time, especially if the font is large. |
e69910cfc3b8
Let TTFont users disable checksum validation. Also, don't parse the same TTF info twice (in TTFontFace and TTFontFile constructors).
mgedmin
parents:
1745
diff
changeset
|
1007 |
""" |
1637 | 1008 |
self.fontName = name |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
1009 |
self.face = TTFontFace(filename, validate=validate, subfontIndex=subfontIndex) |
1637 | 1010 |
self.encoding = TTEncoding() |
2678
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
1011 |
from weakref import WeakKeyDictionary |
38d18a697cd0
reportlab: Python2.5 changes + minor cosmetics and improvements
rgbecker
parents:
2645
diff
changeset
|
1012 |
self.state = WeakKeyDictionary() |
3368
afa025c34493
reportlab: new base font mechanism more fully applied
rgbecker
parents:
3340
diff
changeset
|
1013 |
if asciiReadable is None: |
afa025c34493
reportlab: new base font mechanism more fully applied
rgbecker
parents:
3340
diff
changeset
|
1014 |
asciiReadable = rl_config.ttfAsciiReadable |
2645 | 1015 |
self._asciiReadable = asciiReadable |
1637 | 1016 |
|
2606
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1017 |
def _py_stringWidth(self, text, size, encoding='utf-8'): |
1637 | 1018 |
"Calculate text width" |
3721 | 1019 |
if not isinstance(text,str): |
1020 |
text = str(text, encoding or 'utf-8') # encoding defaults to utf-8 |
|
2606
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1021 |
g = self.face.charWidths.get |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1022 |
dw = self.face.defaultWidth |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1023 |
return 0.001*size*sum([g(ord(u),dw) for u in text]) |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1024 |
stringWidth = _py_stringWidth |
1637 | 1025 |
|
2785
5e9233938058
ttfonts.py: allow for different subset name prefix
rgbecker
parents:
2782
diff
changeset
|
1026 |
def _assignState(self,doc,asciiReadable=None,namePrefix=None): |
2782
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1027 |
'''convenience function for those wishing to roll their own state properties''' |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1028 |
if asciiReadable is None: |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1029 |
asciiReadable = self._asciiReadable |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1030 |
try: |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1031 |
state = self.state[doc] |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1032 |
except KeyError: |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1033 |
state = self.state[doc] = TTFont.State(asciiReadable) |
2785
5e9233938058
ttfonts.py: allow for different subset name prefix
rgbecker
parents:
2782
diff
changeset
|
1034 |
if namePrefix is not None: |
5e9233938058
ttfonts.py: allow for different subset name prefix
rgbecker
parents:
2782
diff
changeset
|
1035 |
state.namePrefix = namePrefix |
2782
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1036 |
return state |
d21452d865f2
ttfonts.py: pdfbase/ttfonts.py: add _assignState, fix scale, remove _bbox
rgbecker
parents:
2774
diff
changeset
|
1037 |
|
2575 | 1038 |
def splitString(self, text, doc, encoding='utf-8'): |
1637 | 1039 |
"""Splits text into a number of chunks, each of which belongs to a |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1040 |
single subset. Returns a list of tuples (subset, string). Use subset |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1041 |
numbers with getSubsetInternalName. Doc is needed for distinguishing |
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1042 |
subsets when building different documents at the same time.""" |
2645 | 1043 |
asciiReadable = self._asciiReadable |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1044 |
try: state = self.state[doc] |
2645 | 1045 |
except KeyError: state = self.state[doc] = TTFont.State(asciiReadable) |
1637 | 1046 |
curSet = -1 |
1047 |
cur = [] |
|
1048 |
results = [] |
|
3721 | 1049 |
if not isinstance(text,str): |
1050 |
text = str(text, encoding or 'utf-8') # encoding defaults to utf-8 |
|
2645 | 1051 |
assignments = state.assignments |
1052 |
subsets = state.subsets |
|
2575 | 1053 |
for code in map(ord,text): |
3326 | 1054 |
if code in assignments: |
2645 | 1055 |
n = assignments[code] |
1637 | 1056 |
else: |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1057 |
if state.frozen: |
3721 | 1058 |
raise pdfdoc.PDFError("Font %s is already frozen, cannot add new character U+%04X" % (self.fontName, code)) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1059 |
n = state.nextCode |
2645 | 1060 |
if n&0xFF==32: |
2501
98a53df9b49e
ttfonts.py: apply Albertas Agejevas' <alga@pov.lt> spaces patch to fix para splitting
rgbecker
parents:
2341
diff
changeset
|
1061 |
# make code 32 always be a space character |
2645 | 1062 |
if n!=32: subsets[n >> 8].append(32) |
2501
98a53df9b49e
ttfonts.py: apply Albertas Agejevas' <alga@pov.lt> spaces patch to fix para splitting
rgbecker
parents:
2341
diff
changeset
|
1063 |
state.nextCode += 1 |
98a53df9b49e
ttfonts.py: apply Albertas Agejevas' <alga@pov.lt> spaces patch to fix para splitting
rgbecker
parents:
2341
diff
changeset
|
1064 |
n = state.nextCode |
98a53df9b49e
ttfonts.py: apply Albertas Agejevas' <alga@pov.lt> spaces patch to fix para splitting
rgbecker
parents:
2341
diff
changeset
|
1065 |
state.nextCode += 1 |
2645 | 1066 |
assignments[code] = n |
1067 |
if n>32: |
|
1068 |
if not(n&0xFF): subsets.append([]) |
|
1069 |
subsets[n >> 8].append(code) |
|
1070 |
else: |
|
1071 |
subsets[0][n] = code |
|
1637 | 1072 |
if (n >> 8) != curSet: |
1073 |
if cur: |
|
2645 | 1074 |
results.append((curSet, ''.join(map(chr,cur)))) |
1637 | 1075 |
curSet = (n >> 8) |
1076 |
cur = [] |
|
1077 |
cur.append(n & 0xFF) |
|
1078 |
if cur: |
|
2645 | 1079 |
results.append((curSet,''.join(map(chr,cur)))) |
1637 | 1080 |
return results |
1081 |
||
1082 |
def getSubsetInternalName(self, subset, doc): |
|
1083 |
"""Returns the name of a PDF Font object corresponding to a given |
|
1084 |
subset of this dynamic font. Use this function instead of |
|
1085 |
PDFDocument.getInternalFontName.""" |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1086 |
try: state = self.state[doc] |
2645 | 1087 |
except KeyError: state = self.state[doc] = TTFont.State(self._asciiReadable) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1088 |
if subset < 0 or subset >= len(state.subsets): |
3721 | 1089 |
raise IndexError('Subset %d does not exist in font %s' % (subset, self.fontName)) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1090 |
if state.internalName is None: |
3326 | 1091 |
state.internalName = state.namePrefix +repr(len(doc.fontMapping) + 1) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1092 |
doc.fontMapping[self.fontName] = '/' + state.internalName |
1637 | 1093 |
doc.delayedFonts.append(self) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1094 |
return '/%s+%d' % (state.internalName, subset) |
1637 | 1095 |
|
1096 |
def addObjects(self, doc): |
|
1097 |
"""Makes one or more PDF objects to be added to the document. The |
|
1098 |
caller supplies the internal name to be used (typically F1, F2, ... in |
|
1099 |
sequence). |
|
1100 |
||
1101 |
This method creates a number of Font and FontDescriptor objects. Every |
|
1102 |
FontDescriptor is a (no more than) 256 character subset of the original |
|
1103 |
TrueType font.""" |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1104 |
try: state = self.state[doc] |
2645 | 1105 |
except KeyError: state = self.state[doc] = TTFont.State(self._asciiReadable) |
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1106 |
state.frozen = 1 |
2645 | 1107 |
for n,subset in enumerate(state.subsets): |
1637 | 1108 |
internalName = self.getSubsetInternalName(n, doc)[1:] |
2582
4a008286f3dc
reportlab: wastefully allow TTFont to use ttc files
rgbecker
parents:
2581
diff
changeset
|
1109 |
baseFontName = "%s+%s%s" % (SUBSETN(n),self.face.name,self.face.subfontNameX) |
1637 | 1110 |
|
1111 |
pdfFont = pdfdoc.PDFTrueTypeFont() |
|
1112 |
pdfFont.__Comment__ = 'Font %s subset %d' % (self.fontName, n) |
|
1113 |
pdfFont.Name = internalName |
|
1114 |
pdfFont.BaseFont = baseFontName |
|
1115 |
||
1116 |
pdfFont.FirstChar = 0 |
|
1117 |
pdfFont.LastChar = len(subset) - 1 |
|
1118 |
||
3721 | 1119 |
widths = list(map(self.face.getCharWidth, subset)) |
1637 | 1120 |
pdfFont.Widths = pdfdoc.PDFArray(widths) |
1121 |
||
1122 |
cmapStream = pdfdoc.PDFStream() |
|
1123 |
cmapStream.content = makeToUnicodeCMap(baseFontName, subset) |
|
1124 |
if doc.compression: |
|
1125 |
cmapStream.filters = [pdfdoc.PDFZCompress] |
|
1126 |
pdfFont.ToUnicode = doc.Reference(cmapStream, 'toUnicodeCMap:' + baseFontName) |
|
1127 |
||
1128 |
pdfFont.FontDescriptor = self.face.addSubsetObjects(doc, baseFontName, subset) |
|
1129 |
||
1130 |
# link it in |
|
1131 |
ref = doc.Reference(pdfFont, internalName) |
|
1132 |
fontDict = doc.idToObject['BasicFonts'].dict |
|
1133 |
fontDict[internalName] = pdfFont |
|
1690
2adbe2b8005a
Allow use of a TTFont on different documents at the same time
mgedmin
parents:
1683
diff
changeset
|
1134 |
del self.state[doc] |
2606
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1135 |
try: |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1136 |
from _rl_accel import _instanceStringWidthTTF |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1137 |
import new |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1138 |
TTFont.stringWidth = new.instancemethod(_instanceStringWidthTTF,None,TTFont) |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1139 |
except ImportError: |
185b990e8e60
ttfonts.py: allow for _rl_accel._instanceStringWidthTTF
rgbecker
parents:
2582
diff
changeset
|
1140 |
pass |