Beispiel #1
0
def main():
    parser = argparse.ArgumentParser(description="Build Mada slanted fonts.")
    parser.add_argument("file", metavar="FILE", help="input font to process")
    parser.add_argument("outfile", metavar="FILE", help="output font to write")
    parser.add_argument("angle", metavar="FILE", help="slant angle", type=float)

    args = parser.parse_args()

    matrix = Identity.skew(math.radians(-args.angle))

    font = Font(args.file)

    info = font.info

    if args.angle < 0:
        style = "Italic"
    else:
        style = "Slanted"
    info.styleName += " " + style
    info.italicAngle = info.postscriptSlantAngle = args.angle

    for glyph in font:
        for contour in glyph:
            for point in contour:
                point.x, point.y = matrix.transformPoint((point.x, point.y))
        for anchor in glyph.anchors:
            anchor.x, anchor.y = matrix.transformPoint((anchor.x, anchor.y))

    font.save(args.outfile)
Beispiel #2
0
def main():
    parser = argparse.ArgumentParser(description="Build Mada slanted fonts.")
    parser.add_argument("file", metavar="FILE", help="input font to process")
    parser.add_argument("outfile", metavar="FILE", help="output font to write")
    parser.add_argument("angle",
                        metavar="FILE",
                        help="slant angle",
                        type=float)

    args = parser.parse_args()

    matrix = Identity.skew(math.radians(-args.angle))

    font = Font(args.file)

    info = font.info

    if args.angle < 0:
        style = "Italic"
    else:
        style = "Slanted"
    info.styleName += " " + style
    info.openTypeNamePreferredSubfamilyName += " " + style
    info.postscriptFullName += " " + style
    info.postscriptFontName += style
    info.italicAngle = info.postscriptSlantAngle = args.angle

    for glyph in font:
        for contour in glyph:
            for point in contour:
                point.x, point.y = matrix.transformPoint((point.x, point.y))
        for anchor in glyph.anchors:
            anchor.x, anchor.y = matrix.transformPoint((anchor.x, anchor.y))

    font.save(args.outfile)
Beispiel #3
0
 def scale(self, pt, center=(0, 0)):
     dx, dy = center
     x, y = pt
     sT = Identity.translate(dx, dy)
     sT = sT.scale(x, y)
     sT = sT.translate(-dx, -dy)
     self.transform(sT)
Beispiel #4
0
 def rotate(self, angle, offset=(0, 0)):
     dx, dy = offset
     radAngle = math.radians(angle)
     rT = Identity.translate(dx, dy)
     rT = rT.rotate(radAngle)
     rT = rT.translate(-dx, -dy)
     self.transform(rT)
	def deepAppendGlyph(self, glyph, gToAppend, offset=(0,0)):
		if not gToAppend.components:
			glyph.appendGlyph(gToAppend, offset)
		else:
			for component in gToAppend.components:
				compGlyph = self.font[component.baseGlyph].copy()

				# handle component transformations
				componentTransformation = component.transformation
				# when undoing a paste anchor or a delete anchor action, RoboFont returns component.transformation as a list instead of a tuple
				if type(componentTransformation) is list:
					componentTransformation = tuple(componentTransformation)
				if componentTransformation != (1, 0, 0, 1, 0, 0): # if component is skewed and/or is shifted
					matrix = componentTransformation[0:4]
					if matrix != (1, 0, 0, 1): # if component is skewed
						transformObj = Identity.transform(matrix + (0, 0)) # ignore the original component's shifting values
						compGlyph.transform(transformObj)

				glyph.appendGlyph(compGlyph, map(sum, zip(component.offset, offset))) # add the two tuples of offset
			for contour in gToAppend:
				glyph.appendContour(contour, offset)

		# if the assembled glyph still has components, recursively remove and replace them 1-by-1 by the glyphs they reference
		if glyph.components:
			nestedComponent = glyph.components[-1] # start from the end
			glyph.removeComponent(nestedComponent)
			glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset)

		return glyph
    def _deepAppendGlyph(self, glyph, gToAppend, font, offset=(0, 0)):
        if not gToAppend.components:
            glyph.appendGlyph(gToAppend, offset)
        else:
            for component in gToAppend.components:
                if component.baseGlyph not in font.keys():
                    # avoid traceback in the case where the selected glyph
                    # is referencing a component whose glyph is not in the font
                    continue

                compGlyph = font[component.baseGlyph].copy()

                if component.transformation != (1, 0, 0, 1, 0, 0):
                    # if component is skewed and/or is shifted:
                    matrix = component.transformation[0:4]
                    if matrix != (1, 0, 0, 1):  # if component is skewed
                        transformObj = Identity.transform(matrix + (0, 0))
                        # ignore the original component's shifting values
                        compGlyph.transform(transformObj)

                # add the two tuples of offset:
                totalOffset = tuple(map(sum, zip(component.offset, offset)))
                glyph.appendGlyph(compGlyph, totalOffset)

            for contour in gToAppend:
                glyph.appendContour(contour, offset)

        # if the assembled glyph still has components, recursively
        # remove and replace them 1-by-1 by the glyphs they reference:
        if glyph.components:
            nestedComponent = glyph.components[-1]
            glyph.removeComponent(nestedComponent)
            glyph = self._deepAppendGlyph(glyph, font[nestedComponent.baseGlyph], font, nestedComponent.offset)

        return glyph
