Skip to content

Commit

Permalink
Rework tldraw text v2 to use proper outline instead of multiple shadows
Browse files Browse the repository at this point in the history
  • Loading branch information
kepstin committed Jun 10, 2024
1 parent f195461 commit 0e5e165
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 48 deletions.
1 change: 0 additions & 1 deletion bbb_presentation_video/renderer/tldraw/fonts/.uuid

This file was deleted.

1 change: 1 addition & 0 deletions bbb_presentation_video/renderer/tldraw/shape/arrow_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

from typing import TypeVar

import cairo
Expand Down
2 changes: 1 addition & 1 deletion bbb_presentation_video/renderer/tldraw/shape/draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def finalize_draw(
if len(shape.points[0]) == 2:
simulate_pressure = True
else:
first_point = shape.points[0]
first_point = cast(Tuple[float, float, float], shape.points[0])
if first_point[2] == 0.5:
simulate_pressure = True

Expand Down
11 changes: 9 additions & 2 deletions bbb_presentation_video/renderer/tldraw/shape/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ def create_pango_layout(


def show_layout_by_lines(
ctx: cairo.Context[CairoSomeSurface], layout: Pango.Layout, *, padding: float = 0
ctx: cairo.Context[CairoSomeSurface],
layout: Pango.Layout,
*,
padding: float = 0,
do_path: bool = False,
) -> None:
"""Show a Pango Layout line by line to manually handle CSS-style line height."""
# TODO: With Pango 1.50 this can be replaced with Pango.attr_line_height_new_absolute
Expand Down Expand Up @@ -128,7 +132,10 @@ def show_layout_by_lines(

ctx.save()
ctx.translate(offset_x, offset_y)
PangoCairo.show_layout_line(ctx, line)
if do_path:
PangoCairo.layout_line_path(ctx, line)
else:
PangoCairo.show_layout_line(ctx, line)
ctx.restore()

ctx.translate(0, line_height)
Expand Down
82 changes: 38 additions & 44 deletions bbb_presentation_video/renderer/tldraw/shape/text_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
show_layout_by_lines,
)
from bbb_presentation_video.renderer.tldraw.utils import (
CANVAS,
FONT_SIZES,
STICKY_FONT_SIZES,
STICKY_PADDING,
Expand All @@ -32,11 +33,7 @@
SizeStyle,
)

gi.require_version("Pango", "1.0")
gi.require_version("PangoCairo", "1.0")

# Set DPI to "72" so we're working directly in Pango point units.
DPI: float = 72.0
TEXT_OUTLINE_WIDTH: float = 2.0

CairoSomeSurface = TypeVar("CairoSomeSurface", bound=cairo.Surface)

Expand All @@ -47,33 +44,35 @@ def finalize_v2_text(
print(f"\tTldraw: Finalizing Text (v2): {id}")

style = shape.style
ctx.rotate(shape.rotation)

stroke = STROKES[style.color]
font_size = FONT_SIZES[style.size]

ctx.rotate(shape.rotation)

# A group is used so the text border and fill can be drawn opaque (to avoid over-draw issues), then
# be blended with alpha afterwards
ctx.push_group()

layout = create_pango_layout(ctx, style, font_size)
layout.set_text(shape.text, -1)

border_thickness = 2
border_color = (1, 1, 1, 1) # White
# Draw the border by offsetting the text in several directions
offsets = [
(-border_thickness, -border_thickness),
(border_thickness, -border_thickness),
(-border_thickness, border_thickness),
(border_thickness, border_thickness),
]

for dx, dy in offsets:
ctx.translate(dx, dy)
ctx.set_source_rgba(*border_color)
show_layout_by_lines(ctx, layout, padding=4)
ctx.translate(-dx, -dy) # Reset translation for next iteration
# Draw text border (outside stroke)
ctx.save()
ctx.set_source_rgb(*CANVAS)
ctx.set_line_width(TEXT_OUTLINE_WIDTH * 2)
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
show_layout_by_lines(ctx, layout, padding=4, do_path=True)
ctx.stroke()
ctx.restore()

ctx.set_source_rgba(stroke.r, stroke.g, stroke.b, style.opacity)
# Draw text
ctx.set_source_rgb(*stroke)
show_layout_by_lines(ctx, layout, padding=4)

# Composite result with opacity applied
ctx.pop_group_to_source()
ctx.paint_with_alpha(style.opacity)


def finalize_v2_label(
ctx: cairo.Context[CairoSomeSurface],
Expand All @@ -88,10 +87,11 @@ def finalize_v2_label(

style = shape.style
stroke = STROKES[ColorStyle.BLACK] # v2 labels are always black
border_color = (1, 1, 1, 1) # White
font_size = FONT_SIZES[style.size]

ctx.save()
# A group is used so the text border and fill can be drawn opaque (to avoid over-draw issues), then
# be blended with alpha afterwards
ctx.push_group()

# Create layout aligning the text horizontally within the shape
style.textAlign = shape.align
Expand All @@ -116,30 +116,24 @@ def finalize_v2_label(
else:
y = bounds.height / 2 - label_size.height / 2 + offset.y

border_thickness = 2

# Draw the border by offsetting the text in several directions
offsets = [
(-border_thickness, -border_thickness),
(border_thickness, -border_thickness),
(-border_thickness, border_thickness),
(border_thickness, border_thickness),
]
ctx.translate(x, y)

for dx, dy in offsets:
ctx.translate(x + dx, y + dy)
ctx.set_source_rgba(*border_color)
show_layout_by_lines(ctx, layout, padding=4)
ctx.translate(-x - dx, -y - dy) # Reset translation for next iteration
# Draw text border (outside stroke)
ctx.save()
ctx.set_source_rgb(*CANVAS)
ctx.set_line_width(TEXT_OUTLINE_WIDTH * 2)
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
show_layout_by_lines(ctx, layout, padding=4, do_path=True)
ctx.stroke()
ctx.restore()

# Draw the original text on top
ctx.translate(x, y)
ctx.set_source_rgba(
stroke.r, stroke.g, stroke.b, style.opacity
) # Set original text color
ctx.set_source_rgb(*stroke)
show_layout_by_lines(ctx, layout, padding=4)

ctx.restore()
# Composite result with opacity applied
ctx.pop_group_to_source()
ctx.paint_with_alpha(style.opacity)

return label_size

Expand Down
10 changes: 10 additions & 0 deletions typings/gi/repository/PangoCairo.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ def create_context(cr: cairo.Context[cairo._SomeSurface]) -> Pango.Context:
directly, you can use :func:`create_layout` instead.
"""

def layout_line_path(
cr: cairo.Context[cairo._SomeSurface], line: Pango.LayoutLine
) -> None:
"""Adds the text in :class:`Pango.LayoutLine` to the current path in the specified cairo context.
The origin of the glyphs (the left edge of the line) will be at the current point of the cairo context.
Since: 1.10
"""

def show_layout(
cr: cairo.Context[cairo._SomeSurface], layout: Pango.Layout
) -> None: ...
Expand Down

0 comments on commit 0e5e165

Please sign in to comment.