1
0
Fork 0
inkscape/share/extensions/cgm_parse.py
Daniel Baumann 02d935e272
Adding upstream version 1.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 23:40:13 +02:00

1293 lines
34 KiB
Python

#!/usr/bin/env python3
# coding=utf-8
#
# Copyright (c) 2024 jonathan.neuhauser@outlook.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Parse binary CGM files
"""
from dataclasses import dataclass, fields, is_dataclass
from io import BufferedIOBase, BytesIO
import struct
from typing import (
ClassVar,
Dict,
List,
Optional,
Tuple,
Type,
Union,
get_type_hints,
get_origin,
get_args,
)
import warnings
import cgm_enums
import inkex
class FunctionRegistry(dict):
def register(self, key):
def decorator(func):
self[key] = func
return func
return decorator
class BinaryCGMCommandParser:
type_registry: Dict[Tuple[int, int], Type] = {}
unsigned_readers = FunctionRegistry()
signed_readers = FunctionRegistry()
real_readers = FunctionRegistry()
readers = FunctionRegistry()
sizers = FunctionRegistry()
def __init__(self, data: Union[bytes, BufferedIOBase]):
if isinstance(data, bytes):
data = BytesIO(data)
# The stream we currently operate on. For long form commands its
# easier to temporarily exchange this reference
self.stream = data
# The main stream of the file
self.outer_stream = data
# Reader functions for basic data types, where precision can be changed
# in the file.
self.integer_precision = 16
self.index_precision = 16
self.real_type = cgm_enums.RealTypeEnum.FIXED_POINT
self.real_precision = (16, 16)
self.vdc_integer_precision = 16
self.vdc_real_type = cgm_enums.RealTypeEnum.FIXED_POINT
self.vdc_real_precision = (16, 16)
self.float_precision = (9, 23)
self.colour_component_precision = 8
self.colour_index_precision = 8
self.vdc_type = cgm_enums.VDCTypeEnum.INTEGER
self.vdc_real_type = cgm_enums.RealTypeEnum.FIXED_POINT
self.vdc_real_precision = (16, 16)
self.colour_model = cgm_enums.ColourModelEnum.RGB
self.colour_selection_mode = cgm_enums.ColourSelectionModeEnum.INDEXED
self.line_width_specification_mode = cgm_enums.WidthSpecificationModeEnum.SCALED
self.marker_size_specification_mode = (
cgm_enums.WidthSpecificationModeEnum.SCALED
)
self.edge_width_specification_mode = cgm_enums.WidthSpecificationModeEnum.SCALED
self.interior_style_specification_mode = (
cgm_enums.WidthSpecificationModeEnum.ABSOLUTE
)
@staticmethod
def register_target_datatype(cls):
if cls.id is not None:
BinaryCGMCommandParser.type_registry[cls.id] = cls
def read(self, bytes):
res = self.stream.read(bytes)
if len(res) != bytes:
raise ValueError("EOF")
return res
def read_padded(self, length):
# An uneven length of a command is padded with a null byte
if length % 2 == 1:
arg_bytes = self.read(length + 1)
assert arg_bytes[-1] == 0
return arg_bytes[:-1]
return self.read(length)
@unsigned_readers.register(8)
def read_u8(self) -> int:
return int.from_bytes(self.read(1), byteorder="big")
@unsigned_readers.register(16)
def read_u16(self) -> int:
return int.from_bytes(self.read(2), byteorder="big")
@unsigned_readers.register(24)
def read_u24(self) -> int:
return int.from_bytes(self.read(3), byteorder="big")
@unsigned_readers.register(32)
def read_u32(self) -> int:
return int.from_bytes(self.read(4), byteorder="big")
@signed_readers.register(8)
def read_i8(self) -> int:
return int.from_bytes(self.read(1), byteorder="big", signed=True)
@signed_readers.register(16)
@readers.register("E")
def read_i16(self) -> int:
return int.from_bytes(self.read(2), byteorder="big", signed=True)
@signed_readers.register(24)
def read_i24(self) -> int:
return int.from_bytes(self.read(3), byteorder="big", signed=True)
@signed_readers.register(32)
def read_i32(self) -> int:
return int.from_bytes(self.read(4), byteorder="big", signed=True)
@real_readers.register((cgm_enums.RealTypeEnum.FLOATING_POINT, 9, 23))
def read_float(self) -> float:
return struct.unpack(">f", self.read(4))[0]
@real_readers.register((cgm_enums.RealTypeEnum.FLOATING_POINT, 12, 52))
def read_double(self) -> float:
return struct.unpack(">d", self.read(8))[0]
@real_readers.register((cgm_enums.RealTypeEnum.FIXED_POINT, 16, 16))
def read_fixed_single(self) -> float:
# Some sample files appear to not be using
# two's complement for signed integers if they occur in a flot.
# IMO that's incorrect according to the specification.
return self.read_i16() + self.read_u16() / 2**16
@real_readers.register((cgm_enums.RealTypeEnum.FIXED_POINT, 32, 32))
def read_fixed_double(self) -> float:
return self.read_i32() + self.read_u32() / 2**32
@readers.register("S")
@readers.register("D")
@readers.register("SF")
def read_str(self) -> str:
"""The first byte indicates the size.
If the size is 255, the first two bytes indicate the size.
From the new size, the first bit is a continuation flag
and the remaining 15 bits are the size"""
data: bytes
size = self.read_u8()
if size != 0xFF:
data = self.read(size)
else:
# Long-form string
self.stream.seek(0)
_data = bytearray()
while True:
cur_size = self.read_u16()
length = cur_size & 0b0111111111111111
_data.extend(self.read(length))
if not cur_size & 0b1000000000000000:
break
data = bytes(_data)
# TODO implement proper string encoding
return data.decode("latin-1")
def get_single_value(self, type_str: Union[str, Tuple[str]]):
if type_str in self.readers:
return BinaryCGMCommandParser.readers[type_str](self)
else:
raise ValueError(f"Unknown data type {type_str}")
def get_value(
self,
type_str: Union[str, Tuple[str, ...]],
target_types: Union[type, Tuple[type]],
length: Optional[int] = None,
):
"""This function reads a value from the stream.
The result can either be:
- a primitive type (e.g. "I", <type int> -> int)
- a tuple of primitive types
(e.g. ("I", "P"), (<type int>, <type Point>) -> (int, Point))
- a dataclass
(e.g. ("P", "E"), <type PolygonLineSetEntry> -> PolygonLineSetEntry)
in this case the type_str applies to the fields of the dataclass,
and the target types of those fields are inferred from the
type hint of the dataclass field
- a list of any of the previous
(e.g. ("I", "R"), typing.List[<type int>, <type float>]
-> List[Tuple[int, float]])
length must be defined for lists
This just calls get_value with the argument of the List typehint,
and collects the result until we've read length bytes
"""
res = None
initial_pos = self.stream.tell()
if isinstance(type_str, str) and isinstance(target_types, type):
if target_types == str and length == 0:
return ""
res = self.get_single_value(type_str)
if type_str not in ("P", "CO", "CD", "CI"):
res = target_types(res)
elif isinstance(type_str, tuple):
if get_origin(target_types) == tuple:
target_types = get_args(target_types)
if isinstance(target_types, tuple):
assert len(target_types) == len(type_str)
res = tuple(
self.get_value(i, t) for t, i in zip(target_types, type_str)
)
elif is_dataclass(target_types):
res = target_types(
*self.get_value(
type_str,
tuple(
get_type_hints(target_types)[t.name]
for t in fields(target_types)
),
)
)
if res is None and get_origin(target_types) == list:
assert length is not None
target_type = get_args(target_types)[0]
try:
field_size = self.get_size(type_str)
# Assert that the remaining bytes fit into the list without residue
assert length % field_size == 0
except ValueError:
# just an assertion here, continue
pass
res = []
while True:
if (self.stream.tell() - initial_pos) >= length:
break
res.append(self.get_value(type_str, target_type))
assert res is not None, "invalid specification for get_value"
# assert isinstance(res, target_types), "bad return type"
return res
@readers.register("IX")
def read_index(self):
return BinaryCGMCommandParser.signed_readers[self.index_precision](self)
@readers.register("SI")
@readers.register("I")
def read_signed(self):
return BinaryCGMCommandParser.signed_readers[self.integer_precision](self)
@readers.register("VDC")
def read_vdc(self):
return (
BinaryCGMCommandParser.signed_readers[self.vdc_integer_precision](self)
if self.vdc_type == cgm_enums.VDCTypeEnum.INTEGER
else BinaryCGMCommandParser.real_readers[
(self.vdc_real_type, *self.vdc_real_precision)
](self)
)
@readers.register("R")
def read_real(self):
return BinaryCGMCommandParser.real_readers[
(self.real_type, *self.real_precision)
](self)
@readers.register("F")
def read_floating(self):
return BinaryCGMCommandParser.real_readers[
(cgm_enums.RealTypeEnum.FLOATING_POINT, *self.float_precision)
](self)
@readers.register("CI")
def read_indexed_colour(self):
return cgm_enums.IndexColour(
self.signed_readers[self.colour_index_precision](self)
)
@readers.register("CCO")
def read_colour_component(self):
return BinaryCGMCommandParser.unsigned_readers[self.colour_component_precision](
self
)
@readers.register("CD")
def read_direct_colour(self):
result = cgm_enums.DirectColour(
*(
self.read_colour_component()
for _ in range(
4 if self.colour_model == cgm_enums.ColourModelEnum.CMYK else 3
)
)
)
result.colour_model = self.colour_model
return result
@readers.register("CO")
def read_colour(self):
if self.colour_selection_mode == cgm_enums.ColourSelectionModeEnum.DIRECT:
return self.read_direct_colour()
else:
return self.read_indexed_colour()
@readers.register("P")
def read_point(self):
return cgm_enums.Point(self.read_vdc(), self.read_vdc())
def read_size(self, mode: cgm_enums.WidthSpecificationModeEnum):
if mode == cgm_enums.WidthSpecificationModeEnum.ABSOLUTE:
return self.read_vdc()
return self.read_real()
@readers.register("LSS")
def read_line_size(self):
return self.read_size(self.line_width_specification_mode)
@readers.register("MSS")
def read_marker_size(self):
return self.read_size(self.marker_size_specification_mode)
@readers.register("ESS")
def read_edge_size(self):
return self.read_size(self.edge_width_specification_mode)
@readers.register("ISS")
def read_interior_style_size(self):
return self.read_size(self.interior_style_specification_mode)
# SIZING FUNCTIONS
def get_size(self, type_str: Union[str, Tuple[str, ...]]):
if type_str in BinaryCGMCommandParser.sizers:
return BinaryCGMCommandParser.sizers[type_str](self)
if isinstance(type_str, tuple):
return sum(self.get_size(i) for i in type_str)
else:
raise ValueError(f"No size know for type {type_str}")
@sizers.register("CI")
def ci_size(self):
return self.colour_index_precision // 8
@sizers.register("CCO")
def cco_size(self):
return self.colour_component_precision // 8
@sizers.register("CD")
def cd_size(self):
return self.cco_size() * (
4 if self.colour_model == cgm_enums.ColourModelEnum.CMYK else 3
)
@sizers.register("IX")
def ix_size(self):
return self.index_precision // 8
@sizers.register("E")
def e_size(self):
return 2
@sizers.register("I")
def i_size(self):
return self.integer_precision // 8
@sizers.register("R")
def r_size(self):
return (self.real_precision[0] + self.real_precision[1]) // 8
@sizers.register("VDC")
def vdc_size(self):
return (
self.vdc_integer_precision // 8
if self.vdc_type == cgm_enums.VDCTypeEnum.INTEGER
else (self.vdc_real_precision[0] + self.vdc_real_precision[1]) // 8
)
@sizers.register("P")
def p_size(self):
return 2 * self.vdc_size()
@sizers.register("CO")
def co_size(self):
if self.colour_selection_mode == cgm_enums.ColourSelectionModeEnum.DIRECT:
return self.cd_size()
else:
return self.ci_size()
def ss_size(self, mode: cgm_enums.WidthSpecificationModeEnum):
if mode == cgm_enums.WidthSpecificationModeEnum.ABSOLUTE:
return self.vdc_size()
return self.r_size()
@sizers.register("LSS")
def lss_size(self):
return self.ss_size(self.line_width_specification_mode)
@sizers.register("MSS")
def marker_size(self):
return self.ss_size(self.marker_size_specification_mode)
@sizers.register("ESS")
def edge_size(self):
return self.ss_size(self.edge_width_specification_mode)
@sizers.register("ISS")
def interior_style_size(self):
return self.ss_size(self.interior_style_specification_mode)
def parse(self):
while True:
try:
header = self.read_u16()
except ValueError:
return
clss = header >> 12
obj_id = (header & 0b0000111111100000) >> 5
total_length = header & 0x1F
if total_length == 31:
# Long-form header
args = bytearray()
total_length = 0
while True:
data = self.read_u16()
length = data & 0b0111111111111111
total_length += length
args.extend(self.read_padded(length))
if not data & 0b1000000000000000:
break
self.stream = BytesIO(bytes(args))
old_pos = self.stream.tell()
interpreted = self.command_factory(clss, obj_id, total_length)
new_pos = self.stream.tell()
if (
new_pos - old_pos not in [total_length] + [total_length - 1]
if total_length % 2 == 0
else []
):
warnings.warn(
f"{interpreted.__class__.__name__}: Expected to read {total_length} bytes, but read {new_pos - old_pos}"
)
new_pos = old_pos + total_length
self.stream.seek(new_pos)
if isinstance(interpreted, ParserModifierCommand):
self.update_readers(interpreted)
# Prepare the stream for the next entry
if self.stream == self.outer_stream:
if new_pos % 2 != 0:
# Fields are always aligned with 2-byte words
self.read(1)
else:
self.stream = self.outer_stream
yield interpreted
if isinstance(interpreted, EndMetafile):
# Sometimes there are trailing characters afterwards, return
return
def parse_data(self, data: BytesIO):
"""Temporarily exchanges the stream by data to read primitives from it,
using the same settings as the current stream"""
outer_stream = self.outer_stream
stream = self.stream
self.outer_stream = data
self.stream = data
result = tuple(self.parse())
self.outer_stream = outer_stream
self.stream = stream
return result
def update_readers(self, interpreted):
if isinstance(interpreted, VdcType):
self.vdc_type = interpreted.vdc_type
elif isinstance(interpreted, IntegerPrecision):
self.integer_precision = interpreted.integer_precision
elif isinstance(interpreted, RealPrecision):
self.real_type = interpreted.representation
self.real_precision = (
interpreted.exponent_whole_size,
interpreted.fraction_size,
)
if interpreted.representation == cgm_enums.RealTypeEnum.FLOATING_POINT:
self.float_precision = (
interpreted.exponent_whole_size,
interpreted.fraction_size,
)
elif isinstance(interpreted, IndexPrecision):
self.index_precision = interpreted.index_precision
elif isinstance(interpreted, ColourIndexPrecision):
self.colour_index_precision = interpreted.colour_index_precision
elif isinstance(interpreted, ColourPrecision):
self.colour_component_precision = interpreted.colour_precision
elif isinstance(interpreted, ColourSelectionMode):
self.colour_selection_mode = interpreted.colour_selection_mode
elif isinstance(interpreted, ColourModel):
self.colour_model = interpreted.colour_model
elif isinstance(interpreted, VDCIntegerPrecision):
self.vdc_integer_precision = interpreted.integer_precision
elif isinstance(interpreted, VDCRealPrecision):
self.vdc_real_type = interpreted.representation
self.vdc_real_precision = (
interpreted.exponent_whole_size,
interpreted.fraction_size,
)
elif isinstance(interpreted, LineWidthSpecificationMode):
self.line_width_specification_mode = interpreted.size_specification
elif isinstance(interpreted, EdgeWidthSpecificationMode):
self.edge_width_specification_mode = interpreted.size_specification
elif isinstance(interpreted, MarkerSizeSpecificationMode):
self.marker_size_specification_mode = interpreted.size_specification
elif isinstance(interpreted, InteriorStyleSpecificationMode):
self.interior_style_specification_mode = interpreted.size_specification
else:
inkex.errormsg(
f"Parser modifier command {interpreted} without associated action"
)
def command_factory(self, group, command, length):
cls = self.type_registry.get((group, command), UnknownCommand)
result = cls.parse_binary(self, length)
if isinstance(result, UnknownCommand):
result.id = (group, command)
return result
@dataclass
class CGMCommand:
id: ClassVar[Tuple[int, int]] = (0, 0)
binary_params: ClassVar[Tuple[Union[str, Tuple[str, ...]], ...]] = tuple()
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BinaryCGMCommandParser.register_target_datatype(cls)
@classmethod
def get_binary_args(
cls,
stream: BinaryCGMCommandParser,
length: int,
):
initial_pos = stream.stream.tell()
for i, (field, types) in enumerate(zip(fields(cls), cls.binary_params)):
outer_type = get_type_hints(cls)[field.name]
yield stream.get_value(
types, outer_type, length - (stream.stream.tell() - initial_pos)
)
@classmethod
def parse_binary(cls, stream: BinaryCGMCommandParser, length):
args = list(cls.get_binary_args(stream, length))
return cls(*args)
6
@dataclass
class NoOp(CGMCommand):
id = (0, 0)
data: bytes
@classmethod
def parse_binary(cls, stream: BinaryCGMCommandParser, length):
return cls(stream.read(length))
@dataclass
class UnknownCommand(CGMCommand):
data: bytes
@classmethod
def parse_binary(cls, stream: BinaryCGMCommandParser, length):
return cls(stream.read(length))
@dataclass
class BeginMetafile(CGMCommand):
id = (0, 1)
binary_params = ("SF",)
name: str
@dataclass
class EndMetafile(CGMCommand):
id = (0, 2)
@dataclass
class BeginPicture(CGMCommand):
id = (0, 3)
binary_params = ("SF",)
name: str
@dataclass
class BeginPictureBody(CGMCommand):
id = (0, 4)
@dataclass
class EndPicture(CGMCommand):
id = (0, 5)
# Metafile descriptor elements
@dataclass
class MetafileVersion(CGMCommand):
id = (1, 1)
binary_params = ("I",)
version: int
@dataclass
class MetafileDescription(CGMCommand):
id = (1, 2)
binary_params = ("SF",)
description: str
class ParserModifierCommand(CGMCommand):
pass
@dataclass
class VdcType(ParserModifierCommand):
id = (1, 3)
binary_params = ("E",)
vdc_type: cgm_enums.VDCTypeEnum
@dataclass
class IntegerPrecision(ParserModifierCommand):
id = (1, 4)
binary_params = ("I",)
integer_precision: int
@dataclass
class RealPrecision(ParserModifierCommand):
id = (1, 5)
binary_params = "E", "I", "I"
representation: cgm_enums.RealTypeEnum
exponent_whole_size: int
fraction_size: int
@dataclass
class IndexPrecision(ParserModifierCommand):
id = (1, 6)
binary_params = ("I",)
index_precision: int
@dataclass
class ColourPrecision(ParserModifierCommand):
id = (1, 7)
binary_params = ("I",)
colour_precision: int
@dataclass
class ColourIndexPrecision(ParserModifierCommand):
id = (1, 8)
binary_params = ("I",)
colour_index_precision: int
@dataclass
class MaximumColourIndex(CGMCommand):
id = (1, 9)
binary_params = ("CI",)
maximum_colour_index: cgm_enums.IndexColour
@dataclass
class ColourValueExtent(CGMCommand):
id = (1, 10)
minimum: Optional[int]
maximum: Optional[int]
first_component: Optional[Tuple[float, float]]
second_component: Optional[Tuple[float, float]]
third_component: Optional[Tuple[float, float]]
@classmethod
def get_binary_args(cls, stream: BinaryCGMCommandParser, length):
if stream.colour_model in (
cgm_enums.ColourModelEnum.CMYK,
cgm_enums.ColourModelEnum.RGB,
):
return stream.read_direct_colour(), stream.read_direct_colour(), 0, 0, 0
else:
return 0, 0, stream.read_real(), stream.read_real(), stream.read_real()
@dataclass
class MetafileElementList(CGMCommand):
id = (1, 11)
elements: List[Tuple[int, int]]
@classmethod
def get_binary_args(cls, stream: BinaryCGMCommandParser, length: int):
num = stream.read_signed()
yield [(stream.read_index(), stream.read_index()) for _ in range(num)]
@dataclass
class MetafileDefaultsReplacement(CGMCommand):
id = (1, 12)
data: List[CGMCommand]
@classmethod
def get_binary_args(cls, stream: BinaryCGMCommandParser, length: int):
data = stream.read(length)
yield stream.parse_data(BytesIO(data))
@dataclass
class FontList(CGMCommand):
id = (1, 13)
binary_params = ("SF",)
fonts: List[str]
@dataclass
class CharacterSetList(CGMCommand):
id = (1, 14)
binary_params = (("E", "SF"),)
specification: List[Tuple[cgm_enums.CharacterSetTypeEnum, str]]
@dataclass
class CharacterCodingAnnouncer(CGMCommand):
id = (1, 15)
binary_params = ("E",)
encoding: cgm_enums.CharacterCodingEnum
@dataclass
class MaximumVDCExtent(CGMCommand):
id = (1, 17)
binary_params = "P", "P"
first_corner: cgm_enums.Point
second_corner: cgm_enums.Point
@dataclass
class ColourModel(ParserModifierCommand):
id = (1, 19)
binary_params = ("IX",)
colour_model: cgm_enums.ColourModelEnum
@dataclass
class ScalingMode(CGMCommand):
id = (2, 1)
binary_params = "E", "F"
scaling_mode: cgm_enums.ScalingModeEnum
metric_scaling_factor: float
@dataclass
class ColourSelectionMode(ParserModifierCommand):
id = (2, 2)
binary_params = ("E",)
colour_selection_mode: cgm_enums.ColourSelectionModeEnum
@dataclass
class LineWidthSpecificationMode(ParserModifierCommand):
id = (2, 3)
binary_params = ("E",)
size_specification: cgm_enums.WidthSpecificationModeEnum
@dataclass
class MarkerSizeSpecificationMode(ParserModifierCommand):
id = (2, 4)
binary_params = ("E",)
size_specification: cgm_enums.WidthSpecificationModeEnum
@dataclass
class EdgeWidthSpecificationMode(ParserModifierCommand):
id = (2, 5)
binary_params = ("E",)
size_specification: cgm_enums.WidthSpecificationModeEnum
@dataclass
class VDCExtent(CGMCommand):
id = (2, 6)
binary_params = "P", "P"
first_corner: cgm_enums.Point
second_corner: cgm_enums.Point
@dataclass
class BackgroundColour(CGMCommand):
id = (2, 7)
binary_params = ("CD",)
colour: cgm_enums.DirectColour
@dataclass
class InteriorStyleSpecificationMode(ParserModifierCommand):
id = (2, 16)
binary_params = ("E",)
size_specification: cgm_enums.WidthSpecificationModeEnum
@dataclass
class VDCIntegerPrecision(ParserModifierCommand):
id = (3, 1)
binary_params = ("I",)
integer_precision: int
@dataclass
class VDCRealPrecision(ParserModifierCommand):
id = (3, 2)
binary_params = "E", "I", "I"
representation: cgm_enums.RealTypeEnum
exponent_whole_size: int
fraction_size: int
@dataclass
class Transparency(CGMCommand):
id = (3, 4)
binary_params = ("E",)
indicator: cgm_enums.TransparencyEnum
@dataclass
class ClipRectangle(CGMCommand):
id = (3, 5)
binary_params = ("P", "P")
p1: cgm_enums.Point
p2: cgm_enums.Point
@dataclass
class ClipIndicator(CGMCommand):
id = (3, 6)
binary_params = ("E",)
clip: cgm_enums.ClipIndicatorEnum
@dataclass
class Polyline(CGMCommand):
id = (4, 1)
binary_params = ("P",)
points: List[cgm_enums.Point]
@dataclass
class DisjointPolyline(CGMCommand):
id = (4, 2)
binary_params = ("P",)
points: List[cgm_enums.Point]
@dataclass
class Polymarker(CGMCommand):
id = (4, 3)
binary_params = ("P",)
points: List[cgm_enums.Point]
@dataclass
class Text(CGMCommand):
id = (4, 4)
binary_params = ("P", "E", "S")
point: cgm_enums.Point
flag: cgm_enums.TextFinalFlag
string: str
@dataclass
class RestrictedText(CGMCommand):
id = (4, 5)
binary_params = ("VDC", "VDC", "P", "E", "S")
width: float
height: float
point: cgm_enums.Point
flag: cgm_enums.TextFinalFlag
string: str
@dataclass
class AppendText(CGMCommand):
id = (4, 6)
binary_params = ("E", "S")
flag: cgm_enums.TextFinalFlag
string: str
@dataclass
class Polygon(CGMCommand):
id = (4, 7)
binary_params = ("P",)
points: List[cgm_enums.Point]
@dataclass
class PolygonSet(CGMCommand):
id = (4, 8)
points: List[cgm_enums.PolygonSetEntry]
binary_params = (("P", "E"),)
# TODO: Cell array (complicated parsing with sub-byte values)
@dataclass
class GeneralizedDrawingPrimitive(CGMCommand):
id = (4, 10)
identifier: int
points: List[cgm_enums.Point]
data: bytes
@classmethod
def get_binary_args(cls, stream: BinaryCGMCommandParser, length: int):
yield stream.read_signed()
num = stream.read_signed()
points = [stream.read_point() for _ in range(num)]
yield points
read = stream.i_size() * 2 + num * stream.p_size()
yield stream.read(length - read)
@dataclass
class Rectangle(CGMCommand):
id = (4, 11)
binary_params = ("P", "P")
p1: cgm_enums.Point
p2: cgm_enums.Point
@dataclass
class Circle(CGMCommand):
id = (4, 12)
binary_params = ("P", "VDC")
center: cgm_enums.Point
radius: float
@dataclass
class CircularArc3Point(CGMCommand):
id = (4, 13)
binary_params = ("P", "P", "P")
p1: cgm_enums.Point
p2: cgm_enums.Point
p3: cgm_enums.Point
@dataclass
class CircularArc3PointClose(CircularArc3Point):
id = (4, 14)
binary_params = ("P", "P", "P", "E") # type: ignore
closure: cgm_enums.ArcClosureEnum
@dataclass
class CircularArcCentre(CGMCommand):
id = (4, 15)
binary_params = ("P", "VDC", "VDC", "VDC", "VDC", "VDC") # type: ignore
centre: cgm_enums.Point
delta_x_start: float
delta_y_start: float
delta_x_end: float
delta_y_end: float
radius: float
@dataclass
class CircularArcCentreClose(CircularArcCentre):
id = (4, 16)
binary_params = ("P", "VDC", "VDC", "VDC", "VDC", "VDC", "E") # type: ignore
closure: cgm_enums.ArcClosureEnum
@dataclass
class Ellipse(CGMCommand):
id = (4, 17)
binary_params = ("P", "P", "P")
center: cgm_enums.Point
point_1: cgm_enums.Point
point_2: cgm_enums.Point
@dataclass
class EllipticalArc(Ellipse):
id = (4, 18)
binary_params = ("P", "P", "P", "VDC", "VDC", "VDC", "VDC") # type: ignore
delta_x_start: float
delta_y_start: float
delta_x_end: float
delta_y_end: float
@dataclass
class EllipticalArcClose(EllipticalArc):
id = (4, 19)
binary_params = ("P", "P", "P", "VDC", "VDC", "VDC", "VDC", "E") # type: ignore
closure: cgm_enums.ArcClosureEnum
@dataclass
class Polybezier(CGMCommand):
id = (4, 26)
binary_params = ("E", "P")
continuous: cgm_enums.PolybezierContinuityEnum
points: List[cgm_enums.Point]
@dataclass
class LineType(CGMCommand):
id = (5, 2)
binary_params = ("IX",)
type: cgm_enums.LineTypeEnum
@dataclass
class LineWidth(CGMCommand):
id = (5, 3)
binary_params = ("LSS",)
width: float
@dataclass
class LineColour(CGMCommand):
id = (5, 4)
binary_params = ("CO",)
colour: cgm_enums.Colour
@dataclass
class MarkerType(CGMCommand):
id = (5, 6)
binary_params = ("E",)
type: cgm_enums.MarkerTypeEnum
@dataclass
class MarkerSize(CGMCommand):
id = (5, 7)
binary_params = ("MSS",)
size: float
@dataclass
class MarkerColour(CGMCommand):
id = (5, 8)
binary_params = ("CO",)
colour: cgm_enums.Colour
@dataclass
class TextFontIndex(CGMCommand):
id = (5, 10)
binary_params = ("IX",)
index: int
@dataclass
class TextPrecision(CGMCommand):
id = (5, 11)
binary_params = ("E",)
text_precision: cgm_enums.TextPrecisionEnum
@dataclass
class CharacterExpansionFactor(CGMCommand):
id = (5, 12)
binary_params = ("R",)
factor: float
@dataclass
class CharacterSpacing(CGMCommand):
id = (5, 13)
binary_params = ("R",)
spacing: float
@dataclass
class TextColour(CGMCommand):
id = (5, 14)
binary_params = ("CO",)
colour: cgm_enums.Colour
@dataclass
class CharacterHeight(CGMCommand):
id = (5, 15)
binary_params = ("VDC",)
height: float
@dataclass
class CharacterOrientation(CGMCommand):
id = (5, 16)
binary_params = ("VDC", "VDC", "VDC", "VDC")
x_character_up: float
y_character_up: float
x_character_base: float
y_character_base: float
@dataclass
class TextPath(CGMCommand):
id = (5, 17)
binary_params = ("E",)
path: cgm_enums.TextPathEnum
@dataclass
class TextAlignment(CGMCommand):
id = (5, 18)
binary_params = ("E", "E", "R", "R")
horizontal_alignment: cgm_enums.TextHorizontalAlignmentEnum
vertical_alignment: cgm_enums.TextVerticalAlignmentEnum
continous_horizontal_alignment: float
continous_vertical_alignment: float
@dataclass
class CharacterSetIndex(CGMCommand):
id = (5, 19)
binary_params = ("IX",)
index: int
@dataclass
class AlternateCharacterSetIndex(CGMCommand):
id = (5, 20)
binary_params = ("IX",)
index: int
@dataclass
class InteriorStyle(CGMCommand):
id = (5, 22)
binary_params = ("E",)
interior_style: cgm_enums.InteriorStyleEnum
@dataclass
class FillColour(CGMCommand):
id = (5, 23)
binary_params = ("CO",)
fill: cgm_enums.Colour
@dataclass
class HatchIndex(CGMCommand):
id = (5, 24)
binary_params = ("IX",)
index: int
@dataclass
class PatternIndex(CGMCommand):
id = (5, 25)
binary_params = ("IX",)
index: int
@dataclass
class EdgeType(CGMCommand):
id = (5, 27)
binary_params = ("IX",)
type: cgm_enums.LineTypeEnum
@dataclass
class EdgeWidth(CGMCommand):
id = (5, 28)
binary_params = ("ESS",)
width: float
@dataclass
class EdgeColour(CGMCommand):
id = (5, 29)
binary_params = ("CO",)
colour: cgm_enums.Colour
@dataclass
class EdgeVisibility(CGMCommand):
id = (5, 30)
binary_params = ("E",)
visibility: cgm_enums.EdgeVisibilityEnum
@dataclass
class ColourTable(CGMCommand):
id = (5, 34)
binary_params = ("CI", "CD")
colour_index: cgm_enums.IndexColour
colours: List[cgm_enums.DirectColour]
@dataclass
class AspectSourceFlags(CGMCommand):
id = (5, 35)
binary_params = (("E", "E"),)
flags: List[Tuple[cgm_enums.ASFTypeEnum, cgm_enums.ASFValue]]
@dataclass
class LineCap(CGMCommand):
id = (5, 37)
binary_params = ("E", "E")
cap: cgm_enums.CapIndicatorEnum
dash_cap: cgm_enums.DashCapIndicatorEnum
@dataclass
class RestrictedTextType(CGMCommand):
id = (5, 42)
binary_params = ("E",)
type: cgm_enums.RestrictionTypeEnum
@dataclass
class ApplicationData(CGMCommand):
id = (7, 2)
binary_params = ("I", "SF")
identifier: int
data: str