Beispiel #1
0
class byA_Path(byA_FrozenClass):
    def __init__(self, *segments, **kw):
        byA_FrozenClass.__init__(self)
        self._segments = Path()
        for p in segments:
            assert isinstance(p, byA_Line) or isinstance(p, byA_CubicBezier)
            if isinstance(p, byA_Line):
                self.insert(-1, p)
            if isinstance(p, byA_CubicBezier):
                self.insert(-1, p)
        if 'closed' in kw:
            self._segments.closed = kw['closed']  # DEPRECATED
        self._freeze("byA_Path")

    def insert(self, index, value):
        assert isinstance(value, byA_Line) or isinstance(
            value, byA_CubicBezier)
        if isinstance(value, byA_Line):
            self._segments.insert(index, value._svgline)
        if isinstance(value, byA_CubicBezier):
            self._segments.insert(index, value._svgcubicbezier)

    def append(self, value):
        assert isinstance(value, byA_Line) or isinstance(
            value, byA_CubicBezier)
        if isinstance(value, byA_Line):
            self._segments.append(value._svgline)
        if isinstance(value, byA_CubicBezier):
            self._segments.append(value._cubicbezier)

    def toStr(self):
        return self._segments.d()
Beispiel #2
0
    def snap(self, tree, threshold):
        def process(points):
            for i, p in enumerate(points):
                best, _, dist = tree.nearest_neighbor([p.real, p.imag])

                if dist < threshold:
                    points[i] = complex(best[0], best[1])
            return points

        path = parse_path(self['d'])
        newPath = Path()
        for seg in path:
            points = process([seg.start, seg.end])

            if isinstance(seg, Line):
                newSeg = Line(*points)
                newPath.append(newSeg)

            elif isinstance(seg, CubicBezier):
                newSeg = CubicBezier(points[0], seg.control1, seg.control2,
                                     points[1])
                newPath.append(newSeg)

        self['d'] = newPath.d()
        return self
Beispiel #3
0
    def d(self, useSandT=False, use_closed_attrib=False, rel=False):
        """Returns a path d-string for the path object.
        For an explanation of useSandT and use_closed_attrib, see the
        compatibility notes in the README."""

        segments = [s._segment for s in self._segments]
        path = Path(*segments)
        return path.d(useSandT, use_closed_attrib, rel)
Beispiel #4
0
def generateInBetweens(poseA, poseB, steps):
    inv = Inventory()

    # make pairs
    pairs = []
    for key in ORDER:
        if key in poseA.inv and key in poseB.inv:
            partA = poseA.inv[key]
            partB = poseB.inv[key]

            if len(partA) != 1 or len(partB) != 1:
                print('Too many parts {0} - A: {1} B: {2}'.format(
                    key, partA.keys(), partB.keys()))
                continue

            pairs.append((key, partA.values()[0], partB.values()[0]))

    # If there are 3 steps, there are 4 gaps between start and finish
    # |------1------2------3------|
    gaps = steps + 1

    # process pairs
    for key, a, b in pairs:
        pathA = parse_path(a['d'])
        pathB = parse_path(b['d'])

        if len(pathA) != len(pathB):
            print('Unmatched segments {0} - A: {1} B: {2}'.format(
                key, pathA, pathB))
            continue

        for step in range(1, gaps):
            newPath = Path()
            for i in range(len(pathA)):
                segA = pathA[i]
                segB = pathB[i]

                if isinstance(segA, Line):
                    points = _deltaPoints([segA.start, segA.end],
                                          [segB.start, segB.end], step, gaps)
                    newPath.append(Line(*points))

                elif isinstance(segA, CubicBezier):
                    points = _deltaPoints(
                        [segA.start, segA.control1, segA.control2, segA.end],
                        [segB.start, segB.control1, segB.control2, segB.end],
                        step, gaps)
                    newPath.append(CubicBezier(*points))

            newPart = Part(newPath.d())
            newPart['x'] = int(_delta(a['x'], b['x'], step, gaps))
            newPart['y'] = int(_delta(a['y'], b['y'], step, gaps))
            newPart['z'] = int(_delta(a['z'], b['z'], step, gaps))

            inv.addPart(key, newPart)
            print(key, step, newPart)

    return inv
Beispiel #5
0
def splitSingleLine(start_end, unitLength, toPoint=False):
    '''
    return strings
    :param start_end:
    :param unitLength:
    :return:
    '''
    if UB.pointEquals(start_end[0], start_end[1]):
        return []
    pathStr = getStraightPath(start_end)
    path = parse_path(pathStr)
    try:
        l = path.length()
        if l > unitLength:
            paths = []
            t = unitLength / l
            ts = 0
            te = t
            for i in range(int(math.ceil(l / unitLength))):
                seg = Line(path.point(ts), path.point(te))
                p = Path(seg)
                if toPoint:
                    paths.append([
                        UB.getPointFromComplex(path.point(ts)),
                        UB.getPointFromComplex(path.point(te))
                    ])
                else:
                    paths.append(p.d())

                ts += t
                te += t
                te = min(1, te)
            if toPoint:
                paths.append([
                    UB.getPointFromComplex(path.point(te)),
                    UB.getPointFromComplex(path.point(1))
                ])
            else:
                paths.append(
                    getStraightPath([
                        UB.getPointFromComplex(path.point(te)),
                        UB.getPointFromComplex(path.point(1))
                    ]))
            return paths
        if toPoint:
            return [start_end]
        else:
            return [pathStr]
    except Exception as e:
        print(start_end, "something wrong with the splitLine", e)
        return []
    def add_outline_path(self,
                         paths,
                         width,
                         height,
                         radii=dict(bl=3, br=3, tl=3, tr=3),
                         type="path"):

        path = Path(*paths)

        self.outline.shape.height = height
        self.outline.shape.width = width
        self.outline.shape.radii = radii
        self.outline.shape.type = type
        self.outline.shape.value = absolute_to_relative_path(path.d())
 def replace_point(self, ptbefore, ptafter):
     pathstr = self._g.elements[0].tostring()
     pathstr = pathstr.split('"')[3]
     pathbefore = parse_path(pathstr)
     pathafter = Path()
     for i in pathbefore:
         if isinstance(i, Line):
             if (isclose(i.start.real, ptbefore._x)
                     and isclose(i.start.imag, ptbefore._y)):
                 i.start = ptafter._x + 1j * ptafter._y
             if (isclose(i.end.real, ptbefore._x)
                     and isclose(i.end.imag, ptbefore._y)):
                 i.end = ptafter._x + 1j * ptafter._y
         pathafter.append(i)
     self.add(svgwrite.path.Path(d=pathafter.d()))
