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