277 lines
9.3 KiB
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 = [];
|
|
}
|
|
} |