This file is indexed.

/usr/lib/python3/dist-packages/cu2qu/ufo.py is in python3-cu2qu 1.3.0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Converts cubic bezier curves to quadratic splines.

Conversion is performed such that the quadratic splines keep the same end-curve
tangents as the original cubics. The approach is iterative, increasing the
number of segments for a spline until the error gets below a bound.

Respective curves from multiple fonts will be converted at once to ensure that
the resulting splines are interpolation-compatible.
"""


from __future__ import print_function, division, absolute_import

import logging
from fontTools.pens.basePen import AbstractPen

from cu2qu import curves_to_quadratic
from cu2qu.pens import ReverseContourPen

__all__ = ['fonts_to_quadratic', 'font_to_quadratic']

DEFAULT_MAX_ERR = 0.001

logger = logging.getLogger(__name__)


class IncompatibleGlyphsError(ValueError):

    def __str__(self):
        return ", ".join(set(repr(glyph.name) for glyph in self.args))


class UnequalZipLengthsError(ValueError):
    pass


_zip = zip
def zip(*args):
    """Ensure each argument to zip has the same length. Also make sure a list is
    returned for python 2/3 compatibility.
    """

    if len(set(len(a) for a in args)) != 1:
        raise UnequalZipLengthsError(*args)
    return list(_zip(*args))


class GetSegmentsPen(AbstractPen):
    """Pen to collect segments into lists of points for conversion.

    Curves always include their initial on-curve point, so some points are
    duplicated between segments.
    """

    def __init__(self):
        self._last_pt = None
        self.segments = []

    def _add_segment(self, tag, *args):
        if tag in ['move', 'line', 'qcurve', 'curve']:
            self._last_pt = args[-1]
        self.segments.append((tag, args))

    def moveTo(self, pt):
        self._add_segment('move', pt)

    def lineTo(self, pt):
        self._add_segment('line', pt)

    def qCurveTo(self, *points):
        self._add_segment('qcurve', self._last_pt, *points)

    def curveTo(self, *points):
        self._add_segment('curve', self._last_pt, *points)

    def closePath(self):
        self._add_segment('close')

    def endPath(self):
        self._add_segment('end')

    def addComponent(self, glyphName, transformation):
        pass


def _get_segments(glyph):
    """Get a glyph's segments as extracted by GetSegmentsPen."""

    pen = GetSegmentsPen()
    glyph.draw(pen)
    return pen.segments


def _set_segments(glyph, segments, reverse_direction):
    """Draw segments as extracted by GetSegmentsPen back to a glyph."""

    glyph.clearContours()
    pen = glyph.getPen()
    if reverse_direction:
        pen = ReverseContourPen(pen)
    for tag, args in segments:
        if tag == 'move':
            pen.moveTo(*args)
        elif tag == 'line':
            pen.lineTo(*args)
        elif tag == 'qcurve':
            pen.qCurveTo(*args[1:])
        elif tag == 'close':
            pen.closePath()
        elif tag == 'end':
            pen.endPath()
        else:
            raise AssertionError('Unhandled segment type "%s"' % tag)


def _segments_to_quadratic(segments, max_err, stats):
    """Return quadratic approximations of cubic segments."""

    assert all(s[0] == 'curve' for s in segments), 'Non-cubic given to convert'

    new_points = curves_to_quadratic([s[1] for s in segments], max_err)
    n = len(new_points[0])
    assert all(len(s) == n for s in new_points[1:]), 'Converted incompatibly'

    spline_length = str(n - 2)
    stats[spline_length] = stats.get(spline_length, 0) + 1

    return [('qcurve', p) for p in new_points]


