Last active
September 18, 2024 10:54
-
-
Save typoman/8eee380080320ad401cad4a86e969a2b to your computer and use it in GitHub Desktop.
A pen class for converting glyph outlines into symbolic representations. The resulting string can be used for quick visual comparison or analysis of glyph shapes without rendering the full outline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Tuple, Optional | |
from fontTools.pens.basePen import BasePen | |
from fontTools.misc.arrayTools import calcBounds | |
import math | |
def round_angle_to_step(angle: float, degree: int) -> int: | |
""" | |
Rounds an angle to the nearest degree increment. | |
Args: | |
angle (float): The angle to be rounded, in degrees. | |
degree (int): The degree increment to round to. | |
Returns: | |
int: The rounded angle, in degrees. | |
# Angle that is exactly divisible by the degree increment | |
>>> round_angle_to_step(90, 30) | |
90 | |
# Angle that is not exactly divisible by the degree increment | |
>>> round_angle_to_step(91, 45) | |
90 | |
# Angle that is greater than 360 degrees | |
>>> round_angle_to_step(370, 22) | |
0 | |
# Angle that is less than 0 degrees | |
>>> round_angle_to_step(-10, 30) | |
360 | |
# Angle that is a multiple of 360 degrees | |
>>> round_angle_to_step(721, 45) | |
0 | |
# Degree increment of 1 | |
>>> round_angle_to_step(91.5, 1) | |
92 | |
# Degree increment of 360 | |
>>> round_angle_to_step(91, 360) | |
0 | |
# Zero angle | |
>>> round_angle_to_step(0, 30) | |
0 | |
""" | |
return round((angle % 360) / degree) * degree | |
def get_abs_vector_angle(dx: float, dy: float) -> float: | |
""" | |
Calculates the absolute angle in degrees from the given vector | |
relative to the positive x-axis (considered to be 0 degree). | |
Args: | |
dx (float): The x component of the vector. | |
dy (float): The y component of the vector. | |
Returns: | |
float: The absolute angle in degrees, normalized to the range [0, 360). | |
# Positive x and y components | |
>>> get_abs_vector_angle(3, 4) | |
53.13010235415595 | |
# Zero x component, positive y component | |
>>> get_abs_vector_angle(0, 4) | |
90.0 | |
# Zero x component, negative y component | |
>>> get_abs_vector_angle(0, -4) | |
270.0 | |
# Positive x component, zero y component | |
>>> get_abs_vector_angle(4, 0) | |
0.0 | |
# Negative x component, zero y component | |
>>> get_abs_vector_angle(-4, 0) | |
180.0 | |
# Positive x and y components, angle greater than 90 degrees | |
>>> get_abs_vector_angle(3, 5) | |
59.03624346792651 | |
# Negative x and y components, angle greater than 180 degrees | |
>>> get_abs_vector_angle(-3, -4) | |
233.13010235415598 | |
# Zero x and y components | |
>>> get_abs_vector_angle(0, 0) | |
0.0 | |
""" | |
angle = math.atan2(dy, dx) | |
angle_degrees = math.degrees(angle) | |
angle_normalized = (angle_degrees + 360) % 360 | |
return angle_normalized | |
def vector_is_zero(dx: float, dy: float) -> bool: | |
""" | |
# Non-zero vector | |
>>> vector_is_zero(1.0, 1.0) | |
False | |
# Vector with small but non-zero components | |
>>> vector_is_zero(1e-11, 1e-11) | |
True | |
""" | |
return abs(dx) < 1e-10 and abs(dy) < 1e-10 | |
def get_vector_direction_char_from_angle(angle_normalized: float) -> str: | |
""" | |
Returns the direction character of a vector based on its angle. | |
The function takes an angle in degrees and returns a character representing | |
the direction of the vector. The direction is determined by dividing the | |
circle into eight octants, each corresponding to a different direction. | |
Args: | |
angle_normalized (float): The angle of the vector in degrees. | |
Returns: | |
str: A character representing the direction of the vector. | |
>>> get_vector_direction_char_from_angle(45) | |
'↗' | |
>>> get_vector_direction_char_from_angle(90) | |
'↑' | |
>>> get_vector_direction_char_from_angle(135) | |
'↖' | |
>>> get_vector_direction_char_from_angle(180) | |
'←' | |
>>> get_vector_direction_char_from_angle(225) | |
'↙' | |
>>> get_vector_direction_char_from_angle(270) | |
'↓' | |
>>> get_vector_direction_char_from_angle(315) | |
'↘' | |
>>> get_vector_direction_char_from_angle(11.5) | |
'→' | |
>>> get_vector_direction_char_from_angle(67.5) | |
'↑' | |
>>> get_vector_direction_char_from_angle(112.5) | |
'↖' | |
>>> get_vector_direction_char_from_angle(157.5) | |
'←' | |
>>> get_vector_direction_char_from_angle(202.5) | |
'↙' | |
>>> get_vector_direction_char_from_angle(247.5) | |
'↓' | |
>>> get_vector_direction_char_from_angle(292.5) | |
'↘' | |
>>> get_vector_direction_char_from_angle(337.5) | |
'→' | |
""" | |
octant = int(((angle_normalized + 22.5) % 360) / 45) | |
directions = ("→", "↗", "↑", "↖", "←", "↙", "↓", "↘") | |
return directions[octant] | |
def get_scaling_factors(x0: float, y0: float, x1: float, y1: float) -> Tuple[float, float]: | |
""" | |
Calculates the scaling factors for a rectangle to make it a square. | |
Args: | |
x0 (float or int): The x-coordinate of the top-left corner of the rectangle. | |
y0 (float or int): The y-coordinate of the top-left corner of the rectangle. | |
x1 (float or int): The x-coordinate of the bottom-right corner of the rectangle. | |
y1 (float or int): The y-coordinate of the bottom-right corner of the rectangle. | |
Returns: | |
tuple: A tuple containing the scaling factors for the x and y axes. | |
# Square input | |
>>> get_scaling_factors(0, 0, 10, 10) | |
(1.0, 1.0) | |
# Rectangle with width > height | |
>>> get_scaling_factors(0, 0, 10, 5) | |
(1.0, 0.5) | |
# Rectangle with height > width | |
>>> get_scaling_factors(0, 0, 5, 10) | |
(0.5, 1.0) | |
# Negative coordinates | |
>>> get_scaling_factors(-10, -10, 10, 10) | |
(1.0, 1.0) | |
# Non-integer coordinates | |
>>> get_scaling_factors(0.5, 0.5, 10.5, 10.5) | |
(1.0, 1.0) | |
# Identical coordinates | |
>>> get_scaling_factors(10, 10, 10, 10) | |
Traceback (most recent call last): | |
... | |
ValueError: Both width and height are zero, cannot calculate scaling factors. | |
# Large coordinates | |
>>> get_scaling_factors(1000, 1000, 2000, 2000) | |
(1.0, 1.0) | |
""" | |
width = abs(x1 - x0) | |
height = abs(y1 - y0) | |
if width == 0 and height == 0: | |
raise ValueError("Both width and height are zero, cannot calculate scaling factors.") | |
# Determine the scaling factors | |
if width > height: | |
scale_x = 1.0 | |
scale_y = height / width | |
else: | |
scale_x = width / height | |
scale_y = 1.0 | |
return (scale_x, scale_y) | |
curve_dir_2_string = { | |
# straight circle quarters | |
"↑←": "◝", | |
"↓→": "◟", | |
"↑→": "◜", | |
"↓←": "◞", | |
"←↓": "◜", | |
"←↑": "◟", | |
"→↓": "◝", | |
"→↑": "◞", | |
# diagonal quarter or smaller arcs | |
"↖←": "◝", | |
"↙←": "◞", | |
"↗→": "◜", | |
"↘→": "◟", | |
"→↗": "◞", | |
"→↘": "◝", | |
"↓↘": "◟", | |
"↑↗": "◜", | |
"↓↙": "◞", | |
"↑↖": "◝", | |
"↘↓": "◝", | |
"↗↑": "◞", | |
"←↖": "◟", | |
"←↙": "◜", | |
"↙↓": "◜", | |
"↖↑": "◟", | |
"↗↙": "◝", | |
"↙↗": "◟", | |
"↖↘": "◜", | |
"↘↖": "◞", | |
# half circles or bigger arcs | |
"↙↖": "◡", | |
"↘↗": "◡", | |
"↗↘": "◠", | |
"↖↙": "◠", | |
"↘↙": "﹚", | |
"↗↖": "﹚", | |
"↙↘": "﹙", | |
"↖↗": "﹙", | |
"↑↘": "◠", | |
"↑↙": "◠", | |
"↓↖": "◡", | |
"↓↗": "◡", | |
"←↗": "﹙", | |
"←↘": "﹙", | |
"→↖": "﹚", | |
"→↙": "﹚", | |
"↖↓": "◠", | |
"↘↑": "◡", | |
"↙↑": "◡", | |
"↗↓": "◠", | |
"→←": "﹚", | |
"←→": "﹙", | |
"↑↓": "◠", | |
"↓↑": "◡", | |
"↘←": "﹚", | |
"↖→": "﹙", | |
"↙→": "﹙", | |
"↗←": "﹚", | |
} | |
wavy_curve_dir_string = { | |
"↑": "≀", | |
"↓": "≀", | |
"→": "∿", | |
"←": "∿", | |
} | |
flat_curve_dir_string = { | |
"↑": "↟", | |
"↓": "↡", | |
"→": "↠", | |
"←": "↞", | |
} | |
def rotation_is_clockwise(v1: Tuple[float, float], v2: Tuple[float, float]) -> Optional[bool]: | |
""" | |
Determines if the rotation from one 2D vector to another is clockwise. This | |
function calculates the cross product of two 2D vectors and uses the result | |
to determine the direction of rotation. | |
Args: | |
v1 (list): The initial 2D vector, represented as [x, y]. | |
v2 (list): The final 2D vector, represented as [x, y]. | |
Returns: | |
bool: True if the rotation is clockwise, False if counterclockwise, and | |
None if the vectors are collinear. | |
# Clockwise rotation | |
>>> v1 = [1, 0] | |
>>> v2 = [0, 1] | |
>>> rotation_is_clockwise(v1, v2) | |
False | |
# Counterclockwise rotation | |
>>> v1 = [1, 0] | |
>>> v2 = [0, -1] | |
>>> rotation_is_clockwise(v1, v2) | |
True | |
# Collinear vectors | |
>>> v1 = [1, 0] | |
>>> v2 = [2, 0] | |
>>> rotation_is_clockwise(v1, v2) | |
# Zero vector | |
>>> v1 = [0, 0] | |
>>> v2 = [1, 0] | |
>>> rotation_is_clockwise(v1, v2) | |
# Vectors with same direction but different magnitude | |
>>> v1 = [1, 0] | |
>>> v2 = [3, 0] | |
>>> rotation_is_clockwise(v1, v2) | |
""" | |
cross_product = v1[0] * v2[1] - v1[1] * v2[0] | |
if cross_product > 0: | |
return False | |
elif cross_product < 0: | |
return True | |
return None | |
class SegmentSymbolsPen(BasePen): | |
""" | |
A pen class for converting glyph outlines into symbolic representations. | |
This class extends BasePen to create a string representation of glyph | |
outlines using directional symbols. It processes moveTo, lineTo, and | |
curveTo commands, converting them into a sequence of characters that | |
represent the path's direction and curvature. Contours are separated | |
by newlines. | |
The resulting string can be used for quick visual comparison or analysis | |
of glyph shapes without rendering the full outline. | |
Attributes: | |
segments_symbols: complete symbolic representation of all segments as a string | |
Examples: | |
# horizontal line | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((1, 0)) | |
>>> pen.segments_symbols | |
'→' | |
# vertical line | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((0, 1)) | |
>>> pen.segments_symbols | |
'↑' | |
# diagonal line | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((1, 1)) | |
>>> pen.segments_symbols | |
'↗' | |
# zero-length line | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((0, 0)) | |
>>> pen.segments_symbols | |
'.' | |
# top side curve | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._curveToOne((0, 1), (1, 1), (1, 0)) | |
>>> pen.segments_symbols | |
'◠' | |
# zero length curve | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._curveToOne((1, 0), (1, 0), (0, 0)) | |
>>> pen.segments_symbols | |
'⟳' | |
# left side curve | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._curveToOne((1, 0), (0.5, 0.5), (0, 1)) | |
>>> pen.segments_symbols | |
'﹚' | |
# close path | |
>>> pen._closePath() | |
>>> pen.segments_symbols | |
'﹚↓' | |
# line directions | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((1, 0)) # right | |
>>> pen._lineTo((1, 1)) # up-right | |
>>> pen._lineTo((0, 1)) # up | |
>>> pen._lineTo((-1, 1)) # up-left | |
>>> pen._lineTo((-1, 0)) # left | |
>>> pen._lineTo((-1, -1)) # down-left | |
>>> pen._lineTo((0, -1)) # down | |
>>> pen._lineTo((1, -1)) # down-right | |
>>> pen._closePath() | |
>>> pen.segments_symbols | |
'→↑←←↓↓→→↖' | |
# some curve directions | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._curveToOne((1, 2), (2, 2), (3, 0)) # top curve | |
>>> pen._curveToOne((4, -2), (5, -2), (6, 0)) # bottom curve | |
>>> pen._curveToOne((8, 0), (8, 2), (6, 2)) # right curve | |
>>> pen._curveToOne((4, 2), (4, 0), (6, 0)) # left curve | |
>>> pen._closePath() | |
>>> pen.segments_symbols | |
'◠◡﹚﹙←' | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((10, 0)) | |
>>> pen._curveToOne((0, 0), (0, 0), (0, 10)) | |
>>> pen._curveToOne((0, 20), (0, 20), (10, 20)) | |
>>> pen._curveToOne((20, 20), (20, 20), (20, 10)) | |
>>> pen._curveToOne((20, 0), (20, 0), (10, 0)) | |
>>> pen.segments_symbols | |
'◟◜◝◞' | |
# wavy and flat curves | |
>>> pen = SegmentSymbolsPen() | |
>>> pen._moveTo((0, 0)) | |
>>> pen._curveToOne((1, 0.1), (2, -0.1), (3, 0)) # wavy horizontal curve | |
>>> pen._lineTo((0, 0)) | |
>>> pen._curveToOne((0.1, 1), (-0.1, 2), (0, 3)) # wavy vertical curve | |
>>> pen._lineTo((2, 3)) | |
>>> pen._curveToOne((2, 3), (1, 3), (0, 3)) # flat horizontal curve | |
>>> pen._curveToOne((0, 2), (0, 1), (0, 0)) # flat vertical curve | |
>>> pen._closePath() | |
>>> pen.segments_symbols | |
'∿←≀→↞↡.' | |
# multiple contours | |
>>> pen = SegmentSymbolsPen() | |
>>> # First contour (square) | |
>>> pen._moveTo((0, 0)) | |
>>> pen._lineTo((0, 1)) | |
>>> pen._lineTo((1, 1)) | |
>>> pen._lineTo((1, 0)) | |
>>> pen._closePath() | |
>>> # Second contour (triangle) | |
>>> pen._moveTo((0.25, 0.25)) | |
>>> pen._lineTo((0.75, 0.25)) | |
>>> pen._lineTo((0.5, 0.75)) | |
>>> pen._closePath() | |
>>> pen.segments_symbols | |
'↑→↓←\\n→↑↙' | |
""" | |
def __init__(self, glyphSet=None): | |
super().__init__(glyphSet) | |
self._path = [] | |
self._start_point = None | |
self._current_point = None | |
def _moveTo(self, pt): | |
if self._path != []: | |
self._path.append("\n") | |
x, y = self._round(pt) | |
self._start_point = (x, y) | |
self._current_point = (x, y) | |
def _lineTo(self, pt): | |
x1, y1 = pt | |
x2, y2 = self._current_point | |
self._current_point = pt | |
dx, dy = self._round((x1 - x2, y1 - y2)) | |
if vector_is_zero(dx, dy): | |
char = "." | |
else: | |
angle = get_abs_vector_angle(dx, dy) | |
char = get_vector_direction_char_from_angle(angle) | |
self._path.append(char) | |
def _curveToOne(self, pt1, pt2, pt3): | |
p0 = self._current_point # start of first tangent | |
p1 = self._round(pt1) # end of first tangent | |
p2 = self._round(pt2) # start of last tangent | |
p3 = self._round(pt3) # end of last tangent | |
self._current_point = pt3 | |
# here I scale the tangents to fit inside a square, | |
# this helps to get a more relevant character. | |
all_points = (p0, p1, p2, p3) | |
bounds = calcBounds(all_points) | |
sx, sy = get_scaling_factors(*bounds) | |
if 0 not in (sx, sy): | |
all_points = map(lambda pt: (pt[0] / sx, pt[1] / sy), all_points) | |
p0, p1, p2, p3 = all_points | |
x0, y0 = p0 | |
x1, y1 = p1 | |
x2, y2 = p2 | |
x3, y3 = p3 | |
# segment vector | |
dx3 = x3 - x0 | |
dy3 = y3 - y0 | |
if vector_is_zero(dx3, dy3): | |
# zero length curve | |
self._path.append("⟳") | |
return | |
# tangent 1 vector | |
dx1 = x1 - x0 | |
dy1 = y1 - y0 | |
if vector_is_zero(dx1, dy1): | |
dx1, dy1 = dx3, dy3 | |
# tangent 2 vector | |
dx2 = x3 - x2 | |
dy2 = y3 - y2 | |
if vector_is_zero(dx2, dy2): | |
dx2, dy2 = dx3, dy3 | |
t1_clockwise = rotation_is_clockwise((dx3, dy3), (dx2, dy2)) | |
t2_clockwise = rotation_is_clockwise((dx3, dy3), (dx1, dy1)) | |
# check if tangents are too close in terms of angle almost forming a line | |
t1_angle = get_abs_vector_angle(dx1, dy1) | |
t2_angle = get_abs_vector_angle(dx2, dy2) | |
da = t1_angle - t2_angle | |
if abs(da) < 45: | |
if t1_clockwise == t2_clockwise: | |
if sx > sy: | |
dy3 = 0 | |
else: | |
dx3 = 0 | |
# segment dir symbol | |
s_char = get_vector_direction_char_from_angle(get_abs_vector_angle(dx3, dy3)) | |
if t1_clockwise is None: | |
# absolutely flat curve, rare and weird! | |
self._path.append(flat_curve_dir_string[s_char]) | |
else: | |
# wavy curve | |
self._path.append(wavy_curve_dir_string[s_char]) | |
return | |
t1_angle = round_angle_to_step(t1_angle, 90) | |
t2_angle = round_angle_to_step(t2_angle, 90) | |
t1_char = get_vector_direction_char_from_angle(t1_angle) | |
t2_char = get_vector_direction_char_from_angle(t2_angle) | |
# print(t1_char, t2_char) | |
abs_curve_dir = t1_char + t2_char | |
self._path.append(curve_dir_2_string[abs_curve_dir]) | |
def _closePath(self): | |
if self._start_point: | |
self._lineTo(self._start_point) | |
self._start_point = None | |
self._current_point = None | |
def _endPath(self): | |
self._start_point = None | |
self._current_point = None | |
def _round(self, pt): | |
return tuple(round(v, 1) for v in pt) | |
@property | |
def segments_symbols(self) -> str: | |
return "".join(self._path) | |
def _test_more_arcs(start, control1, control2, end): | |
""" | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (-2, 1)) | |
'◝' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (-2, -1)) | |
'◞' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (2, 1)) | |
'◜' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (2, -1)) | |
'◟' | |
>>> _test_more_arcs((0, 0), (2, 0), (2, 0), (2, 1)) | |
'◞' | |
>>> _test_more_arcs((0, 0), (2, 0), (2, 0), (2, -1)) | |
'◝' | |
>>> _test_more_arcs((0, 0), (0, -2), (0, -2), (1, -2)) | |
'◟' | |
>>> _test_more_arcs((0, 0), (0, 2), (0, 2), (1, 2)) | |
'◜' | |
>>> _test_more_arcs((0, 0), (0, -2), (0, -2), (-1, -2)) | |
'◞' | |
>>> _test_more_arcs((0, 0), (0, 2), (0, 2), (-1, 2)) | |
'◝' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (1, -2)) | |
'◝' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (1, 2)) | |
'◞' | |
>>> _test_more_arcs((0, 0), (-2, 0), (-2, 0), (-2, 1)) | |
'◟' | |
>>> _test_more_arcs((0, 0), (-2, 0), (-2, 0), (-2, -1)) | |
'◜' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (-1, -2)) | |
'◜' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (-1, 2)) | |
'◟' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (-1, -1)) | |
'◝' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (1, 1)) | |
'◟' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (1, -1)) | |
'◜' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (-1, 1)) | |
'◞' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (-1, 1)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (1, 1)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (1, -1)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (-1, -1)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (-1, -1)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (-1, 1)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (1, -1)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (1, 1)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (0, 2), (0, 2), (2, -2)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (0, 2), (0, 2), (-2, -2)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (0, -2), (0, -2), (-2, 2)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (0, -2), (0, -2), (2, 2)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (-2, 0), (-2, 0), (2, 2)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (-2, 0), (-2, 0), (2, -2)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (2, 0), (2, 0), (-2, 2)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (2, 0), (2, 0), (-2, -2)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (-1, -1)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (1, 1)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (-1, 1)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (1, -1)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (2, 0), (2, 0), (-2, 0)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (-2, 0), (-2, 0), (2, 0)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (0, 2), (0, 2), (0, -2)) | |
'◠' | |
>>> _test_more_arcs((0, 0), (0, -2), (1, -2), (1, 2)) | |
'◡' | |
>>> _test_more_arcs((0, 0), (1, -1), (1, -1), (-1, -1)) | |
'﹚' | |
>>> _test_more_arcs((0, 0), (-1, 1), (-1, 1), (1, 1)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (-1, -1), (-1, -1), (1, -1)) | |
'﹙' | |
>>> _test_more_arcs((0, 0), (1, 1), (1, 1), (-1, 1)) | |
'﹚' | |
""" | |
pen = SegmentSymbolsPen() | |
pen._moveTo(start) | |
pen._curveToOne(control1, control2, end) | |
return pen.segments_symbols | |
# for diagnosing | |
# def draw_curve(start, control1, control2, end): | |
# fill(None) | |
# stroke(0) | |
# pen = BezierPath() | |
# pen.moveTo(start) | |
# pen.curveTo(control1, control2, end) | |
# drawPath(pen) | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For visualizng the _test_more_arcs in drawbot: