Example #1
0
def flip_path(upside_down_path):
    path = []
    _, _, min_y, max_y = upside_down_path.bbox()
    offset = max_y + min_y
    for segment in upside_down_path._segments:
        if type(segment) is Line:
            path.append(
                Line(complex(segment.start.real, -segment.start.imag + offset),
                     complex(segment.end.real, -segment.end.imag + offset)))
        elif type(segment) is Arc:
            path.append(
                Arc(complex(segment.start.real,
                            -segment.start.imag + offset), segment.radius,
                    abs(180 - segment.rotation), segment.large_arc,
                    not segment.sweep,
                    complex(segment.end.real, -segment.end.imag + offset)))
        elif type(segment) is QuadraticBezier:
            path.append(
                QuadraticBezier(
                    complex(segment.start.real, -segment.start.imag + offset),
                    complex(segment.control.real,
                            -segment.control.imag + offset),
                    complex(segment.end.real, -segment.end.imag + offset)))
        else:
            raise ValueError(f"Unknown type: {type(segment)}")

    return Path(*path)
Example #2
0
    def save_points_as_svg_handler(points, size, image_number):
        # filename like file:///home/gdshen/Pictures/00000.jpg
        filename = os.path.join(project_path, svg_dir, image_number + '.svg')
        paths = []
        for i in range(size):
            start_point_x = points.property(i).property('startPoint').property(
                'X').toInt()
            start_point_y = points.property(i).property('startPoint').property(
                'Y').toInt()
            control_point_x = points.property(i).property(
                'controlPoint').property('X').toInt()
            control_point_y = points.property(i).property(
                'controlPoint').property('Y').toInt()
            target_point_x = points.property(i).property(
                'targetPoint').property('X').toInt()
            target_point_y = points.property(i).property(
                'targetPoint').property('Y').toInt()
            print(start_point_x, start_point_y, control_point_x,
                  control_point_y, target_point_x, target_point_y)
            paths.append(
                Path(
                    QuadraticBezier(complex(start_point_x, start_point_y),
                                    complex(control_point_x, control_point_y),
                                    complex(target_point_x, target_point_y))))

        wsvg(paths=paths, filename=filename)
Example #3
0
    def qCurveTo(self, *points):
        cpoints = [self.convertPoint(self._lastOnCurve)]
        cpoints.extend([self.convertPoint(p) for p in points])

        if len(cpoints) <= 3:
            self._contour.append(QuadraticBezier(*cpoints))
        else:
            # a starting on-curve point, two or more off-curve points, and a final on-curve point
            startPoint = cpoints[0]
            for i in range(1, len(cpoints) - 2):
                impliedPoint = ((cpoints[i] + cpoints[i + 1]) / 2)
                self._contour.append(
                    QuadraticBezier(startPoint, cpoints[i], impliedPoint))
                startPoint = impliedPoint
            self._contour.append(
                QuadraticBezier(startPoint, cpoints[-2], cpoints[-1]))
        self.logger.debug(f"qCurveTo({points})")
        self._lastOnCurve = points[-1]
def sample_points_from_curves(curves, template):
    curves = apply_templates(curves).squeeze()
    curves = curves.reshape([-1, 6])[:sum(templates.topology[:template])]
    segments = []
    points = []
    for curve in curves:
        segment = []
        for point in curve.reshape([-1, 2]):
            segment.append(complex(*point).conjugate())
        segments.append(QuadraticBezier(*segment))
    for seg in segments:
        length = seg.length()
        for a in np.linspace(0, 1, num=length * n_points_per_unit_length):
            points.append(seg.point(seg.ilength(a * length)))

    return [(p.real, -p.imag) for p in points]