def _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats):
    """Do the actual conversion of a set of compatible glyphs, after arguments
    have been set up.

    Return True if the glyphs were modified, else return False.
    """

    try:
        segments_by_location = zip(*[_get_segments(g) for g in glyphs])
    except UnequalZipLengthsError:
        raise IncompatibleGlyphsError(*glyphs)
    if not any(segments_by_location):
        return False

    # always modify input glyphs if reverse_direction is True
    glyphs_modified = reverse_direction

    new_segments_by_location = []
    for segments in segments_by_location:
        tag = segments[0][0]
        if not all(s[0] == tag for s in segments[1:]):
            raise IncompatibleGlyphsError(*glyphs)
        if tag == 'curve':
            segments = _segments_to_quadratic(segments, max_err, stats)
            glyphs_modified = True
        new_segments_by_location.append(segments)

    if glyphs_modified:
        new_segments_by_glyph = zip(*new_segments_by_location)
        for glyph, new_segments in zip(glyphs, new_segments_by_glyph):
            _set_segments(glyph, new_segments, reverse_direction)

    return glyphs_modified


def glyphs_to_quadratic(
        glyphs, max_err=None, reverse_direction=False, stats=None):
    """Convert the curves of a set of compatible of glyphs to quadratic.

    All curves will be converted to quadratic at once, ensuring interpolation
    compatibility. If this is not required, calling glyphs_to_quadratic with one
    glyph at a time may yield slightly more optimized results.

    Return True if glyphs were modified, else return False.

    Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines.
    """
    if stats is None:
        stats = {}

    if not max_err:
        # assume 1000 is the default UPEM
        max_err = DEFAULT_MAX_ERR * 1000

    if isinstance(max_err, (list, tuple)):
        max_errors = max_err
    else:
        max_errors = [max_err] * len(glyphs)
    assert len(max_errors) == len(glyphs)

    return _glyphs_to_quadratic(glyphs, max_errors, reverse_direction, stats)


def fonts_to_quadratic(
        fonts, max_err_em=None, max_err=None, reverse_direction=False,
        stats=None, dump_stats=False):
    """Convert the curves of a collection of fonts to quadratic.

    All curves will be converted to quadratic at once, ensuring interpolation
    compatibility. If this is not required, calling fonts_to_quadratic with one
    font at a time may yield slightly more optimized results.

    Return True if fonts were modified, else return False.

    Raises IncompatibleGlyphsError if same-named glyphs from different fonts
    have non-interpolatable outlines.
    """

    if stats is None:
        stats = {}

    if max_err_em and max_err:
        raise TypeError('Only one of max_err and max_err_em can be specified.')
    if not (max_err_em or max_err):
        max_err_em = DEFAULT_MAX_ERR

    if isinstance(max_err, (list, tuple)):
        assert len(max_err) == len(fonts)
        max_errors = max_err
    elif max_err:
        max_errors = [max_err] * len(fonts)

    if isinstance(max_err_em, (list, tuple)):
        assert len(fonts) == len(max_err_em)
        max_errors = [f.info.unitsPerEm * e
                      for f, e in zip(fonts, max_err_em)]
    elif max_err_em:
        max_errors = [f.info.unitsPerEm * max_err_em for f in fonts]

    modified = False
    for name in set().union(*(f.keys() for f in fonts)):
        glyphs = []
        cur_max_errors = []
        for font, error in zip(fonts, max_errors):
            if name in font:
                glyphs.append(font[name])
                cur_max_errors.append(error)
        modified |= _glyphs_to_quadratic(
            glyphs, cur_max_errors, reverse_direction, stats)

    if modified and dump_stats:
        spline_lengths = sorted(stats.keys())
        logger.info('New spline lengths: %s' % (', '.join(
                    '%s: %d' % (l, stats[l]) for l in spline_lengths)))
    return modified


def glyph_to_quadratic(glyph, **kwargs):
    """Convenience wrapper around glyphs_to_quadratic, for just one glyph.
    Return True if the glyph was modified, else return False.
    """

    return glyphs_to_quadratic([glyph], **kwargs)


def font_to_quadratic(font, **kwargs):
    """Convenience wrapper around fonts_to_quadratic, for just one font.
    Return True if the font was modified, else return False.
    """

    return fonts_to_quadratic([font], **kwargs)