File: gstate.py

package info (click to toggle)
python-rlpycairo 0.3.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 112 kB
  • sloc: python: 391; makefile: 7
file content (275 lines) | stat: -rw-r--r-- 8,959 bytes parent folder | download
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
__all__ = (
        'GState',
        )

import sys
try:
    import cairocffi as cairo   #prefer cairocffi
except ImportError:
    import cairo
from reportlab.lib.colors import toColor
from reportlab.graphics.transform import mmult
from PIL import Image as PILImage

class GState(object):
    __fill_rule_values = (1,0)

    def __init__(self, width=1, height=1, bg='white', fmt='RGB24'):
        self._fmt = fmt
        self.surface = cairo.ImageSurface(self.__str2format(fmt), width, height)
        self.width = width
        self.height = height
        self.ctx = ctx = cairo.Context(self.surface)
        if fmt=='RGB24':
            self.__set_source_color__ = lambda c:ctx.set_source_rgb(*c.rgb())
        elif fmt=='ARGB32':
            self.__set_source_color__ = lambda c:ctx.set_source_rgba(*c.rgba())
        else:
            raise ValueError('Bad fmt=%r for rlPyCairo.GState' % fmt)
        ctx.set_antialias(cairo.ANTIALIAS_BEST)
        self._in_transform = self._out_transform = (1,0,0,-1,0,height)
        self.ctm = (1,0,0,1,0,0)
        self.fillColor = bg
        ctx.rectangle(0,0,width,height)
        self.pathFill()
        self.pathBegin()
        self.__fillColor = self.__strokeColor = None
        def _text2PathDescription(text, x, y):
            try:
                from reportlab.graphics.utils import text2PathDescription, FTTextPath
                gs = FTTextPath()
            except ImportError:
                try:
                    from _rl_renderPM import gstate
                except ImportError:
                    try:
                        from reportlab.graphics._renderPM import gstate
                    except ImportError as _e:
                        raise ImportError('freetype-py is not installed and no libart based _renderPM can be imported') from _e
                from reportlab.graphics.utils import text2PathDescription
                gs = gstate(1,1)
            def _text2PathDescription(text, x, y):
                return text2PathDescription(
                                text, x=x, y=y,
                                fontName=self.fontName, fontSize=self.fontSize,
                                truncate=False, gs=gs,
                                )
            self._text2PathDescription = _text2PathDescription
            return _text2PathDescription(text, x, y)
        self._text2PathDescription = _text2PathDescription
        self.__pathOpMap__ = dict(
                moveTo=ctx.move_to,
                lineTo=ctx.line_to,
                curveTo=ctx.curve_to,
                closePath=ctx.close_path,
                )
        self.textRenderMode = 0

    @staticmethod
    def __str2format(fmt):
        return getattr(cairo,'FORMAT_'+fmt)

    @property
    def pixBuf(self):
        ba = self.surface.get_data()
        #despite the name they store it in 32 bits; we need to remove 8
        ba = bytearray(ba)
        if sys.byteorder=='little':
            #BGRA --> RGBA
            for i in range(0,len(ba),4):
                ba[i:i+3] = bytearray(reversed(ba[i:i+3]))
        else:
            #ARGB --> RGBA
            for i in range(0,len(ba),4):
                ba[i+3],ba[i:i+3] = ba[i],ba[i+1:i+4]
        if self._fmt=='RGB24':
            del ba[3::4] #we have red green blue spare
        return bytes(ba)

    @property
    def ctm(self):
        return mmult(self._out_transform,tuple(self.ctx.get_matrix()))

    @ctm.setter
    def ctm(self,mx):
        nctm = mmult(self._in_transform,mx)
        self.ctx.set_matrix(cairo.Matrix(*nctm))

    @property
    def fillColor(self):
        return self.__fillColor

    @fillColor.setter
    def fillColor(self,c):
        self.__fillColor = toColor(c) if c is not None else c

    @property
    def strokeColor(self):
        return self.__strokeColor

    @strokeColor.setter
    def strokeColor(self,c):
        self.__strokeColor = toColor(c) if c is not None else c

    @property
    def strokeWidth(self):
        return self.ctx.get_line_width()

    @strokeWidth.setter
    def strokeWidth(self, w):
        return self.ctx.set_line_width(w)

    @property
    def dashArray(self):
        return self.ctx.get_dash()

    @dashArray.setter
    def dashArray(self, da):
        if not da or not isinstance(da,(list,tuple)):
            da = 0, ()
        else:
            if isinstance(da[0],(list,tuple)):
                da = da[1],da[0]

        return self.ctx.set_dash(da[1], da[0])

    #lucky Cairo uses the same linCap/Join number values as PDF
    @property
    def lineCap(self):
        return int(self.ctx.get_line_cap())

    @lineCap.setter
    def lineCap(self, v):
        return self.ctx.set_line_cap(int(v))

    @property
    def lineJoin(self):
        return int(self.ctx.get_line_join())

    @lineJoin.setter
    def lineJoin(self, v):
        return self.ctx.set_line_join(int(v))

    #the values are the other way round from PDF
    @property
    def fillMode(self):
        return self.__fill_rule_values[int(self.ctx.get_fill_rule())]

    @fillMode.setter
    def fillMode(self, v):
        return self.ctx.set_fill_rule(self.__fill_rule_values[int(v)])

    def beginPath(self):
        self.ctx.new_path()

    def moveTo(self, x, y):
        self.ctx.move_to(float(x), float(y))

    def lineTo(self, x, y):
        self.ctx.line_to(float(x), float(y))

    def pathClose(self):
        self.ctx.close_path()

    def pathFill(self,fillMode=None):
        if self.__fillColor:
            if fillMode is not None:
                ofm = self.fillMode
                if ofm!=fillMode: self.fillMode = fillMode
            self.__set_source_color__(self.__fillColor)
            self.ctx.fill_preserve()
            if fillMode is not None and ofm!=fillMode: self.fillMode = ofm

    def pathStroke(self):
        if self.__strokeColor and self.strokeWidth>0:
            self.__set_source_color__(self.__strokeColor)
            self.ctx.stroke_preserve()

    def curveTo(self, x1, y1, x2, y2, x3, y3):
        self.ctx.curve_to(float(x1), float(y1),float(x2), float(y2),float(x3), float(y3))

    def pathBegin(self):
        self.ctx.new_path()

    def clipPathClear(self):
        self.ctx.rest_clip()

    def clipPathSet(self):
        ctx = self.ctx
        oPath = ctx.copy_path()
        ctx.new_path()
        ctx.clip()
        ctx.new_path()
        ctx.append_path(oPath)

    def clipPathAdd(self):
        self.ctx.clip_preserve()

    def setFont(self, fontName, fontSize):
        self.fontName = fontName
        self.fontSize = fontSize

    def drawString(self, x, y, text): 
        opMap = self.__pathOpMap__
        oPath = self.ctx.copy_path()
        oFM = self.fillMode
        tRM = self.textRenderMode
        try:
            self.ctx.new_path()
            for op in self._text2PathDescription(text, x, y):
                #call one of ctx.move_to/line_to/curve_to/close_path
                opMap[op[0]](*op[1:])
            if tRM in (0,2,4,6):
                self.pathFill(0)
            if tRM in (1,2,5,6):
                self.pathStroke()
            if tRM>=4:
                self.ctx.clip_preserve()
        finally:
            self.ctx.new_path()
            self.ctx.append_path(oPath)
            self.fillMode = oFM

    @classmethod
    def __fromPIL(cls, im, fmt='RGB24', alpha=1.0, forceAlpha=False):
        mode = im.mode
        im = im.copy()
        argb = fmt=='ARGB32'
        if mode=='RGB':
            im.putalpha(int(alpha * 255))
            if alpha!=1 and argb: im = im.convert('RGBa')
        elif mode=='RGBA' or forceAlpha:
            if forceAlpha:
                im.putalpha(int(alpha * 255))
            if argb: im = im.convert('RGBa')
        elif mode=='RGBa' or forceAlpha:
            if forceAlpha:
                im = im.convert('RGBA')
                im.putalpha(int(alpha * 255))
                if argb: im = im.convert('RGBa')
        fmt = cls.__str2format(fmt)
        # TODO need to fix this up for bigendian when cairo order is not BGRa
        if sys.byteorder=='little':
            ba = im.tobytes('raw','BGRa')
        else:
            ba = bytearray(im.tobytes('raw','RGBa'))
            for i in range(0,len(ba),4):
                ba[i],ba[i+1:i+4] = ba[i+3],ba[i:i+3]
            ba = bytes(ba)
        return cairo.ImageSurface.create_for_data(bytearray(ba),
                fmt, im.width, im.height,
                cairo.ImageSurface.format_stride_for_width(fmt,im.width),
                )

    def _aapixbuf(self, x, y, dstW, dstH,
                    data, srcW, srcH, planes=3,
                    ):
        ctx = self.ctx
        ctx.save()
        ctx.set_antialias(cairo.ANTIALIAS_DEFAULT)
        ctx.set_operator(cairo.OPERATOR_OVER)
        ctx.translate(x,y+dstH)
        ctx.scale(dstW/float(srcW),-dstH/float(srcH))
        ctx.set_source_surface(self.__fromPIL(data,self._fmt, forceAlpha=False))
        ctx.paint()
        ctx.restore()