From 9baf8b455ec80f738db74c5c29cdc9d8091c34a3 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sat, 27 Apr 2024 13:36:34 +0100 Subject: [PATCH 1/8] Add checkbox paragraph style --- src/rmscene/scene_items.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rmscene/scene_items.py b/src/rmscene/scene_items.py index f6cb5cf..f54db0e 100644 --- a/src/rmscene/scene_items.py +++ b/src/rmscene/scene_items.py @@ -144,6 +144,7 @@ class ParagraphStyle(enum.IntEnum): BOLD = 3 BULLET = 4 BULLET2 = 5 + CHECKBOX = 6 END_MARKER = CrdtId(0, 0) From ebc787b7ade86e4831dda19892d17fd74aa01c90 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sat, 27 Apr 2024 13:37:21 +0100 Subject: [PATCH 2/8] add type folio usage count --- src/rmscene/scene_stream.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 1d9863e..7d1185b 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -217,7 +217,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 +230,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 +241,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 From f0c33e6674b2621f8966eac2c4145feaca7a332d Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 28 Apr 2024 00:24:44 +0100 Subject: [PATCH 3/8] add sceneinfo class (block type 13) --- src/rmscene/scene_stream.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 7d1185b..07acf1b 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -99,6 +99,50 @@ def to_stream(self, writer: TaggedBlockWriter): writer.data.write_bytes(self.data) +@dataclass +class SceneInfo(Block): + BLOCK_TYPE: tp.ClassVar = 0x0D + + current_layer_timestamp: CrdtId + current_layer: CrdtId + + background_visible_timestamp: CrdtId + background_visible: bool + + root_document_visible_timestamp: CrdtId + root_document_visible: bool + + @classmethod + def from_stream(cls, stream: TaggedBlockReader) -> SceneInfo: + with stream.read_subblock(1): + current_layer_timestamp = stream.read_id(1) + current_layer = stream.read_id(2) + with stream.read_subblock(2): + background_visible_timestamp = stream.read_id(1) + background_visible = stream.read_bool(2) + with stream.read_subblock(3): + root_document_visible_timestamp = stream.read_id(1) + root_document_visible = stream.read_bool(2) + + return SceneInfo(current_layer_timestamp=current_layer_timestamp, + current_layer=current_layer, + background_visible_timestamp=background_visible_timestamp, + background_visible=background_visible, + root_document_visible_timestamp=root_document_visible_timestamp, + root_document_visible=root_document_visible) + + def to_stream(self, writer: TaggedBlockWriter): + with writer.write_subblock(1): + writer.data.write_crdt_id(self.current_layer_timestamp) + writer.data.write_crdt_id(self.current_layer) + with writer.write_subblock(2): + writer.data.write_crdt_id(self.background_visible_timestamp) + writer.data.write_bool(self.background_visible) + with writer.write_subblock(3): + writer.data.write_crdt_id(self.root_document_visible_timestamp) + writer.data.write_bool(self.root_document_visible) + + @dataclass class AuthorIdsBlock(Block): BLOCK_TYPE: tp.ClassVar = 0x09 From 1f5de6ead2c09f0d10d970284d3eb291f96bece6 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 28 Apr 2024 00:25:11 +0100 Subject: [PATCH 4/8] add SceneTombstoneItemBlock class (block type 8) --- src/rmscene/scene_stream.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 07acf1b..dc775d9 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -439,6 +439,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) @@ -544,6 +546,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 From 8da3b66cce79f551a95bbb2cf9bbd96e87af67e5 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 28 Apr 2024 01:09:21 +0100 Subject: [PATCH 5/8] add support for the "move_id" field which is present on some SceneLineItemBlock items. --- src/rmscene/scene_items.py | 1 + src/rmscene/scene_stream.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/rmscene/scene_items.py b/src/rmscene/scene_items.py index f54db0e..e921bfe 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: CrdtId ## Text diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index dc775d9..63a23c6 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -399,7 +399,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): @@ -415,6 +420,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 From 1065b56757af4c696adc0bc168544b79b6dc0ab5 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 28 Apr 2024 09:40:40 +0100 Subject: [PATCH 6/8] use read_lww_*/write_lww_* api --- src/rmscene/scene_stream.py | 38 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 63a23c6..875b9ea 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -103,44 +103,26 @@ def to_stream(self, writer: TaggedBlockWriter): class SceneInfo(Block): BLOCK_TYPE: tp.ClassVar = 0x0D - current_layer_timestamp: CrdtId - current_layer: CrdtId + current_layer: LwwValue[CrdtId] - background_visible_timestamp: CrdtId - background_visible: bool + background_visible: LwwValue[bool] - root_document_visible_timestamp: CrdtId - root_document_visible: bool + root_document_visible: LwwValue[bool] @classmethod def from_stream(cls, stream: TaggedBlockReader) -> SceneInfo: - with stream.read_subblock(1): - current_layer_timestamp = stream.read_id(1) - current_layer = stream.read_id(2) - with stream.read_subblock(2): - background_visible_timestamp = stream.read_id(1) - background_visible = stream.read_bool(2) - with stream.read_subblock(3): - root_document_visible_timestamp = stream.read_id(1) - root_document_visible = stream.read_bool(2) + 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_timestamp=current_layer_timestamp, - current_layer=current_layer, - background_visible_timestamp=background_visible_timestamp, + return SceneInfo(current_layer=current_layer, background_visible=background_visible, - root_document_visible_timestamp=root_document_visible_timestamp, root_document_visible=root_document_visible) def to_stream(self, writer: TaggedBlockWriter): - with writer.write_subblock(1): - writer.data.write_crdt_id(self.current_layer_timestamp) - writer.data.write_crdt_id(self.current_layer) - with writer.write_subblock(2): - writer.data.write_crdt_id(self.background_visible_timestamp) - writer.data.write_bool(self.background_visible) - with writer.write_subblock(3): - writer.data.write_crdt_id(self.root_document_visible_timestamp) - writer.data.write_bool(self.root_document_visible) + 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 From cc720a229585d8bfd109d73f2624db8b7f834f85 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 28 Apr 2024 09:48:28 +0100 Subject: [PATCH 7/8] remove stray lines between values --- src/rmscene/scene_stream.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 875b9ea..6255aea 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -104,9 +104,7 @@ class SceneInfo(Block): BLOCK_TYPE: tp.ClassVar = 0x0D current_layer: LwwValue[CrdtId] - background_visible: LwwValue[bool] - root_document_visible: LwwValue[bool] @classmethod From 67575d22778b3322380ffb8df13c47a02682a56c Mon Sep 17 00:00:00 2001 From: Rick Lupton Date: Sun, 26 May 2024 14:15:50 +0100 Subject: [PATCH 8/8] Make tests pass - Fix extra data test (since the previous extra data example is now handled properly!) - Add test rm data file including new block - Make the `move_id` attribute of the Line scene item be optional. - Adjust the version info of the new 0x0D SceneInfo block and update version info of the 0x02 TreeNodeBlock to make roundtripping work. --- src/rmscene/scene_items.py | 2 +- src/rmscene/scene_stream.py | 10 ++++++++++ tests/data/With_SceneInfo_Block.rm | Bin 0 -> 7113 bytes tests/test_scene_stream.py | 12 ++++++++---- 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 tests/data/With_SceneInfo_Block.rm diff --git a/src/rmscene/scene_items.py b/src/rmscene/scene_items.py index e921bfe..81ba84e 100644 --- a/src/rmscene/scene_items.py +++ b/src/rmscene/scene_items.py @@ -127,7 +127,7 @@ class Line(SceneItem): points: list[Point] thickness_scale: float starting_length: float - move_id: CrdtId + move_id: tp.Optional[CrdtId] = None ## Text diff --git a/src/rmscene/scene_stream.py b/src/rmscene/scene_stream.py index 6255aea..e932120 100644 --- a/src/rmscene/scene_stream.py +++ b/src/rmscene/scene_stream.py @@ -103,6 +103,10 @@ def to_stream(self, writer: TaggedBlockWriter): 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] @@ -188,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 diff --git a/tests/data/With_SceneInfo_Block.rm b/tests/data/With_SceneInfo_Block.rm new file mode 100644 index 0000000000000000000000000000000000000000..f87cd8a05379603f2fe5141649b1e472c5749f00 GIT binary patch literal 7113 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