Lorem ipsum spiral II

2025-08-18

"""2025-08-18
Lorem ipsum spiral II
Espiral com o texto de Lorem Ipsum.
ericof.com
png
Sketch,py5,CreativeCoding
"""

from sketches.utils.draw import canvas
from sketches.utils.helpers import sketches as helpers
from sketches.utils.helpers import text as txt_helpers

import math
import py5


sketch = helpers.info_for_sketch(__file__, __doc__)

ESP_EXTRA = 0.2
MARGEM = 100
CENTRALIZAR_GLIFO = True
INNER_STEP_SCALE = 0.50
CLOSE_EPS = 1.0


def lado_atual(
    lado: int, left: float, top: float, right: float, bottom: float
) -> tuple[float, float, float]:
    if lado == 0:  # top: (left, top) -> (right, top)
        x0, y0 = left, top
        side_len = right - left
    elif lado == 1:  # right: (right, top) -> (right, bottom)
        x0, y0 = right, top
        side_len = bottom - top
    elif lado == 2:  # bottom: (right, bottom) -> (left, bottom)
        x0, y0 = right, bottom
        side_len = right - left
    else:  # left: (left, bottom) -> (left, top)
        x0, y0 = left, bottom
        side_len = bottom - top
    return x0, y0, side_len


def espiral_retangular(
    texto: str,
    limites: tuple[float, float, float, float],
    lista_fontes: list[str],
    tamanho: int = 16,
    esp_extra: float = 0.0,
):
    left, top, right, bottom = limites
    if not texto:
        return

    fontes = [py5.create_font(fonte, tamanho) for fonte in lista_fontes]
    # Precompute line height and inner step (how much we inset per loop).
    line_height = py5.text_ascent() + py5.text_descent()
    inner_step = max(1.0, line_height * INNER_STEP_SCALE)

    # Directions: 0:right, 1:down, 2:left, 3:up
    # Each entry: (dx, dy, angle)
    dirs = (
        (1.0, 0.0, 0.0),  # → along top
        (0.0, 1.0, math.pi / 2),  # ↓ along right
        (-1.0, 0.0, math.pi),  # ← along bottom
        (0.0, -1.0, -math.pi / 2),  # ↑ along left
    )

    i = 0  # index into text
    s = 0.0  # distance along current side
    side = 0  # 0..3 which side we are drawing

    while True:
        fonte = py5.random_choice(fontes)
        # Stop if rectangle collapsed
        if right - left <= CLOSE_EPS or bottom - top <= CLOSE_EPS:
            break

        x0, y0, side_len = lado_atual(side, left, top, right, bottom)
        dx, dy, ang = dirs[side]
        # Degenerate side—advance to next side (and possibly inset) then continue
        if side_len <= CLOSE_EPS:
            side += 1
            if side == 4:
                # completed a full loop; inset and restart at side 0
                side = 0
                left += inner_step
                top += inner_step
                right -= inner_step
                bottom -= inner_step
                s = 0.0
            continue

        # Current glyph and its advance
        ch = texto[i]
        py5.text_font(fonte)
        w = py5.text_width(ch)
        adv = w + esp_extra

        # Where we want the glyph: centered or left-aligned on s
        place_at = s + (0.5 * w if CENTRALIZAR_GLIFO else 0.0)

        # If this placement would exceed the current side, carry surplus to next side
        if place_at > side_len - CLOSE_EPS:
            surplus = place_at - side_len
            side += 1
            if side == 4:
                # full loop finished → inset and restart on top side
                side = 0
                left += inner_step
                top += inner_step
                right -= inner_step
                bottom -= inner_step
            s = max(0.0, surplus)  # carry leftover distance to the new side
            continue

        # Compute world position and draw the glyph
        px = x0 + dx * place_at
        py = y0 + dy * place_at
        with py5.push_matrix():
            py5.translate(px, py)
            py5.rotate(ang)
            py5.text_align(py5.CENTER if CENTRALIZAR_GLIFO else py5.LEFT, py5.CENTER)
            py5.text(ch, 0, 0)

        # Advance along side and move to next character
        s += adv
        i += 1
        if i >= len(texto):
            i = 0


def setup():
    py5.size(*helpers.DIMENSOES.external, py5.P3D)
    cor_fundo = py5.color(0)
    py5.background(0)
    py5.color_mode(py5.HSB, 360, 100, 100)
    py5.fill(250)
    texto = txt_helpers.resource_text("ipsum.txt")
    limites = (MARGEM, MARGEM, py5.width - MARGEM, py5.height - MARGEM)
    fontes = [py5.random_choice(py5.Py5Font.list()) for _ in range(10)]
    espiral_retangular(
        texto,
        lista_fontes=fontes,
        limites=limites,
        esp_extra=ESP_EXTRA,
    )
    # Credits and go
    canvas.sketch_frame(
        sketch, cor_fundo, "large_transparent_white", "transparent_white"
    )


def key_pressed():
    key = py5.key
    if key == " ":
        save_and_close()


def save_and_close():
    py5.no_loop()
    canvas.save_sketch_image(sketch)
    py5.exit_sketch()


if __name__ == "__main__":
    py5.run_sketch()