mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 20:02:20 +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>
84 lines
3.6 KiB
Python
84 lines
3.6 KiB
Python
"""Static contract tests for Text on a Path.
|
|
|
|
A text layer can be laid along a vector path layer: each glyph advances by its
|
|
measured width and is rotated to the local tangent of the path. The renderer
|
|
lives in text-layer.js, the attach/detach glue and control ops in
|
|
galleryEditor.js, and the Type > Path menu entries in the topbar/wiring. These
|
|
are plain JavaScript, so the tests assert the source-level contract.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
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_text_data_model_has_path():
|
|
src = _read(TEXT)
|
|
assert " path: null," in src
|
|
# normalizeTextData keeps only finite {x,y} points, else null.
|
|
assert "path: Array.isArray(value.path)" in src
|
|
assert ".filter((point) => Number.isFinite(Number(point?.x)) && Number.isFinite(Number(point?.y)))" in src
|
|
assert ".map((point) => ({ x: Number(point.x), y: Number(point.y) }))" in src
|
|
|
|
|
|
def test_path_renderer_lays_glyphs_along_tangents():
|
|
src = _read(TEXT)
|
|
assert "export function pathTextPadding(text)" in src
|
|
assert "export function hasTextPath(value)" in src
|
|
assert "export function pathTextCanvas(text)" in src
|
|
# Arc-length lookup over the polyline.
|
|
assert "segments.push({ a, dx, dy, len, start: total });" in src
|
|
assert "const atDistance = (distance) => {" in src
|
|
assert "angle: Math.atan2(seg.dy, seg.dx)" in src
|
|
# Each glyph advances by its measured width (+ letterSpacing) and rotates.
|
|
assert "const glyphWidth = ctx.measureText(char).width;" in src
|
|
assert "const mid = distance + glyphWidth / 2;" in src
|
|
assert "ctx.translate(pos.x, pos.y);" in src
|
|
assert "ctx.rotate(pos.angle);" in src
|
|
assert "distance += glyphWidth + (Number(text.letterSpacing) || 0);" in src
|
|
|
|
|
|
def test_renderer_prefers_path_over_warp():
|
|
src = _read(TEXT)
|
|
assert "const base = hasTextPath(layer.text)" in src
|
|
assert "? pathTextCanvas(layer.text)" in src
|
|
assert ": warpCanvas(baseTextCanvas(layer.text), layer.text.warp, layer.text.warpAmount);" in src
|
|
|
|
|
|
def test_gallery_editor_glue_and_ops():
|
|
src = _read(GALLERY)
|
|
assert "pathTextPadding as _pathTextPadding," in src
|
|
assert "function _attachTextToPath(textLayer, pathLayer) {" in src
|
|
assert "function _detachTextFromPath(textLayer) {" in src
|
|
# Attach copies the path into the text frame and offsets the layer to match.
|
|
assert "textLayer.text = _normalizeTextData({ ...textLayer.text, path: local });" in src
|
|
assert "state.layerOffsets.set(textLayer.id, { x: Math.round(pathOffset.x + minX - pad), y: Math.round(pathOffset.y + minY - pad) });" in src
|
|
for op in ["'path.add':", "'text.attachToPath':", "'text.detachFromPath':"]:
|
|
assert op in src, op
|
|
assert "window.geEditorControl" not in src or "_attachTextToPath(textLayer, pathLayer)" in src
|
|
assert "const result = _attachTextToPath(textLayer, pathLayer);" in src
|
|
|
|
|
|
def test_type_menu_and_wiring():
|
|
menu = _read(TOPBAR)
|
|
assert '<div class="ge-menu-label">Path</div>' in menu
|
|
assert 'data-type-action="text-on-path"' in menu
|
|
assert 'data-type-action="text-off-path"' in menu
|
|
wire = _read(EXTRA_WIRE)
|
|
assert "window.geEditorControl?.invoke('text.attachToPath', {});" in wire
|
|
assert "window.geEditorControl?.invoke('text.detachFromPath', {});" in wire
|
|
|
|
|
|
def test_sw_cache_name_bumped():
|
|
assert re.search(r"odysseus-v\d+-pr40-editor-precache", _read(SW))
|