summaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/svgLib/path/parser.py
diff options
context:
space:
mode:
authormaxim-yurchuk <[email protected]>2025-02-11 13:26:52 +0300
committermaxim-yurchuk <[email protected]>2025-02-11 13:57:59 +0300
commitf895bba65827952ed934b2b46f9a45e30a191fd2 (patch)
tree03260c906d9ec41cdc03e2a496b15d407459cec0 /contrib/python/fonttools/fontTools/svgLib/path/parser.py
parent5f7060466f7b9707818c2091e1a25c14f33c3474 (diff)
Remove deps on pandas
<https://github.com/ydb-platform/ydb/pull/14418> <https://github.com/ydb-platform/ydb/pull/14419> \-- аналогичные правки в gh Хочу залить в обход синка, чтобы посмотреть удалится ли pandas в нашей gh репе через piglet commit_hash:abca127aa37d4dbb94b07e1e18cdb8eb5b711860
Diffstat (limited to 'contrib/python/fonttools/fontTools/svgLib/path/parser.py')
-rw-r--r--contrib/python/fonttools/fontTools/svgLib/path/parser.py322
1 files changed, 0 insertions, 322 deletions
diff --git a/contrib/python/fonttools/fontTools/svgLib/path/parser.py b/contrib/python/fonttools/fontTools/svgLib/path/parser.py
deleted file mode 100644
index 18c8e77f7f7..00000000000
--- a/contrib/python/fonttools/fontTools/svgLib/path/parser.py
+++ /dev/null
@@ -1,322 +0,0 @@
-# SVG Path specification parser.
-# This is an adaptation from 'svg.path' by Lennart Regebro (@regebro),
-# modified so that the parser takes a FontTools Pen object instead of
-# returning a list of svg.path Path objects.
-# The original code can be found at:
-# https://github.com/regebro/svg.path/blob/4f9b6e3/src/svg/path/parser.py
-# Copyright (c) 2013-2014 Lennart Regebro
-# License: MIT
-
-from .arc import EllipticalArc
-import re
-
-
-COMMANDS = set("MmZzLlHhVvCcSsQqTtAa")
-ARC_COMMANDS = set("Aa")
-UPPERCASE = set("MZLHVCSQTA")
-
-COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
-
-# https://www.w3.org/TR/css-syntax-3/#number-token-diagram
-# but -6.e-5 will be tokenized as "-6" then "-5" and confuse parsing
-FLOAT_RE = re.compile(
- r"[-+]?" # optional sign
- r"(?:"
- r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?" # int/float
- r"|"
- r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)" # float with leading dot (e.g. '.42')
- r")"
-)
-BOOL_RE = re.compile("^[01]")
-SEPARATOR_RE = re.compile(f"[, \t]")
-
-
-def _tokenize_path(pathdef):
- arc_cmd = None
- for x in COMMAND_RE.split(pathdef):
- if x in COMMANDS:
- arc_cmd = x if x in ARC_COMMANDS else None
- yield x
- continue
-
- if arc_cmd:
- try:
- yield from _tokenize_arc_arguments(x)
- except ValueError as e:
- raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e
- else:
- for token in FLOAT_RE.findall(x):
- yield token
-
-
-ARC_ARGUMENT_TYPES = (
- ("rx", FLOAT_RE),
- ("ry", FLOAT_RE),
- ("x-axis-rotation", FLOAT_RE),
- ("large-arc-flag", BOOL_RE),
- ("sweep-flag", BOOL_RE),
- ("x", FLOAT_RE),
- ("y", FLOAT_RE),
-)
-
-
-def _tokenize_arc_arguments(arcdef):
- raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s]
- if not raw_args:
- raise ValueError(f"Not enough arguments: '{arcdef}'")
- raw_args.reverse()
-
- i = 0
- while raw_args:
- arg = raw_args.pop()
-
- name, pattern = ARC_ARGUMENT_TYPES[i]
- match = pattern.search(arg)
- if not match:
- raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}")
-
- j, k = match.span()
- yield arg[j:k]
- arg = arg[k:]
-
- if arg:
- raw_args.append(arg)
-
- # wrap around every 7 consecutive arguments
- if i == 6:
- i = 0
- else:
- i += 1
-
- if i != 0:
- raise ValueError(f"Not enough arguments: '{arcdef}'")
-
-
-def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
- """Parse SVG path definition (i.e. "d" attribute of <path> elements)
- and call a 'pen' object's moveTo, lineTo, curveTo, qCurveTo and closePath
- methods.
-
- If 'current_pos' (2-float tuple) is provided, the initial moveTo will
- be relative to that instead being absolute.
-
- If the pen has an "arcTo" method, it is called with the original values
- of the elliptical arc curve commands:
-
- .. code-block::
-
- pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y))
-
- Otherwise, the arcs are approximated by series of cubic Bezier segments
- ("curveTo"), one every 90 degrees.
- """
- # 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.
- current_pos = complex(*current_pos)
-
- elements = list(_tokenize_path(pathdef))
- # Reverse for easy use of .pop()
- elements.reverse()
-
- start_pos = None
- command = None
- last_control = None
-
- have_arcTo = hasattr(pen, "arcTo")
-
- 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))
- )
- last_command = command # Used by S and T
-
- 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
-
- # M is not preceded by Z; it's an open subpath
- if start_pos is not None:
- pen.endPath()
-
- pen.moveTo((current_pos.real, current_pos.imag))
-
- # 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 current_pos != start_pos:
- pen.lineTo((start_pos.real, start_pos.imag))
- pen.closePath()
- current_pos = start_pos
- start_pos = None
- command = None # You can't have implicit commands after closing.
-
- elif command == "L":
- x = elements.pop()
- y = elements.pop()
- pos = float(x) + float(y) * 1j
- if not absolute:
- pos += current_pos
- pen.lineTo((pos.real, pos.imag))
- current_pos = pos
-
- elif command == "H":
- x = elements.pop()
- pos = float(x) + current_pos.imag * 1j
- if not absolute:
- pos += current_pos.real
- pen.lineTo((pos.real, pos.imag))
- current_pos = pos
-
- elif command == "V":
- y = elements.pop()
- pos = current_pos.real + float(y) * 1j
- if not absolute:
- pos += current_pos.imag * 1j
- pen.lineTo((pos.real, pos.imag))
- 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
-
- pen.curveTo(
- (control1.real, control1.imag),
- (control2.real, control2.imag),
- (end.real, end.imag),
- )
- current_pos = end
- last_control = control2
-
- 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 - last_control
-
- 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
-
- pen.curveTo(
- (control1.real, control1.imag),
- (control2.real, control2.imag),
- (end.real, end.imag),
- )
- current_pos = end
- last_control = control2
-
- 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
-
- pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
- current_pos = end
- last_control = control
-
- 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 - last_control
-
- end = float(elements.pop()) + float(elements.pop()) * 1j
-
- if not absolute:
- end += current_pos
-
- pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
- current_pos = end
- last_control = control
-
- elif command == "A":
- rx = abs(float(elements.pop()))
- ry = abs(float(elements.pop()))
- rotation = float(elements.pop())
- arc_large = bool(int(elements.pop()))
- arc_sweep = bool(int(elements.pop()))
- end = float(elements.pop()) + float(elements.pop()) * 1j
-
- if not absolute:
- end += current_pos
-
- # if the pen supports arcs, pass the values unchanged, otherwise
- # approximate the arc with a series of cubic bezier curves
- if have_arcTo:
- pen.arcTo(
- rx,
- ry,
- rotation,
- arc_large,
- arc_sweep,
- (end.real, end.imag),
- )
- else:
- arc = arc_class(
- current_pos, rx, ry, rotation, arc_large, arc_sweep, end
- )
- arc.draw(pen)
-
- current_pos = end
-
- # no final Z command, it's an open path
- if start_pos is not None:
- pen.endPath()