def write_glyph(dir, font, glyph, master_id):
    if glyph not in font.glyphs:
        print("Unknown glyph %s" % glyph)
        return
    print(glyph)

    if master_id is None:
        master_id = 0
    master = font.masters[master_id]
    layer = font.glyphs[glyph].layers[master.id]
    svg = etree.Element(
        "svg",
        width=str(layer.width),
        height=str(master.ascender - master.descender),
        xmlns="http://www.w3.org/2000/svg",
    )
    et = etree.ElementTree(svg)

    t = Identity.scale(1, -1).translate(0, -master.ascender)
    commands = add_paths(layer, t)

    # Components
    for c in layer.components:
        comp_layer = font.glyphs[c.name].layers[master.id]
        commands += add_paths(comp_layer, t, c.transform)

    svg_path = etree.fromstring(f'<path d="{commands}" />')
    svg.append(svg_path)

    et.write("%s/%s.svg" % (dir, glyph), pretty_print=True)
Beispiel #8
0
 def skew(self, angle, offset=(0, 0)):
     dx, dy = offset
     x, y = angle
     x, y = math.radians(x), math.radians(y)
     sT = Identity.translate(dx, dy)
     sT = sT.skew(x, y)
     sT = sT.translate(-dx, -dy)
     self.transform(sT)
Beispiel #9
0
def _alignment_transformation(segment):
    # Returns a transformation which aligns a segment horizontally at the
    # origin. Apply this transformation to curves and root-find to find
    # intersections with the segment.
    start = segment[0]
    end = segment[-1]
    angle = math.atan2(end[1] - start[1], end[0] - start[0])
    return Identity.rotate(-angle).translate(-start[0], -start[1])
Beispiel #10
0
def trianglePath(x, y, size, angle):
    thirdSize = size / 3
    pen = QtPen({})
    tPen = TransformPen(pen, Identity.rotate(angle))
    tPen.moveTo((-thirdSize, size))
    tPen.lineTo((-thirdSize, -size))
    tPen.lineTo((2 * thirdSize, 0))
    tPen.closePath()
    return pen.path.translated(x, y)
Beispiel #11
0
def trianglePath(x, y, size, angle):
    thirdSize = size / 3
    pen = QtPen({})
    tPen = TransformPen(
        pen, Identity.rotate(angle))
    tPen.moveTo((-thirdSize, size))
    tPen.lineTo((-thirdSize, -size))
    tPen.lineTo((2 * thirdSize, 0))
    tPen.closePath()
    return pen.path.translated(x, y)
    def deepAppendGlyph(self, glyph, gToAppend, offset=(0, 0)):
        if not gToAppend.components:
            glyph.appendGlyph(gToAppend, offset)
        else:
            for component in gToAppend.components:
                # avoid traceback in the case where the selected glyph is
                # referencing a component whose glyph is not in the font
                if component.baseGlyph not in self.font.keys():
                    print("WARNING: %s is referencing a glyph named %s, which "
                          "does not exist in the font." %
                          (self.font.selection[0], component.baseGlyph))
                    continue

                compGlyph = self.font[component.baseGlyph].copy()

                # handle component transformations
                componentTransformation = component.transformation
                # when undoing a paste anchor or a delete anchor action,
                # RoboFont returns component.transformation as a list instead
                # of a tuple
                if type(componentTransformation) is list:
                    componentTransformation = tuple(componentTransformation)
                # if component is skewed and/or is shifted
                if componentTransformation != (1, 0, 0, 1, 0, 0):
                    matrix = componentTransformation[0:4]
                    if matrix != (1, 0, 0, 1):  # if component is skewed
                        # ignore the original component's shifting values
                        if self.rf3:
                            compGlyph.transform(matrix + (0, 0))
                        else:
                            transformObj = Identity.transform(matrix + (0, 0))
                            compGlyph.transform(transformObj)

                # add the two tuples of offset
                glyph.appendGlyph(
                    compGlyph, tuple(map(sum, zip(component.offset, offset))))
            for contour in gToAppend:
                glyph.appendContour(contour, offset)

        # if the assembled glyph still has components, recursively
        # remove and replace them 1-by-1 by the glyphs they reference
        if glyph.components:
            nestedComponent = glyph.components[-1]  # start from the end
            glyph.removeComponent(nestedComponent)
            glyph = self.deepAppendGlyph(glyph,
                                         self.font[nestedComponent.baseGlyph],
                                         nestedComponent.offset)
        return glyph
Beispiel #13
0
    def _decompose_to_cubic_curves(self):
        if self.center_point is None and not self._parametrize():
            return

        point_transform = Identity.rotate(self.angle).scale(self.rx, self.ry)

        # Some results of atan2 on some platform implementations are not exact
        # enough. So that we get more cubic curves than expected here. Adding 0.001f
        # reduces the count of sgements to the correct count.
        num_segments = int(ceil(fabs(self.theta_arc / (PI_OVER_TWO + 0.001))))
        for i in range(num_segments):
            start_theta = self.theta1 + i * self.theta_arc / num_segments
            end_theta = self.theta1 + (i + 1) * self.theta_arc / num_segments

            t = (4 / 3) * tan(0.25 * (end_theta - start_theta))
            if not isfinite(t):
                return

            sin_start_theta = sin(start_theta)
            cos_start_theta = cos(start_theta)
            sin_end_theta = sin(end_theta)
            cos_end_theta = cos(end_theta)

            point1 = complex(
                cos_start_theta - t * sin_start_theta,
                sin_start_theta + t * cos_start_theta,
            )
            point1 += self.center_point
            target_point = complex(cos_end_theta, sin_end_theta)
            target_point += self.center_point
            point2 = target_point
            point2 += complex(t * sin_end_theta, -t * cos_end_theta)

            point1 = _map_point(point_transform, point1)
            point2 = _map_point(point_transform, point2)
            target_point = _map_point(point_transform, target_point)

            yield point1, point2, target_point
Beispiel #14
0
def main(args=None):
    parser = argparse.ArgumentParser(
        description="Transform X anchor positions in VOLT/VTP files.")
    parser.add_argument("input",
                        metavar="INPUT",
                        help="input font/VTP file to process")
    parser.add_argument("output",
                        metavar="OUTPUT",
                        help="output font/VTP file")
    parser.add_argument("-a",
                        "--angle",
                        type=float,
                        required=True,
                        help="the slant angle (in degrees)")

    options = parser.parse_args(args)

    font = None
    try:
        font = TTFont(options.input)
        if "TSIV" in font:
            indata = font["TSIV"].data.decode("utf-8")
        else:
            log.error('"TSIV" table is missing, font was not saved from VOLT?')
            return 1
    except TTLibError:
        with open(options.input) as f:
            indata = f.read()

    transform = Identity.skew(options.angle * math.pi / 180)
    outdata = anchor_re.sub(lambda m: replace(m, transform), indata)

    if font is not None:
        font["TSIV"].data = outdata.encode("utf-8")
        font.save(options.output)
    else:
        with open(options.output, "w") as f:
            f.write(outdata)
Beispiel #15
0
    def _decompose_to_cubic_curves(self):
        if self.center_point is None and not self._parametrize():
            return

        point_transform = Identity.rotate(self.angle).scale(self.rx, self.ry)

        # Some results of atan2 on some platform implementations are not exact
        # enough. So that we get more cubic curves than expected here. Adding 0.001f
        # reduces the count of sgements to the correct count.
        num_segments = int(ceil(fabs(self.theta_arc / (PI_OVER_TWO + 0.001))))
        for i in range(num_segments):
            start_theta = self.theta1 + i * self.theta_arc / num_segments
            end_theta = self.theta1 + (i + 1) * self.theta_arc / num_segments

            t = (4 / 3) * tan(0.25 * (end_theta - start_theta))
            if not isfinite(t):
                return

            sin_start_theta = sin(start_theta)
            cos_start_theta = cos(start_theta)
            sin_end_theta = sin(end_theta)
            cos_end_theta = cos(end_theta)

            point1 = complex(
                cos_start_theta - t * sin_start_theta,
                sin_start_theta + t * cos_start_theta,
            )
            point1 += self.center_point
            target_point = complex(cos_end_theta, sin_end_theta)
            target_point += self.center_point
            point2 = target_point
            point2 += complex(t * sin_end_theta, -t * cos_end_theta)

            point1 = _map_point(point_transform, point1)
            point2 = _map_point(point_transform, point2)
            target_point = _map_point(point_transform, target_point)

            yield point1, point2, target_point
Beispiel #16
0
    def _deepAppendGlyph(self, glyph, gToAppend, font, offset=(0, 0)):
        if not gToAppend.components:
            glyph.appendGlyph(gToAppend, offset)
        else:
            for component in gToAppend.components:
                if component.baseGlyph not in font.keys():
                    # avoid traceback in the case where the selected glyph
                    # is referencing a component whose glyph is not in the font
                    continue

                compGlyph = font[component.baseGlyph].copy()

                if component.transformation != (1, 0, 0, 1, 0, 0):
                    # if component is skewed and/or is shifted:
                    matrix = component.transformation[0:4]
                    if matrix != (1, 0, 0, 1):  # if component is skewed
                        transformObj = Identity.transform(matrix + (0, 0))
                        # ignore the original component's shifting values
                        compGlyph.transform(transformObj)

                # add the two tuples of offset:
                totalOffset = map(sum, zip(component.offset, offset))
                glyph.appendGlyph(compGlyph, totalOffset)

            for contour in gToAppend:
                glyph.appendContour(contour, offset)

        # if the assembled glyph still has components, recursively
        # remove and replace them 1-by-1 by the glyphs they reference:
        if glyph.components:
            nestedComponent = glyph.components[-1]
            glyph.removeComponent(nestedComponent)
            glyph = self._deepAppendGlyph(glyph,
                                          font[nestedComponent.baseGlyph],
                                          font, nestedComponent.offset)

        return glyph