Beispiel #8
0
def flatten_beziers(svg_d):
    sp = parse_path(svg_d)
    spn = SVGPath()

    for seg in sp:
        if isinstance(seg, Line):
            spn.append(seg)
        elif isinstance(seg, CubicBezier):
            B = [seg.bpoints()]
            foo = np.dot(B, CUBIC_TO_POLY_SAMPLE)
            spn.extend([Line(x, y) for x, y in zip(foo[0, :-1], foo[0, 1:])])
        else:
            raise RuntimeError(f"unsupported {seg}")

    return spn.d()
    def add_route(self,
                  paths,
                  layer='bottom',
                  stroke_width=0.4,
                  style="stroke",
                  type="path"):

        path = Path(*paths)

        self._add_route_to_layer(
            {
                "stroke-width": stroke_width,
                "style": style,
                "type": type,
                "value": absolute_to_relative_path(path.d())
            },
            layer=layer)
Beispiel #10
0
    def quantize(self):
        path = parse_path(self['d'])
        newPath = Path()
        for seg in path:
            if isinstance(seg, Line):
                newSeg = Line(
                    complex(round(seg.start.real), round(seg.start.imag)),
                    complex(round(seg.end.real), round(seg.end.imag)))
                newPath.append(newSeg)

            elif isinstance(seg, CubicBezier):
                newSeg = CubicBezier(
                    complex(round(seg.start.real), round(seg.start.imag)),
                    complex(round(seg.control1.real),
                            round(seg.control1.imag)),
                    complex(round(seg.control2.real),
                            round(seg.control2.imag)),
                    complex(round(seg.end.real), round(seg.end.imag)))
                newPath.append(newSeg)

        self['d'] = newPath.d()
        return self
Beispiel #11
0
def text2pathd(text, group_transform=(1, 0, 0, 1, 0, 0)):
    attributes = dom2dict(text)
    if "font-size" in attributes:
        font_size = float(attributes["font-size"])
    elif "style" in attributes:
        if attributes["style"].find("font-size") >= 0:
            font_size = attributes["style"].split("font-size:")[1].split(
                ";")[0]
            font_size = float(font_size.replace("px", ""))
        else:
            font_size = 12
    else:
        font_size = 12
    if "x" in attributes:
        x_global_offset = float(attributes["x"])
    else:
        x_global_offset = 0
    if "y" in attributes:
        y_global_offset = float(attributes["y"])
    else:
        y_global_offset = 0
    if hasattr(text.childNodes[0], "data"):
        text_string = text.childNodes[0].data
    else:
        flow_para = text.getElementsByTagName('flowPara')
        if flow_para:
            text_string = flow_para[0].childNodes[0].data
    # strip newline characters from the string, they aren't rendered in svg
    text_string = text_string.replace("\n", "").replace("\r", "")

    def tuple_to_imag(t):
        return t[0] + t[1] * 1j

    # keep fonts with repository, as dealing with importing fonts across platforms is a
    # nightmare
    foldername = os_path.dirname(os_path.abspath(__file__))
    face = Face(os_path.join(foldername, 'Vera.ttf'))

    face.set_char_size(48 * 64)
    scale = font_size / face.size.height
    outlines = []
    current_x = 0
    transform = get_transform(text)
    transform = combine_transforms(transform, group_transform)
    x_global_offset, y_global_offset = transform_point(
        [x_global_offset, y_global_offset], transform)
    for i, letter in enumerate(text_string):
        face.load_char(letter)
        outline = face.glyph.outline
        if i != 0:
            kerning = face.get_kerning(text_string[i - 1], text_string[i])
            kerning_x = kerning.x
        else:
            kerning_x = 0

        if text_string[i] == ' ':
            # a space is usually 30% of the widest character, capital W
            char_width = face.size.max_advance * 0.3
            char_height = 0
            char_offset = 0
        else:
            char_width = outline.get_bbox().xMax
            char_offset = face.size.height - outline.get_bbox().yMax
            char_height = outline.get_bbox().yMax

        outline_dict = {}
        current_x += kerning_x
        outline_dict["points"] = [
            (scale * (p[0] + current_x) + x_global_offset,
             scale * (char_offset + char_height - p[1]) + y_global_offset)
            for p in outline.points
        ]
        outline_dict["contours"] = outline.contours
        outline_dict["tags"] = outline.tags
        outlines.append(outline_dict)
        current_x += char_width

    paths = []
    for outline in outlines:
        start, end = 0, 0
        for i in range(len(outline["contours"])):
            end = outline["contours"][i]
            points = outline["points"][start:end + 1]
            points.append(points[0])
            tags = outline["tags"][start:end + 1]
            tags.append(tags[0])

            segments = [
                [
                    points[0],
                ],
            ]
            for j in range(1, len(points)):
                segments[-1].append(points[j])
                if tags[j] and j < (len(points) - 1):
                    segments.append([
                        points[j],
                    ])
            for segment in segments:
                if len(segment) == 2:
                    paths.append(
                        Line(start=tuple_to_imag(segment[0]),
                             end=tuple_to_imag(segment[1])))
                elif len(segment) == 3:
                    paths.append(
                        QuadraticBezier(start=tuple_to_imag(segment[0]),
                                        control=tuple_to_imag(segment[1]),
                                        end=tuple_to_imag(segment[2])))
                elif len(segment) == 4:
                    C = ((segment[1][0] + segment[2][0]) / 2.0,
                         (segment[1][1] + segment[2][1]) / 2.0)

                    paths.append(
                        QuadraticBezier(start=tuple_to_imag(segment[0]),
                                        control=tuple_to_imag(segment[1]),
                                        end=tuple_to_imag(C)))
                    paths.append(
                        QuadraticBezier(start=tuple_to_imag(C),
                                        control=tuple_to_imag(segment[2]),
                                        end=tuple_to_imag(segment[3])))
            start = end + 1

    path = Path(*paths)
    return path.d()
angle = 45
translate_y = 1


def c(a):
    return cos(a * pi / 180.)


def s(a):
    return sin(a * pi / 180.)


transform1 = c(angle), s(angle), -s(angle), c(angle), 0, 0
transform2 = 1, 0, 0, 1, 0, translate_y
transform = combine_transforms(transform1, transform2)
transform_m = "matrix({},{},{},{},{},{})".format(*transform)
dg = dwg.add(dwg.g(transform=transform_m))
dg.add(dwg.path(d=line.d(), stroke_width=1, stroke=rgb(255, 0, 0)))
line_transformed = parse_path(transform_path(transform, line.d()))
print(transform_path(transform, line.d()))
print(line_transformed)

dwg.add(dwg.path(d=line_transformed.d(), stroke_width=1, stroke=rgb(0, 255,
                                                                    0)))
'''
dwg.add(dwg.path(d=line.d(), stroke_width=1, stroke=rgb(0, 0, 255)))
dwg.add(dwg.path(d=line.rotated(-45).d(), stroke_width=1, stroke=rgb(0, 0, 255)))
'''
dwg.save()
xml = xml.dom.minidom.parse(path_filename)
open(path_filename, "w").write(xml.toprettyxml())
Beispiel #13
0
class Char2SvgPath:
    '''
    '''
    CHAR_SIZE = 2048

    def __init__(self, char: str, font: str, load_gpos_table=True):
        '''
        '''
        self.char = char
        self.font = font

        # load font
        self.face = freetype.Face(self.font)

        self.face.set_char_size(self.CHAR_SIZE,
                                self.CHAR_SIZE)  # -> x_ppem = y_ppem = 32
        '''
        The character widths and heights are specified in 1/64th of points. 
        A point is a physical distance, equaling 1/72th of an inch. Normally, it is not equivalent to a pixel.
        Value of 0 for the character width means ‘same as character height’, 
        value of 0 for the character height means ‘same as character width’. 
        
        Otherwise, it is possible to specify different character widths and heights.
        The horizontal and vertical device resolutions are expressed in dots-per-inch, or dpi. 
        Standard values are 72 or 96 dpi for display devices like the screen. 
        The resolution is used to compute the character pixel size from the character point size.
        Value of 0 for the horizontal resolution means ‘same as vertical resolution’, 
        value of 0 for the vertical resolution means ‘same as horizontal resolution’. 
        
        If both values are zero, 72 dpi is used for both dimensions.
        '''
        # initialize a character - option FT_LOAD_NO_SCALE mandatory
        self.face.load_char(
            self.char, freetype.FT_LOAD_NO_SCALE | freetype.FT_LOAD_NO_BITMAP
            | freetype.FT_KERNING_UNSCALED)

        # glyph info
        self.glyph_index = self.face.get_char_index(self.char)
        self.glyph_adv = self.face.get_advance(
            self.glyph_index,
            freetype.FT_LOAD_NO_SCALE | freetype.FT_LOAD_NO_BITMAP)

        self.bbox = self.face.glyph.outline.get_bbox()

        # evaluating the path:
        # --------------------

        # values for y-flip - if not set, use bbox
        self.yflip_value = None
        # values for x-translation - if not set, use bbox
        self.xshift_value = None

        # to eval
        self.path = None

        # GPOS kerning - with ziafont !
        if load_gpos_table:
            self.gpos = self.get_gpos_table()

    def get_gpos_table(self):
        '''
        from ziafont!
        '''
        import struct
        from datetime import datetime, timedelta
        from collections import namedtuple

        Table = namedtuple('Table', ['checksum', 'offset', 'length'])

        class FontReader(io.BytesIO):
            ''' Class for reading from Font File '''

            # TTF/OTF is big-endian (>)

            def readuint32(self, offset: int = None) -> int:
                ''' Read 32-bit unsigned integer '''
                if offset:
                    self.seek(offset)
                return struct.unpack('>I', self.read(4))[0]

            def readuint16(self, offset: int = None) -> int:
                ''' Read 16-bit unsigned integer '''
                if offset:
                    self.seek(offset)
                return struct.unpack('>H', self.read(2))[0]

            def readuint8(self, offset: int = None) -> int:
                ''' Read 8-bit unsigned integer '''
                if offset:
                    self.seek(offset)
                return struct.unpack('>B', self.read(1))[0]

            def readint8(self, offset: int = None) -> int:
                ''' Read 8-bit signed integer '''
                if offset:
                    self.seek(offset)
                return struct.unpack('>b', self.read(1))[0]

            def readdate(self, offset: int = None) -> datetime:
                ''' Read datetime '''
                if offset:
                    self.seek(offset)
                mtime = self.readuint32() * 0x100000000 + self.readuint32()
                fontepoch = datetime(1904, 1, 1)
                mdate = fontepoch + timedelta(seconds=mtime)
                return mdate

            def readint16(self, offset: int = None) -> int:
                ''' Read 16-bit signed integer '''
                if offset:
                    self.seek(offset)
                return struct.unpack('>h', self.read(2))[0]

            def readshort(self, offset: int = None) -> float:
                ''' Read "short" fixed point number (S1.14) '''
                x = self.readint16()
                return float(x) * 2**-14

            def readvaluerecord(self, fmt: int) -> dict:
                ''' Read a GPOS "ValueRecord" into a dictionary. Zero values will be omitted. '''
                record = {}
                if fmt & 0x0001:
                    record['x'] = self.readint16()
                if fmt & 0x0002:
                    record['y'] = self.readint16()
                if fmt & 0x0004:
                    record['xadvance'] = self.readint16()
                if fmt & 0x0008:
                    record['yadvance'] = self.readint16()
                if fmt & 0x0010:
                    record['xpladeviceoffset'] = self.readuint16()
                if fmt & 0x0020:
                    record['ypladeviceoffset'] = self.readuint16()
                if fmt & 0x0040:
                    record['xadvdeviceoffset'] = self.readuint16()
                if fmt & 0x0080:
                    record['yadvdeviceoffset'] = self.readuint16()
                return record

        with open(self.font, 'rb') as f:
            self.fontfile = FontReader(f.read())

            self.fontfile.seek(0)
            scalartype = self.fontfile.readuint32()
            numtables = self.fontfile.readuint16()
            searchrange = self.fontfile.readuint16()
            entryselector = self.fontfile.readuint16()
            rangeshift = self.fontfile.readuint16()  # numtables*16-searchrange

            # Table Directory (table 5)
            self.tables = {}
            for i in range(numtables):
                tag = self.fontfile.read(4).decode()
                self.tables[tag] = Table(checksum=self.fontfile.readuint32(),
                                         offset=self.fontfile.readuint32(),
                                         length=self.fontfile.readuint32())
            #for table in self.tables.keys():
            #    if table != 'head':
            #        self._verifychecksum(table)

            if 'glyf' not in self.tables:
                raise ValueError('Unsupported font (no glyf table).')

            if 'GPOS' in self.tables:
                return gpos.Gpos(self.tables['GPOS'].offset, self.fontfile)
            else:
                return None

    def set_yflip_value(self, val):
        '''
        '''
        self.yflip_value = val

    def set_xshift_value(self, val):
        '''
        '''
        self.xshift_value = val

    def calc_shift(self, next_ch: str):
        '''
        '''
        self.face.has_kerning

        shift = self.glyph_adv

        adv = 0

        if self.face.has_kerning:
            if self.gpos:
                # Only getting x-advance for first glyph.
                next_cc = Char2SvgPath(next_ch,
                                       self.font,
                                       load_gpos_table=False)
                adv = self.gpos.kern(self.glyph_index,
                                     next_cc.glyph_index)[0].get(
                                         'xadvance', 0)
            else:
                vector = self.face.get_kerning(self.char, next_ch)
                adv = vector.x

        #x += std::floor((glyph.lsb_delta - prevRsbDelta + kerning + 31) / 64.0);

        return shift + adv

    def calc_path(self, fontsize: float) -> Path:
        '''
        fontsize: svg font-size in px
        '''
        def tuple2complex(t):
            return t[0] + t[1] * 1j

        yflip = self.yflip_value
        if yflip == None:
            yflip = self.bbox.yMax

        xshift = self.xshift_value
        if xshift == None:
            xshift = self.bbox.xMin

        # extra scaling
        scaling = fontsize / self.CHAR_SIZE
        '''
        You'll need to flip the y values of the points in order to render
        the characters right-side-up:
        '''
        outline: freetype.Outline = self.face.glyph.outline

        # shift and flip the points
        outline_points = [((pt[0] - xshift) * scaling,
                           (yflip - pt[1]) * scaling) for pt in outline.points]
        '''
        The face has three lists of interest: the points, the tags, and the contours. 
        The points are the x/y coordinates of the start and end points of lines and  control points. 
        The tags indicate what type of point it is, where tag values of 0 are control points. 
        Finally, the contours are the end point list index for each shape. 
        Characters like i or ! have two shapes, most others have only one contour. 
        So, for each contour, we want to pick out only the tags and points for that contour.
        '''
        start, end = 0, 0
        paths = []

        for i in range(len(outline.contours)):
            end = outline.contours[i]
            points = outline_points[start:end + 1]
            points.append(points[0])
            tags = outline.tags[start:end + 1]
            tags.append(tags[0])
            '''
            Next, we want to split the points up into path segments, using the tags. If the tags are 0, 
            add the point to the current segment, else create a new segment, 
            so that control points stay with their path segments:
            '''
            segments = [
                [
                    points[0],
                ],
            ]
            for j in range(1, len(points)):
                segments[-1].append(points[j])
                if tags[j] and j < (len(points) - 1):
                    segments.append([
                        points[j],
                    ])
            '''
            Then convert the segments to lines. 
            '''
            for segment in segments:
                #print("segment (len=%d)" % len(segment))

                if len(segment) == 2:
                    paths.append(
                        Line(start=tuple2complex(segment[0]),
                             end=tuple2complex(segment[1])))

                elif len(segment) == 3:
                    C12 = segment[1]

                    P1 = segment[0]
                    P2 = segment[2]

                    paths.append(
                        QuadraticBezier(start=tuple2complex(P1),
                                        control=tuple2complex(C12),
                                        end=tuple2complex(P2)))

                elif len(segment) == 4:
                    C12 = segment[1]
                    C23 = segment[2]

                    P1 = segment[0]
                    P2 = ((segment[1][0] + segment[2][0]) / 2.0,
                          (segment[1][1] + segment[2][1]) / 2.0)
                    P3 = segment[3]

                    paths.append(
                        QuadraticBezier(start=tuple2complex(P1),
                                        control=tuple2complex(C12),
                                        end=tuple2complex(P2)))
                    paths.append(
                        QuadraticBezier(start=tuple2complex(P2),
                                        control=tuple2complex(C23),
                                        end=tuple2complex(P3)))

                elif len(segment) == 5:
                    C12 = segment[1]
                    C23 = segment[2]
                    C34 = segment[3]

                    P1 = segment[0]
                    P2 = ((segment[1][0] + segment[2][0]) / 2.0,
                          (segment[1][1] + segment[2][1]) / 2.0)
                    P3 = ((segment[2][0] + segment[3][0]) / 2.0,
                          (segment[2][1] + segment[3][1]) / 2.0)
                    P4 = segment[4]

                    paths.append(
                        QuadraticBezier(start=tuple2complex(P1),
                                        control=tuple2complex(C12),
                                        end=tuple2complex(P2)))
                    paths.append(
                        QuadraticBezier(start=tuple2complex(P2),
                                        control=tuple2complex(C23),
                                        end=tuple2complex(P3)))
                    paths.append(
                        QuadraticBezier(start=tuple2complex(P3),
                                        control=tuple2complex(C34),
                                        end=tuple2complex(P4)))

                else:
                    # with algo
                    N = len(segment) - 1

                    # first
                    Ps = segment[0]
                    Ctrl = segment[1]
                    Pe = ((segment[1][0] + segment[2][0]) / 2.0,
                          (segment[1][1] + segment[2][1]) / 2.0)

                    paths.append(
                        QuadraticBezier(start=tuple2complex(Ps),
                                        control=tuple2complex(Ctrl),
                                        end=tuple2complex(Pe)))

                    # second - ...
                    for k in range(2, len(segment) - 2):
                        Ps = ((segment[k - 1][0] + segment[k][0]) / 2.0,
                              (segment[k - 1][1] + segment[k][1]) / 2.0)
                        Ctrl = segment[k]
                        Pe = ((segment[k][0] + segment[k + 1][0]) / 2.0,
                              (segment[k][1] + segment[k + 1][1]) / 2.0)

                        paths.append(
                            QuadraticBezier(start=tuple2complex(Ps),
                                            control=tuple2complex(Ctrl),
                                            end=tuple2complex(Pe)))

                    # last
                    Ps = ((segment[N - 2][0] + segment[N - 1][0]) / 2.0,
                          (segment[N - 2][1] + segment[N - 1][1]) / 2.0)
                    Ctrl = segment[N - 1]
                    Pe = segment[N]

                    paths.append(
                        QuadraticBezier(start=tuple2complex(Ps),
                                        control=tuple2complex(Ctrl),
                                        end=tuple2complex(Pe)))
            '''
            Set the start location to the end location and continue. 
            You can use the svgpathtools Path to merge the paths:
            '''
            start = end + 1

        self.path = Path(*paths)

        return self.path

    def calc_path_freetype_decompose(self, fontsize: float) -> Path:
        '''
        '''
        outline: freetype.Outline = self.face.glyph.outline

        def move_to(a, ctx):
            ctx.append("M {},{}".format(a.x, a.y))

        def line_to(a, ctx):
            ctx.append("L {},{}".format(a.x, a.y))

        def conic_to(a, b, ctx):
            ctx.append("Q {},{} {},{}".format(a.x, a.y, b.x, b.y))

        def cubic_to(a, b, c, ctx):
            ctx.append("C {},{} {},{} {},{}".format(a.x, a.y, b.x, b.y, c.x,
                                                    c.y))

        ctx: List[str] = []

        outline.decompose(ctx,
                          move_to=move_to,
                          line_to=line_to,
                          conic_to=conic_to,
                          cubic_to=cubic_to)

        path_str = " ".join(ctx)

        # build a Path instance
        path = parse_path(path_str)

        # this path is not at the right position - it has to be scaled, shifted and flipped
        yflip = self.yflip_value
        if yflip == None:
            yflip = self.bbox.yMax

        xshift = self.xshift_value
        if xshift == None:
            xshift = self.bbox.xMin

        # extra scaling
        scaling = fontsize / self.CHAR_SIZE

        # let's go
        path = path.translated(-xshift)

        # flipping means a special transformation ... matrix(1,0,  0,-1,  0,7.5857)
        apath = Path()

        tf = np.identity(3)
        tf[1][1] = -1
        for seg in path:
            aseg = xxpath.transform(seg, tf)
            apath.append(aseg)

        # and the companion translation
        path = apath.translated(yflip * 1j)

        # finally
        path = path.scaled(scaling, scaling)

        self.path = path

        return self.path

    def write_path(self, prefix=""):
        print("path %s: %s" % (prefix, self.path.d()))
        wsvg(self.path,
             filename="char2path_%s_%s_convert.svg" % (self.char, prefix))