Example #5
0
def draw_glyph(font, char, ctx, offset=(0, 0), color=(0.6, 0.6, 0.6)):
    try:
        face = Face('data/ttfs/{}.ttf'.format(font))
    except:
        face = Face('data/ttfs/{}.otf'.format(font))
    face.set_char_size(48*64)
    face.load_char(char)
    outline = face.glyph.outline
    contours = [-1] + outline.contours
    segment = []
    segments = []
    paths = []
    for i in range(len(outline.points)):
        segment.append(complex(*outline.points[i]).conjugate())
        tag = int(bin(outline.tags[i])[2])
        try:
            j = contours.index(i)
            if tag == 0:
                segment.append(complex(*outline.points[contours[j-1]+1]).conjugate())
                tag = 2
            else:
                tag = 3
        except ValueError:
            pass
        if tag > 0:
            if len(segment) == 1:
                pass
            elif len(segment) == 2:
                segments.append(Line(*segment))
            elif len(segment) == 3:
                segments.append(QuadraticBezier(*segment))
            elif len(segment) == 4:
                segments.append(CubicBezier(*segment))
            else:
                for k in range(len(segment)-1):
                    A, C = segment[k:k+2]
                    B = (A+C) / 2
                    segments.append(QuadraticBezier(A, B, C))

            if tag == 1:
                segment = [complex(*outline.points[i]).conjugate()]
            elif tag == 2:
                paths.append(Path(*segments))
                segments = []
                segment = []
            else:
                segments.append(Line(segment[-1], complex(*outline.points[contours[j-1]+1]).conjugate()))
                paths.append(Path(*segments))
                segments = []
                segment = []

    xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
    factor = 0.8 / max(xmax-xmin, ymax-ymin)
    for i, path in enumerate(paths):
        paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor)
    xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
    xmargin = (1 - (xmax-xmin)) / 2
    ymargin = (1 - (ymax-ymin)) / 2
    for i, path in enumerate(paths):
        paths[i] = path.translated(complex(xmargin, ymargin))

    ctx.set_source_rgb(*color)
    ctx.new_path()
    ctx.set_line_width(0.02)
    x, y = offset
    for path in paths:
        ctx.move_to(path[0].bpoints()[0].real + x, path[0].bpoints()[0].imag + y)
        for seg in path:
            bpoints = seg.bpoints()
            if len(bpoints) == 2:
                ctx.line_to(bpoints[1].real + x, bpoints[1].imag + y)
            elif len(bpoints) == 3:
                ctx.curve_to(bpoints[0].real * 1/3 + bpoints[1].real * 2/3 + x,
                             bpoints[0].imag * 1/3 + bpoints[1].imag * 2/3 + y,
                             bpoints[1].real * 2/3 + bpoints[2].real * 1/3 + x,
                             bpoints[1].imag * 2/3 + bpoints[2].imag * 1/3 + y,
                             bpoints[2].real + x, bpoints[2].imag + y)
            elif len(bpoints) == 4:
                ctx.curve_to(bpoints[1].real + x, bpoints[1].imag + y,
                             bpoints[2].real + x, bpoints[2].imag + y,
                             bpoints[3].real + x, bpoints[3].imag + y)
    ctx.fill()
