diff --git a/src/rmscene/scene_items.py b/src/rmscene/scene_items.py index f6cb5cf..81ba84e 100644 --- a/src/rmscene/scene_items.py +++ b/src/rmscene/scene_items.py @@ -127,6 +127,7 @@ class Line(SceneItem): points: list[Point] thickness_scale: float starting_length: float + move_id: tp.Optional[CrdtId] = None ## Text @@ -144,6 +145,7 @@ class ParagraphStyle(enum.IntEnum): BOLD = 3 BULLET = 4 BULLET2 = 5 + CHECKBOX = 6 END_MARKER = CrdtId(0, 0) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 1d9863e..e932120 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -99,6 +99,34 @@ def to_stream(self, writer: TaggedBlockWriter): writer.data.write_bytes(self.data) +@dataclass +class SceneInfo(Block): + BLOCK_TYPE: tp.ClassVar = 0x0D + + def version_info(self, _) -> tuple[int, int]: + """Return (min_version, current_version) to use when writing.""" + return (0, 1) + + current_layer: LwwValue[CrdtId] + background_visible: LwwValue[bool] + root_document_visible: LwwValue[bool] + + @classmethod + def from_stream(cls, stream: TaggedBlockReader) -> SceneInfo: + current_layer = stream.read_lww_id(1) + background_visible = stream.read_lww_bool(2) + root_document_visible = stream.read_lww_bool(3) + + return SceneInfo(current_layer=current_layer, + background_visible=background_visible, + root_document_visible=root_document_visible) + + def to_stream(self, writer: TaggedBlockWriter): + writer.write_lww_id(1, self.current_layer) + writer.write_lww_bool(2, self.background_visible) + writer.write_lww_bool(3, self.root_document_visible) + + @dataclass class AuthorIdsBlock(Block): BLOCK_TYPE: tp.ClassVar = 0x09 @@ -164,6 +192,12 @@ def to_stream(self, writer: TaggedBlockWriter): class TreeNodeBlock(Block): BLOCK_TYPE: tp.ClassVar = 0x02 + def version_info(self, writer: TaggedBlockWriter) -> tuple[int, int]: + """Return (min_version, current_version) to use when writing.""" + version = writer.options.get("version", Version("9999")) + # XXX this is a guess about which version this changed in + return (1, 2) if (version >= Version("3.4")) else (1, 1) + group: si.Group @classmethod @@ -217,7 +251,7 @@ def version_info(self, _) -> tuple[int, int]: merges_count: int text_chars_count: int text_lines_count: int - _unknown: int = 0 + type_folio_use_count: int = 0 @classmethod def from_stream(cls, stream: TaggedBlockReader) -> PageInfoBlock: @@ -230,7 +264,7 @@ def from_stream(cls, stream: TaggedBlockReader) -> PageInfoBlock: text_lines_count=stream.read_int(4), ) if stream.bytes_remaining_in_block(): - info._unknown = stream.read_int(5) + info.type_folio_use_count = stream.read_int(5) return info def to_stream(self, writer: TaggedBlockWriter): @@ -241,7 +275,7 @@ def to_stream(self, writer: TaggedBlockWriter): writer.write_int(4, self.text_lines_count) version = writer.options.get("version", Version("9999")) if version >= Version("3.2.2"): - writer.write_int(5, self._unknown) + writer.write_int(5, self.type_folio_use_count) @dataclass @@ -355,7 +389,12 @@ def line_from_stream(stream: TaggedBlockReader, version: int = 2) -> si.Line: # XXX unused timestamp = stream.read_id(6) - return si.Line(color, tool, points, thickness_scale, starting_length) + if stream.bytes_remaining_in_block() >= 3: + move_id = stream.read_id(7) + else: + move_id = None + + return si.Line(color, tool, points, thickness_scale, starting_length, move_id) def line_to_stream(line: si.Line, writer: TaggedBlockWriter, version: int = 2): @@ -371,6 +410,8 @@ def line_to_stream(line: si.Line, writer: TaggedBlockWriter, version: int = 2): # XXX didn't save timestamp = CrdtId(0, 1) writer.write_id(6, timestamp) + if line.move_id is not None: + writer.write_id(7, line.move_id) @dataclass @@ -395,6 +436,8 @@ def from_stream(cls, stream: TaggedBlockReader) -> SceneItemBlock: subclass = SceneLineItemBlock elif block_type == SceneTextItemBlock.BLOCK_TYPE: subclass = SceneTextItemBlock + elif block_type == SceneTombstoneItemBlock.BLOCK_TYPE: + subclass = SceneTombstoneItemBlock else: raise ValueError( "unknown scene type %d in %s" % (block_type, stream.current_block) @@ -500,6 +543,17 @@ def glyph_range_to_stream(stream: TaggedBlockWriter, item: si.GlyphRange): stream.data.write_float64(rect.h) +class SceneTombstoneItemBlock(SceneItemBlock): + BLOCK_TYPE: tp.ClassVar = 0x08 + + @classmethod + def value_from_stream(cls, reader: TaggedBlockReader): + pass + + def value_to_stream(self, writer: TaggedBlockWriter, value): + pass + + class SceneGlyphItemBlock(SceneItemBlock): BLOCK_TYPE: tp.ClassVar = 0x03 ITEM_TYPE: tp.ClassVar = 0x01 diff --git a/tests/data/With_SceneInfo_Block.rm b/tests/data/With_SceneInfo_Block.rm new file mode 100644 index 0000000..f87cd8a Binary files /dev/null and b/tests/data/With_SceneInfo_Block.rm differ diff --git a/tests/test_scene_stream.py b/tests/test_scene_stream.py index 50a09e3..7b035bb 100644 --- a/tests/test_scene_stream.py +++ b/tests/test_scene_stream.py @@ -45,6 +45,7 @@ def _hex_lines(b, n=32): ("Lines_v2_updated.rm", "3.2"), # extra 7fXXXX part of Line data was added ("Wikipedia_highlighted_p1.rm", "3.1"), ("Wikipedia_highlighted_p2.rm", "3.1"), + ("With_SceneInfo_Block.rm", "3.4"), # XXX version? ], ) def test_full_roundtrip(test_file, version): @@ -259,15 +260,17 @@ def test_write_blocks(): def test_blocks_keep_unknown_data(): - # The "7f 010f" is new, unknown data + # The "8f 010f" is represents new, unknown data -- note that this might need + # to be changed in future if the next id starts to actually be used in a + # future update! data_hex = """ - 56000000 00020205 + 59000000 00020205 1f 0219 2f 021e 3f 0000 4f 0000 54 0000 0000 - 6c 4000 0000 + 6c 4300 0000 03 14 0f000000 24 00000000 @@ -278,11 +281,12 @@ def test_blocks_keep_unknown_data(): 83c2622d 30c30000 08000000 6f 0001 7f 010f + 8f 0101 """ buf = BytesIO(HEADER_V6 + bytes.fromhex(data_hex)) block = next(read_blocks(buf)) assert isinstance(block, SceneLineItemBlock) - assert block.extra_data == bytes.fromhex("7f 010f") + assert block.extra_data == bytes.fromhex("8f 0101") def test_error_in_block_contained():