mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 11:52:25 +02:00
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.1 KiB
Python
96 lines
4.1 KiB
Python
"""Static contract tests for curved Text on a Path.
|
|
|
|
The editor stores paths as polylines. To lay text along a *curve* we flatten
|
|
analytic curves (ellipse/arc, quadratic and cubic Bezier, Catmull-Rom smoothing)
|
|
into a dense polyline (path-geometry.js); the arc-length text renderer in
|
|
text-layer.js then centres the run on the path and rotates each glyph to the
|
|
local tangent. These are plain JavaScript, so the tests assert the source-level
|
|
contract.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
GEOMETRY = ROOT / "static/js/editor/path-geometry.js"
|
|
TEXT = ROOT / "static/js/editor/text-layer.js"
|
|
GALLERY = ROOT / "static/js/galleryEditor.js"
|
|
TOPBAR = ROOT / "static/js/editor/build/topbar.js"
|
|
EXTRA_WIRE = ROOT / "static/js/editor/wire-extra-menus.js"
|
|
SW = ROOT / "static/sw.js"
|
|
|
|
|
|
def _read(path):
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_curve_helpers_are_offline_and_exported():
|
|
src = _read(GEOMETRY)
|
|
for name in ("ellipsePoints", "cubicBezierPoints", "quadraticBezierPoints", "smoothPathPoints"):
|
|
assert f"export function {name}(" in src, name
|
|
# Pure geometry only: no network / AI, no DOM dependencies.
|
|
assert "fetch(" not in src
|
|
assert "XMLHttpRequest" not in src
|
|
assert "document." not in src
|
|
|
|
|
|
def test_ellipse_flattens_to_a_dense_ring():
|
|
src = _read(GEOMETRY)
|
|
# Full sweep by default and a closed ring (the +1 point re-joins the start).
|
|
assert "start = 0, end = Math.PI * 2" in src
|
|
assert "for (let i = 0; i <= n; i += 1) {" in src
|
|
# Subdivision grows with arc length so a large curve stays smooth.
|
|
assert "Math.ceil(arcLength / stepSize(Math.max(radiusX, radiusY)))" in src
|
|
assert "points.push({ x: centerX + dx, y: centerY + dy });" in src
|
|
|
|
|
|
def test_bezier_uses_de_casteljau():
|
|
src = _read(GEOMETRY)
|
|
assert "export function cubicBezierPoints(p0, p1, p2, p3, count = 0)" in src
|
|
assert "const u = 1 - t;" in src
|
|
assert "u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x" in src
|
|
assert "export function quadraticBezierPoints(p0, p1, p2, count = 0)" in src
|
|
# Catmull-Rom smoothing for coarse hand-drawn points.
|
|
assert "export function smoothPathPoints(points, count = 0)" in src
|
|
|
|
|
|
def test_path_text_centres_the_run_on_the_path():
|
|
src = _read(TEXT)
|
|
# Pre-pass measures the whole run, then the starting distance is centred.
|
|
assert "const glyphs = [...String(text.content || '')];" in src
|
|
assert "let textWidth = 0;" in src
|
|
assert "let distance = Math.max(0, (total - textWidth) / 2);" in src
|
|
# The per-glyph advance and tangent rotation are unchanged.
|
|
assert "const mid = distance + glyphWidth / 2;" in src
|
|
assert "ctx.rotate(pos.angle);" in src
|
|
assert "distance += glyphWidth + (Number(text.letterSpacing) || 0);" in src
|
|
|
|
|
|
def test_gallery_editor_wires_curve_ops():
|
|
src = _read(GALLERY)
|
|
assert "cubicBezierPoints as _cubicBezierPoints" in src
|
|
assert "ellipsePoints as _ellipsePoints" in src
|
|
assert "smoothPathPoints as _smoothPathPoints" in src
|
|
for op in ["'path.addCurve':", "'text.onCircle':", "'text.attachToPath':"]:
|
|
assert op in src, op
|
|
# addCurve flattens each supported analytic curve type.
|
|
assert "throw new Error('unknown curve type: ' + type)" in src
|
|
assert "_cubicBezierPoints(c[0], c[1], c[2], c[3])" in src
|
|
assert "_quadraticBezierPoints(c[0], c[1], c[2])" in src
|
|
assert "_smoothPathPoints(Array.isArray(p.points) ? p.points : [])" in src
|
|
# onCircle builds a ring and attaches the text.
|
|
assert "const pts = _ellipsePoints(cx, cy, radius, radius, { start: Math.PI / 2, end: Math.PI / 2 + Math.PI * 2 });" in src
|
|
assert "const result = _attachTextToPath(textLayer, layer);" in src
|
|
|
|
|
|
def test_type_menu_and_wiring_for_circle():
|
|
menu = _read(TOPBAR)
|
|
assert 'data-type-action="text-on-circle"' in menu
|
|
assert "<span>Text on a Circle</span>" in menu
|
|
wire = _read(EXTRA_WIRE)
|
|
assert "window.geEditorControl?.invoke('text.onCircle', {});" in wire
|
|
|
|
|
|
def test_sw_cache_name_bumped():
|
|
assert re.search(r"odysseus-v\d+-pr40-editor-precache", _read(SW))
|