Example #6
0
def process_letter(font_char):
    try:
        font, char = font_char
        name = "{}_{}".format(char, os.path.splitext(font)[0])
        face = Face('data/fonts/ttfs/{}'.format(font))
        face.set_char_size(48 * 64)
        face.load_char(char)
        outline = face.glyph.outline
        contours = [-1] + outline.contours
        segment = []
        segments = []
        paths = []
        for i in range(len(outline.points)):
            segment.append(complex(*outline.points[i]).conjugate())
            tag = int(bin(outline.tags[i])[2])
            try:
                j = contours.index(i)
                if tag == 0:
                    segment.append(
                        complex(*outline.points[contours[j - 1] +
                                                1]).conjugate())
                    tag = 2
                else:
                    tag = 3
            except ValueError:
                pass
            if tag > 0:
                if len(segment) == 1:
                    pass
                elif len(segment) == 2:
                    segments.append(Line(*segment))
                elif len(segment) == 3:
                    segments.append(QuadraticBezier(*segment))
                elif len(segment) == 4:
                    segments.append(CubicBezier(*segment))
                else:
                    for k in range(len(segment) - 1):
                        A, C = segment[k:k + 2]
                        B = (A + C) / 2
                        segments.append(QuadraticBezier(A, B, C))

                if tag == 1:
                    segment = [complex(*outline.points[i]).conjugate()]
                elif tag == 2:
                    paths.append(Path(*segments))
                    segments = []
                    segment = []
                else:
                    segments.append(
                        Line(
                            segment[-1],
                            complex(*outline.points[contours[j - 1] +
                                                    1]).conjugate()))
                    paths.append(Path(*segments))
                    segments = []
                    segment = []

        xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
        factor = 0.8 / max(xmax - xmin, ymax - ymin)
        for i, path in enumerate(paths):
            paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor)
        xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
        xmargin = (1 - (xmax - xmin)) / 2
        ymargin = (1 - (ymax - ymin)) / 2
        for i, path in enumerate(paths):
            paths[i] = path.translated(complex(xmargin, ymargin))

        points = []
        surface = cairo.ImageSurface(cairo.Format.RGB24, opt.img_size,
                                     opt.img_size)
        ctx = cairo.Context(surface)
        ctx.scale(opt.img_size, opt.img_size)
        ctx.set_source_rgba(1, 1, 1)
        ctx.rectangle(0, 0, 1, 1)
        ctx.fill()

        ctx.set_source_rgb(0, 0, 0)
        ctx.new_path()
        ctx.set_line_width(0.02)
        for path in paths:
            ctx.move_to(path[0].bpoints()[0].real, path[0].bpoints()[0].imag)
            for seg in path:
                bpoints = seg.bpoints()
                if len(bpoints) == 2:
                    ctx.line_to(bpoints[1].real, bpoints[1].imag)
                elif len(bpoints) == 3:
                    ctx.curve_to(
                        bpoints[0].real * 1 / 3 + bpoints[1].real * 2 / 3,
                        bpoints[0].imag * 1 / 3 + bpoints[1].imag * 2 / 3,
                        bpoints[1].real * 2 / 3 + bpoints[2].real * 1 / 3,
                        bpoints[1].imag * 2 / 3 + bpoints[2].imag * 1 / 3,
                        bpoints[2].real, bpoints[2].imag)
                elif len(bpoints) == 4:
                    ctx.curve_to(bpoints[1].real, bpoints[1].imag,
                                 bpoints[2].real, bpoints[2].imag,
                                 bpoints[3].real, bpoints[3].imag)
            for t in np.linspace(0,
                                 1,
                                 num=opt.n_points_sampled // len(paths) + 1):
                points.append(path.point(t))
        ctx.fill()

        n_points = len(points)
        points = np.array(points,
                          dtype=np.complex64).view(np.float32).reshape([-1, 2])
        np.random.shuffle(points)
        np.save('data/fonts/points/{}.npy'.format(name), points)

        grid = np.mgrid[-0.25:1.25:opt.img_size * 1.5j,
                        -0.25:1.25:opt.img_size * 1.5j].T[:, :, None, :]
        distances = np.empty((grid.shape[0], grid.shape[1]))
        for i in range(grid.shape[0]):
            for j in range(grid.shape[0]):
                distances[i, j] = np.amin(
                    np.linalg.norm(grid[i, j] - points, axis=1))
        if not np.isnan(distances).any():
            np.save('data/fonts/distances/{}.npy'.format(name), distances)
            surface.write_to_png('data/fonts/pngs/{}.png'.format(name))
        else:
            return e
    except Exception as e:
        return e

    return None
Example #7
0
def parse_path(pathdef, current_pos=0j, tree_element=None):
    # In the SVG specs, initial movetos are absolute, even if
    # specified as 'm'. This is the default behavior here as well.
    # But if you pass in a current_pos variable, the initial moveto
    # will be relative to that current_pos. This is useful.
    elements = list(_tokenize_path(pathdef))
    # Reverse for easy use of .pop()
    elements.reverse()

    if tree_element is None:
        segments = Path()
    else:
        segments = Path(tree_element=tree_element)

    start_pos = None
    command = None

    while elements:

        if elements[-1] in COMMANDS:
            # New command.
            last_command = command  # Used by S and T
            command = elements.pop()
            absolute = command in UPPERCASE
            command = command.upper()
        else:
            # If this element starts with numbers, it is an implicit command
            # and we don't change the command. Check that it's allowed:
            if command is None:
                raise ValueError(
                    "Unallowed implicit command in %s, position %s" %
                    (pathdef, len(pathdef.split()) - len(elements)))

        if command == 'M':
            # Moveto command.
            x = elements.pop()
            y = elements.pop()
            pos = float(x) + float(y) * 1j
            if absolute:
                current_pos = pos
            else:
                current_pos += pos

            # when M is called, reset start_pos
            # This behavior of Z is defined in svg spec:
            # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
            start_pos = current_pos

            # Implicit moveto commands are treated as lineto commands.
            # So we set command to lineto here, in case there are
            # further implicit commands after this moveto.
            command = 'L'

        elif command == 'Z':
            # Close path
            if not (current_pos == start_pos):
                segments.append(Line(current_pos, start_pos))
            segments.closed = True
            current_pos = start_pos
            command = None

        elif command == 'L':
            x = elements.pop()
            y = elements.pop()
            pos = float(x) + float(y) * 1j
            if not absolute:
                pos += current_pos
            segments.append(Line(current_pos, pos))
            current_pos = pos

        elif command == 'H':
            x = elements.pop()
            pos = float(x) + current_pos.imag * 1j
            if not absolute:
                pos += current_pos.real
            segments.append(Line(current_pos, pos))
            current_pos = pos

        elif command == 'V':
            y = elements.pop()
            pos = current_pos.real + float(y) * 1j
            if not absolute:
                pos += current_pos.imag * 1j
            segments.append(Line(current_pos, pos))
            current_pos = pos

        elif command == 'C':
            control1 = float(elements.pop()) + float(elements.pop()) * 1j
            control2 = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control1 += current_pos
                control2 += current_pos
                end += current_pos

            segments.append(CubicBezier(current_pos, control1, control2, end))
            current_pos = end

        elif command == 'S':
            # Smooth curve. First control point is the "reflection" of
            # the second control point in the previous path.

            if last_command not in 'CS':
                # If there is no previous command or if the previous command
                # was not an C, c, S or s, assume the first control point is
                # coincident with the current point.
                control1 = current_pos
            else:
                # The first control point is assumed to be the reflection of
                # the second control point on the previous command relative
                # to the current point.
                control1 = current_pos + current_pos - segments[-1].control2

            control2 = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control2 += current_pos
                end += current_pos

            segments.append(CubicBezier(current_pos, control1, control2, end))
            current_pos = end

        elif command == 'Q':
            control = float(elements.pop()) + float(elements.pop()) * 1j
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                control += current_pos
                end += current_pos

            segments.append(QuadraticBezier(current_pos, control, end))
            current_pos = end

        elif command == 'T':
            # Smooth curve. Control point is the "reflection" of
            # the second control point in the previous path.

            if last_command not in 'QT':
                # If there is no previous command or if the previous command
                # was not an Q, q, T or t, assume the first control point is
                # coincident with the current point.
                control = current_pos
            else:
                # The control point is assumed to be the reflection of
                # the control point on the previous command relative
                # to the current point.
                control = current_pos + current_pos - segments[-1].control

            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                end += current_pos

            segments.append(QuadraticBezier(current_pos, control, end))
            current_pos = end

        elif command == 'A':
            radius = float(elements.pop()) + float(elements.pop()) * 1j
            rotation = float(elements.pop())
            arc = float(elements.pop())
            sweep = float(elements.pop())
            end = float(elements.pop()) + float(elements.pop()) * 1j

            if not absolute:
                end += current_pos

            segments.append(Arc(current_pos, radius, rotation, arc, sweep,
                                end))
            current_pos = end

    return segments
Example #8
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
Example #9
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()
Example #10
0
def char(ch):
    def tuple_to_imag(t):
        return t[0] + t[1] * 1j

    from freetype import Face
    #face = Face('/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf')
    face = Face(ddd.DATA_DIR + '/fonts/OpenSansEmoji.ttf')
    face.set_char_size(48 * 64)
    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:
                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!

    from svgpath2mpl import parse_path
    #svgpath = 'M10 10 C 20 20, 40 20, 50 10Z'
    mpl_path = parse_path(path_d)
    coords = mpl_path.to_polygons()

    # Add or subtract
    char_2d = ddd.polygon(coords[0])
    for c in coords[1:]:
        ng = ddd.polygon(c)
        #print (ng.geom.is_valid)
        if not ng.geom.is_valid: continue
        if char_2d.contains(ng):
            char_2d = char_2d.subtract(ng)
        else:
            char_2d = char_2d.union(ng)

    #result = ddd.group([ddd.polygon(c) for c in coords], empty=2)
    result = char_2d
    result = result.scale([1.0 / (48 * 64), -1.0 / (48 * 64)])

    result = result.simplify(0.005)  #

    return result
Example #11
0
def char_to_path(char: str) -> Tuple[Path, freetype.GlyphMetrics]:
    if len(char) != 1:
        raise ValueError(f'1 character at a time; got {len(char)}\n"{char}"')
    if re.search(r"\s", char):  # Can't handle white-space characters yet
        raise ValueError(f'char cannot be whitespace: "{char}"')

    face = get_font_face()
    # Shamelessly stolen from <http://chatherineh.github.io/programming/2018/02/01/text-to-svg-paths>
    # Request nominal size (in points)
    # The height is set to the width if the height is left as 0
    face.set_char_size(width=char_size)

    # Load character into glyph slot so all methods apply to that glyph
    face.load_char(char)

    # Grab the outline of the glyph, and select only the y coordinate of the points
    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(semgent) == 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, face.glyph.metrics)
    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)
