Files
AI-Writie-Assistant/server/venv/lib/python3.9/site-packages/ezdxf/addons/hpgl2/tokenizer.py
T
2026-04-16 10:01:11 +08:00

225 lines
5.9 KiB
Python

# Copyright (c) 2023, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import NamedTuple, Sequence
class Command(NamedTuple):
name: str
args: Sequence[bytes]
ESCAPE = 27
SEMICOLON = ord(";")
PERCENT = ord("%")
MINUS = ord("-")
QUOTE_CHAR = ord('"')
CHAR_A = ord("A")
CHAR_B = ord("B")
CHAR_Z = ord("Z")
DEFAULT_TEXT_TERMINATOR = 3
# Enter HPGL/2 mode commands
# b"%-0B", # ??? not documented (assumption)
# b"%-1B", # ??? not documented (really exist)
# b"%-2B", # ??? not documented (assumption)
# b"%-3B", # ??? not documented (assumption)
# b"%0B", # documented in the HPGL2 reference by HP
# b"%1B", # documented
# b"%2B", # documented
# b"%3B", # documented
def get_enter_hpgl2_mode_command_length(s: bytes, i: int) -> int:
try:
if s[i] != ESCAPE:
return 0
if s[i + 1] != PERCENT:
return 0
length = 4
if s[i + 2] == MINUS:
i += 1
length = 5
# 0, 1, 2 or 3 + "B"
if 47 < s[i + 2] < 52 and s[i + 3] == CHAR_B:
return length
except IndexError:
pass
return 0
KNOWN_START_SEQUENCES = [b"BPIN", b"BP;IN", b"INPS", b"IN;PS", b"INDF", b"IN;DF"]
def has_known_start_sequence(b: bytes) -> bool:
for start_sequence in KNOWN_START_SEQUENCES:
if b.startswith(start_sequence):
return True
return False
def find_hpgl2_entry_point(s: bytes, start: int) -> int:
while True:
try:
index = s.index(b"%", start)
except ValueError:
return len(s)
length = get_enter_hpgl2_mode_command_length(s, index)
if length:
return index + length
start += 2
def hpgl2_commands(s: bytes) -> list[Command]:
"""Low level plot file parser, extracts the HPGL/2 from the byte stream `b`.
.. Important::
This parser expects the "Enter HPGL/2 mode" escape sequence to recognize
HPGL/2 commands. The sequence looks like this: ``[ESC]%1B``, multiple variants
of this sequence are supported.
"""
text_terminator = DEFAULT_TEXT_TERMINATOR
def find_terminator(i: int) -> int:
while i < length:
c = s[i]
if c == QUOTE_CHAR:
i = find_mark(i + 1, QUOTE_CHAR)
elif (CHAR_A <= c <= CHAR_Z) or c == SEMICOLON or c == ESCAPE:
break
i += 1
return i
def find_mark(i: int, mark: int) -> int:
while i < length and s[i] != mark:
i += 1
return i + 1
def append_command(b: bytes) -> None:
if b[:2] == b"DT":
nonlocal text_terminator
if len(b) > 2:
text_terminator = b[2]
else:
text_terminator = DEFAULT_TEXT_TERMINATOR
else:
commands.append(make_command(b))
commands: list[Command] = []
length = len(s)
if has_known_start_sequence(s):
index = 0
else:
index = find_hpgl2_entry_point(s, 0)
while index < length:
char = s[index]
start = index
if char == ESCAPE:
# HPGL/2 does not use escape sequences, whatever this sequence is,
# HPGL/2 mode was left. Find next entry point into HPGL/2 mode:
index = find_hpgl2_entry_point(s, index)
continue
if char <= 32: # skip all white space and control chars between commands
index += 1
continue
index_plus_2 = index + 2
if index_plus_2 >= length:
append_command(s[index:])
break
command = s[start:index_plus_2]
if command == b"PE":
index = find_mark(index_plus_2, SEMICOLON)
index -= 1 # exclude terminator ";" from command args
elif command == b"LB":
index = find_mark(index_plus_2, text_terminator)
# include special terminator in command args,
# otherwise the parser is confused
else:
index = find_terminator(index_plus_2)
append_command(s[start:index])
if index < length and s[index] == SEMICOLON:
index += 1
return commands
def make_command(cmd: bytes) -> Command:
if not cmd:
return Command("NOOP", tuple())
name = cmd[:2].decode()
if name == "PE":
args = (bytes([c for c in cmd[2:] if c > 32]),)
else:
args = tuple(s for s in cmd[2:].split(b",")) # type: ignore
return Command(name, args)
def fractional_bits(decimal_places: int) -> int:
return round(decimal_places * 3.33)
def pe_encode(value: float, frac_bits: int = 0, base: int = 64) -> bytes:
if frac_bits:
value *= 1 << frac_bits
x = round(value)
else:
x = round(value)
if x >= 0:
x *= 2
else:
x = abs(x) * 2 + 1
chars = bytearray()
while x >= base:
x, r = divmod(x, base)
chars.append(63 + r)
if base == 64:
chars.append(191 + x)
else:
chars.append(95 + x)
return bytes(chars)
def pe_decode(
s: bytes, frac_bits: int = 0, base=64, start: int = 0
) -> tuple[list[float], int]:
def _decode():
factors.reverse()
x = 0
for f in factors:
x = x * base + f
factors.clear()
if x & 1:
x = -(x - 1)
x = x >> 1
return x
n = 1 << frac_bits
if base == 64:
terminator = 191
else:
terminator = 95
values: list[float] = []
factors = []
for index in range(start, len(s)):
value = s[index]
if value < 63:
return values, index
if value >= terminator:
factors.append(value - terminator)
x = _decode()
if frac_bits:
values.append(x / n)
else:
values.append(float(x))
else:
factors.append(value - 63)
return values, len(s)