swf2ass/src/ass/containerTag.php

261 lines
8.7 KiB
PHP

<?php
namespace swf2ass\ass;
use MathPHP\LinearAlgebra\MatrixFactory;
use swf2ass\ClipPath;
use swf2ass\ColorTransform;
use swf2ass\DrawPath;
use swf2ass\FillStyleRecord;
use swf2ass\Gradient;
use swf2ass\LineStyleRecord;
use swf2ass\MatrixTransform;
use swf2ass\Shape;
use swf2ass\StyleRecord;
use swf2ass\Vector2;
class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPathTag, ASSClipPathTag {
/** @var ASSTag[] */
private array $tags = [];
/** @var ASSTag[][] */
private array $transitions = [];
private ?MatrixTransform $bakeTransforms = null;
public function transitionColor(ASSLine $line, ColorTransform $transform): ?containerTag {
$container = clone $this;
$index = $line->end - $line->start;
if(!isset($container->transitions[$index])){
$container->transitions[$index] = [];
}
foreach ($container->tags as $tag) {
if ($tag instanceof ASSColorTag) {
$newTag = $tag->transitionColor($line, $transform);
if ($newTag === null) {
return null;
}
if(!$newTag->equals($tag)){
$container->transitions[$index][] = $newTag;
}
}
}
return $container;
}
public function transitionMatrixTransform(ASSLine $line, MatrixTransform $transform): ?containerTag {
if($this->bakeTransforms !== null){
if(!$transform->getMatrix()->submatrix(0, 0, 1, 1)->isEqual($this->bakeTransforms->getMatrix()->submatrix(0, 0, 1, 1))){ //Do not allow matrix changes but moves
return null;
}
}
$container = clone $this;
$index = $line->end - $line->start;
if(!isset($container->transitions[$index])){
$container->transitions[$index] = [];
}
foreach ($container->tags as $i => $tag) {
if ($tag instanceof ASSPositioningTag) {
$newTag = $tag->transitionMatrixTransform($line, $transform);
if ($newTag === null) {
return null;
}
if(!$newTag->equals($tag)){
//Special case!
if($newTag instanceof positionTag){
$container->tags[$i] = $newTag;
}else{
$container->transitions[$index][] = $newTag;
}
}
}
}
return $container;
}
public function transitionStyleRecord(ASSLine $line, StyleRecord $record): ?containerTag {
$container = clone $this;
$index = $line->end - $line->start;
if(!isset($container->transitions[$index])){
$container->transitions[$index] = [];
}
foreach ($container->tags as $tag) {
if ($tag instanceof ASSStyleTag) {
$newTag = $tag->transitionStyleRecord($line, $record);
if ($newTag === null) {
return null;
}
if(!$newTag->equals($tag)){
$container->transitions[$index][] = $newTag;
}
}
}
return $container;
}
protected function try_append(?ASSTag $tag, $throw_on_null = false) {
if ($tag !== null) {
$this->tags[] = $tag;
return;
}
if ($throw_on_null) {
throw new \Exception();
}
}
public static function fromPathEntry(DrawPath $path, ?ClipPath $clip, ?ColorTransform $colorTransform, ?MatrixTransform $matrixTransform, bool $bakeTransforms = false): containerTag {
$container = new containerTag();
$container->try_append(new clipTag($clip));
if($path->style instanceof LineStyleRecord){ //Convert to fill
}
$container->try_append(borderTag::fromStyleRecord($path->style));
$container->try_append(shadowTag::fromStyleRecord($path->style));
$container->try_append(lineColorTag::fromStyleRecord($path->style)->applyColorTransform($colorTransform));
$container->try_append(fillColorTag::fromStyleRecord($path->style)->applyColorTransform($colorTransform));
$matrixTransform = $matrixTransform ?? MatrixTransform::identity();
if($bakeTransforms){
$container->bakeTransforms = $matrixTransform;
$container->try_append(positionTag::fromMatrixTransform($matrixTransform));
$drawTag = new drawTag($path->commands);
if(!$matrixTransform->getMatrix()->isEqual(MatrixFactory::identity(3))){
$drawTag = $drawTag->applyMatrixTransform($matrixTransform, false);
}
$container->try_append($drawTag);
}else{
$container->try_append(positionTag::fromMatrixTransform($matrixTransform));
$container->try_append(matrixTransformTag::fromMatrixTransform($matrixTransform));
$container->try_append(new drawTag($path->commands));
}
return $container;
}
public static function fromMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag {
throw new \Exception();
}
public static function fromStyleRecord(StyleRecord $record): ?ASSStyleTag {
throw new \Exception();
}
public function equals(ASSTag $tag): bool {
if ($tag instanceof $this and count($this->tags) === count($tag->tags)) {
$tags = $this->tags;
$otherTags = $tag->tags;
foreach ($tags as $i => $t) {
foreach ($otherTags as $j => $t2) {
if ($t->equals($t2)) {
unset($tags[$i]);
unset($otherTags[$j]);
}
break;
}
if (isset($tags[$i])) {
break;
}
}
return count($tags) === 0 and count($otherTags) === 0;
}
return false;
}
public function encode(ASSEventTime $event): string {
$ret = "";
foreach ($this->tags as $tag) {
if(!($tag instanceof drawingTag)){
$ret .= $tag->encode($event);
}
}
foreach ($this->transitions as $index => $transitions){
if(count($transitions) === 0){
continue;
}
//TODO: clone $line?
//TODO: animations with smoothing really don't play well. maybe allow them when only one animation "direction" exists, or smooth them manually?
//Or just don't animate MatrixTransform / do it in a single tick
if(ASSRenderer::getSetting("smoothTransitions", false)){
$startTime = $event->getMillisecondsFromStartOffset($index - 1) + 1;
$endTime = $event->getMillisecondsFromStartOffset($index + 1) - 1;
}else{
$startTime = $event->getMillisecondsFromStartOffset($index) - 1;
$endTime = $event->getMillisecondsFromStartOffset($index);
}
$ret .= "\\t({$startTime},{$endTime},";
foreach ($transitions as $tag){
$ret .= $tag->encode($event);
}
$ret .= ")";
}
foreach ($this->tags as $tag) {
if($tag instanceof drawingTag){
$ret .= $tag->encode($event);
}
}
return $ret;
}
public function transitionShape(ASSLine $line, Shape $shape): ?ASSPathTag {
$container = clone $this;
$index = $line->end - $line->start;
if(!isset($container->transitions[$index])){
$container->transitions[$index] = [];
}
foreach ($container->tags as $tag) {
if ($tag instanceof ASSPathTag) {
$newTag = $tag->transitionShape($line, $shape);
if ($newTag === null) {
return null;
}
if(!$newTag->equals($tag)){
$container->transitions[$index][] = $newTag;
}
}
}
return $container;
}
public function transitionClipPath(ASSLine $line, ?ClipPath $clip): ?ASSClipPathTag {
$container = clone $this;
$index = $line->end - $line->start;
if(!isset($container->transitions[$index])){
$container->transitions[$index] = [];
}
foreach ($container->tags as $tag) {
if ($tag instanceof ASSClipPathTag) {
$newTag = $tag->transitionClipPath($line, $clip);
if ($newTag === null) {
return null;
}
if(!$newTag->equals($tag)){
$container->transitions[$index][] = $newTag;
}
}
}
return $container;
}
}