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
)
],
)
PPS: It seems like remarkable uses some sort of smoothing on the strokes. A moving average (window size=5) on the stroke points comes close to their result.
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