1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
|
# Copyright 1997-1998 by Corporation for National Research Initiatives.
# See the file LICENSE for details.
"""Object-oriented interface to the t1lib Type1 font rasterizer.
This is the interface that should actually be used; _t1lib is the actual
C extension which exposes the low-level interface.
Constants
---------
LOG_ERROR
Log only serious errors. This can include such events as memory
allocation failures.
LOG_WARNING
Log anything that might be useful in diagnosing misbehavior in a debugged
application. This can include such information as missing font metrics
(AFM) files.
LOG_STATISTIC
Various informational messages are included in the log; this can include
memory consumption information.
LOG_DEBUG
Logging level to support debugging. All sorts of mess gets included in
the log. This should be used primarily when debugging t1lib or tracking
really nasty memory problems.
"""
__version__ = "0.3"
# Note that the docstrings in this module are heavily stylized to allow
# document generation using a Python program I've been using for such things;
# see http://weyr.cnri.reston.va.us/~fdrake/progenv/ for more information.
import _t1lib
import string
from _t1lib import \
LOG_ERROR, \
LOG_WARNING, \
LOG_STATISTIC, \
LOG_DEBUG, \
PFAB_PATH, \
AFM_PATH, \
ENC_PATH
def init(logging=0):
"""Initialize the library.
logging -- flag to indicate whether logging should be enabled
Use of this function is only necessary to enable logging. In that case,
this must be called before any other calls to this module have been made.
"""
_t1lib.InitLib(logging)
def printLog(message, level=LOG_WARNING):
"""Add an entry to the t1lib.log file.
message -- string containing the text to write out
level -- severity level; one of LOG_ERROR, LOG_WARNING, LOG_STATISTIC,
or LOG_DEBUG
The name of the calling function or method is determined automatically
and cannot be supplied by the caller.
"""
# This is a little evil, buts gets it right while reducing the clutter
# in the caller.
funcname = sys.exc_info()[2].tb_frame.f_back.f_code.co_name
_t1lib.PrintLog(funcname, message, level)
def setLogLevel(level):
"""Set the level of severity at which log entries are written to the log.
level -- severity level; one of LOG_ERROR, LOG_WARNING, LOG_STATISTIC,
or LOG_DEBUG
"""
_t1lib.SetLogLevel(level)
def setDeviceResolutions(xres, yres):
"""Set the resolution of the output device in dots-per-inch.
xres -- horizontal DPI
yres -- vertical DPI
"""
_t1lib.SetDeviceResolutions(xres, yres)
_init_gray = 0
def setAAGrayValues(white, gray75, gray50, gray25, black):
"""Set the values used for each level of gray for anti-aliasing.
Each value is interpreted as an unsigned 32-bit integer. Long integers
may be used.
"""
global _init_gray
_t1lib.AASetGrayValues(white, gray75, gray50, gray25, black)
_init_gray = 1
def _init_gray_values():
global _init_gray_values
if not _init_gray:
setAAGrayValues(0xffffffff, 0xa8a8a8a8, 0x77777777, 0x38383838, 0)
_init_gray_values = _noinit_gray_values
def _noinit_gray_values():
# faster version to replace the original after initialization
pass
class T1Error(Exception):
# This is really a dummy class, so that the documentation can be
# generated for the exception object.
"""Raised by primitive t1lib operations that fail.
The string value describes what went wrong.
"""
pass
# This is the real thing; we don't really want to mask this one,
# but keep the docstring around.
_t1lib.T1Error.__doc__ = T1Error.__doc__
T1Error = _t1lib.T1Error
class UnknownFontError(T1Error):
"""Raised when a requested fontname is not recognized.
Public Members
--------------
fontname -- requested font name
"""
def __init__(self, fontname):
"""UNDOCUMENTED"""
self.fontname = fontname
T1Error.__init__(self, fontname)
class EncodingFormatError(T1Error):
"""Raised when a font encoding file has a formatting error."""
pass
class _Unpackable:
"""Mixin class that allows objects to behave as sequences.
This is mostly interesting to allow unpacking a BBox or MetricsInfo
instance into the component values rather than offer multiple interfaces
on the FontSetter class for each output treatment.
"""
def __init__(self, stuff):
self.__stuff = stuff
def __getitem__(self, index):
return self.__stuff[index]
class BBox(_Unpackable):
"""A bounding box.
Public Members
--------------
llx -- lower-left x coordinate
lly -- lower-left y coordinate
urx -- upper-right x coordinate
ury -- upper-right y coordinate
All values are measured in character space units.
"""
def __init__(self, bbox):
self.llx, self.lly, self.urx, self.ury = bbox
_Unpackable.__init__(self, bbox)
def getHeight(self):
"""Return the height of the region."""
return self.ury - self.lly
def getWidth(self):
"""Return the width of the region."""
return self.urx - self.llx
class MetricsInfo(_Unpackable):
"""Metrics information derived from AFM data.
Public Members
--------------
width --
bbox -- bounding box of the glyph which would be generated for string
numchars -- number of characters in the string measured
charpos -- vector of numchars integers; see below
string -- the string measured
All values are measured in character space units.
The charpos vector contains values which represent the horizontal
escapement of string[:i + 1], where i is an index into string.
"""
def __init__(self, metrics, string):
self.width, self.bbox, self.numchars, self.charpos = metrics
self.string = string
if type(self.bbox) is type(()):
self.bbox = BBox(self.bbox)
metrics = self.width, self.bbox, self.numchars, self.charpos
_Unpackable.__init__(self, metrics)
class Glyph:
"""A rasterized bitmap.
Public Members
--------------
bits -- string representing the bitmap as generated by t1lib
ascent -- number of pixels image rises above the baseline
descent -- number of pixels image descends below the baseline
leftSideBearing -- number of pixels between the origin of the glyph
and the leftmost bitmap pixel
rightSideBearing -- number of pixels between the origin of the glyph
and the rightmost bitmap pixel
characterWidth -- width of the glyph, including character margins;
this is the amount of horizontal movement needed
before placing a subsequent bitmap
bpp -- number of data bits per pixel
"""
def __init__(self, bits, metrics, bpp):
"""Initialize a Glyph instance.
bits -- string containing the bitmaps as generated by t1lib
metrics -- 5-tuple representing the metrics field of a t1lib
GLYPH structure
bpp -- bits per pixel
See the t1lib documentation for information on the format of the
bits field.
"""
self.bits = bits
self.bpp = bpp
self.ascent, self.descent, \
self.leftSideBearing, self.rightSideBearing, \
self.advanceX, self.advanceY = metrics
# backward compatibility
self.characterWidth = self.advanceX
def getHeight(self):
"""Return the height of the bitmap.
Character margin areas are not included.
"""
return self.ascent + self.descent
def getWidth(self):
"""Return the width of the bitmap.
Character margin areas are not included.
"""
return self.rightSideBearing - self.leftSideBearing
_font_cache = {}
def getFont(fontname):
"""Return a font object for a named font.
fontname -- PostScript name of the font to be located
If the named font is not found, an UnknownFontError exception is
raised. The fontname value is case-sensitive.
Font objects returned by this function will always refer to 'physical'
fonts in t1lib terminology; logical fonts generated from the same
physical base may exist.
"""
if _font_cache.has_key(fontname):
return _font_cache[fontname]
# determine the FontID that is associated with this font:
for id in range(_t1lib.Get_no_fonts()):
try:
fname = _t1lib.GetFontName(id)
except T1Error:
# attempt to set a character to get the font loaded
_t1lib.LoadFont(id)
fname = _t1lib.GetFontName(id)
if fname == fontname:
font = T1Font(id, id, fontname)
_font_cache[fontname] = font
return font
raise UnknownFontError(fontname)
class T1Font:
"""A font.
Instances of this class should not be created explicitly in user code.
"""
def __init__(self, id, phys_id, fontname, peers=None,
extension=0.0, slant=0.0, encoding=None):
"""UNDOCUMENTED"""
self.id = id
self.phys_id = phys_id
self.fontname = fontname
if peers is None:
peers = []
self.__peers = peers
self.__extension = extension
self.__slant = slant
self.__encoding = encoding
def __repr__(self):
"""UNDOCUMENTED"""
return "<%s.%s for %s (id=%s)>" \
% (self.__module__, self.__class__.__name__,
self.fontname, self.id)
def isLogical(self):
"""Return true iff this font is a t1lib 'logical' font."""
return self.phys_id != self.id
def isPhysical(self):
"""Return true iff this font is a t1lib 'physical' font."""
return self.phys_id == self.id
def getLogicalPeers(self):
"""Return sequence of all logical fonts generated from the physical
font associated with this font.
"""
return tuple(self.__peers)
def getPhysicalFont(self):
"""Return the t1lib 'physical' font associated with this font."""
if self.id == self.phys_id:
return self
return getFont(self.fontname)
def getCharName(self, char):
"""Return the PostScript name of a character in the current encoding.
char -- character for which the name is desired
"""
return _t1lib.GetCharName(self.id, char)
def getEncodingIndex(self, charname):
"""Return the index of a character in the current encoding.
charname -- PostScript character name
If the character is not present in the current encoding, a T1Error
exception is raised.
"""
return _t1lib.GetEncodingIndex(self.id, charname)
def getLigatures(self, char):
return _t1lib.QueryLigs(self.id, char)
def getKerning(self, char1, char2):
"""Return the kerning for a character pair.
The value returned is measured in character space units. If no
AFM data is available, 0 is returned.
"""
return _t1lib.GetKerning(self.id, char1, char2)
def getEncoding(self):
"""Return the current encoding vector."""
if self.__encoding is None:
self.__encoding = tuple(
map(_t1lib.GetCharName, [self.id] * 256, map(chr, range(256))))
return self.__encoding
def setEncoding(self, encoding=None):
"""Change the encoding vector.
encoding -- new encoding vector
If the new encoding vector is different from the old vector, the
bitmap cache is cleared.
"""
if encoding != self.__encoding:
self.reset()
_t1lib.ReencodeFont(self.id, encoding)
self.__encoding = encoding
def getExtension(self):
"""Return the current extension factor for the font."""
return self.__extension
def setExtension(self, extend):
"""Change the extension of the font.
extend -- the new extension factor
The new slant replaces the old extension; it is not a modifier.
If the new extension factor is different from the old factor, the
bitmap cache is cleared.
"""
if extend != self.__entension:
self.reset()
_t1lib.ExtendFont(self.id, extend)
self.__extension = extend
def getSlant(self):
"""Return the current slant factor for the font."""
return self.__slant
def setSlant(self, slant):
"""Change the slant of the font.
slant -- the new slant factor
The new slant replaces the old slant; it is not a modifier. If
the new slant factor is different from the old facter, the bitmap
cache is cleared.
"""
if slant != self.__slant:
self.reset()
_t1lib.SlantFont(self.id, slant)
self.__slant = slant
def copy(self):
"""Return a new logical font based on the same physical font as
this font.
"""
# If this is a logical font, things may look a little different
# than they do for the physical font. Update these attributes
# in the new font object.
if self.id != self.phys_id:
font = getFont(self.fontname).copy()
font.setEncoding(self.getEncoding())
font.setExtension(self.getExtension())
font.setSlant(self.getSlant())
else:
id = _t1lib.CopyFont(self.phys_id)
font = T1Font(id, self.phys_id, self.fontname, self.__peers,
self.__extension, self.__slant, self.__encoding)
self.__peers.append(font)
return font
def reset(self):
"""Delete all cached bitmaps for this font."""
_t1lib.DeleteAllSizes(self.id)
def delete(self):
"""Attempt to free all memory used by this font.
If this is a physical font still referenced by any logical fonts,
a T1Error exception will be raised.
"""
# not recommended...
_t1lib.DeleteFont(self.id)
if self.id == self.phys_id:
del _font_cache[self.fontname]
else:
self.__peers.remove(self)
def getInfo(self):
"""Retrieve a FontInfo instance for this font."""
return FontInfo(self)
class FontInfo:
"""Font information for a specific font.
Public Members
--------------
bbox -- bounding box for the font
familyName -- font family name. This may be shared among roman, italic,
or other varieties of a font.
fontName -- PostScript name of the font
fullName -- full name of the font, for human consumption
isFixedPitch -- true if the font is monospaced
italicAngle -- angle off of vertical for italic fonts
notice -- copyright notice
underlinePosition -- recommended underlining position raltive to the
baseline, in character space units
underlineThickness -- recommended underlining thickness, in character
space units
version -- version string from a Type 1 font
weight -- string describing the font weight
"""
def __init__(self, font):
id = font.id
self.bbox = BBox(_t1lib.GetFontBBox(id))
self.extension = font.getExtension()
self.familyName = _t1lib.GetFamilyName(id)
self.fontName = self.fontname = font.fontname
self.fullName = _t1lib.GetFullName(id)
self.isFixedPitch = _t1lib.GetIsFixedPitch(id)
self.italicAngle = _t1lib.GetItalicAngle(id)
self.notice = _t1lib.GetNotice(id)
self.slant = font.getSlant()
self.underlinePosition = _t1lib.GetUnderlinePosition(id)
self.underlineThickness = _t1lib.GetUnderlineThickness(id)
self.version = _t1lib.GetVersion(id)
self.weight = _t1lib.GetWeight(id)
class FontSetter:
def __init__(self, font, size, angle=0.0, spaceoff=0.0, kerning=0, bpp=1):
"""Initialize a FontSetter instance.
font -- T1Font instance
size -- point size of the rasterized characters
angle -- rotation angle of the rasterized image (measured
counter-clockwise)
spaceoff --
kerning -- flag indicating whether pairwise kerning should be used
bpp -- bits-per-pixel for the generated rasterizations
"""
self.font = font
self.size = float(size)
self.angle = float(angle)
self.spaceoff = float(spaceoff)
self.kerning = kerning and 1 or 0
if bpp not in (1, 8, 16, 24, 32):
raise ValueError, "illegal bit depth"
self.bpp = bpp
def __repr__(self):
"""UNDOCUMENTED"""
s = "<%s.%s for %s (id=%s) at %spt" % \
(self.__module__, self.__class__.__name__, self.font.fontname,
self.font.id, self.size)
if self.kerning:
s = s + ", kerned"
# how to indicate non-zero spaceoff?
return s + ">"
def setChar(self, char):
"""Rasterize a single character.
char -- the character to rasterize
Returns a Glyph instance.
"""
if self.bpp == 1:
stuff = _t1lib.SetChar(self.font.id, char, self.size, self.angle)
else:
_init_gray_values()
_setAADepth(self.bpp)
stuff = _t1lib.AASetChar(self.font.id, char, self.size, self.angle)
bits, metrics, bpp = stuff
return self.newGlyph(bits, metrics, bpp)
def setString(self, string):
"""Rasterize a string.
string -- the string to rasterize
Returns a Glyph instance.
"""
if self.bpp == 1:
stuff = _t1lib.SetString(self.font.id, string, self.spaceoff,
self.kerning, self.size, self.angle)
else:
_init_gray_values()
_setAADepth(self.bpp)
stuff = _t1lib.AASetString(self.font.id, string, self.spaceoff,
self.kerning, self.size, self.angle)
bits, metrics, bpp = stuff
return self.newGlyph(bits, metrics, bpp)
def getMetrics(self, string):
"""Return metrics information for a string.
string -- the string to compute metrics information for.
"""
stuff = _t1lib.GetMetricsInfo(self.font.id, string,
self.spaceoff, self.kerning)
return MetricsInfo(stuff,string)
def getWidth(self, string):
"""Return the width of a string in character space units.
string -- the string to measure
"""
return _t1lib.GetStringWidth(self.font.id, string,
self.spaceoff, self.kerning)
def getBBox(self, string):
"""Return the bounding box for a string.
string -- string to compute the bounding box for
Returns a BBox instance.
"""
stuff = _t1lib.GetStringBBox(self.font.id, string,
self.spaceoff, self.kerning)
return BBox(stuff)
def setBitsPerPixel(self, bpp):
"""Set the number of bits per pixel for the resulting glyphs.
bpp -- new bit depth
If bpp is not 1, 8, 16, 24, or 32, ValueError is raised.
"""
if bpp == self.bpp:
return
if bpp not in (1, 8, 16, 24, 32):
raise ValueError, "illegal bit depth"
self.bpp = bpp
def newGlyph(self, bits, metrics, bpp):
"""Return a glyph object for the specified information.
bits -- string representing the raw bitmap generated by t1lib
metrics --
bpp -- bits-per-pixel for the bitmap
"""
return Glyph(bits, metrics, bpp)
def resetSize(self):
"""Clear bitmap cache in underlying t1lib."""
_t1lib.DeleteSize(self.font.id, self.size)
_prev_aa_depth = 0
def _setAADepth(bpp):
global _prev_aa_depth
if _prev_aa_depth != bpp:
_t1lib.AASetBitsPerPixel(bpp)
_prev_aa_depth = bpp
def loadEncoding(fp_or_filename):
"""Return an encoding vector read from a file.
fp_or_filename -- either a file-like object or the name of a disk file
If a string is passed in, it will be used as a filename and passed to
open(), otherwise fp_or_filename will be treated as any file-like
object and assumed to have a readline() method.
"""
if type(fp_or_filename) is type(''):
fp = open(fp_or_filename)
else:
fp = fp_or_filename
encoding = []
started = 0
while len(encoding) < 256:
line = fp.readline()
if not line:
raise EncodingFormatError(
"EOF reached before encoding was defined")
if started:
stuff = string.split(line)
if not stuff:
raise EncodingFormatError(
"encountered line without character name in encoding")
encoding.append(stuff[0])
elif len(line) > 9:
if string.lower(line[:9]) == "encoding=" \
and line[9] in string.whitespace:
started = 1
return tuple(encoding)
class SearchPath:
__type_map = {
_t1lib.AFM_PATH: 'afm',
_t1lib.PFAB_PATH: 'font',
_t1lib.ENC_PATH: 'encoding',
}
def __init__(self, which):
try:
self.__type = self.__type_map[which]
except KeyError:
raise ValueError, "illegal t1lib path type"
self.__which = which
def append(self, entry):
_t1lib.AddToFileSearchPath(self.__which, _t1lib.APPEND_PATH, entry)
def insert(self, index, entry):
list = self.__get()
list.insert(index, entry)
self.__set(list)
def count(self, entry):
return self.__get().count(entry)
def index(self, entry):
return self.__get().index(entry)
def remove(self, entry):
list = self.__get()
list.remove(entry)
self.__set(list)
def __len__(self):
return len(self.__get())
def __getitem__(self, index):
return self.__get()[index]
def __setitem__(self, index, entry):
list = self.__get()
list[index] = entry
self.__set(list)
def __delitem__(self, index):
list = self.__get()
del list[index]
self.__set(list)
def __getslice__(self, start, stop):
return self.__get()[start:stop]
def __setslice__(self, start, stop, entries):
list = self.__get()
list[start:stop] = entries
self.__set(list)
def __delslice__(self, start, stop):
list = self.__get()
del list[start:stop]
self.__set(list)
def __repr__(self):
return "<t1lib.SearchPath for %s files>" % self.__type
# These methods don't really make sense, but we'll implement them for
# completeness.
def reverse(self):
pass
def sort(self):
pass
# These are internal only.
def __get(self):
return string.split(_t1lib.GetFileSearchPath(self.__which), ':')
def __set(self, list):
v = string.join(list, ':')
print "new path =", `v`
_t1lib.SetFileSearchPath(self.__which, v)
pfab_path = SearchPath(_t1lib.PFAB_PATH)
afm_path = SearchPath(_t1lib.AFM_PATH)
enc_path = SearchPath(_t1lib.ENC_PATH)
|