wsvg(path, filename="text.svg")
def sample_points_from_font(font, char):
    try:
        face = Face('data/ttfs/{}.ttf'.format(font))
    except:
        face = Face('data/ttfs/{}.otf'.format(font))
    face.set_char_size(48 * 64)
    face.load_char(char)
    outline = face.glyph.outline
    contours = [-1] + outline.contours
    segment = []
    segments = []
    paths = []
    for i in range(len(outline.points)):
        segment.append(complex(*outline.points[i]).conjugate())
        tag = int(bin(outline.tags[i])[2])
        try:
            j = contours.index(i)
            if tag == 0:
                segment.append(
                    complex(*outline.points[contours[j - 1] + 1]).conjugate())
                tag = 2
            else:
                tag = 3
        except ValueError:
            pass
        if tag > 0:
            if len(segment) == 1:
                pass
            elif len(segment) == 2:
                segments.append(Line(*segment))
            elif len(segment) == 3:
                segments.append(QuadraticBezier(*segment))
            elif len(segment) == 4:
                segments.append(CubicBezier(*segment))
            else:
                for k in range(len(segment) - 1):
                    A, C = segment[k:k + 2]
                    B = (A + C) / 2
                    segments.append(QuadraticBezier(A, B, C))

            if tag == 1:
                segment = [complex(*outline.points[i]).conjugate()]
            elif tag == 2:
                paths.append(Path(*segments))
                segments = []
                segment = []
            else:
                segments.append(
                    Line(
                        segment[-1],
                        complex(*outline.points[contours[j - 1] +
                                                1]).conjugate()))
                paths.append(Path(*segments))
                segments = []
                segment = []

    xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
    factor = 0.8 / max(xmax - xmin, ymax - ymin)
    for i, path in enumerate(paths):
        paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor)
    xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths)
    xmargin = (1 - (xmax - xmin)) / 2
    ymargin = (1 - (ymax - ymin)) / 2
    for i, path in enumerate(paths):
        paths[i] = path.translated(complex(xmargin, ymargin))

    points = []
    for path in paths:
        for seg in path:
            length = seg.length()
            for a in np.linspace(0, 1, num=length * n_points_per_unit_length):
                points.append(seg.point(seg.ilength(a * length)))

    return [(p.real, p.imag) for p in points]
Example #14
0
    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
Example #15
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)