Beispiel #14
0
    def renderLabel(self, inString):
        dwg = svgwrite.Drawing()  # SVG drawing in memory
        strIdx = 0  # Used to iterate over inString
        xOffset = 100  # Cumulative character placement offset
        yOffset = 0  # Cumulative character placement offset
        charSizeX = 8  # Character size constant
        charSizeY = 8  # Character size constant
        baseline = 170  # Y value of text baseline
        glyphBounds = [
        ]  # List of boundingBox objects to track rendered character size
        finalSegments = []  # List of output paths
        escaped = False  # Track whether the current character was preceded by a '\'
        lineover = False  # Track whether the current character needs to be lined over
        lineoverList = []

        # If we can't find the typeface that the user requested, we have to quit
        try:
            face = Face(
                os.path.dirname(os.path.abspath(__file__)) + '/typeface/' +
                self.fontName + '.ttf')
            face.set_char_size(charSizeX, charSizeY, 200, 200)
        except Exception as e:
            print(e)
            print("WARN: No Typeface found with the name " + self.fontName +
                  ".ttf")
            sys.exit(0)  # quit Python

        # If the typeface that the user requested exists, but there's no position table for it, we'll continue with a warning
        try:
            table = __import__(
                'KiBuzzard.KiBuzzard.buzzard.typeface.' + self.fontName,
                globals(), locals(), ['glyphPos'])
            glyphPos = table.glyphPos
            spaceDistance = table.spaceDistance
        except:
            glyphPos = 0
            spaceDistance = 60
            print(
                "WARN: No Position Table found for this typeface. Composition will be haphazard at best."
            )

        # If there's lineover text, drop the text down to make room for the line
        dropBaseline = False
        a = False
        x = 0
        while x < len(inString):
            if x > 0 and inString[x] == '\\':
                a = True
                if x != len(inString) - 1:
                    x += 1
            if inString[x] == '!' and not a:
                dropBaseline = True
            a = False
            x += 1
        if dropBaseline:
            baseline = 190

        # Draw and compose the glyph portion of the tag
        for charIdx in range(len(inString)):
            # Check whether this character is a space
            if inString[charIdx] == ' ':
                glyphBounds.append(boundingBox(0, 0, 0, 0))
                xOffset += spaceDistance
                continue
            # Check whether this character is a backslash that isn't escaped
            # and isn't the first character (denoting a backslash-shaped tag)
            if inString[charIdx] == '\\' and charIdx > 0 and not escaped:
                glyphBounds.append(boundingBox(0, 0, 0, 0))
                escaped = True
                continue
            # If this is a non-escaped '!' mark the beginning of lineover
            if inString[charIdx] == '!' and not escaped:
                glyphBounds.append(boundingBox(0, 0, 0, 0))
                lineover = True
                # If we've hit the end of the string but not the end of the lineover
                # go ahead and finish it out
                if charIdx == len(inString) - 1 and len(lineoverList) > 0:
                    linePaths = []
                    linePaths.append(
                        Line(start=complex(lineoverList[0], 10),
                             end=complex(xOffset, 10)))
                    linePaths.append(
                        Line(start=complex(xOffset, 10),
                             end=complex(xOffset, 30)))
                    linePaths.append(
                        Line(start=complex(xOffset, 30),
                             end=complex(lineoverList[0], 30)))
                    linePaths.append(
                        Line(start=complex(lineoverList[0], 30),
                             end=complex(lineoverList[0], 10)))
                    linepath = Path(*linePaths)
                    linepath = elPath(linepath.d())
                    finalSegments.append(linepath)
                    lineover = False
                    lineoverList.clear()
                continue
            # All special cases end in 'continue' so if we've gotten here we can clear our flags
            if escaped:
                escaped = False

            face.load_char(
                inString[charIdx])  # Load character curves from font
            outline = face.glyph.outline  # Save character curves to var
            y = [t[1] for t in outline.points]
            # flip the points
            outline_points = [(p[0], max(y) - p[1]) for p in outline.points]
            start, end = 0, 0
            paths = []
            box = 0
            yOffset = 0

            for i in range(len(outline.contours)):
                end = outline.contours[i]
                points = outline_points[start:end + 1]
                points.append(points[0])
                tags = outline.tags[start:end + 1]
                tags.append(tags[0])
                segments = [
                    [
                        points[0],
                    ],
                ]
                box = boundingBox(points[0][0], points[0][1], points[0][0],
                                  points[0][1])
                for j in range(1, len(points)):
                    if not tags[j]:  # if this point is off-path
                        if tags[j - 1]:  # and the last point was on-path
                            segments[-1].append(
                                points[j])  # toss this point onto the segment
                        elif not tags[j -
                                      1]:  # and the last point was off-path
                            # get center point of two
                            newPoint = ((points[j][0] + points[j - 1][0]) /
                                        2.0,
                                        (points[j][1] + points[j - 1][1]) /
                                        2.0)
                            segments[-1].append(
                                newPoint
                            )  # toss this new point onto the segment
                            segments.append(
                                [
                                    newPoint,
                                    points[j],
                                ]
                            )  # and start a new segment with the new point and this one
                    elif tags[j]:  # if this point is on-path
                        segments[-1].append(
                            points[j])  # toss this point onto the segment
                        if j < (len(points) - 1):
                            segments.append(
                                [
                                    points[j],
                                ]
                            )  # and start a new segment with this point if we're not at the end

                for segment in segments:
                    if len(segment) == 2:
                        paths.append(
                            Line(start=tuple_to_imag(segment[0]),
                                 end=tuple_to_imag(segment[1])))

                    elif len(segment) == 3:
                        paths.append(
                            QuadraticBezier(start=tuple_to_imag(segment[0]),
                                            control=tuple_to_imag(segment[1]),
                                            end=tuple_to_imag(segment[2])))
                start = end + 1

            # Derive bounding box of character
            for segment in paths:
                i = 0
                while i < 10:
                    point = segment.point(0.1 * i)
                    if point.real > box.xMax:
                        box.xMax = point.real
                    if point.imag > box.yMax:
                        box.yMax = point.imag
                    if point.real < box.xMin:
                        box.xMin = point.real
                    if point.imag < box.yMin:
                        box.yMin = point.imag
                    i += 1

            glyphBounds.append(box)
            path = Path(*paths)
            if glyphPos != 0:
                try:
                    xOffset += glyphPos[inString[charIdx]].real
                    yOffset = glyphPos[inString[charIdx]].imag
                except:
                    pass
            if lineover and len(lineoverList) == 0:
                lineoverList.append(xOffset)
                lineover = False

            if (lineover and len(lineoverList) > 0):
                linePaths = []
                linePaths.append(
                    Line(start=complex(lineoverList[0], 10),
                         end=complex(xOffset, 10)))
                linePaths.append(
                    Line(start=complex(xOffset, 10), end=complex(xOffset, 30)))
                linePaths.append(
                    Line(start=complex(xOffset, 30),
                         end=complex(lineoverList[0], 30)))
                linePaths.append(
                    Line(start=complex(lineoverList[0], 30),
                         end=complex(lineoverList[0], 10)))
                linepath = Path(*linePaths)
                linepath = elPath(linepath.d())
                finalSegments.append(linepath)
                lineover = False
                lineoverList.clear()

            pathTransform = Matrix.translate(xOffset,
                                             baseline + yOffset - box.yMax)
            path = elPath(path.d()) * pathTransform
            path = elPath(path.d())
            finalSegments.append(path)
            xOffset += 30
            if glyphPos != 0:
                try:
                    xOffset -= glyphPos[inString[charIdx]].real
                except:
                    pass
            xOffset += (glyphBounds[charIdx].xMax - glyphBounds[charIdx].xMin)
            strIdx += 1

        if self.leftCap == '' and self.rightCap == '':
            for i in range(len(finalSegments)):
                svgObj = dwg.add(dwg.path(finalSegments[i].d()))
                svgObj['fill'] = "#000000"
        else:
            #draw the outline of the label as a filled shape and
            #subtract each latter from it
            tagPaths = []
            if self.rightCap == 'round':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Arc(start=complex(xOffset, 0),
                        radius=complex(100, 100),
                        rotation=180,
                        large_arc=1,
                        sweep=1,
                        end=complex(xOffset, 200)))
            elif self.rightCap == 'square':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0),
                         end=complex(xOffset + 50, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 0),
                         end=complex(xOffset + 50, 200)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 200),
                         end=complex(xOffset, 200)))
            elif self.rightCap == 'pointer':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0),
                         end=complex(xOffset + 50, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 0),
                         end=complex(xOffset + 100, 100)))
                tagPaths.append(
                    Line(start=complex(xOffset + 100, 100),
                         end=complex(xOffset + 50, 200)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 200),
                         end=complex(xOffset, 200)))
            elif self.rightCap == 'flagtail':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0),
                         end=complex(xOffset + 100, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset + 100, 0),
                         end=complex(xOffset + 50, 100)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 100),
                         end=complex(xOffset + 100, 200)))
                tagPaths.append(
                    Line(start=complex(xOffset + 100, 200),
                         end=complex(xOffset, 200)))
            elif self.rightCap == 'fslash':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0),
                         end=complex(xOffset + 50, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 0),
                         end=complex(xOffset, 200)))
            elif self.rightCap == 'bslash':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0),
                         end=complex(xOffset + 50, 200)))
                tagPaths.append(
                    Line(start=complex(xOffset + 50, 200),
                         end=complex(xOffset, 200)))
            elif self.rightCap == '' and self.leftCap != '':
                tagPaths.append(
                    Line(start=complex(100, 0), end=complex(xOffset, 0)))
                tagPaths.append(
                    Line(start=complex(xOffset, 0), end=complex(xOffset, 200)))

            if self.leftCap == 'round':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Arc(start=complex(100, 200),
                        radius=complex(100, 100),
                        rotation=180,
                        large_arc=0,
                        sweep=1,
                        end=complex(100, 0)))
            elif self.leftCap == 'square':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(50, 200)))
                tagPaths.append(
                    Line(start=complex(50, 200), end=complex(50, 0)))
                tagPaths.append(Line(start=complex(50, 0), end=complex(100,
                                                                       0)))
            elif self.leftCap == 'pointer':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(50, 200)))
                tagPaths.append(
                    Line(start=complex(50, 200), end=complex(0, 100)))
                tagPaths.append(Line(start=complex(0, 100), end=complex(50,
                                                                        0)))
                tagPaths.append(Line(start=complex(50, 0), end=complex(100,
                                                                       0)))
            elif self.leftCap == 'flagtail':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(0, 200)))
                tagPaths.append(
                    Line(start=complex(0, 200), end=complex(50, 100)))
                tagPaths.append(Line(start=complex(50, 100), end=complex(0,
                                                                         0)))
                tagPaths.append(Line(start=complex(0, 0), end=complex(100, 0)))
            elif self.leftCap == 'fslash':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(50, 200)))
                tagPaths.append(
                    Line(start=complex(50, 200), end=complex(100, 0)))
            elif self.leftCap == 'bslash':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(50, 0)))
                tagPaths.append(Line(start=complex(50, 0), end=complex(100,
                                                                       0)))
            elif self.leftCap == '' and self.rightCap != '':
                tagPaths.append(
                    Line(start=complex(xOffset, 200), end=complex(100, 200)))
                tagPaths.append(
                    Line(start=complex(100, 200), end=complex(100, 0)))

            path = Path(*tagPaths)
            for i in range(len(finalSegments)):
                path = elPath(path.d() + " " + finalSegments[i].reverse())
            tagObj = dwg.add(dwg.path(path.d()))
            tagObj['fill'] = "#000000"

        dwg['width'] = xOffset + 100
        dwg['height'] = 250

        #dwg.saveas('out.svg')

        print('create svg')

        return dwg