Beispiel #17
0
def drawGlyphPoints(
        painter, glyph, scale,
        drawStartPoints=True, drawOnCurves=True, drawOffCurves=True,
        drawCoordinates=False, drawBluesMarkers=True,
        onCurveColor=None, onCurveSmoothColor=None, offCurveColor=None,
        otherColor=None, backgroundColor=None):
    """
    Draws a Glyph_ *glyph*’s points.

    .. _Glyph: http://ts-defcon.readthedocs.org/en/ufo3/objects/glyph.html
    """
    if onCurveColor is None:
        onCurveColor = defaultColor("glyphOnCurvePoints")
    if onCurveSmoothColor is None:
        onCurveSmoothColor = defaultColor("glyphOnCurveSmoothPoints")
    if offCurveColor is None:
        offCurveColor = defaultColor("glyphOffCurvePoints")
    if otherColor is None:
        otherColor = defaultColor("glyphOtherPoints")
    if backgroundColor is None:
        backgroundColor = defaultColor("background")
    bluesMarkerColor = defaultColor("glyphBluesMarker")
    notchColor = defaultColor("glyphContourStroke").lighter(200)
    # get the outline data
    outlineData = glyph.getRepresentation("defconQt.OutlineInformation")
    points = []
    # blue zones markers
    if drawBluesMarkers and drawOnCurves:
        font = glyph.font
        blues = []
        if font.info.postscriptBlueValues:
            blues += font.info.postscriptBlueValues
        if font.info.postscriptOtherBlues:
            blues += font.info.postscriptOtherBlues
        if blues:
            blues_ = set(blues)
            size = 13 * scale
            snapSize = 17 * scale
            painter.save()
            pen = painter.pen()
            pen.setColor(QColor(255, 255, 255, 125))
            pen.setWidth(0)
            painter.setPen(pen)
            for point in outlineData["onCurvePoints"]:
                x, y = point["point"]
                # TODO: we could add a non-overlapping interval tree special
                # cased for borders
                for yMin, yMax in zip(blues[::2], blues[1::2]):
                    if not (y >= yMin and y <= yMax):
                        continue
                    # if yMin > 0 and y == yMin or yMin <= 0 and y == yMax:
                    if y in blues_:
                        path = lozengePath(x, y, snapSize)
                    else:
                        path = ellipsePath(x, y, size)
                    painter.fillPath(path, bluesMarkerColor)
                    painter.drawPath(path)
            painter.restore()
    # handles
    if drawOffCurves and outlineData["offCurvePoints"]:
        painter.save()
        painter.setPen(otherColor)
        for x1, y1, x2, y2 in outlineData["bezierHandles"]:
            drawLine(painter, x1, y1, x2, y2)
        painter.restore()
    # on curve
    if drawOnCurves and outlineData["onCurvePoints"]:
        size = 6.5 * scale
        smoothSize = 8 * scale
        startSize = 7 * scale
        loneStartSize = 12 * scale
        painter.save()
        notchPath = QPainterPath()
        path = QPainterPath()
        smoothPath = QPainterPath()
        for point in outlineData["onCurvePoints"]:
            x, y = point["point"]
            points.append((x, y))
            # notch
            if "smoothAngle" in point:
                angle = point["smoothAngle"]
                t = Identity.rotate(angle)
                x1, y1 = t.transformPoint((-1.35 * scale, 0))
                x2, y2 = -x1, -y1
                notchPath.moveTo(x1 + x, y1 + y)
                notchPath.lineTo(x2 + x, y2 + y)
            # points
            if drawStartPoints and "startPointAngle" in point:
                angle = point["startPointAngle"]
                if angle is not None:
                    pointPath = trianglePath(x, y, startSize, angle)
                else:
                    pointPath = ellipsePath(x, y, loneStartSize)
            elif point["smooth"]:
                pointPath = ellipsePath(x, y, smoothSize)
            else:
                pointPath = rectanglePath(x, y, size)
            # store the path
            if point["smooth"]:
                smoothPath.addPath(pointPath)
            else:
                path.addPath(pointPath)
        # stroke
        pen = QPen(onCurveColor)
        pen.setWidthF(1.2 * scale)
        painter.setPen(pen)
        painter.drawPath(path)
        pen.setColor(onCurveSmoothColor)
        painter.setPen(pen)
        painter.drawPath(smoothPath)
        # notch
        pen.setColor(notchColor)
        pen.setWidth(0)
        painter.setPen(pen)
        painter.drawPath(notchPath)
        painter.restore()
    # off curve
    if drawOffCurves and outlineData["offCurvePoints"]:
        # points
        offSize = 4.25 * scale
        path = QPainterPath()
        for point in outlineData["offCurvePoints"]:
            x, y = point["point"]
            pointPath = ellipsePath(x, y, offSize)
            path.addPath(pointPath)
        pen = QPen(offCurveColor)
        pen.setWidthF(2.5 * scale)
        painter.save()
        painter.setPen(pen)
        painter.drawPath(path)
        painter.fillPath(path, QBrush(backgroundColor))
        painter.restore()
    # coordinates
    if drawCoordinates:
        painter.save()
        painter.setPen(otherColor)
        font = painter.font()
        font.setPointSize(7)
        painter.setFont(font)
        for x, y in points:
            posX = x
            # TODO: We use + here because we align on top. Consider abstracting
            # yOffset.
            posY = y + 6 * scale
            x = round(x, 1)
            if int(x) == x:
                x = int(x)
            y = round(y, 1)
            if int(y) == y:
                y = int(y)
            text = "%d  %d" % (x, y)
            drawTextAtPoint(painter, text, posX, posY, scale,
                            xAlign="center", yAlign="top")
        painter.restore()
