1# -*- encoding: utf-8 -*- 2# 3# 4# Copyright (C) 2005-2011 Jörg Lehmann <joerg@pyx-project.org> 5# Copyright (C) 2006-2011 Michael Schindler <m-schindler@users.sourceforge.net> 6# Copyright (C) 2005-2011 André Wobst <wobsta@pyx-project.org> 7# 8# This file is part of PyX (https://pyx-project.org/). 9# 10# PyX is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# PyX is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with PyX; if not, write to the Free Software 22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 23 24import logging 25from pyx import bbox, baseclasses, deco, path, pswriter, pdfwriter, svgwriter, trafo, unit 26from . import t1file, afmfile 27 28logger = logging.getLogger("pyx") 29 30############################################################################## 31# PS resources 32############################################################################## 33 34class PST1file(pswriter.PSresource): 35 36 """ PostScript font definition included in the prolog """ 37 38 def __init__(self, t1file, glyphnames, charcodes): 39 """ include type 1 font t1file stripped to the given glyphnames""" 40 self.type = "t1file" 41 self.t1file = t1file 42 self.id = t1file.name 43 self.glyphnames = set(glyphnames) 44 self.charcodes = set(charcodes) 45 46 def merge(self, other): 47 self.glyphnames.update(other.glyphnames) 48 self.charcodes.update(other.charcodes) 49 50 def output(self, file, writer, registry): 51 file.write("%%%%BeginFont: %s\n" % self.t1file.name) 52 if writer.stripfonts: 53 if self.glyphnames: 54 file.write("%%Included glyphs: %s\n" % " ".join(self.glyphnames)) 55 if self.charcodes: 56 file.write("%%Included charcodes: %s\n" % " ".join([str(charcode) for charcode in self.charcodes])) 57 self.t1file.getstrippedfont(self.glyphnames, self.charcodes).outputPS(file, writer) 58 else: 59 self.t1file.outputPS(file, writer) 60 file.write("\n%%EndFont\n") 61 62 63_ReEncodeFont = pswriter.PSdefinition("ReEncodeFont", b"""{ 64 5 dict 65 begin 66 /newencoding exch def 67 /newfontname exch def 68 /basefontname exch def 69 /basefontdict basefontname findfont def 70 /newfontdict basefontdict maxlength dict def 71 basefontdict { 72 exch dup dup /FID ne exch /Encoding ne and 73 { exch newfontdict 3 1 roll put } 74 { pop pop } 75 ifelse 76 } forall 77 newfontdict /FontName newfontname put 78 newfontdict /Encoding newencoding put 79 newfontname newfontdict definefont pop 80 end 81}""") 82 83 84class PSreencodefont(pswriter.PSresource): 85 86 """ reencoded PostScript font""" 87 88 def __init__(self, basefontname, newfontname, encoding): 89 """ reencode the font """ 90 91 self.type = "reencodefont" 92 self.basefontname = basefontname 93 self.id = self.newfontname = newfontname 94 self.encoding = encoding 95 96 def output(self, file, writer, registry): 97 file.write("%%%%BeginResource: %s\n" % self.newfontname) 98 file.write("/%s /%s\n[" % (self.basefontname, self.newfontname)) 99 vector = [None] * len(self.encoding) 100 for glyphname, charcode in list(self.encoding.items()): 101 vector[charcode] = glyphname 102 for i, glyphname in enumerate(vector): 103 if i: 104 if not (i % 8): 105 file.write("\n") 106 else: 107 file.write(" ") 108 file.write("/%s" % glyphname) 109 file.write("]\n") 110 file.write("ReEncodeFont\n") 111 file.write("%%EndResource\n") 112 113 114_ChangeFontMatrix = pswriter.PSdefinition("ChangeFontMatrix", b"""{ 115 5 dict 116 begin 117 /newfontmatrix exch def 118 /newfontname exch def 119 /basefontname exch def 120 /basefontdict basefontname findfont def 121 /newfontdict basefontdict maxlength dict def 122 basefontdict { 123 exch dup dup /FID ne exch /FontMatrix ne and 124 { exch newfontdict 3 1 roll put } 125 { pop pop } 126 ifelse 127 } forall 128 newfontdict /FontName newfontname put 129 newfontdict /FontMatrix newfontmatrix readonly put 130 newfontname newfontdict definefont pop 131 end 132}""") 133 134 135class PSchangefontmatrix(pswriter.PSresource): 136 137 """ change font matrix of a PostScript font""" 138 139 def __init__(self, basefontname, newfontname, newfontmatrix): 140 """ change the font matrix """ 141 142 self.type = "changefontmatrix" 143 self.basefontname = basefontname 144 self.id = self.newfontname = newfontname 145 self.newfontmatrix = newfontmatrix 146 147 def output(self, file, writer, registry): 148 file.write("%%%%BeginResource: %s\n" % self.newfontname) 149 file.write("/%s /%s\n" % (self.basefontname, self.newfontname)) 150 file.write(str(self.newfontmatrix)) 151 file.write("\nChangeFontMatrix\n") 152 file.write("%%EndResource\n") 153 154 155############################################################################## 156# PDF resources 157############################################################################## 158 159class PDFfont(pdfwriter.PDFobject): 160 161 def __init__(self, fontname, basefontname, charcodes, fontdescriptor, encoding, metric): 162 pdfwriter.PDFobject.__init__(self, "font", fontname) 163 164 self.fontname = fontname 165 self.basefontname = basefontname 166 self.charcodes = set(charcodes) 167 self.fontdescriptor = fontdescriptor 168 self.encoding = encoding 169 self.metric = metric 170 171 def merge(self, other): 172 self.charcodes.update(other.charcodes) 173 174 def write(self, file, writer, registry): 175 file.write("<<\n" 176 "/Type /Font\n" 177 "/Subtype /Type1\n") 178 file.write("/Name /%s\n" % self.fontname) 179 file.write("/BaseFont /%s\n" % self.basefontname) 180 firstchar = min(self.charcodes) 181 lastchar = max(self.charcodes) 182 file.write("/FirstChar %d\n" % firstchar) 183 file.write("/LastChar %d\n" % lastchar) 184 file.write("/Widths\n" 185 "[") 186 if self.encoding: 187 encoding = self.encoding.getvector() 188 else: 189 if self.fontdescriptor.fontfile.t1file.encoding is None: 190 self.fontdescriptor.fontfile.t1file._encoding() 191 encoding = self.fontdescriptor.fontfile.t1file.encoding 192 for i in range(firstchar, lastchar+1): 193 if i: 194 if not (i % 8): 195 file.write("\n") 196 else: 197 file.write(" ") 198 if i in self.charcodes: 199 if self.metric is not None: 200 file.write("%i" % self.metric.width_ds(encoding[i])) 201 else: 202 file.write("%i" % self.fontdescriptor.fontfile.t1file.getglyphinfo(encoding[i])[0]) 203 else: 204 file.write("0") 205 file.write(" ]\n") 206 file.write("/FontDescriptor %d 0 R\n" % registry.getrefno(self.fontdescriptor)) 207 if self.encoding: 208 file.write("/Encoding %d 0 R\n" % registry.getrefno(self.encoding)) 209 file.write(">>\n") 210 211 212class PDFstdfont(pdfwriter.PDFobject): 213 214 def __init__(self, basename): 215 pdfwriter.PDFobject.__init__(self, "font", "stdfont-%s" % basename) 216 self.name = basename # name is ignored by acroread 217 self.basename = basename 218 219 def write(self, file, writer, registry): 220 file.write("<</BaseFont /%s\n" % self.basename) 221 file.write("/Name /%s\n" % self.name) 222 file.write("/Type /Font\n") 223 file.write("/Subtype /Type1\n") 224 file.write(">>\n") 225 226# the 14 standard fonts that are always available in PDF 227PDFTimesRoman = PDFstdfont("Times-Roman") 228PDFTimesBold = PDFstdfont("Times-Bold") 229PDFTimesItalic = PDFstdfont("Times-Italic") 230PDFTimesBoldItalic = PDFstdfont("Times-BoldItalic") 231PDFHelvetica = PDFstdfont("Helvetica") 232PDFHelveticaBold = PDFstdfont("Helvetica-Bold") 233PDFHelveticaOblique = PDFstdfont("Helvetica-Oblique") 234PDFHelveticaBoldOblique = PDFstdfont("Helvetica-BoldOblique") 235PDFCourier = PDFstdfont("Courier") 236PDFCourierBold = PDFstdfont("Courier-Bold") 237PDFCourierOblique = PDFstdfont("Courier-Oblique") 238PDFCourierBoldOblique = PDFstdfont("Courier-BoldOblique") 239PDFSymbol = PDFstdfont("Symbol") 240PDFZapfDingbats = PDFstdfont("ZapfDingbats") 241 242 243class PDFfontdescriptor(pdfwriter.PDFobject): 244 245 def __init__(self, fontname, fontfile, metric): 246 pdfwriter.PDFobject.__init__(self, "fontdescriptor", fontname) 247 self.fontname = fontname 248 self.fontfile = fontfile 249 self.metric = metric 250 251 def write(self, file, writer, registry): 252 file.write("<<\n" 253 "/Type /FontDescriptor\n" 254 "/FontName /%s\n" % self.fontname) 255 if self.metric is not None: 256 self.metric.writePDFfontinfo(file) 257 else: 258 self.fontfile.t1file.writePDFfontinfo(file) 259 if self.fontfile is not None: 260 file.write("/FontFile %d 0 R\n" % registry.getrefno(self.fontfile)) 261 file.write(">>\n") 262 263 264class PDFfontfile(pdfwriter.PDFobject): 265 266 def __init__(self, t1file, glyphnames, charcodes): 267 pdfwriter.PDFobject.__init__(self, "fontfile", t1file.name) 268 self.t1file = t1file 269 self.glyphnames = set(glyphnames) 270 self.charcodes = set(charcodes) 271 272 def merge(self, other): 273 self.glyphnames.update(other.glyphnames) 274 self.charcodes.update(other.charcodes) 275 276 def write(self, file, writer, registry): 277 if writer.stripfonts: 278 self.t1file.getstrippedfont(self.glyphnames, self.charcodes).outputPDF(file, writer) 279 else: 280 self.t1file.outputPDF(file, writer) 281 282 283class PDFencoding(pdfwriter.PDFobject): 284 285 def __init__(self, encoding, name): 286 pdfwriter.PDFobject.__init__(self, "encoding", name) 287 self.encoding = encoding 288 289 def getvector(self): 290 # As self.encoding might be appended after the constructur has set it, 291 # we need to defer the calculation until the whole content was constructed. 292 vector = [None] * len(self.encoding) 293 for glyphname, charcode in list(self.encoding.items()): 294 vector[charcode] = glyphname 295 return vector 296 297 def write(self, file, writer, registry): 298 file.write("<<\n" 299 "/Type /Encoding\n" 300 "/Differences\n" 301 "[0") 302 for i, glyphname in enumerate(self.getvector()): 303 if i: 304 if not (i % 8): 305 file.write("\n") 306 else: 307 file.write(" ") 308 file.write("/%s" % glyphname) 309 file.write("]\n" 310 ">>\n") 311 312 313############################################################################## 314# SVG resources 315############################################################################## 316 317 318_glyphnames = {glyphname: str for str, glyphname in afmfile.unicodestring.items()} 319_charcodes = {i: chr(i) for i in range(32, 127)} # 0x20 (space) to 0x7e (tilde) 320 321 322class SVGT1mapping: 323 324 def __init__(self, glyphnames, charcodes): 325 # glyphnames and charcodes are not stored as sets, but are dicts 326 # mapping the values to unicode characters. If the glyphnames and 327 # charcodes are contained in _glyphnames and _charcodes, use those 328 # values, otherwise use the private use areas A and B. 329 self.private_glyphname = 0xf0000 330 self.private_charcode = 0x100000 331 self.glyphnames = {} 332 self.charcodes = {} 333 self.merge_glyphnames(glyphnames) 334 self.merge_charcodes(charcodes) 335 336 def merge_glyphnames(self, glyphnames): 337 for glyphname in glyphnames: 338 if glyphname not in self.glyphnames: 339 if glyphname in _glyphnames: 340 self.glyphnames[glyphname] = _glyphnames[glyphname] 341 else: 342 self.glyphnames[glyphname] = chr(self.private_glyphname) 343 self.private_glyphname += 1 344 345 def merge_charcodes(self, charcodes): 346 for charcode in charcodes: 347 if charcode not in self.charcodes: 348 if charcode in _charcodes: 349 self.charcodes[charcode] = _charcodes[charcode] 350 else: 351 self.charcodes[charcode] = chr(self.private_charcode) 352 self.private_charcode += 1 353 354 355class SVGT1file(svgwriter.SVGresource, SVGT1mapping): 356 357 """ PostScript font definition included in the prolog """ 358 359 def __init__(self, t1file, glyphnames, charcodes): 360 """ include type 1 font t1file stripped to the given glyphnames""" 361 self.t1file = t1file 362 svgwriter.SVGresource.__init__(self, "t1file", t1file.name) 363 SVGT1mapping.__init__(self, glyphnames, charcodes) 364 365 def merge(self, other): 366 # Note that merging the glyphnames and charcodes does not alter 367 # any existing mapping to the private use areas for self (but for 368 # other). If you merge before use, the mapping by self.glyphnames 369 # and self.charcodes is already updated and also copied to "other". 370 self.merge_glyphnames(other.glyphnames.keys()) 371 self.merge_charcodes(other.charcodes.keys()) 372 other.glyphnames = self.glyphnames 373 other.charcodes = self.charcodes 374 375 def output(self, xml, writer, registry): 376 xml.startSVGElement("font", {}) 377 xml.startSVGElement("font-face", {"font-family": self.t1file.name}) 378 xml.endSVGElement("font-face") 379 for glyphname in self.glyphnames: 380 glyphpath = self.t1file.getglyphpath_pt(0, 0, glyphname, 1000, convertcharcode=False) 381 attrs = {"unicode": self.glyphnames[glyphname], 382 "horiz-adv-x": "%f" % glyphpath.wx_pt, 383 "d": glyphpath.path.returnSVGdata(inverse_y=False)} 384 xml.startSVGElement("glyph", attrs) 385 xml.endSVGElement("glyph") 386 for charcode in self.charcodes: 387 glyphpath = self.t1file.getglyphpath_pt(0, 0, charcode, 1000, convertcharcode=True) 388 attrs = {"unicode": self.charcodes[charcode], 389 "horiz-adv-x": "%f" % glyphpath.wx_pt, 390 "d": glyphpath.path.returnSVGdata(inverse_y=False)} 391 xml.startSVGElement("glyph", attrs) 392 xml.endSVGElement("glyph") 393 xml.endSVGElement("font") 394 395 396############################################################################## 397# basic PyX text output 398############################################################################## 399 400class font: 401 402 def text(self, x, y, charcodes, size_pt, **kwargs): 403 return self.text_pt(unit.topt(x), unit.topt(y), charcodes, size_pt, **kwargs) 404 405 406class T1font(font): 407 408 def __init__(self, t1file, metric=None): 409 self.t1file = t1file 410 self.name = t1file.name 411 self.metric = metric 412 413 def text_pt(self, x, y, charcodes, size_pt, **kwargs): 414 return T1text_pt(self, x, y, charcodes, size_pt, **kwargs) 415 416 417class T1builtinfont(T1font): 418 419 def __init__(self, name, metric): 420 self.name = name 421 self.t1file = None 422 self.metric = metric 423 424 425class selectedfont: 426 427 def __init__(self, name, size_pt): 428 self.name = name 429 self.size_pt = size_pt 430 431 def __ne__(self, other): 432 return self.name != other.name or self.size_pt != other.size_pt 433 434 def outputPS(self, file, writer): 435 file.write("/%s %f selectfont\n" % (self.name, self.size_pt)) 436 437 def outputPDF(self, file, writer): 438 file.write("/%s %f Tf\n" % (self.name, self.size_pt)) 439 440 441class text_pt(baseclasses.canvasitem): 442 443 def requiretextregion(self): 444 return True 445 446 447class T1text_pt(text_pt): 448 449 def __init__(self, font, x_pt, y_pt, charcodes, size_pt, decoding=afmfile.unicodestring, slant=None, ignorebbox=False, kerning=False, ligatures=False, spaced_pt=0): 450 if decoding is not None: 451 self.glyphnames = [decoding[character] for character in charcodes] 452 self.decode = True 453 else: 454 self.charcodes = charcodes 455 self.decode = False 456 self.font = font 457 self.x_pt = x_pt 458 self.y_pt = y_pt 459 self.size_pt = size_pt 460 self.slant = slant 461 self.ignorebbox = ignorebbox 462 self.kerning = kerning 463 self.ligatures = ligatures 464 self.spaced_pt = spaced_pt 465 self._textpath = None 466 467 if self.kerning and not self.decode: 468 raise ValueError("decoding required for font metric access (kerning)") 469 if self.ligatures and not self.decode: 470 raise ValueError("decoding required for font metric access (ligatures)") 471 if self.ligatures: 472 self.glyphnames = self.font.metric.resolveligatures(self.glyphnames) 473 474 def bbox(self): 475 if self.font.metric is None: 476 logger.warning("We are about to extract the bounding box from the path of the text. This is slow and differs from the font metric information. You should provide an afm file whenever possible.") 477 return self.textpath().bbox() 478 if not self.decode: 479 raise ValueError("decoding required for font metric access (bbox)") 480 if self.kerning: 481 kerning_correction = sum(value or 0 for i, value in enumerate(self.font.metric.resolvekernings(self.glyphnames, self.size_pt)) if i%2) 482 else: 483 kerning_correction = 0 484 return bbox.bbox_pt(self.x_pt, 485 self.y_pt+self.font.metric.depth_pt(self.glyphnames, self.size_pt), 486 self.x_pt+self.font.metric.width_pt(self.glyphnames, self.size_pt) + (len(self.glyphnames)-1)*self.spaced_pt + kerning_correction, 487 self.y_pt+self.font.metric.height_pt(self.glyphnames, self.size_pt)) 488 489 def getencodingname(self, encodings): 490 """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints 491 If no such encoding can be found or extended, a new encoding is added to encodings 492 """ 493 glyphnames = set(self.glyphnames) 494 if len(glyphnames) > 256: 495 raise ValueError("glyphs do not fit into one single encoding") 496 for encodingname, encoding in list(encodings.items()): 497 glyphsmissing = [] 498 for glyphname in glyphnames: 499 if glyphname not in list(encoding.keys()): 500 glyphsmissing.append(glyphname) 501 502 if len(glyphsmissing) + len(encoding) < 256: 503 # new glyphs fit in existing encoding which will thus be extended 504 for glyphname in glyphsmissing: 505 encoding[glyphname] = len(encoding) 506 return encodingname 507 # create a new encoding for the glyphnames 508 encodingname = "encoding%d" % len(encodings) 509 encodings[encodingname] = dict([(glyphname, i) for i, glyphname in enumerate(glyphnames)]) 510 return encodingname 511 512 def textpath(self): 513 if self._textpath is None: 514 if self.decode: 515 if self.kerning: 516 data = self.font.metric.resolvekernings(self.glyphnames, self.size_pt) 517 else: 518 data = self.glyphnames 519 else: 520 data = self.charcodes 521 self._textpath = path.path() 522 x_pt = self.x_pt 523 y_pt = self.y_pt 524 for i, value in enumerate(data): 525 if self.kerning and i % 2: 526 if value is not None: 527 x_pt += value 528 else: 529 if i: 530 x_pt += self.spaced_pt 531 glyphpath = self.font.t1file.getglyphpath_pt(x_pt, y_pt, value, self.size_pt, convertcharcode=not self.decode) 532 self._textpath += glyphpath.path 533 x_pt += glyphpath.wx_pt 534 y_pt += glyphpath.wy_pt 535 return self._textpath 536 537 def processPS(self, file, writer, context, registry, bbox): 538 if not self.ignorebbox: 539 bbox += self.bbox() 540 541 if writer.textaspath and not self.font.t1file: 542 logger.warning("Cannot output text as path when font not given by a font file (like for builtin fonts).") 543 if writer.textaspath and self.font.t1file: 544 deco.decoratedpath(self.textpath(), fillstyles=[]).processPS(file, writer, context, registry, bbox) 545 else: 546 # register resources 547 if self.font.t1file is not None: 548 if self.decode: 549 registry.add(PST1file(self.font.t1file, self.glyphnames, [])) 550 else: 551 registry.add(PST1file(self.font.t1file, [], self.charcodes)) 552 553 fontname = self.font.name 554 if self.decode: 555 encodingname = self.getencodingname(writer.encodings.setdefault(self.font.name, {})) 556 encoding = writer.encodings[self.font.name][encodingname] 557 newfontname = "%s-%s" % (fontname, encodingname) 558 registry.add(_ReEncodeFont) 559 registry.add(PSreencodefont(fontname, newfontname, encoding)) 560 fontname = newfontname 561 562 if self.slant: 563 newfontmatrix = trafo.trafo_pt(matrix=((1, self.slant), (0, 1))) 564 if self.font.t1file is not None: 565 newfontmatrix = newfontmatrix * self.font.t1file.fontmatrix 566 newfontname = "%s-slant%f" % (fontname, self.slant) 567 registry.add(_ChangeFontMatrix) 568 registry.add(PSchangefontmatrix(fontname, newfontname, newfontmatrix)) 569 fontname = newfontname 570 571 # select font if necessary 572 sf = selectedfont(fontname, self.size_pt) 573 if context.selectedfont is None or sf != context.selectedfont: 574 context.selectedfont = sf 575 sf.outputPS(file, writer) 576 577 file.write("%f %f moveto (" % (self.x_pt, self.y_pt)) 578 if self.decode: 579 if self.kerning: 580 data = self.font.metric.resolvekernings(self.glyphnames, self.size_pt) 581 else: 582 data = self.glyphnames 583 else: 584 data = self.charcodes 585 for i, value in enumerate(data): 586 if self.kerning and i % 2: 587 if value is not None: 588 file.write(") show\n%f 0 rmoveto (" % (value+self.spaced_pt)) 589 elif self.spaced_pt: 590 file.write(") show\n%f 0 rmoveto (" % self.spaced_pt) 591 else: 592 if i and not self.kerning and self.spaced_pt: 593 file.write(") show\n%f 0 rmoveto (" % self.spaced_pt) 594 if self.decode: 595 value = encoding[value] 596 if 32 < value < 127 and chr(value) not in "()[]<>\\": 597 file.write("%s" % chr(value)) 598 else: 599 file.write("\\%03o" % value) 600 file.write(") show\n") 601 602 def processPDF(self, file, writer, context, registry, bbox): 603 if not self.ignorebbox: 604 bbox += self.bbox() 605 606 if writer.textaspath and not self.font.t1file: 607 logger.warning("Cannot output text as path when font not given by a font file (like for builtin fonts).") 608 if writer.textaspath and self.font.t1file: 609 deco.decoratedpath(self.textpath(), fillstyles=[]).processPDF(file, writer, context, registry, bbox) 610 else: 611 if self.decode: 612 encodingname = self.getencodingname(writer.encodings.setdefault(self.font.name, {})) 613 encoding = writer.encodings[self.font.name][encodingname] 614 charcodes = [encoding[glyphname] for glyphname in self.glyphnames] 615 else: 616 charcodes = self.charcodes 617 618 # create resources 619 fontname = self.font.name 620 if self.decode: 621 newfontname = "%s-%s" % (fontname, encodingname) 622 _encoding = PDFencoding(encoding, newfontname) 623 fontname = newfontname 624 else: 625 _encoding = None 626 if self.font.t1file is not None: 627 if self.decode: 628 fontfile = PDFfontfile(self.font.t1file, self.glyphnames, []) 629 else: 630 fontfile = PDFfontfile(self.font.t1file, [], self.charcodes) 631 else: 632 fontfile = None 633 fontdescriptor = PDFfontdescriptor(self.font.name, fontfile, self.font.metric) 634 font = PDFfont(fontname, self.font.name, charcodes, fontdescriptor, _encoding, self.font.metric) 635 636 # register resources 637 if fontfile is not None: 638 registry.add(fontfile) 639 registry.add(fontdescriptor) 640 if _encoding is not None: 641 registry.add(_encoding) 642 registry.add(font) 643 644 registry.addresource("Font", fontname, font, procset="Text") 645 646 if self.slant is None: 647 slantvalue = 0 648 else: 649 slantvalue = self.slant 650 651 # select font if necessary 652 sf = selectedfont(fontname, self.size_pt) 653 if context.selectedfont is None or sf != context.selectedfont: 654 context.selectedfont = sf 655 sf.outputPDF(file, writer) 656 657 # convert inter-character spacing to font units 658 spaced = self.spaced_pt*1000/self.size_pt 659 660 if self.kerning or spaced: 661 file.write("1 0 %f 1 %f %f Tm [(" % (slantvalue, self.x_pt, self.y_pt)) 662 else: 663 file.write("1 0 %f 1 %f %f Tm (" % (slantvalue, self.x_pt, self.y_pt)) 664 if self.decode: 665 if self.kerning: 666 data = self.font.metric.resolvekernings(self.glyphnames) 667 else: 668 data = self.glyphnames 669 else: 670 data = self.charcodes 671 672 for i, value in enumerate(data): 673 if self.kerning and i % 2: 674 if value is not None: 675 file.write(")%f(" % (-value-spaced)) 676 elif spaced: 677 file.write(")%f(" % (-spaced)) 678 else: 679 if i and not self.kerning and spaced: 680 file.write(")%f(" % (-spaced)) 681 if self.decode: 682 value = encoding[value] 683 if 32 <= value <= 127 and chr(value) not in "()[]<>\\": 684 file.write("%s" % chr(value)) 685 else: 686 file.write("\\%03o" % value) 687 if self.kerning or spaced: 688 file.write(")] TJ\n") 689 else: 690 file.write(") Tj\n") 691 692 def processSVG(self, xml, writer, context, registry, bbox): 693 if not self.ignorebbox: 694 bbox += self.bbox() 695 696 # this is too common to be warned about as textaspath is the 697 # default for svg due to the missing font support by current browsers 698 # 699 # if writer.textaspath and not self.font.t1file: 700 # logger.warning("Cannot output text as path when font not given by a font file (like for builtin fonts).") 701 702 if writer.textaspath and self.font.t1file: 703 deco.decoratedpath(self.textpath(), fillstyles=[]).processSVG(xml, writer, context, registry, bbox) 704 else: 705 if self.font.t1file is not None: 706 if self.decode: 707 t1mapping = SVGT1file(self.font.t1file, self.glyphnames, []) 708 else: 709 t1mapping = SVGT1file(self.font.t1file, [], self.charcodes) 710 registry.add(t1mapping) 711 else: 712 if self.decode: 713 t1mapping = SVGT1mapping(self.glyphnames, []) 714 else: 715 t1mapping = SVGT1mapping([], self.charcodes) 716 717 fontname = self.font.name 718 719 if self.decode: 720 if self.kerning: 721 data = self.font.metric.resolvekernings(self.glyphnames, self.size_pt) 722 else: 723 data = self.glyphnames 724 else: 725 data = self.charcodes 726 attrs = {"x": "%f" % self.x_pt, 727 "y": "%f" % -self.y_pt, 728 "font-size": "%f" % self.size_pt, 729 "font-family": fontname, 730 "fill": context.fillcolor} 731 if context.fillopacity: 732 attrs["opacity"] = "%f" % context.fillopacity 733 if self.slant: 734 trafo.trafo_pt(matrix=((1, self.slant), (0, 1))).outputSVGattrs(attrs, writer, context, registry) 735 xml.startSVGElement("text", attrs) 736 tspan = False 737 for i, value in enumerate(data): 738 if self.kerning and i % 2: 739 if value is not None: 740 if tspan: 741 xml.endSVGElement("tspan") 742 xml.startSVGElement("tspan", {"dx": "%f" % (value + self.spaced_pt)}) 743 tspan = True 744 elif self.spaced_pt: 745 if tspan: 746 xml.endSVGElement("tspan") 747 xml.startSVGElement("tspan", {"dx": "%f" % (self.spaced_pt)}) 748 tspan = True 749 else: 750 if i and not self.kerning and self.spaced_pt: 751 if tspan: 752 xml.endSVGElement("tspan") 753 xml.startSVGElement("tspan", {"dx": "%f" % (self.spaced_pt)}) 754 tspan = True 755 if self.decode: 756 xml.characters(t1mapping.glyphnames[value]) 757 else: 758 xml.characters(t1mapping.charcodes[value]) 759 if tspan: 760 xml.endSVGElement("tspan") 761 xml.endSVGElement("text") 762