Beispiel #15
0
def flatten_shape(i, all_paths, merge_paths):
    dwg = Drawing("merge_output%s.svg" % i, profile='tiny')

    def draw_line(start, end, offset=0.0):
        start += offset
        end += offset
        dwg.add(
            dwg.line(start=(start.real, start.imag),
                     end=(end.real, end.imag),
                     stroke_width=4,
                     stroke=rgb(255, 0, 0)))

    dwg.add(
        dwg.path(
            **{
                'd': all_paths[i].d(),
                'fill': "none",
                'stroke-width': 4,
                'stroke': rgb(0, 0, 0)
            }))
    dwg.add(
        dwg.path(
            **{
                'd': merge_paths[i].d(),
                'fill': "none",
                'stroke-width': 4,
                'stroke': rgb(255, 0, 0)
            }))
    bbox = calc_overall_bbox(all_paths[i])
    width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2])
    margin = 40
    lower = min(bbox[2], bbox[3]) + height + margin
    left = min(bbox[0], bbox[1]) + margin

    def draw_marker(loc, col=rgb(255, 0, 0), offset=(left, lower)):
        dwg.add(
            dwg.circle(center=(loc.real + offset[0], loc.imag + offset[1]),
                       r=4,
                       fill=col))

    max_axis = max(width, height)
    num_lines = 10
    points = [merge_paths[i].point(j / num_lines)
              for j in range(num_lines)] + [merge_paths[i].point(1.0)]
    angles = [
        asin((points[j + 1].imag - points[j].imag) /
             abs(points[j + 1] - points[j])) for j in range(num_lines)
    ]

    ends = [max_axis * (sin(angle) + cos(angle) * 1j) for angle in angles]
    intersection_clips = []
    for j, end in enumerate(ends):
        end_point = end + points[j]
        intersections = other_paths[i].intersect(
            Line(start=points[j], end=end_point))

        for intersection in intersections[0]:
            intersection_point = intersection[1].point(intersection[2])
            target = merge_paths[i].length() * (
                1 - j / num_lines) + abs(intersection_point - points[j]) * 1j
            intersection_clips.append(
                PathClip(index=other_paths[i].index(intersection[1]),
                         t=intersection[2],
                         target=target))
            if j % 10 == 0:
                draw_line(points[j], intersection_point)
                draw_marker(intersection_point, rgb(0, 255, 0), (0, 0))
            break

    # make the flexed points by chopping the chunks of the other paths out, then
    # translating and rotating them such that their end points line up with the diff lines
    def transform_side(sides, targets, angle_offset=0):
        def angle(point1, point2):
            diff = point1 - point2
            if diff.real == 0:
                return 90.0
            return atan(diff.imag / diff.real) * 180.0 / pi

        # change this so that it has two targets
        transformed_side = Path(*sides)
        source_angle = angle(transformed_side.end, transformed_side.start) - \
                       angle(targets[0], targets[1])
        transformed_side = transformed_side.rotated(-source_angle +
                                                    angle_offset)
        source = transformed_side.end if angle_offset == 0 else transformed_side.start
        diff = targets[1] - source
        transformed_side = transformed_side.translated(diff)
        draw_marker(targets[0], rgb(0, 200, 200))
        draw_marker(targets[1], rgb(0, 255, 255))
        transformed_diff = abs(transformed_side.start - transformed_side.end)
        targets_diff = abs(targets[0] - targets[1])
        if transformed_diff < targets_diff:
            transformed_side.insert(
                0, Line(start=targets[0], end=transformed_side.start))
        elif transformed_diff > targets_diff:
            # pop elements off until the transformed diff is smaller
            while transformed_diff > targets_diff:
                transformed_side.pop(0)
                transformed_diff = abs(transformed_side.start -
                                       transformed_side.end)
            print("path", transformed_side)
            print("path is longer", transformed_diff - targets_diff)
        return transformed_side

    start_index = 0
    curr_t = 0
    flexed_path = []
    t_resolution = 0.01
    if intersection_clips[0].index > intersection_clips[-1].index or \
        (intersection_clips[0].index == intersection_clips[-1].index and
         intersection_clips[0].t > intersection_clips[-1].t):
        intersection_clips.reverse()
    # add the end of the shape to the intersection clips
    intersection_clips.append(
        PathClip(index=len(other_paths[i]) - 1,
                 t=1.0,
                 target=merge_paths[i].length()))
    last_target = 0
    for clip in intersection_clips:
        sides = []
        print("boundaries", start_index, clip.index, curr_t, clip.t)
        upper_t = clip.t if start_index == clip.index else 1.0
        while start_index <= clip.index and curr_t < upper_t:
            curr_seg = other_paths[i][start_index]
            while curr_t < upper_t:
                max_t = curr_t + t_resolution if curr_t + t_resolution < clip.t else clip.t
                sides.append(
                    Line(start=curr_seg.point(curr_t),
                         end=curr_seg.point(max_t)))
                curr_t += t_resolution
            curr_t = upper_t
            if start_index != clip.index:
                curr_t = 0.0
            if upper_t == 1.0:
                start_index += 1
                upper_t = clip.t if start_index == clip.index else 1.0
        if len(sides) != 0:
            flexed_path.append(
                transform_side(sides, [last_target, clip.target]))
        last_target = clip.target

    straight_path = [Line(start=0, end=merge_paths[i].length())]
    for p in flexed_path:
        p = p.translated(left + lower * 1j)
        dwg.add(
            dwg.path(d=p.d(),
                     fill="none",
                     stroke_width=4,
                     stroke=rgb(255, 0, 0)))

    transformed_path = flexed_path + straight_path
    transformed_path = Path(*transformed_path).translated(left + lower * 1j)
    dwg.add(
        dwg.path(d=transformed_path.d(),
                 fill="none",
                 stroke_width=4,
                 stroke=rgb(0, 0, 0)))
    bbox = calc_overall_bbox(list(all_paths[i]) + list(transformed_path))

    width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2])
    dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width, height)
    dwg.save()
    return flexed_path
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc, svg2paths, parse_path, wsvg
from pcbmode.utils.svg import absolute_to_relative_path

