mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-13 19:52:20 +02:00
159 lines
6.4 KiB
Python
159 lines
6.4 KiB
Python
import json
|
|
import subprocess
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MODULE = (ROOT / "static/js/editor/text-layer.js").as_uri()
|
|
|
|
|
|
def run_node(body: str):
|
|
script = textwrap.dedent(
|
|
f"""
|
|
import {{ normalizeTextData, rasterizeTextLayer, renderTextLayer }} from {json.dumps(MODULE)};
|
|
let id = 0;
|
|
function canvas(tag='canvas') {{
|
|
const value = {{tag, width:0, height:0, draws:[], fills:[], strokes:[]}};
|
|
const ctx = {{
|
|
font:'', textBaseline:'', textAlign:'left', fillStyle:'', strokeStyle:'',
|
|
lineWidth:0, lineJoin:'', imageSmoothingEnabled:false, imageSmoothingQuality:'low',
|
|
measureText(text) {{ return {{width:String(text).length * 10}}; }},
|
|
fillText(...args) {{ value.fills.push(args); }},
|
|
strokeText(...args) {{ value.strokes.push(args); }},
|
|
drawImage(...args) {{ value.draws.push(args.map(item => item?.tag || item)); }},
|
|
clearRect() {{}}, translate() {{}}, rotate() {{}}, scale() {{}},
|
|
}};
|
|
value.getContext = () => ctx;
|
|
return value;
|
|
}}
|
|
globalThis.document = {{createElement:() => canvas(`canvas-${{++id}}`)}};
|
|
{body}
|
|
"""
|
|
)
|
|
result = subprocess.run(
|
|
["node", "--input-type=module", "-e", script],
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_text_renderer_keeps_editable_metadata_and_builds_raster_cache():
|
|
result = run_node(
|
|
"""
|
|
const target = canvas('target');
|
|
const layer = {
|
|
canvas:target, ctx:target.getContext('2d'),
|
|
text:{
|
|
content:'Hello\\nWorld', fontFamily:'Georgia', fontSize:20,
|
|
fontWeight:'700', fontStyle:'italic', align:'center', color:'#ff0000',
|
|
strokeColor:'#000000', strokeWidth:2, lineHeight:1.5,
|
|
transform:{scaleX:2,scaleY:1.5,rotation:15,flipH:true,flipV:false},
|
|
},
|
|
};
|
|
renderTextLayer(layer);
|
|
console.log(JSON.stringify({
|
|
kind:layer.kind, content:layer.text.content, font:layer.text.fontFamily,
|
|
transform:layer.text.transform, size:[layer.canvas.width,layer.canvas.height],
|
|
finalDraw:layer.canvas.draws.at(-1),
|
|
}));
|
|
"""
|
|
)
|
|
|
|
assert result["kind"] == "text"
|
|
assert result["content"] == "Hello\nWorld"
|
|
assert result["font"] == "Georgia"
|
|
assert result["transform"] == {
|
|
"scaleX": 2, "scaleY": 1.5, "rotation": 15, "flipH": True, "flipV": False
|
|
}
|
|
assert result["size"][0] > 100
|
|
assert result["size"][1] > 80
|
|
assert result["finalDraw"][1:] == [0, 0]
|
|
|
|
|
|
def test_text_data_normalization_bounds_invalid_values():
|
|
result = run_node(
|
|
"""
|
|
console.log(JSON.stringify(normalizeTextData({
|
|
content:42, fontSize:-5, align:'diagonal', lineHeight:99,
|
|
transform:{scaleX:0,scaleY:'bad',rotation:'bad',flipV:1},
|
|
})));
|
|
"""
|
|
)
|
|
|
|
assert result["content"] == "42"
|
|
assert result["fontSize"] == 1
|
|
assert result["align"] == "left"
|
|
assert result["lineHeight"] == 5
|
|
assert result["letterSpacing"] == 0
|
|
assert result["frameWidth"] == 320
|
|
assert result["frameHeight"] == 0
|
|
assert result["verticalAlign"] == "top"
|
|
assert result["transform"]["scaleX"] == 0.01
|
|
assert result["transform"]["scaleY"] == 1
|
|
assert result["transform"]["rotation"] == 0
|
|
assert result["transform"]["flipV"] is True
|
|
|
|
|
|
def test_rasterize_text_layer_clears_retained_metadata_only_for_text():
|
|
result = run_node(
|
|
"""
|
|
const text = {kind:'text', text:{content:'Still editable'}};
|
|
const raster = {kind:'raster', text:null};
|
|
console.log(JSON.stringify({
|
|
changed:rasterizeTextLayer(text), text,
|
|
rasterChanged:rasterizeTextLayer(raster), raster,
|
|
}));
|
|
"""
|
|
)
|
|
|
|
assert result["changed"] is True
|
|
assert result["text"] == {"kind": "raster", "text": None}
|
|
assert result["rasterChanged"] is False
|
|
assert result["raster"] == {"kind": "raster", "text": None}
|
|
|
|
|
|
def test_text_tool_is_retained_across_editor_subsystems():
|
|
editor = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8")
|
|
toolbar = (ROOT / "static/js/editor/build/toolbar.js").read_text(encoding="utf-8")
|
|
controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8")
|
|
codec = (ROOT / "static/js/editor/document-codec.js").read_text(encoding="utf-8")
|
|
geometry = (ROOT / "static/js/editor/document-geometry.js").read_text(encoding="utf-8")
|
|
transform = (ROOT / "static/js/editor/tools/transform-session.js").read_text(encoding="utf-8")
|
|
panel = (ROOT / "static/js/editor/layer-panel.js").read_text(encoding="utf-8")
|
|
merge = (ROOT / "static/js/editor/wire-merge-buttons.js").read_text(encoding="utf-8")
|
|
|
|
assert "{ id: 'text', label: 'Text'" in toolbar
|
|
assert "key: 'T'" in toolbar
|
|
assert 'id="ge-text-section"' in controls
|
|
assert "if (state.tool === 'text') return _placeText(e);" in editor
|
|
assert "kind: l.kind || 'raster'" in editor
|
|
assert "text: _cloneDocumentValue(l.text, null)" in editor
|
|
assert "kind: layer.kind || 'raster'" in codec
|
|
assert "text: cloneDocumentValue(layer.text, null)" in codec
|
|
assert "scaleTextLayer(layer, scaleX, scaleY)" in geometry
|
|
assert "rotateTextLayer(layer, normalized)" in geometry
|
|
assert "renderTextLayer(layer)" in transform
|
|
assert 'id="ge-text-rasterize"' in controls
|
|
assert "layer.kind = 'raster'" in editor
|
|
assert "layer.text = null" in editor
|
|
assert "rasterizeTextLayer(layer);" in panel
|
|
assert "rasterizeTextLayer(layer);" in merge
|
|
assert "_rasterizeTextLayer(layer)" in editor
|
|
assert 'id="ge-text-letter-spacing"' in controls
|
|
assert 'id="ge-text-frame-width"' in controls
|
|
assert 'id="ge-text-frame-height"' in controls
|
|
assert 'id="ge-text-vertical-align"' in controls
|
|
assert 'id="ge-text-auto-width"' in controls
|
|
assert 'autoWidth: !!document.getElementById(\'ge-text-auto-width\')?.checked' in editor
|
|
assert "createTextEditOverlay" in editor
|
|
|
|
|
|
def test_text_auto_width_control_does_not_share_move_tool_selector():
|
|
controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8")
|
|
assert 'class="ge-text-auto-width-option"' in controls
|
|
assert 'class="ge-auto-select-option ge-text-auto-width-option"' not in controls
|