cranim

toolkit for rendering concepts from cryptography and computer science in manim

View on GitHub

Example Gallery

Showcase

class ECBvsCBCExample(Scene):
    # Demonstrates how plaintext changes propagate in ECB and CBC modes.
    def construct(self):
        ecb = ECBBlocks(bytes(16*5), direction=RIGHT, zoom=0.36)
        cbc = CBCBlocks(bytes(16*5))
        cbc.zoom(ecb.width / cbc.width)

        for e, c in zip(ecb, cbc):
            e.align_to(c, DOWN)

        self.add(
            ecb.move_to(LEFT*(32/9)),
            cbc.move_to(RIGHT*(32/9)).align_to(ecb, DOWN),
            DashedLine(3*UP, 3*DOWN, color=C_STROKE),
        )

        ecb_block = ecb[2]
        cbc_block = cbc[2]

        self.play(LaggedStart(
            FocusOn(ecb_block.pt[0].get_edge_center(LEFT)),
            Rewrite(ecb_block, b'\xff'+bytes(15)),
            lag_ratio=0.7
        ))
        self.wait()
        self.play(LaggedStart(
            FocusOn(cbc_block.pt[0].get_edge_center(LEFT)),
            Rewrite(cbc_block, b'\xff'+bytes(15)),
            lag_ratio=0.7
        ))

class CTSExample(Scene):
    # Demonstrates modifying CBCBlock to create a figure for CTS mode
    def construct(self):
        block_0 = CBCBlock(direction=DOWN, margin=3)
        block_1 = CBCBlock(prev=block_0, direction=DOWN, margin=3.4)
        block_2 = CBCBlock(msg=[None]*3, prev=block_1, direction=DOWN, margin=3.4, padfunc=bytes)

        block_1.remove(block_1.enc_to_ct)
        block_2.remove(block_2.enc_to_ct)
        block_2.remove(block_2.ct_to_xor)
        block_2.ct.remove(*block_2.ct[3:])

        stroke = DEFAULT_STROKE_WIDTH*0.66
        enc_to_xor = BendyArrow(block_1.enc, DOWN, block_2.xor, UP, stroke_width=stroke)
        enc_to_ct = BendyArrow(block_1.enc, DOWN, block_2.ct, UP, stroke_width=stroke, offset_ratio=0.524)
        enc_to_ct_2 = BendyArrow(block_2.enc, RIGHT, block_1.ct, LEFT, stroke_width=stroke)
        enc_to_ct_2_bg = Rectangle(width=0.12, height=0.3, stroke_opacity=0, fill_color=C_SCENE_BG, fill_opacity=1).move_to(enc_to_ct_2)

        cts = ZoomableVGroup(
            block_0, block_1, block_2,
            enc_to_xor, enc_to_ct, enc_to_ct_2_bg, enc_to_ct_2,
        ).center().zoom(0.7)

        self.add(cts)

class SlideExample1(Scene):
    def construct(self):
        token = list('email=foo@bar.com&uid=10&role=user')
        last_len = len(token[32:])
        pad_len = 16-last_len
        block_1 = Block(token[:16], c_fills=C_PT)
        block_2 = Block(token[16:32], c_fills=C_PT)
        block_3 = PaddingBlock(token[32:])

        ZoomableVGroup(block_1, block_2, block_3, zoom=0.8).arrange(DOWN).center()
        self.add(block_1, block_2, block_3)
        self.wait(0.5)
        self.play(
            LaggedStart(
                FadeOut(block_3[2], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                FadeOut(block_3[3], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                Slide(*block_1, *block_2, *block_3, start=7, stop=-14, shift=2),
                FadeIn(new_o_1 := block_1[7].copy(), scale=0.5),
                FadeIn(new_o_2 := block_1[8].copy(), scale=0.5),
                lag_ratio=0.25,
            ),
            AnimationGroup(*[Rewrite(byte, 12, C_PAD) for byte in block_3[4:]]),
            run_time=5,
        )
        self.wait()

        new_block_1 = Block.from_boxes(*block_1[:7], new_o_1, new_o_2, *block_1[7:14])
        new_block_2 = Block.from_boxes(*block_1[-2:], *block_2[:-2])
        new_block_3 = Block.from_boxes(*block_2[-2:], *block_3[:2], *block_3[4:])
        admin_block = Block(list("admin") + [11]*11, c_fills=C_PT, zoom=0.8)
        self.play(
            Group(
                new_block_1,
                admin_block,
                new_block_2,
                new_block_3,
            ).animate.arrange(DOWN).align_to(new_block_1, UP),
        )
        self.wait()
        self.play(
            admin_block.animate.move_to(new_block_2),
            new_block_2.animate.move_to(admin_block),
        )
        self.wait(0.5)
        self.play(
            Rewrite(admin_block, c_fills=[C_PT]*5+[C_PAD]*11),
            FadeOut(new_block_3),
        )
        self.wait()

class CodeRewriteExample1(Scene):
    def construct(self):
        kwargs = {"language": "python", "tab_width": 4, "font": "Monospace", "style": "inkpot", "background": "window", "line_spacing": 0.6, "insert_line_no": False}
        code_1 = Code("code/padding_oracle_1.py", **kwargs).scale(0.5)
        code_2 = Code("code/padding_oracle_2.py", **kwargs).scale(0.5).align_to(code_1, UL)
        self.add(code_1)
        self.wait(0.5)
        self.play(CodeRewrite(code_1, code_2))
        self.wait(0.5)

BendyArrow

class BendyArrowExample1(Scene):
    def construct(self):
        boxes = VGroup(*[ByteBox(i, C_PT) for i in range(7)]).arrange_in_grid(buff=1.5)
        self.add(boxes)

        self.add(BendyArrow(boxes[0], DOWN, boxes[4], UP))
        arrow = BendyArrow(boxes[1], RIGHT, boxes[5], LEFT)
        self.add(arrow)

        # these arrows' paths are arbitrary, but i think they look neat
        # directional arguments indicate where to come out of (point into) the source (destination)
        self.add(
            BendyArrow(boxes[0], DOWN, boxes[4], UP),
            BendyArrow(boxes[4], DOWN, boxes[6], RIGHT),
            BendyArrow(boxes[5], DOWN, boxes[6], RIGHT),
            BendyArrow(boxes[6], UP, boxes[3], DOWN),
            BendyArrow(boxes[1], RIGHT, boxes[5], LEFT),
            BendyArrow(boxes[5], UP, boxes[2], DOWN),
        )

Block

class SlideExample1(Scene):
    def construct(self):
        token = list('email=foo@bar.com&uid=10&role=user')
        last_len = len(token[32:])
        pad_len = 16-last_len
        block_1 = Block(token[:16], c_fills=C_PT)
        block_2 = Block(token[16:32], c_fills=C_PT)
        block_3 = PaddingBlock(token[32:])

        ZoomableVGroup(block_1, block_2, block_3, zoom=0.8).arrange(DOWN).center()
        self.add(block_1, block_2, block_3)
        self.wait(0.5)
        self.play(
            LaggedStart(
                FadeOut(block_3[2], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                FadeOut(block_3[3], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                Slide(*block_1, *block_2, *block_3, start=7, stop=-14, shift=2),
                FadeIn(new_o_1 := block_1[7].copy(), scale=0.5),
                FadeIn(new_o_2 := block_1[8].copy(), scale=0.5),
                lag_ratio=0.25,
            ),
            AnimationGroup(*[Rewrite(byte, 12, C_PAD) for byte in block_3[4:]]),
            run_time=5,
        )
        self.wait()

        new_block_1 = Block.from_boxes(*block_1[:7], new_o_1, new_o_2, *block_1[7:14])
        new_block_2 = Block.from_boxes(*block_1[-2:], *block_2[:-2])
        new_block_3 = Block.from_boxes(*block_2[-2:], *block_3[:2], *block_3[4:])
        admin_block = Block(list("admin") + [11]*11, c_fills=C_PT, zoom=0.8)
        self.play(
            Group(
                new_block_1,
                admin_block,
                new_block_2,
                new_block_3,
            ).animate.arrange(DOWN).align_to(new_block_1, UP),
        )
        self.wait()
        self.play(
            admin_block.animate.move_to(new_block_2),
            new_block_2.animate.move_to(admin_block),
        )
        self.wait(0.5)
        self.play(
            Rewrite(admin_block, c_fills=[C_PT]*5+[C_PAD]*11),
            FadeOut(new_block_3),
        )
        self.wait()

class BlockExample1(Scene):
    def construct(self):
        block = Block(bytes(16), c_fills=C_PT)
        self.add(block)
        self.wait()
        self.play(Rewrite(block, [0xFF]*16, [C_PT, C_CT]*8))
        self.wait()

class BlockExample2(Scene):
    def construct(self):
        from itertools import product
        group = VGroup()
        for color, size in product((C_BOX_BG, GRAY), (8, 16, 24)):
            group.add(Block(range(size), c_fills=color, block_size=None))  # type: ignore  # TODO fix type sig
        group.arrange_in_grid(cols=1, buff=0.55)
        self.add(group)

class BlockExample3(Scene):
    # demonstrates cranim's custom handling of characters with tails (e.g. q, y).
    # manim does not provide a built-in way of recognizing/handling descenders,
    # and so centering them will take them off the baseline. That looks awful,
    # so cranim's ByteBox automatically re-aligns them.
    def construct(self):
        from string import ascii_lowercase, ascii_uppercase, punctuation
        block_style = {"c_fills": C_PT, "block_size": None, "zoom": 0.7}
        blocks_style = {**block_style, "block_size": 32}
        lower = Block(list(ascii_lowercase), **block_style)
        upper = Block(list(ascii_uppercase), **block_style)
        text = Block(list('Mr Jock, TV quiz PhD, bags few lynx'), **block_style)
        punc = Block(list(punctuation), **block_style)
        hexes = Blocks(range(256), **blocks_style)
        blocks = Group(lower, upper, text, punc, *hexes)
        blocks.arrange(DOWN).center()
        self.add(blocks)

BoxLen

class BoxLenExample1(Scene):
    def construct(self):
        box = BoxLen("2^8")
        self.add(box)  # argument can be str, int, or anything else accepted by MathTex
        print(self.mobjects)
        self.play(box.update_rhs("256", transform=TextRewrite))
        print(self.mobjects)

class BoxLenExample2(Scene):
    def construct(self):
        pad_blk = PaddingBlock()
        msg_len = BoxLen(0)
        pad_len = BoxLen(16, c_box=C_PAD)

        self.add(
            pad_blk,
            msg_len.next_to(pad_blk, DOWN*2).align_to(pad_blk, LEFT),
            pad_len.next_to(pad_blk, DOWN*2).align_to(pad_blk, RIGHT)
        )

        dec = lambda a, b: AnimationGroup(FadeOut(a, shift=0.2*UP), FadeIn(b, shift=0.2*UP))
        inc = lambda a, b: AnimationGroup(FadeOut(a, shift=0.2*DOWN), FadeIn(b, shift=0.2*DOWN))

        for i in range(1, 16):
            self.play(
                Rewrite(pad_blk, bytes(i)),
                msg_len.update_rhs(i, transform=inc),
                pad_len.update_rhs(16-i, transform=dec),
                run_time=0.8
            )
            self.wait(0.2)

BufferToText

class BufferToTextExample1(Scene):
    def construct(self):
        pt_msg = "This is a plaintext string!"
        text = Text(pt_msg).scale(0.7).shift(UP/3)
        buff = Blocks(pt_msg, c_fills=C_PT, zoom=0.7, n_cols=2, buff=1/3).shift(DOWN/3)
        self.add(buff)
        self.wait(0.5)
        self.play(BufferToText(buff, text, lag_ratio=0.05, unpad=True))
        self.wait(0.5)

ByteBox

class ByteBoxExample1(Scene):
    def construct(self):
        # contents can be str, int, bytes, or None
        box_1 = ByteBox(0xf, c_fill=C_CT)  # numeric values are zero-padded as need be
        box_2 = ByteBox('0')   # length-1 strings are center-aligned
        box_3 = ByteBox('hi', c_fill=C_STROKE, c_text=C_BOX_BG)  # any string is allowed (but the box is sized to 2 chars)
        box_4 = ByteBox(None, c_fill=GRAY)  # to disable byte text, pass b=None
        self.add(VGroup(box_1, box_2, box_3, box_4).arrange_in_grid())

class ByteBoxExample2(Scene):
    def construct(self):
        box = ByteBox('aa')

        # demonstrate rewriting
        self.play(FadeIn(box))
        self.wait(0.5)
        self.play(Rewrite(box, 'bb'))

        # integer values are converted to hex and 0-padded
        for i in (1, 0x23):
            self.play(Rewrite(box, i))
            self.wait(0.2)

        # rewrite is compatible with zoom
        self.play(box.animate.zoom(6))  # zoom() is like scale(), but it also adjusts stroke_width
        self.wait(0.5)                  # so that border lines maintain their relative weight
        self.play(Rewrite(box, '4'))
        self.wait(0.5)

        # zoom state persists through .copy()
        box_2 = box.copy()
        box_3 = box.copy().rewrite('7')
        self.play(Group(box, box_2).animate.arrange(RIGHT, buff=1.2).center())
        #self.wait(0.5)

        # pass None to erase a box. it can still be re-populated at will
        self.play(Rewrite(box, None), Rewrite(box_2, None))
        self.play(LaggedStart(
            Rewrite(box, '5', c_fill=C_PT),
            Rewrite(box_2, '6', c_text=RED),
            lag_ratio=0.33
        ))
        self.play(
            ReplacementTransform(VGroup(box.box, box_2.box), box_3.box),
            ReplacementTransform(VGroup(box.text, box_2.text), box_3.text)
        )
        self.play(Rewrite(box_3, '8', c_fill=C_STROKE, c_text=C_BOX_BG))
        self.play(Rewrite(box_3, '9'))
        self.play(FadeOut(box_3, scale=1.2))

CBCBlock

class CBCExample1(Scene):
    def construct(self):
        cbc = CBCBlock(zoom=0.8)
        self.add(cbc)

class CBCExample2(Scene):
    def construct(self):
        block_1 = CBCBlock(msg=bytes(16))
        block_2 = CBCBlock(msg=bytes(), prev=block_1)
        cbc_group = ZoomableVGroup(block_1, block_2, zoom=0.55)
        self.add(cbc_group.center())

class CBCExample3(Scene):
    def construct(self):
        block_1 = CBCBlock(msg=bytes(16), direction=DOWN)
        block_2 = CBCBlock(msg=bytes(16), prev=block_1, direction=DOWN)
        block_3 = CBCBlock(msg=bytes(12), prev=block_2, direction=DOWN)
        self.add(ZoomableVGroup(block_1, block_2, block_3, zoom=2/3).center())

class CBCExample4(MovingCameraScene):  # Loops seamlessly
    @staticmethod
    def introduce(cbc):
        # custom creation animation - Create(cbc) and Write(cbc) also work,
        # but i think this looks nicer
        return LaggedStart(
            AnimationGroup(    
                Write(cbc.ct_to_xor),    
                Write(cbc.pt_to_xor),
                Write(cbc.xor_to_enc),
                Write(cbc.enc_to_ct),
                FadeIn(cbc.xor),
                FadeIn(cbc.enc),
            ),
            FadeIn(cbc.ct, shift=0.5*DOWN, run_time=0.5),
            lag_ratio=0.2
        )

    def construct(self):
        iv_block  = CBCBlock()
        chained_1 = CBCBlock(prev=iv_block)
        chained_2 = CBCBlock(prev=chained_1)
        chained_3 = CBCBlock(prev=chained_2)
        chained_4 = CBCBlock(prev=chained_3)
        chained_5 = CBCBlock(prev=chained_4)
        chained_6 = CBCBlock(prev=chained_5)

        self.add(iv_block, chained_1, chained_2, chained_3, chained_4.pt, chained_5.pt, chained_6.pt)
        self.camera.frame.scale(2.2).move_to(chained_2.xor_to_enc)
        self.play(
            self.introduce(chained_4),
            self.camera.frame.animate(rate_func=linear).move_to(chained_3.xor_to_enc),
            run_time=2
        )

class CBCExample5(Scene):
    def construct(self):
        # accepts and automatically encodes strings (assumes ASCII)
        blocks = CBCBlocks("YELLOW SUBMARINE"*2, zoom=2/3)
        self.add(blocks)

class CBCExample6(Scene):
    def construct(self):
        blocks = CBCBlocks("YELLOW SUBMARINE"*3, zoom=2/3)
        self.add(blocks)
        self.wait(0.5)
        self.play(Rewrite(blocks[0], "MISTER BOATSWAIN"))
        self.wait(0.5)

CodeRewrite

class CodeRewriteExample1(Scene):
    def construct(self):
        kwargs = {"language": "python", "tab_width": 4, "font": "Monospace", "style": "inkpot", "background": "window", "line_spacing": 0.6, "insert_line_no": False}
        code_1 = Code("code/padding_oracle_1.py", **kwargs).scale(0.5)
        code_2 = Code("code/padding_oracle_2.py", **kwargs).scale(0.5).align_to(code_1, UL)
        self.add(code_1)
        self.wait(0.5)
        self.play(CodeRewrite(code_1, code_2))
        self.wait(0.5)

Cycle

class CycleExample1(Scene):
    def construct(self):
        box = ByteBox(0)
        self.add(box)
        self.wait(0.5)
        self.play(Cycle(box, range(256)))
        self.wait(0.5)

ECBBlock

class ECBExample1(Scene):
    def construct(self):
        self.add(ECBBlock())

class ECBExample3(Scene):
    def construct(self):
        blocks = ECBBlocks(bytes(16*8), direction=RIGHT, pbuff=0.4)
        self.add(blocks.zoom(0.7))
        self.wait()
        new_msg = bytes(15) + b'\x01'
        new_pt_fills, new_ct_fills = [C_PT]*15 + [darken(C_PT, amount=0.25)], darken(C_CT, amount=0.25)
        self.play(*[
            Rewrite(row, new_msg, c_pt=new_pt_fills, c_ct=new_ct_fills)
            for row in blocks[4:]
        ])
        self.wait()

class ECBExample4(Scene):
    def construct(self):
        # you can use ASCII strings as inputs and they will be automatically encoded
        blocks = ECBBlocks("YELLOW SUBMARINE"*2, direction=RIGHT)
        self.add(blocks.zoom(0.7))

ECBBlocks

class ECBExample2(Scene):
    def construct(self):
        self.add(ECBBlocks(bytes(32), zoom=0.7))

FuncBox

class FuncBoxExample1(Scene):
    def construct(self):
        self.add(FuncBox("H"))

class FuncBoxExample2(Scene):
    def construct(self):
        from hashlib import sha256
        msg = b'aaaa'
        img = sha256(msg).digest()

        input_block = Block(msg, c_fills=C_PT, block_size=None)
        output_block_1 = Block(img[:16], c_fills=C_CT)  # first half of digest
        output_block_2 = Block(img[16:], c_fills=C_CT)  # second half of digest

        input_block.shift(UP*1.5)
        output_block_1.shift(DOWN*1.5)
        output_block_2.next_to(output_block_1, DOWN, buff=0.00)

        func_box = FuncBox.with_arrows(
            (input_block, DOWN, UP),
            (output_block_1, DOWN, UP),
            r"H",
        )

        self.add(input_block, output_block_1, output_block_2, func_box)

class FuncBoxExample3(Scene):
    def construct(self):
        from hashlib import sha256
        msg = b'aaaa'
        hsh = sha256(msg).digest()

        input_block = Block(msg, c_fills=C_PT, block_size=None)
        output_block_1 = Block(hsh[:16], c_fills=C_CT)  # first half of digest
        output_block_2 = Block(hsh[16:], c_fills=C_CT)  # second half of digest

        input_block.shift(UP*1.5)
        output_block_1.shift(DOWN*1.5)
        output_block_2.next_to(output_block_1, DOWN, buff=0.00)

        func_box = FuncBox.with_arrows(
            (input_block, DOWN, UP),
            (output_block_1, DOWN, UP),
            "H",
        )

        self.play(FadeIn(input_block), run_time=1)
        self.play(Write(func_box))
        self.play(FadeIn(output_block_1, output_block_2, shift=DOWN*0.5), run_time=0.8)
        self.wait()

PaddingBlock

class PaddingBlockExample1(Scene):
    def construct(self):
        #global pb
        pb = PaddingBlock(bytes(15))
        self.add(pb)
        self.wait(0.5)
        for i in (14, 13, 12):
            self.play(Rewrite(pb, bytes(i)))
            self.wait(0.25)
        for i in range(11, 0, -1):
            pb.rewrite(bytes(i))
            self.wait(0.25)
        pb.rewrite(bytes(0))
        self.wait()
        self.play(FadeOut(pb))

class PaddingBlockExample2(Scene):
    def construct(self):
        pb = PaddingBlock(bytes(6))
        self.add(pb)
        self.wait(1)
        self.play(Rewrite(pb, bytes(6), padfunc=lambda l: b'\x00'*l))
        self.wait(0.17)
        self.play(Wiggle(pb[5:7], n_wiggles=17, run_time=4))
        self.play(Rewrite(pb, bytes(6), padfunc=lambda l: b'\x80' + b'\x00'*(l-1)))
        self.wait(1)

class PaddingBlockExample3(Scene):
    def construct(self):
        self.add(Point(), Point(), Point(), Point())
        block = PaddingBlock(
            [None]*3,  # ensure message bytes are blank
            c_pad=C_BOX_BG,
            padfunc=lambda l: [None]*l   # set padding bytes to None
        )

        # get local references (this is ugly - how could we clean it up?)
        braces = block.get_braces(pad_text="?")
        msg_brace, pad_brace, blk_brace = braces.msg_brace, braces.pad_brace, braces.blk_brace
        msg_text, pad_text, blk_text = braces.msg_text, braces.pad_text, braces.blk_text

        # introduce message bytes
        self.play(
            FadeIn(block.boxes[:3]),
            FadeIn(msg_brace, msg_text, shift=DOWN)
        )
        self.wait(0.5)

        # introduce block
        self.play(
            FadeIn(block.boxes[3:]),
            FadeIn(blk_brace, blk_text, shift=UP),
        )
        self.play(
            FadeIn(pad_brace, pad_text, shift=DOWN)  # but wait - what's this?
        )
        self.wait(0.5)

        # reveal: padding!
        block.c_pad=PURPLE
        self.play(
            Rewrite(block, [None]*3),
            #pad_text.animate.become(pad_brace.get_text("Padding").set_color(C_EDGE))
            Transform(braces, block.get_braces())
        )

        # try changing the message size and moving the braces accordingly
        for size in (5, 15, 8):
            self.wait(0.5)
            self.play(
                Rewrite(block, [None]*size),
                Transform(braces, block.get_braces())
            )

        self.wait(0.5)
        self.play(FadeOut(block, braces))
        self.wait(0.5)

class BoxLenExample2(Scene):
    def construct(self):
        pad_blk = PaddingBlock()
        msg_len = BoxLen(0)
        pad_len = BoxLen(16, c_box=C_PAD)

        self.add(
            pad_blk,
            msg_len.next_to(pad_blk, DOWN*2).align_to(pad_blk, LEFT),
            pad_len.next_to(pad_blk, DOWN*2).align_to(pad_blk, RIGHT)
        )

        dec = lambda a, b: AnimationGroup(FadeOut(a, shift=0.2*UP), FadeIn(b, shift=0.2*UP))
        inc = lambda a, b: AnimationGroup(FadeOut(a, shift=0.2*DOWN), FadeIn(b, shift=0.2*DOWN))

        for i in range(1, 16):
            self.play(
                Rewrite(pad_blk, bytes(i)),
                msg_len.update_rhs(i, transform=inc),
                pad_len.update_rhs(16-i, transform=dec),
                run_time=0.8
            )
            self.wait(0.2)

Slide

class SlideExample1(Scene):
    def construct(self):
        token = list('email=foo@bar.com&uid=10&role=user')
        last_len = len(token[32:])
        pad_len = 16-last_len
        block_1 = Block(token[:16], c_fills=C_PT)
        block_2 = Block(token[16:32], c_fills=C_PT)
        block_3 = PaddingBlock(token[32:])

        ZoomableVGroup(block_1, block_2, block_3, zoom=0.8).arrange(DOWN).center()
        self.add(block_1, block_2, block_3)
        self.wait(0.5)
        self.play(
            LaggedStart(
                FadeOut(block_3[2], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                FadeOut(block_3[3], scale=0.5, rate_func=rate_functions.ease_in_cubic),
                Slide(*block_1, *block_2, *block_3, start=7, stop=-14, shift=2),
                FadeIn(new_o_1 := block_1[7].copy(), scale=0.5),
                FadeIn(new_o_2 := block_1[8].copy(), scale=0.5),
                lag_ratio=0.25,
            ),
            AnimationGroup(*[Rewrite(byte, 12, C_PAD) for byte in block_3[4:]]),
            run_time=5,
        )
        self.wait()

        new_block_1 = Block.from_boxes(*block_1[:7], new_o_1, new_o_2, *block_1[7:14])
        new_block_2 = Block.from_boxes(*block_1[-2:], *block_2[:-2])
        new_block_3 = Block.from_boxes(*block_2[-2:], *block_3[:2], *block_3[4:])
        admin_block = Block(list("admin") + [11]*11, c_fills=C_PT, zoom=0.8)
        self.play(
            Group(
                new_block_1,
                admin_block,
                new_block_2,
                new_block_3,
            ).animate.arrange(DOWN).align_to(new_block_1, UP),
        )
        self.wait()
        self.play(
            admin_block.animate.move_to(new_block_2),
            new_block_2.animate.move_to(admin_block),
        )
        self.wait(0.5)
        self.play(
            Rewrite(admin_block, c_fills=[C_PT]*5+[C_PAD]*11),
            FadeOut(new_block_3),
        )
        self.wait()

TextToBuffer

class TextToBufferExample1(Scene):
    def construct(self):
        pt_msg = "This is a plaintext string!"
        text = Text(pt_msg).scale(0.7).shift(UP/3)
        buff = Blocks(pt_msg, c_fills=C_PT, zoom=0.7, n_cols=2, buff=1/3).shift(DOWN/3)
        self.add(text)
        self.wait(0.5)
        self.play(TextToBuffer(text, buff, lag_ratio=0.05))
        self.wait(0.5)