print("Build SVG Path from lines")


seg1 = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j) # A cubic beginning at (300, 100) and ending at (200, 300)
seg2 = Line(200+300j, 250+350j) # A line beginning at (200, 300) and ending at (250, 350)
path = Path(seg1, seg2) # A path traversing the cubic and then the line

# (svgpathtools only creates absolute path co-ordinates)

print(path.d())



print("Create SVG")
#(svgpathtools only creates absolute path co-ordinates)

outline_path = parse_path("m 10.858333,11 c 2.140681,-0.340482 4.281363,-0.680964 6.422044,-1.0214454 1.69236,-1.0123173 3.384721,-2.0246345 5.077081,-3.0369518 1.2656,-2.1059322 2.531201,-4.2118644 3.796801,-6.31779663 0.663858,-2.34543537 1.327716,-4.69087077 1.991574,-7.03630647 -0.223524,-1.829043 -0.447047,-3.6580857 -0.670571,-5.4871287 -0.600087,-0.758457 -1.200175,-1.516914 -1.800262,-2.275371 -17.1166667,0 -34.2333333,0 -51.35,0 -0.600087,0.758457 -1.200175,1.516914 -1.800262,2.275371 -0.223524,1.829043 -0.447047,3.6580857 -0.670571,5.4871287 0.663858,2.3454357 1.327716,4.6908711 1.991574,7.03630647 1.2656,2.10593223 2.531201,4.21186443 3.796801,6.31779663 1.69236,1.0123173 3.384721,2.0246345 5.077081,3.0369518 2.140681,0.3404814 4.281363,0.6809634 6.422044,1.0214454 7.2388887,0 14.4777773,0 21.716666,0 z", current_pos=0)
wsvg(outline_path, filename='svg/out/output1.svg')