Beispiel #18
0
    def _parametrize(self):
        # convert from endopoint to center parametrization:
        # https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter

        # If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
        # "lineto") joining the endpoints.
        # http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
        rx = fabs(self.rx)
        ry = fabs(self.ry)
        if not (rx and ry):
            return False

        # If the current point and target point for the arc are identical, it should
        # be treated as a zero length path. This ensures continuity in animations.
        if self.target_point == self.current_point:
            return False

        mid_point_distance = (self.current_point - self.target_point) * 0.5

        point_transform = Identity.rotate(-self.angle)

        transformed_mid_point = _map_point(point_transform, mid_point_distance)
        square_rx = rx * rx
        square_ry = ry * ry
        square_x = transformed_mid_point.real * transformed_mid_point.real
        square_y = transformed_mid_point.imag * transformed_mid_point.imag

        # Check if the radii are big enough to draw the arc, scale radii if not.
        # http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
        radii_scale = square_x / square_rx + square_y / square_ry
        if radii_scale > 1:
            rx *= sqrt(radii_scale)
            ry *= sqrt(radii_scale)
            self.rx, self.ry = rx, ry

        point_transform = Scale(1 / rx, 1 / ry).rotate(-self.angle)

        point1 = _map_point(point_transform, self.current_point)
        point2 = _map_point(point_transform, self.target_point)
        delta = point2 - point1

        d = delta.real * delta.real + delta.imag * delta.imag
        scale_factor_squared = max(1 / d - 0.25, 0.0)

        scale_factor = sqrt(scale_factor_squared)
        if self.sweep == self.large:
            scale_factor = -scale_factor

        delta *= scale_factor
        center_point = (point1 + point2) * 0.5
        center_point += complex(-delta.imag, delta.real)
        point1 -= center_point
        point2 -= center_point

        theta1 = atan2(point1.imag, point1.real)
        theta2 = atan2(point2.imag, point2.real)

        theta_arc = theta2 - theta1
        if theta_arc < 0 and self.sweep:
            theta_arc += TWO_PI
        elif theta_arc > 0 and not self.sweep:
            theta_arc -= TWO_PI

        self.theta1 = theta1
        self.theta2 = theta1 + theta_arc
        self.theta_arc = theta_arc
        self.center_point = center_point

        return True
Beispiel #19
0
    def _parametrize(self):
        # convert from endopoint to center parametrization:
        # https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter

        # If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
        # "lineto") joining the endpoints.
        # http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
        rx = fabs(self.rx)
        ry = fabs(self.ry)
        if not (rx and ry):
            return False

        # If the current point and target point for the arc are identical, it should
        # be treated as a zero length path. This ensures continuity in animations.
        if self.target_point == self.current_point:
            return False

        mid_point_distance = (self.current_point - self.target_point) * 0.5

        point_transform = Identity.rotate(-self.angle)

        transformed_mid_point = _map_point(point_transform, mid_point_distance)
        square_rx = rx * rx
        square_ry = ry * ry
        square_x = transformed_mid_point.real * transformed_mid_point.real
        square_y = transformed_mid_point.imag * transformed_mid_point.imag

        # Check if the radii are big enough to draw the arc, scale radii if not.
        # http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
        radii_scale = square_x / square_rx + square_y / square_ry
        if radii_scale > 1:
            rx *= sqrt(radii_scale)
            ry *= sqrt(radii_scale)
            self.rx, self.ry = rx, ry

        point_transform = Scale(1 / rx, 1 / ry).rotate(-self.angle)

        point1 = _map_point(point_transform, self.current_point)
        point2 = _map_point(point_transform, self.target_point)
        delta = point2 - point1

        d = delta.real * delta.real + delta.imag * delta.imag
        scale_factor_squared = max(1 / d - 0.25, 0.0)

        scale_factor = sqrt(scale_factor_squared)
        if self.sweep == self.large:
            scale_factor = -scale_factor

        delta *= scale_factor
        center_point = (point1 + point2) * 0.5
        center_point += complex(-delta.imag, delta.real)
        point1 -= center_point
        point2 -= center_point

        theta1 = atan2(point1.imag, point1.real)
        theta2 = atan2(point2.imag, point2.real)

        theta_arc = theta2 - theta1
        if theta_arc < 0 and self.sweep:
            theta_arc += TWO_PI
        elif theta_arc > 0 and not self.sweep:
            theta_arc -= TWO_PI

        self.theta1 = theta1
        self.theta2 = theta1 + theta_arc
        self.theta_arc = theta_arc
        self.center_point = center_point

        return True
