Skip to content

use svg.py #15

@ghost

Description

I propose the following:

  1. use svg.py to create the svg
  2. use width and height as inputs to blocks_to_svg
  3. add SceneGlyphItemBlock (highlighted text)

Comment on 2): If a pdf (which might have different width,height) is annotated, the svg needs to have the same width and height.
Using this approach the pdf and annotations (remarkable lines) are aligned (at least in my tests).

All in all, it also makes the code much more readable.

PS: the same logic could be used for SceneTree.

Cheers

from typing import Iterable

import svg
from rmscene import Block, SceneLineItemBlock, RootTextBlock, SceneGlyphItemBlock

from .writing_tools import Pen, remarkable_palette


def blocks_to_svg(blocks: Iterable[Block], width: float, height: float) -> str:
    """Convert Blocks to SVG."""
    elements = []

    for block in list(blocks):
        if isinstance(block, SceneLineItemBlock):
            if block.item.value is not None:
                elements.append(stroke(block))
        elif isinstance(block, RootTextBlock):
            if block.value is not None:
                elements.append(text(block))
        elif isinstance(block, SceneGlyphItemBlock):
            elements.append(rect(block))

    xpos_shift = width / 2

    g = svg.G(
        transform=[svg.Translate(xpos_shift, 0)],
        id="p1",
        style="display:inline",
        elements=elements,
    )
    result = svg.SVG(width=width, height=height, elements=[g])
    return result.as_str()


def stroke(block: SceneLineItemBlock) -> svg.Element:
    points = []
    pen = Pen.create(
        block.item.value.tool.value,
        block.item.value.color.value,
        block.item.value.thickness_scale,
    )
    last_xpos = -1.0
    last_ypos = -1.0
    last_segment_width: float = 0

    for point_id, point in enumerate(block.item.value.points):
        xpos = point.x
        ypos = point.y
        if point_id % pen.segment_length == 0:
            segment_color = pen.get_segment_color(
                point.speed,
                point.direction,
                point.width,
                point.pressure,
                last_segment_width,
            )
            segment_width = pen.get_segment_width(
                point.speed,
                point.direction,
                point.width,
                point.pressure,
                last_segment_width,
            )
            segment_opacity = pen.get_segment_opacity(
                point.speed,
                point.direction,
                point.width,
                point.pressure,
                last_segment_width,
            )
            if last_xpos != -1.0:
                points += [last_xpos, last_ypos]

        last_xpos = xpos
        last_ypos = ypos
        last_segment_width = segment_width
        points += [xpos, ypos]

    return svg.Polyline(
        style=f"fill:none;stroke:{segment_color};stroke-width:{segment_width};opacity:{segment_opacity}",
        stroke_linecap=pen.stroke_linecap,  # type: ignore
        points=points,  # type: ignore
    )


def rect(block: SceneGlyphItemBlock) -> svg.Element:
    """
    Highlighted text.
    """
    value = block.item.value
    color = "rgb" + str(tuple(remarkable_palette[value.color]))
    rectangle = value.rectangles[0]
    return svg.Rect(
        x=rectangle.x,
        y=rectangle.y,
        width=rectangle.w,
        height=rectangle.h,
        fill=color,
        fill_opacity=0.3,
    )


def text(block: RootTextBlock) -> svg.Element:
    """
    Text is split on newlines and using TSpans and their 'dy' attribute,
    lines are emulated using a spacing value of 1.2em.
    """
    text = "".join([i[1] for i in block.value.items.items()])  # type: ignore
    lines = text.splitlines()

    return svg.G(
        style="font: 50px serif",
        elements=[
            svg.Text(
                x=block.value.pos_x,
                y=block.value.pos_y,
                elements=[svg.TSpan(dy="1.2em", text=line) for line in lines],  # type: ignore
            )
        ],
    )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions