swf2ass/src/ShapeConverter.php

277 lines
9.3 KiB
PHP

<?php
namespace swf2ass;
class ShapeConverter {
private StyleList $styles;
private ?ActivePath $fill_style0 = null;
private ?ActivePath $fill_style1 = null;
private ?ActivePath $line_style = null;
private Vector2 $position;
private PendingPathMap $fills;
private PendingPathMap $strokes;
public DrawPathList $commands;
private bool $finished = false;
private array $firstElement;
private ?array $secondElement;
public function __construct(array $firstElement, StyleList $currentStyles, ?array $secondElement = null) {
$this->styles = $currentStyles;
$this->position = new Vector2(0, 0);
$this->fills = new PendingPathMap();
$this->strokes = new PendingPathMap();
$this->firstElement = $firstElement;
$this->secondElement = $secondElement;
$this->commands = new DrawPathList([]);
}
public function convert(bool $flipElements = false){
if($this->finished){
return;
}
reset($this->firstElement);
if($this->secondElement !== null){
reset($this->secondElement);
}
do{
$b = current($this->secondElement ?? []);
if($b === false){
$b = null;
}
$a = current($this->firstElement);
if($a === false){
if($b !== null){
throw new \Exception("a finished, b did not");
}
break;
}
if($b === null){
if($flipElements){
throw new \Exception("b finished, a did not");
}
$this->handleNode($a);
next($this->firstElement);
continue;
}
if($this->finished){
var_dump($a);
var_dump($b);
throw new \Exception("More paths after end");
}
if($a["type"] === $b["type"]){
if($a["type"] === "StyleChangeRecord"){
foreach (["stateNewStyles", "stateLineStyle", "stateFillStyle0", "stateFillStyle1", "fillStyles", "lineStyles", "fillStyle0", "fillStyle1", "lineStyle"] as $k){
if(isset($a[$k])){
$b[$k] = $a[$k];
}
}
if(!$flipElements and $a["stateMoveTo"] === 0 and $b["stateMoveTo"] === 1){
$a["stateMoveTo"] = $b["stateMoveTo"];
$a["moveDeltaX"] = $this->position->x;
$a["moveDeltaY"] = $this->position->y;
}
if($flipElements and $a["stateMoveTo"] === 1 and $b["stateMoveTo"] === 0){
$b["stateMoveTo"] = $a["stateMoveTo"];
$b["moveDeltaX"] = $this->position->x;
$b["moveDeltaY"] = $this->position->y;
}
$this->handleNode($flipElements ? $b : $a);
}
next($this->firstElement);
next($this->secondElement);
}elseif ($a["type"] === "StyleChangeRecord"){
$b = $a;
if($b["stateMoveTo"] === 1){
$b["moveDeltaX"] = $this->position->x;
$b["moveDeltaY"] = $this->position->y;
}
$this->handleNode($flipElements ? $b : $a);
next($this->firstElement);
}elseif ($b["type"] === "StyleChangeRecord"){
$a = $b;
if($a["stateMoveTo"] === 1){
$a["moveDeltaX"] = $this->position->x;
$a["moveDeltaY"] = $this->position->y;
}
$this->handleNode($flipElements ? $b : $a);
next($this->secondElement);
}else{
//Curve/Line records can differ
$this->handleNode($flipElements ? $b : $a);
next($this->firstElement);
next($this->secondElement);
}
}while(true);
$this->flush_layer();
}
private function handleNode(array $node){
if ($node["type"] === "StyleChangeRecord") {
if (isset($node["moveDeltaX"])) {
$move = MoveRecord::fromArray($node, $this->position);
$this->position = $move->to;
$this->flush_paths();
}
if (isset($node["fillStyles"]) or isset($node["lineStyles"])) {
$this->flush_layer();
$this->styles = StyleList::fromArray($node);
}
if (isset($node["fillStyle1"])) {
if ($this->fill_style1 !== null) {
$this->fills->merge_path($this->fill_style1, true);
}
$id = $node["fillStyle1"];
$this->fill_style1 = $id > 0 ? new ActivePath($id, $this->position) : null;
}
if (isset($node["fillStyle0"])) {
if ($this->fill_style0 !== null) {
if (!$this->fill_style0->segment->is_empty()) {
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
}
$id = $node["fillStyle0"];
$this->fill_style0 = $id > 0 ? new ActivePath($id, $this->position) : null;
}
if (isset($node["lineStyle"])) {
if ($this->line_style !== null) {
$this->strokes->merge_path($this->line_style, false);
}
$id = $node["lineStyle"];
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
}
} else if ($node["type"] === "StraightEdgeRecord") {
$line = LineRecord::fromArray($node, $this->position);
$this->visit_point($line->to, false);
$this->position = $line->to;
} else if ($node["type"] === "CurvedEdgeRecord") {
$curve = QuadraticCurveRecord::fromArray($node, $this->position);
$this->visit_point($curve->control, true);
$this->visit_point($curve->anchor, false);
$this->position = $curve->anchor;
} else if ($node["type"] === "EndShapeRecord") {
$this->finished = true;
}
}
public function visit_point(Vector2 $coordinate, bool $isBezierControlPoint) {
$point = new VisitedPoint($coordinate, $isBezierControlPoint);
if ($this->fill_style0 !== null) {
$this->fill_style0->add_point($point);
}
if ($this->fill_style1 !== null) {
$this->fill_style1->add_point($point);
}
if ($this->line_style !== null) {
$this->line_style->add_point($point);
}
}
public function flush_paths() {
if ($this->fill_style1 !== null) {
$this->fills->merge_path($this->fill_style1, true);
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
}
if ($this->fill_style0 !== null) {
if (!$this->fill_style0->segment->is_empty()) {
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
$this->fill_style0 = new ActivePath($this->fill_style0->style, $this->position);
}
if ($this->line_style !== null) {
$this->strokes->merge_path($this->line_style, false);
$this->line_style = new ActivePath($this->line_style->style, $this->position);
}
}
public function flush_layer() {
$this->flush_paths();
$this->fill_style0 = null;
$this->fill_style1 = null;
$this->line_style = null;
foreach ($this->fills->map as $styleId => $path) {
assert($styleId > 0 && $styleId < count($this->styles->fillStyles)); // ?????
$style = $this->styles->getFillStyle($styleId - 1);
if ($style === null) {
var_dump($this->styles);
var_dump($styleId);
}
$this->commands->commands[] = DrawPath::fill($style, $path->getShape());
}
$this->fills->map = [];
foreach ($this->strokes->map as $styleId => $path) {
assert($styleId > 0 && $styleId < count($this->styles->lineStyles)); // ?????
$style = $this->styles->getLineStyle($styleId - 1);
//wrap around all segments, even if closed. ASS does NOT like them otherwise. so we draw everything backwards to have border around the line, not just on one side
$newSegments = new PendingPath();
foreach ($path->segments as $segment) {
$other = clone $segment;
$other->flip();
$segment->merge($other);
$newSegments->merge_path($segment, false);
}
if(count($newSegments->segments) > 0){
//Reduce width of line style to account for double border
$fixedStyle = clone $style;
$fixedStyle->width /= 2;
$this->commands->commands[] = DrawPath::stroke($fixedStyle, $newSegments->getShape());
}
//TODO: leave this as-is and create a fill in renderer
}
$this->strokes->map = [];
}
}