Beispiel #20
0
 def skew(self, angle, offset=(0, 0)):
     xRad = math.radians(angle[0])
     yRad = math.radians(angle[1])
     rT = Identity.translate(offset[0], offset[1])
     rT = rT.skew(xRad, yRad)
     self.transform(rT)
Beispiel #21
0
def drawGlyphPoints(painter,
                    glyph,
                    scale,
                    drawStartPoints=True,
                    drawOnCurves=True,
                    drawOffCurves=True,
                    drawCoordinates=False,
                    drawSelection=True,
                    drawBluesMarkers=True,
                    onCurveColor=None,
                    onCurveSmoothColor=None,
                    offCurveColor=None,
                    otherColor=None,
                    backgroundColor=None):
    if onCurveColor is None:
        onCurveColor = defaultColor("glyphOnCurvePoints")
    if onCurveSmoothColor is None:
        onCurveSmoothColor = defaultColor("glyphOnCurveSmoothPoints")
    if offCurveColor is None:
        offCurveColor = defaultColor("glyphOffCurvePoints")
    if otherColor is None:
        otherColor = defaultColor("glyphOtherPoints")
    if backgroundColor is None:
        backgroundColor = defaultColor("background")
    bluesMarkerColor = defaultColor("glyphBluesMarker")
    notchColor = defaultColor("glyphContourStroke").lighter(200)
    # get the outline data
    outlineData = glyph.getRepresentation("defconQt.OutlineInformation")
    points = []
    # blue zones markers
    if drawBluesMarkers and drawOnCurves:
        font = glyph.font
        blues = []
        if font.info.postscriptBlueValues:
            blues += font.info.postscriptBlueValues
        if font.info.postscriptOtherBlues:
            blues += font.info.postscriptOtherBlues
        if blues:
            blues_ = set(blues)
            size = 13 * scale
            selectedSize = 15 * scale
            snapSize = 17 * scale
            selectedSnapSize = 20 * scale
            painter.save()
            pen = painter.pen()
            pen.setColor(QColor(255, 255, 255, 125))
            pen.setWidth(0)
            painter.setPen(pen)
            for point in outlineData["onCurvePoints"]:
                x, y = point["point"]
                # TODO: we could add a non-overlapping interval tree special
                # cased for borders
                selected = drawSelection and point.get("selected", False)
                if selected:
                    size_ = selectedSize
                    snapSize_ = selectedSnapSize
                else:
                    size_ = size
                    snapSize_ = snapSize
                for yMin, yMax in zip(blues[::2], blues[1::2]):
                    if not (y >= yMin and y <= yMax):
                        continue
                    # if yMin > 0 and y == yMin or yMin <= 0 and y == yMax:
                    if y in blues_:
                        path = lozengePath(x, y, snapSize_)
                    else:
                        path = ellipsePath(x, y, size_)
                    painter.fillPath(path, bluesMarkerColor)
                    painter.drawPath(path)
            painter.restore()
    # handles
    if drawOffCurves and outlineData["offCurvePoints"]:
        painter.save()
        painter.setPen(otherColor)
        for x1, y1, x2, y2 in outlineData["bezierHandles"]:
            drawLine(painter, x1, y1, x2, y2)
        painter.restore()
    # on curve
    if drawOnCurves and outlineData["onCurvePoints"]:
        size = 6.5 * scale
        selectedSize = 8.5 * scale
        smoothSize = 8 * scale
        selectedSmoothSize = 10 * scale
        startSize = 7 * scale
        selectedStartSize = 9 * scale
        loneStartSize = 12 * scale
        selectedLoneStartSize = 14 * scale
        painter.save()
        notchPath = QPainterPath()
        paths = (QPainterPath(), QPainterPath())
        smoothPaths = (QPainterPath(), QPainterPath())
        for point in outlineData["onCurvePoints"]:
            x, y = point["point"]
            points.append((x, y))
            # notch
            if "smoothAngle" in point:
                angle = point["smoothAngle"]
                t = Identity.rotate(angle)
                x1, y1 = t.transformPoint((-1.35 * scale, 0))
                x2, y2 = -x1, -y1
                x1 += x
                y1 += y
                x2 += x
                y2 += y
                notchPath.moveTo(x1, y1)
                notchPath.lineTo(x2, y2)
            # points
            selected = drawSelection and point.get("selected", False)
            if selected:
                size_ = selectedSize
                smoothSize_ = selectedSmoothSize
                startSize_ = selectedStartSize
                loneStartSize_ = selectedLoneStartSize
            else:
                size_ = size
                smoothSize_ = smoothSize
                startSize_ = startSize
                loneStartSize_ = loneStartSize
            if drawStartPoints and "startPointAngle" in point:
                angle = point["startPointAngle"]
                if angle is not None:
                    pointPath = trianglePath(x, y, startSize_, angle)
                else:
                    pointPath = ellipsePath(x, y, loneStartSize_)
            elif point["smooth"]:
                pointPath = ellipsePath(x, y, smoothSize_)
            else:
                pointPath = rectanglePath(x, y, size_)
            # store the path
            if point["smooth"]:
                smoothPaths[selected].addPath(pointPath)
            else:
                paths[selected].addPath(pointPath)
        path, selectedPath = paths
        smoothPath, selectedSmoothPath = smoothPaths
        # fill
        selectedPath.setFillRule(Qt.WindingFill)
        selectedSmoothPath.setFillRule(Qt.WindingFill)
        painter.fillPath(selectedPath, onCurveColor)
        painter.fillPath(selectedSmoothPath, onCurveSmoothColor)
        # stroke
        pen = QPen(onCurveColor)
        pen.setWidthF(1.2 * scale)
        painter.setPen(pen)
        painter.drawPath(path)
        pen.setColor(onCurveSmoothColor)
        painter.setPen(pen)
        painter.drawPath(smoothPath)
        # notch
        pen.setColor(notchColor)
        pen.setWidth(0)
        painter.setPen(pen)
        painter.drawPath(notchPath)
        painter.restore()
    # off curve
    if drawOffCurves and outlineData["offCurvePoints"]:
        # points
        offSize = 4.25 * scale
        selectedOffSize = 6.75 * scale
        path = QPainterPath()
        selectedPath = QPainterPath()
        selectedPath.setFillRule(Qt.WindingFill)
        for point in outlineData["offCurvePoints"]:
            x, y = point["point"]
            selected = drawSelection and point.get("selected", False)
            if selected:
                offSize_ = selectedOffSize
            else:
                offSize_ = offSize
            pointPath = ellipsePath(x, y, offSize_)
            if selected:
                selectedPath.addPath(pointPath)
            else:
                path.addPath(pointPath)
        pen = QPen(offCurveColor)
        pen.setWidthF(2.5 * scale)
        painter.save()
        painter.setPen(pen)
        painter.drawPath(path)
        painter.fillPath(path, QBrush(backgroundColor))
        painter.fillPath(selectedPath, QBrush(offCurveColor.lighter(135)))
        painter.restore()
    # coordinates
    if drawCoordinates:
        painter.save()
        painter.setPen(otherColor)
        font = painter.font()
        font.setPointSize(7)
        painter.setFont(font)
        for x, y in points:
            posX = x
            # TODO: We use + here because we align on top. Consider abstracting
            # yOffset.
            posY = y + 6 * scale
            x = round(x, 1)
            if int(x) == x:
                x = int(x)
            y = round(y, 1)
            if int(y) == y:
                y = int(y)
            text = "%d  %d" % (x, y)
            drawTextAtPoint(painter,
                            text,
                            posX,
                            posY,
                            scale,
                            xAlign="center",
                            yAlign="top")
        painter.restore()
Beispiel #22
0
 def rotate(self, angle, offset=(0, 0)):
     radAngle = math.radians(angle)
     rT = Identity.translate(offset[0], offset[1])
     rT = rT.rotate(radAngle)
     rT = rT.translate(-offset[0], -offset[1])
     self.transform(rT)