p = parse_path("m 4.6147194,11.014797 c 0.04717,0.459725 -0.00339,0.582458 0.024033,1.056225", current_pos=0)

print p

print("Parse SVG")
paths, attributes = svg2paths('svg/in/triangle_relative.svg')

for path in paths:
    print absolute_to_relative_path(path.d())
Beispiel #17
0
    def char(self, ch):
        def tuple_to_imag(t):
            return t[0] + t[1] * 1j

        #face = Face('/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf')
        face = Face(ddd.DATA_DIR + '/fonts/OpenSansEmoji.ttf')
        face.set_char_size(self.char_size)
        face.load_char(ch)

        #kerning = face.get_kerning(ch, 'x')  # or from previous, actually?
        #print(kerning)

        outline = face.glyph.outline
        y = [t[1] for t in outline.points]
        # flip the points
        outline_points = [(p[0], max(y) - p[1]) for p in outline.points]

        start, end = 0, 0
        paths = []

        for i in range(len(outline.contours)):
            end = outline.contours[i]
            points = outline_points[start:end + 1]
            points.append(points[0])
            tags = outline.tags[start:end + 1]
            tags.append(tags[0])

            segments = [
                [
                    points[0],
                ],
            ]
            for j in range(1, len(points)):
                segments[-1].append(points[j])
                if tags[j] and j < (len(points) - 1):
                    segments.append([
                        points[j],
                    ])

            for segment in segments:
                if len(segment) == 2:
                    paths.append(
                        Line(start=tuple_to_imag(segment[0]),
                             end=tuple_to_imag(segment[1])))
                elif len(segment) == 3:
                    paths.append(
                        QuadraticBezier(start=tuple_to_imag(segment[0]),
                                        control=tuple_to_imag(segment[1]),
                                        end=tuple_to_imag(segment[2])))
                elif len(segment) == 4:
                    paths.append(
                        CubicBezier(start=tuple_to_imag(segment[0]),
                                    control1=tuple_to_imag(segment[1]),
                                    control2=tuple_to_imag(segment[2]),
                                    end=tuple_to_imag(segment[3])))
                    #C = ((segment[1][0] + segment[2][0]) / 2.0,
                    #     (segment[1][1] + segment[2][1]) / 2.0)
                    #paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]),
                    #                             control=tuple_to_imag(segment[1]),
                    #                             end=tuple_to_imag(C)))
                    #paths.append(QuadraticBezier(start=tuple_to_imag(C),
                    #                             control=tuple_to_imag(segment[2]),
                    #                             end=tuple_to_imag(segment[3])))

            start = end + 1

        path = Path(*paths)
        #wsvg(path, filename="/tmp/test.svg")
        path_d = path.d()

        # https://gis.stackexchange.com/questions/301605/how-to-create-shape-in-shapely-from-an-svg-path-element
        # This page also has info about SVG reading!

        #svgpath = 'M10 10 C 20 20, 40 20, 50 10Z'
        mpl_path = parse_path(path_d)

        coords = mpl_path.to_polygons(closed_only=True)

        item = None
        for c in coords:  # coords[1:]:
            if len(c) < 3: continue
            ng = ddd.polygon(c)  #.clean(eps=char_size / 100)  #.convex_hull()
            #ng.show()
            if item is None:
                item = ng
            elif item.contains(ng):
                item = item.subtract(ng)
            else:
                item = item.union(ng)
            item = item.clean(
                eps=self.char_size /
                200)  # Note that this is effectively limiting resolution

        #result = ddd.group([ddd.polygon(c) for c in coords], empty=2)
        result = item
        result = result.scale([1.0 / self.char_size, -1.0 / self.char_size])
        result = result.simplify(
            0.005)  # Note that this is effectively limiting resolution

        return (result, face)