Proper path drawing
This commit is contained in:
parent
f968dcf144
commit
61d79768aa
28
src/ActivePath.php
Normal file
28
src/ActivePath.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ActivePath {
|
||||
/** @var PathSegment */
|
||||
public PathSegment $segment;
|
||||
public int $style;
|
||||
|
||||
|
||||
/**
|
||||
* @param $styleId
|
||||
* @param Coordinate $start
|
||||
*/
|
||||
public function __construct($styleId, Coordinate $start) {
|
||||
$this->style = $styleId;
|
||||
$this->segment = new PathSegment($start);
|
||||
}
|
||||
|
||||
public function add_point(VisitedPoint $point){
|
||||
$this->segment->add_point($point);
|
||||
}
|
||||
|
||||
public function flip() {
|
||||
$this->segment->flip();
|
||||
}
|
||||
}
|
299
src/BitmapConverter.php
Normal file
299
src/BitmapConverter.php
Normal file
|
@ -0,0 +1,299 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class BitmapConverter {
|
||||
/** @var Color[][] */
|
||||
private array $pixelMatrix;
|
||||
private Coordinate $resolution;
|
||||
|
||||
public function __construct(array $pixelMatrix) {
|
||||
$this->pixelMatrix = $pixelMatrix;
|
||||
$this->resolution = new Coordinate(count($pixelMatrix[0]), count($pixelMatrix));
|
||||
}
|
||||
|
||||
private static function calculateAreaOfPath($path) {
|
||||
$totalArea = 0;
|
||||
/** @var Coordinate[][] $subPolygons */
|
||||
$subPolygons = [];
|
||||
/** @var Coordinate[] $currentPolygon */
|
||||
$currentPolygon = [];
|
||||
foreach ($path as $n) {
|
||||
if ($n instanceof MoveRecord) {
|
||||
if (count($currentPolygon) > 0) {
|
||||
$subPolygons[] = $currentPolygon;
|
||||
$currentPolygon = [];
|
||||
}
|
||||
} else if ($n instanceof LineRecord) {
|
||||
if (count($currentPolygon) === 0) {
|
||||
$currentPolygon[] = $n->start;
|
||||
}
|
||||
$currentPolygon[] = $n->coord;
|
||||
}
|
||||
}
|
||||
if (count($currentPolygon) > 0) {
|
||||
$subPolygons[] = $currentPolygon;
|
||||
$currentPolygon = [];
|
||||
}
|
||||
|
||||
foreach ($subPolygons as $polygon) {
|
||||
$NumPoints = count($polygon);
|
||||
|
||||
if ($polygon[$NumPoints - 1]->equals($polygon[0])) {
|
||||
$NumPoints--;
|
||||
} else {
|
||||
//Add the first point at the end of the array.
|
||||
$polygon[$NumPoints] = $polygon[0];
|
||||
}
|
||||
|
||||
if ($NumPoints < 3) {
|
||||
break;
|
||||
} else {
|
||||
$area = 0;
|
||||
$lastPoint = $polygon[$NumPoints - 1];
|
||||
foreach ($polygon as $point) {
|
||||
$area += ($lastPoint->x * $point->y - $lastPoint->y * $point->x);
|
||||
$lastPoint = $point;
|
||||
}
|
||||
$totalArea += ($area / 2.0);
|
||||
}
|
||||
}
|
||||
return $totalArea;
|
||||
}
|
||||
|
||||
|
||||
private static function normalizePath($path) {
|
||||
$newPath = [];
|
||||
$lastCursor = new Coordinate(0, 0);
|
||||
foreach ($path as $e) {
|
||||
/** @var LineRecord|MoveRecord $e */
|
||||
|
||||
if (!$e->start->equals($lastCursor)) {
|
||||
$newPath[] = new MoveRecord($e->start, $lastCursor);
|
||||
}
|
||||
$newPath[] = $e;
|
||||
$lastCursor = $e->coord;
|
||||
}
|
||||
|
||||
return $newPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LineRecord[] $path
|
||||
*/
|
||||
private static function cleanupPath(array $path) {
|
||||
$path = array_values($path);
|
||||
/** @var LineRecord[] $newPath */
|
||||
$newPath = [];
|
||||
$currentPath = null;
|
||||
|
||||
$cornerMap = [];
|
||||
foreach ($path as $i => $p) {
|
||||
if (!isset($cornerMap[$p->start->x . "_" . $p->start->y])) {
|
||||
$cornerMap[$p->start->x . "_" . $p->start->y] = [];
|
||||
}
|
||||
$cornerMap[$p->start->x . "_" . $p->start->y][$i] = $p;
|
||||
if (!isset($cornerMap[$p->coord->x . "_" . $p->coord->y])) {
|
||||
$cornerMap[$p->coord->x . "_" . $p->coord->y] = [];
|
||||
}
|
||||
$cornerMap[$p->coord->x . "_" . $p->coord->y][$i] = $p;
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
while (count($path) > 0) {
|
||||
$currentPath = $path[$index++] ?? null;
|
||||
if ($currentPath !== null) {
|
||||
$dupes = 0;
|
||||
foreach ($cornerMap[$currentPath->start->x . "_" . $currentPath->start->y] as $i => $p) {
|
||||
if ((($currentPath->start->equals($p->start) and $currentPath->coord->equals($p->coord)) or ($currentPath->start->equals($p->coord) and $currentPath->coord->equals($p->start)))) {
|
||||
unset($path[$i]);
|
||||
unset($cornerMap[$k1 = $p->start->x . "_" . $p->start->y][$i]);
|
||||
unset($cornerMap[$k2 = $p->coord->x . "_" . $p->coord->y][$i]);
|
||||
if ($currentPath !== $p) {
|
||||
++$dupes;
|
||||
}
|
||||
|
||||
if (count($cornerMap[$k1]) === 0) {
|
||||
unset($cornerMap[$k1]);
|
||||
}
|
||||
if (count($cornerMap[$k2]) === 0) {
|
||||
unset($cornerMap[$k2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($dupes === 0) {
|
||||
$newPath[] = $currentPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cornerMap = [];
|
||||
foreach ($newPath as $i => $p) {
|
||||
if (!isset($cornerMap[$p->start->x . "_" . $p->start->y])) {
|
||||
$cornerMap[$p->start->x . "_" . $p->start->y] = [];
|
||||
}
|
||||
$cornerMap[$p->start->x . "_" . $p->start->y][$i] = $p;
|
||||
if (!isset($cornerMap[$p->coord->x . "_" . $p->coord->y])) {
|
||||
$cornerMap[$p->coord->x . "_" . $p->coord->y] = [];
|
||||
}
|
||||
$cornerMap[$p->coord->x . "_" . $p->coord->y][$i] = $p;
|
||||
}
|
||||
|
||||
$sortedPath = [];
|
||||
$startPath = null;
|
||||
$currentVector = null;
|
||||
$index = 0;
|
||||
while (count($newPath) > 0) {
|
||||
$currentPath = $newPath[$index++] ?? null;
|
||||
if ($currentPath !== null) {
|
||||
$startPath = $currentPath;
|
||||
$currentVector = $currentPath->coord->sub($currentPath->start);
|
||||
$sortedPath[] = $startPath;
|
||||
unset($newPath[$index - 1]);
|
||||
unset($cornerMap[$startPath->start->x . "_" . $startPath->start->y][$index - 1]);
|
||||
unset($cornerMap[$startPath->coord->x . "_" . $startPath->coord->y][$index - 1]);
|
||||
|
||||
while (($nextPath = self::findNextCorner($currentPath->coord, $newPath, $cornerMap)) !== null) {
|
||||
$nextVector = $nextPath->coord->sub($nextPath->start);
|
||||
if ($nextVector->equals($currentVector)) { //Enlongate
|
||||
$currentPath->coord = $nextPath->coord;
|
||||
continue;
|
||||
}
|
||||
$sortedPath[] = $nextPath;
|
||||
|
||||
if ($nextPath->coord->equals($nextPath->start)) { //Reached the end!
|
||||
$startPath = null;
|
||||
break;
|
||||
}
|
||||
$currentPath = $nextPath;
|
||||
$currentVector = $nextVector;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $sortedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Coordinate $corner
|
||||
* @param LineRecord[] $path
|
||||
*
|
||||
* @return ?LineRecord
|
||||
*/
|
||||
private static function findNextCorner(Coordinate $corner, array &$path, array &$cornerMap): ?LineRecord {
|
||||
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
|
||||
unset($path[$i]);
|
||||
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
|
||||
unset($cornerMap[$p->coord->x . "_" . $p->coord->y][$i]);
|
||||
if ($p->start->equals($corner)) {
|
||||
return $p;
|
||||
} else if ($p->coord->equals($corner)) {
|
||||
return $p->reverse();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static function getBoundingBox($path) {
|
||||
$bb = new Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new Coordinate(PHP_INT_MIN, PHP_INT_MIN));
|
||||
/*if(count($path) > 0){
|
||||
$first = reset($path);
|
||||
if($first instanceof MoveRecord or $first instanceof LineRecord){
|
||||
$bb->topLeft = $first->coord;
|
||||
$bb->bottomRight = $first->coord;
|
||||
}
|
||||
}*/
|
||||
foreach ($path as $i => $e) {
|
||||
/** @var LineRecord|MoveRecord $e */
|
||||
if ($i === 0 and $e instanceof MoveRecord) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$bb->inBounds($e->coord)) {
|
||||
$bb->topLeft->x = min($e->coord->x, $bb->topLeft->x);
|
||||
$bb->topLeft->y = min($e->coord->y, $bb->topLeft->y);
|
||||
$bb->bottomRight->x = max($e->coord->x, $bb->bottomRight->x);
|
||||
$bb->bottomRight->y = max($e->coord->y, $bb->bottomRight->y);
|
||||
}
|
||||
if (!$bb->inBounds($e->start)) {
|
||||
$bb->topLeft->x = min($e->start->x, $bb->topLeft->x);
|
||||
$bb->topLeft->y = min($e->start->y, $bb->topLeft->y);
|
||||
$bb->bottomRight->x = max($e->start->x, $bb->bottomRight->x);
|
||||
$bb->bottomRight->y = max($e->start->y, $bb->bottomRight->y);
|
||||
}
|
||||
}
|
||||
|
||||
return $bb;
|
||||
}
|
||||
|
||||
public function render(bool $doFullFrameOptimizations): DrawPathList {
|
||||
$colors = [];
|
||||
$paths = [];
|
||||
foreach ($this->pixelMatrix as $y => $row) {
|
||||
foreach ($row as $x => $color) {
|
||||
$k = $color->toString(true);
|
||||
if (!isset($paths[$k])) {
|
||||
$colors[$k] = $color;
|
||||
$paths[$k] = [];
|
||||
}
|
||||
foreach ((new Square(new Coordinate($x, $y), 1))->draw() as $p) {
|
||||
$paths[$k][] = $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$optimizedPaths = [];
|
||||
|
||||
foreach ($paths as $k => $path) {
|
||||
$path = self::cleanupPath($path);
|
||||
$path = self::normalizePath($path);
|
||||
$optimizedPaths[$k] = $path;
|
||||
}
|
||||
|
||||
if ($doFullFrameOptimizations) {
|
||||
uasort($optimizedPaths, function ($a, $b) {
|
||||
return self::calculateAreaOfPath($a) - self::calculateAreaOfPath($b);
|
||||
});
|
||||
|
||||
//Largest path
|
||||
$k0 = array_key_first($optimizedPaths);
|
||||
$path = $optimizedPaths[$k0];
|
||||
$optimizedPaths[$k0] = [];
|
||||
foreach ((new Rectangle(new Coordinate(0, 0), $this->resolution))->draw() as $p) {
|
||||
$optimizedPaths[$k0][] = $p;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var DrawPath[] $commands */
|
||||
$commands = [];
|
||||
|
||||
foreach ($optimizedPaths as $k => $path){
|
||||
$edges = [];
|
||||
$bb = self::getBoundingBox($path);
|
||||
$pos = $bb->topLeft->multiply(TWIP_SIZE);
|
||||
|
||||
$edges[] = new MoveRecord($pos, new Coordinate(0, 0));
|
||||
|
||||
foreach ($path as $edge){
|
||||
if($edge instanceof MoveRecord){
|
||||
$edges[] = new MoveRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
|
||||
}else if($edge instanceof LineRecord){
|
||||
$edges[] = new LineRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
|
||||
}else{
|
||||
var_dump($edge);
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
|
||||
$commands[] = DrawPath::fill(new FillStyleRecord($colors[$k]), new Shape($edges));
|
||||
}
|
||||
|
||||
return new DrawPathList($commands);
|
||||
}
|
||||
}
|
|
@ -8,11 +8,22 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
public Coordinate $size;
|
||||
/** @var Color[][] */
|
||||
public $pixels;
|
||||
private DrawPathList $drawPathList;
|
||||
|
||||
public function __construct($id, Coordinate $size, array $pixels) {
|
||||
$this->id = $id;
|
||||
$this->size = $size;
|
||||
$this->pixels = $pixels;
|
||||
|
||||
$converter = new BitmapConverter($this->pixels);
|
||||
$this->drawPathList = $converter->render(true);
|
||||
}
|
||||
|
||||
public function getId(){
|
||||
return $this->id;
|
||||
}
|
||||
public function getShapeList() : DrawPathList{
|
||||
return $this->drawPathList;
|
||||
}
|
||||
|
||||
static function fromXML(\DOMElement $element): BitmapDefinition {
|
||||
|
@ -63,4 +74,5 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
|
||||
return (string)$im;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,10 @@ class Color {
|
|||
$this->alpha = $alpha;
|
||||
}
|
||||
|
||||
public function equals(?Color $other): bool {
|
||||
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and $other->alpha == $this->alpha;
|
||||
}
|
||||
|
||||
public function distance(Color $color): float {
|
||||
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2));
|
||||
}
|
||||
|
@ -22,6 +26,7 @@ class Color {
|
|||
public function toString($nullAlpha = false) {
|
||||
$c = "\\1c&H" . strtoupper(Utils::padHex(dechex($this->b ?? 0))) . strtoupper(Utils::padHex(dechex($this->g ?? 0))) . strtoupper(Utils::padHex(dechex($this->r ?? 0))) . "&";
|
||||
if (($this->alpha === null or $this->alpha === 0) and $nullAlpha) {
|
||||
|
||||
} else {
|
||||
$c .= "\\1a&H" . strtoupper(Utils::padHex(dechex($this->alpha ?? 0))) . "&";
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ColorTransform {
|
|||
$styles->color1 = $styles->original_color1->applyTransform($this);
|
||||
}
|
||||
if ($styles->original_color3 instanceof Color) {
|
||||
//$styles->color3 = $styles->original_color3->applyTransform($this);
|
||||
$styles->color3 = $styles->original_color3->applyTransform($this);
|
||||
}
|
||||
|
||||
return $styles;
|
||||
|
|
|
@ -32,6 +32,10 @@ class Coordinate {
|
|||
return new Coordinate($this->x - $b->x, $this->y - $b->y);
|
||||
}
|
||||
|
||||
public function multiply($size): Coordinate {
|
||||
return new Coordinate($this->x * $size, $this->y * $size);
|
||||
}
|
||||
|
||||
public function divide($size): Coordinate {
|
||||
return new Coordinate($this->x / $size, $this->y / $size);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ class CubicCurveRecord implements Record {
|
|||
}
|
||||
|
||||
public static function fromQuadraticRecord(QuadraticCurveRecord $q): CubicCurveRecord {
|
||||
return new CubicCurveRecord(new Coordinate(($q->start->x + 2 * $q->control->x) / 3, ($q->start->y + 2 * $q->control->y) / 3), new Coordinate(($q->anchor->x + 2 * $q->control->x) / 3, ($q->anchor->y + 2 * $q->control->y) / 3), $q->anchor, $q->start);
|
||||
return new CubicCurveRecord(
|
||||
$q->start->add($q->control->multiply(2))->divide(3),
|
||||
$q->anchor->add($q->control->multiply(2))->divide(3),
|
||||
$q->anchor,
|
||||
$q->start
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ class DisplayEntry {
|
|||
public $depth;
|
||||
public $clipDepth = 0;
|
||||
|
||||
|
||||
public function getLines(CurrentState $currentState) {
|
||||
if ($this->clipDepth > 0) {
|
||||
return [];
|
||||
|
@ -26,114 +25,24 @@ class DisplayEntry {
|
|||
$endTime = ($this->startFrame + 1) * (1 / $currentState->frameRate);
|
||||
|
||||
|
||||
if ($this->object instanceof ShapeDefinition) {
|
||||
|
||||
|
||||
if ($this->object instanceof ObjectDefinition) {
|
||||
$shapeDef = $this->object;
|
||||
|
||||
//TODO: animation
|
||||
|
||||
$currentShapeList = $shapeDef->styles;
|
||||
|
||||
foreach ($shapeDef->shapeList->shapes as $shape) {
|
||||
foreach ($shapeDef->getShapeList()->commands as $path){
|
||||
$currentFrameStyles = [new StyleContainer()];
|
||||
|
||||
$edges = $shape->edges;
|
||||
if ($shape->styleRecord->styleList instanceof StyleList) {
|
||||
$currentShapeList = $shape->styleRecord->styleList;
|
||||
}
|
||||
|
||||
$fillStyleInside = null;
|
||||
if ($shape->styleRecord->fillStyleInside > 0) {
|
||||
$fillStyleInside = $currentShapeList->getFillStyle($shape->styleRecord->fillStyleInside - 1);
|
||||
}
|
||||
|
||||
$lineStyle = null;
|
||||
if ($shape->styleRecord->lineStyle > 0) {
|
||||
$lineStyle = $currentShapeList->getLineStyle($shape->styleRecord->lineStyle - 1);
|
||||
|
||||
$hasUnopenedPath = false;
|
||||
$newEdges = [];
|
||||
$currentSegment = [];
|
||||
foreach ($edges as $edge) {
|
||||
if (($edge instanceof MoveRecord) and count($currentSegment) > 0) {
|
||||
$reversedPath = array_reverse($currentSegment);
|
||||
$end = $reversedPath[0]->reverse()->start;
|
||||
$start = $currentSegment[0]->start;
|
||||
$diff = abs($end->x - $start->x) + abs($end->y - $start->y);
|
||||
//Close paths that are left open by drawing backwards
|
||||
if ($diff >= 1) {
|
||||
foreach ($reversedPath as $node) {
|
||||
$n = $node->reverse();
|
||||
$newEdges[] = $n;
|
||||
}
|
||||
$hasUnopenedPath = true;
|
||||
}
|
||||
$currentSegment = [];
|
||||
}
|
||||
|
||||
$newEdges[] = $edge;
|
||||
$currentSegment[] = $edge;
|
||||
}
|
||||
|
||||
if (count($currentSegment) > 0) {
|
||||
$reversedPath = array_reverse($currentSegment);
|
||||
$end = $reversedPath[0]->reverse()->start;
|
||||
$start = $currentSegment[0]->start;
|
||||
$diff = abs($end->x - $start->x) + abs($end->y - $start->y);
|
||||
//Close paths that are left open by drawing backwards
|
||||
if ($diff >= 1) {
|
||||
foreach ($reversedPath as $node) {
|
||||
$n = $node->reverse();
|
||||
$newEdges[] = $n;
|
||||
}
|
||||
$hasUnopenedPath = true;
|
||||
}
|
||||
$currentSegment = [];
|
||||
}
|
||||
|
||||
if ($hasUnopenedPath) {
|
||||
|
||||
$edges = $newEdges;
|
||||
|
||||
if (end($edges) instanceof MoveRecord) {
|
||||
array_pop($edges);
|
||||
}
|
||||
//Need to half the border for double path
|
||||
if ($lineStyle instanceof LineStyle) {
|
||||
$lineStyle = clone $lineStyle;
|
||||
$lineStyle->width /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($fillStyleInside instanceof LinearGradient) {
|
||||
$fillStyleInside = $fillStyleInside->colors[1]->color;
|
||||
}
|
||||
|
||||
if ($lineStyle instanceof LineStyle and $lineStyle->item instanceof LinearGradient) {
|
||||
$oStyle = $lineStyle;
|
||||
$lineStyle = new LineStyle();
|
||||
$lineStyle->item = $oStyle->item->colors[1]->color;
|
||||
$lineStyle->width = $oStyle->width;
|
||||
}
|
||||
|
||||
if ($fillStyleInside !== null) {
|
||||
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $fillStyleInside;
|
||||
}
|
||||
|
||||
if ($lineStyle !== null) {
|
||||
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $lineStyle->item;
|
||||
$currentFrameStyles[0]->bord = round($lineStyle->width / TWIP_SIZE, 1);
|
||||
if($path->style instanceof LineStyleRecord){ //Stroke
|
||||
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $path->style->color;
|
||||
$currentFrameStyles[0]->bord = round($path->style->width / TWIP_SIZE, 1);
|
||||
}else if($path->style instanceof FillStyleRecord){ //Fill
|
||||
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $path->style->fill instanceof Gradient ? $path->style->fill->getItems()[0]->color : $path->style->fill; //TODO: gradient?
|
||||
}
|
||||
|
||||
$currentFrameStyles[0]->shad = 0;
|
||||
|
||||
$assLine = "{\\an7\\shad0\\p1}";
|
||||
|
||||
foreach ($edges as $edge) {
|
||||
foreach ($path->commands->edges as $edge) {
|
||||
if ($edge instanceof MoveRecord) {
|
||||
$coords = $edge->coord->toPixel();
|
||||
$assLine .= "m " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
|
||||
|
@ -161,6 +70,10 @@ class DisplayEntry {
|
|||
$lastLineStart = $startTime;
|
||||
$lastFrameStart = $this->startFrame;
|
||||
|
||||
$debugInfo = "oid:".$shapeDef->getId();
|
||||
|
||||
$savedFrameStyles = $currentFrameStyles;
|
||||
|
||||
$af = 0;
|
||||
$frames = 0;
|
||||
foreach ($this->frames as $frame => $frameInformation) {
|
||||
|
@ -180,6 +93,16 @@ class DisplayEntry {
|
|||
$frameInformation->colorTransform->applyToStyleContainer($frameStyle);
|
||||
}
|
||||
|
||||
$lastSavedStyle = end($savedFrameStyles);
|
||||
$savedStyle = clone $lastSavedStyle;
|
||||
$savedStyle->resetTransformedStyles();
|
||||
if ($frameInformation->transform instanceof MatrixTransform) {
|
||||
$frameInformation->transform->applyToStyleContainer($savedStyle);
|
||||
}
|
||||
if ($frameInformation->colorTransform instanceof ColorTransform) {
|
||||
$frameInformation->colorTransform->applyToStyleContainer($savedStyle);
|
||||
}
|
||||
|
||||
if ($frames === 0) {
|
||||
$currentFrameStyles[array_key_last($currentFrameStyles)] = $frameStyle;
|
||||
} else if (!$lastStyle->isEqualish($frameStyle)) {
|
||||
|
@ -189,7 +112,7 @@ class DisplayEntry {
|
|||
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, $frame, $frame);
|
||||
}
|
||||
if ($transition === false) {
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,objectid:{$shapeDef->id},0,0,0,af:" . ($af++) . ",";
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
|
||||
foreach ($currentFrameStyles as $i => $style) {
|
||||
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
|
||||
}
|
||||
|
@ -197,15 +120,16 @@ class DisplayEntry {
|
|||
|
||||
$lastLineStart = $currentTime;
|
||||
$lastFrameStart = $frame;
|
||||
$currentFrameStyles = [$frameStyle];
|
||||
$currentFrameStyles = [$savedStyle];
|
||||
} else if ($transition instanceof StyleContainer) {
|
||||
$currentFrameStyles[] = $transition;
|
||||
$savedFrameStyles[] = $savedStyle;
|
||||
}
|
||||
}
|
||||
++$frames;
|
||||
}
|
||||
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,objectid:{$shapeDef->id},0,0,0,af:" . ($af++) . ",";
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
|
||||
foreach ($currentFrameStyles as $i => $style) {
|
||||
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
|
||||
}
|
||||
|
|
27
src/DrawPath.php
Normal file
27
src/DrawPath.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class DrawPath {
|
||||
public StyleRecord $style;
|
||||
public bool $is_closed;
|
||||
public Shape $commands;
|
||||
|
||||
|
||||
public static function fill(FillStyleRecord $style, Shape $shape): DrawPath {
|
||||
$p = new DrawPath();
|
||||
$p->style = $style;
|
||||
$p->commands = $shape;
|
||||
$p->is_closed = true;
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function stroke(LineStyleRecord $style, Shape $shape, bool $is_closed): DrawPath {
|
||||
$p = new DrawPath();
|
||||
$p->style = $style;
|
||||
$p->commands = $shape;
|
||||
$p->is_closed = $is_closed;
|
||||
return $p;
|
||||
}
|
||||
}
|
24
src/DrawPathList.php
Normal file
24
src/DrawPathList.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class DrawPathList {
|
||||
/** @var DrawPath[] */
|
||||
public array $commands;
|
||||
|
||||
public function __construct($commands = []) {
|
||||
$this->commands = $commands;
|
||||
}
|
||||
|
||||
public function merge(DrawPathList $b): DrawPathList {
|
||||
return new DrawPathList(array_merge($this->commands, $b->commands));
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, StyleList $currentStyles): DrawPathList {
|
||||
|
||||
$converter = new ShapeConverter($element, $currentStyles);
|
||||
|
||||
return $converter->commands;
|
||||
}
|
||||
}
|
13
src/FillStyleRecord.php
Normal file
13
src/FillStyleRecord.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class FillStyleRecord implements StyleRecord {
|
||||
/** @var Gradient|Color */
|
||||
public $fill;
|
||||
|
||||
public function __construct($fill){
|
||||
$this->fill = $fill;
|
||||
}
|
||||
}
|
12
src/Gradient.php
Normal file
12
src/Gradient.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
interface Gradient {
|
||||
|
||||
/**
|
||||
* @return GradientItem[]
|
||||
*/
|
||||
public function getItems() : array;
|
||||
public function getMatrixTransform() : ?MatrixTransform;
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace swf2ass;
|
|||
|
||||
class GradientItem {
|
||||
public $position;
|
||||
public $color;
|
||||
public Color $color;
|
||||
|
||||
public function __construct($position, Color $color) {
|
||||
$this->position = $position;
|
||||
|
|
|
@ -19,10 +19,7 @@ class LineRecord implements Record {
|
|||
return new LineRecord($this->start, $this->coord);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): LineRecord {
|
||||
$x = (int)$element->getAttribute("x");
|
||||
$y = (int)$element->getAttribute("y");
|
||||
|
||||
return new LineRecord(new Coordinate($cursorX + $x, $cursorY + $y), new Coordinate($cursorX, $cursorY));
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): LineRecord {
|
||||
return new LineRecord($cursor->add(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class LineStyle {
|
||||
/** @var Color|LinearGradient */
|
||||
public $item;
|
||||
public $width;
|
||||
}
|
20
src/LineStyleRecord.php
Normal file
20
src/LineStyleRecord.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class LineStyleRecord implements StyleRecord {
|
||||
public int $width;
|
||||
public Color $color;
|
||||
|
||||
public ?FillStyleRecord $fill_style = null;
|
||||
|
||||
//TODO: join/cap/etc style
|
||||
public bool $allow_close = true;
|
||||
|
||||
public function __construct(int $width, Color $color){
|
||||
$this->width = $width;
|
||||
$this->color = $color;
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace swf2ass;
|
||||
|
||||
class LinearGradient {
|
||||
class LinearGradient implements Gradient {
|
||||
/** @var GradientItem[] */
|
||||
public $colors;
|
||||
/** @var MatrixTransform|null */
|
||||
|
@ -12,6 +12,12 @@ class LinearGradient {
|
|||
$this->colors = $colors;
|
||||
$this->transform = $transform;
|
||||
}
|
||||
public function getItems() : array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): LinearGradient {
|
||||
$colors = [];
|
||||
|
|
|
@ -19,10 +19,7 @@ class MoveRecord implements Record {
|
|||
return new MoveRecord($this->start, $this->coord);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): MoveRecord {
|
||||
$x = (int)$element->getAttribute("x");
|
||||
$y = (int)$element->getAttribute("y");
|
||||
|
||||
return new MoveRecord(new Coordinate($x, $y), new Coordinate($cursorX, $cursorY));
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): MoveRecord {
|
||||
return new MoveRecord(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
|
||||
}
|
||||
}
|
|
@ -4,4 +4,6 @@ namespace swf2ass;
|
|||
|
||||
interface ObjectDefinition {
|
||||
|
||||
public function getId();
|
||||
public function getShapeList() : DrawPathList;
|
||||
}
|
100
src/PathSegment.php
Normal file
100
src/PathSegment.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
use mysql_xdevapi\Exception;
|
||||
|
||||
class PathSegment {
|
||||
/** @var VisitedPoint[] */
|
||||
public array $points;
|
||||
|
||||
|
||||
public function __construct(Coordinate $start) {
|
||||
$this->points = [new VisitedPoint($start, false)];
|
||||
}
|
||||
|
||||
public function flip() {
|
||||
$this->points = array_reverse($this->points, false);
|
||||
}
|
||||
|
||||
public function add_point(VisitedPoint $point){
|
||||
$this->points[] = $point;
|
||||
}
|
||||
|
||||
public function start(): Coordinate {
|
||||
return reset($this->points)->pos;
|
||||
}
|
||||
|
||||
public function end(): Coordinate {
|
||||
return end($this->points)->pos;
|
||||
}
|
||||
|
||||
public function is_empty(): bool {
|
||||
return count($this->points) <= 1;
|
||||
}
|
||||
|
||||
public function is_closed(): bool {
|
||||
return $this->start()->equals($this->end());
|
||||
}
|
||||
|
||||
public function swap(PathSegment $other){
|
||||
[$this->points, $other->points] = [$other->points, $this->points];
|
||||
}
|
||||
|
||||
public function merge(PathSegment $other){
|
||||
$this->points = array_merge($this->points, array_slice($other->points, 1));
|
||||
}
|
||||
|
||||
public function try_merge(PathSegment $other, bool $directed) : bool{
|
||||
if($other->end()->equals($this->start())){
|
||||
$this->swap($other);
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if($this->end()->equals($other->start())){
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if(!$directed and $this->end()->equals($other->end())){
|
||||
$other->flip();
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if(!$directed and $this->start()->equals($other->start())){
|
||||
$other->flip();
|
||||
$this->swap($other);
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getShape() : Shape {
|
||||
if($this->is_empty()){
|
||||
throw new Exception();
|
||||
}
|
||||
$shape = new Shape();
|
||||
|
||||
$first = reset($this->points);
|
||||
|
||||
$pos = new Coordinate(0, 0);
|
||||
$shape->edges[] = new MoveRecord($first->pos, $pos);
|
||||
$pos = $first->pos;
|
||||
|
||||
while (($point = next($this->points)) !== false){
|
||||
if(!$point->is_bezier_control){
|
||||
$shape->edges[] = new LineRecord($point->pos, $pos);
|
||||
$pos = $point->pos;
|
||||
}else{
|
||||
$end = next($this->points);
|
||||
if($end === false){
|
||||
throw new \Exception("Bezier without endpoint");
|
||||
}
|
||||
|
||||
$shape->edges[] = new QuadraticCurveRecord($point->pos, $end->pos, $pos);
|
||||
$pos = $end->pos;
|
||||
}
|
||||
}
|
||||
|
||||
return $shape;
|
||||
}
|
||||
}
|
47
src/PendingPath.php
Normal file
47
src/PendingPath.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class PendingPath {
|
||||
|
||||
/** @var PathSegment[] */
|
||||
public array $segments = [];
|
||||
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PathSegment $new_segment
|
||||
* @param bool $directed
|
||||
*/
|
||||
public function merge_path(PathSegment $new_segment, bool $directed){
|
||||
if(!$new_segment->is_empty()){
|
||||
$merged = null;
|
||||
foreach ($this->segments as $i => $segment){
|
||||
if($segment->try_merge($new_segment, $directed)){
|
||||
unset($this->segments[$i]);
|
||||
$merged = $segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($merged === null){
|
||||
$this->segments[] = $new_segment;
|
||||
}else{
|
||||
$this->merge_path($merged, $directed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getShape() : Shape {
|
||||
$shape = new Shape();
|
||||
foreach ($this->segments as $segment){
|
||||
$shape->edges = array_merge($shape->edges, $segment->getShape()->edges);
|
||||
}
|
||||
|
||||
return $shape;
|
||||
}
|
||||
}
|
22
src/PendingPathMap.php
Normal file
22
src/PendingPathMap.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class PendingPathMap {
|
||||
/** @var PendingPath[] */
|
||||
public array $map = [];
|
||||
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
public function merge_path(ActivePath $path, bool $directed){
|
||||
if(!isset($this->map[$path->style])){
|
||||
$this->map[$path->style] = new PendingPath();
|
||||
}
|
||||
|
||||
$this->map[$path->style]->merge_path($path->segment, $directed);
|
||||
}
|
||||
}
|
|
@ -21,12 +21,10 @@ class QuadraticCurveRecord implements Record {
|
|||
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): QuadraticCurveRecord {
|
||||
$controlX = (int)$element->getAttribute("x1");
|
||||
$controlY = (int)$element->getAttribute("y1");
|
||||
$anchorX = (int)$element->getAttribute("x2");
|
||||
$anchorY = (int)$element->getAttribute("y2");
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): QuadraticCurveRecord {
|
||||
$control = $cursor->add(new Coordinate((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
|
||||
$anchor = $control->add(new Coordinate((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
|
||||
|
||||
return new QuadraticCurveRecord(new Coordinate($cursorX + $controlX, $cursorY + $controlY), new Coordinate($cursorX + $controlX + $anchorX, $cursorY + $controlY + $anchorY), new Coordinate($cursorX, $cursorY));
|
||||
return new QuadraticCurveRecord($control, $anchor, $cursor);
|
||||
}
|
||||
}
|
30
src/RadialGradient.php
Normal file
30
src/RadialGradient.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class RadialGradient implements Gradient {
|
||||
/** @var GradientItem[] */
|
||||
public $colors;
|
||||
/** @var MatrixTransform|null */
|
||||
public ?MatrixTransform $transform;
|
||||
|
||||
public function __construct($colors = [], MatrixTransform $transform = null) {
|
||||
$this->colors = $colors;
|
||||
$this->transform = $transform;
|
||||
}
|
||||
public function getItems() : array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): RadialGradient {
|
||||
$colors = [];
|
||||
foreach ($element->getElementsByTagName("GradientItem") as $item) {
|
||||
$colors[] = GradientItem::fromXML($item);
|
||||
}
|
||||
$matrix = $element->getElementsByTagName("matrix");
|
||||
return new RadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null);
|
||||
}
|
||||
}
|
|
@ -4,12 +4,13 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class Shape {
|
||||
/** @var QuadraticCurveRecord[]|CubicCurveRecord[]|LineRecord[]|MoveRecord[] */
|
||||
public $edges;
|
||||
public $styleRecord;
|
||||
/** @var Record[] */
|
||||
public array $edges;
|
||||
|
||||
public function __construct($edges = [], StyleRecord $record = null) {
|
||||
/**
|
||||
* @param Record[] $edges
|
||||
*/
|
||||
public function __construct(array $edges = []) {
|
||||
$this->edges = $edges;
|
||||
$this->styleRecord = $record;
|
||||
}
|
||||
}
|
176
src/ShapeConverter.php
Normal file
176
src/ShapeConverter.php
Normal file
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ShapeConverter {
|
||||
|
||||
private StyleList $styles;
|
||||
|
||||
private ?ActivePath $fill_style0 = null;
|
||||
private ?ActivePath $fill_style1 = null;
|
||||
private ?ActivePath $line_style = null;
|
||||
|
||||
private Coordinate $position;
|
||||
|
||||
private PendingPathMap $fills;
|
||||
private PendingPathMap $strokes;
|
||||
|
||||
public DrawPathList $commands;
|
||||
|
||||
|
||||
|
||||
public function __construct(\DOMElement $element, StyleList $currentStyles) {
|
||||
$this->styles = $currentStyles;
|
||||
$this->position = new Coordinate(0, 0);
|
||||
$this->fills = new PendingPathMap();
|
||||
$this->strokes = new PendingPathMap();
|
||||
$this->commands = new DrawPathList([]);
|
||||
|
||||
foreach ($element->getElementsByTagName("edges")->item(0)->childNodes as $node) {
|
||||
if ($node->nodeName === "ShapeSetup") {
|
||||
//Utils::dump_element($node);
|
||||
if ($node->hasAttribute("x")) {
|
||||
$move = MoveRecord::fromXML($node, $this->position);
|
||||
$this->position = $move->coord;
|
||||
|
||||
$this->flush_paths();
|
||||
}
|
||||
|
||||
if ($node->hasChildNodes()) {
|
||||
$this->flush_layer();
|
||||
|
||||
$this->styles = StyleList::fromXML($node->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
|
||||
}
|
||||
|
||||
|
||||
if ($node->hasAttribute("fillStyle1")) {
|
||||
if($this->fill_style1 !== null){
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
}
|
||||
|
||||
$id = (int)$node->getAttribute("fillStyle1");
|
||||
|
||||
|
||||
$this->fill_style1 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if ($node->hasAttribute("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 = (int)$node->getAttribute("fillStyle0");
|
||||
$this->fill_style0 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if ($node->hasAttribute("lineStyle")) {
|
||||
if($this->line_style !== null){
|
||||
$this->strokes->merge_path($this->line_style, false);
|
||||
}
|
||||
|
||||
$id = (int)$node->getAttribute("lineStyle");
|
||||
|
||||
|
||||
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
}else if ($node->nodeName === "LineTo") {
|
||||
$curve = LineRecord::fromXML($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->coord, false);
|
||||
|
||||
$this->position = $curve->coord;
|
||||
}else if ($node->nodeName === "CurveTo") {
|
||||
$curve = QuadraticCurveRecord::fromXML($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->control, true);
|
||||
$this->visit_point($curve->anchor, false);
|
||||
|
||||
$this->position = $curve->anchor;
|
||||
}
|
||||
}
|
||||
|
||||
$this->flush_layer();
|
||||
}
|
||||
|
||||
public function visit_point(Coordinate $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);
|
||||
|
||||
|
||||
foreach ($path->segments as $segment){
|
||||
$segmentStyle = $style;
|
||||
//Close non-closed segments by double drawing backwards
|
||||
if(!$segment->is_closed()){
|
||||
$other = clone $segment;
|
||||
$other->flip();
|
||||
$segment->merge($other);
|
||||
|
||||
//Reduce width of line style to account for double border
|
||||
$segmentStyle = clone $style;
|
||||
$segmentStyle->width /= 2;
|
||||
}
|
||||
|
||||
$this->commands->commands[] = DrawPath::stroke($segmentStyle, $segment->getShape(), $segment->is_closed());
|
||||
}
|
||||
}
|
||||
$this->strokes->map = [];
|
||||
}
|
||||
}
|
|
@ -5,25 +5,33 @@ namespace swf2ass;
|
|||
class ShapeDefinition implements ObjectDefinition {
|
||||
public $id;
|
||||
public Rectangle $bounds;
|
||||
public StyleList $styles;
|
||||
public ShapeList $shapeList;
|
||||
public DrawPathList $shapeList;
|
||||
|
||||
public function __construct($id, Rectangle $bounds, StyleList $styles, ShapeList $shapes) {
|
||||
public function __construct($id, Rectangle $bounds, DrawPathList $shapes) {
|
||||
$this->id = $id;
|
||||
$this->bounds = $bounds;
|
||||
$this->styles = $styles;
|
||||
$this->shapeList = $shapes;
|
||||
}
|
||||
|
||||
static function fromXML(\DOMElement $element): ShapeDefinition {
|
||||
$shapes = new ShapeList([]);
|
||||
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
|
||||
public function getId(){
|
||||
return $this->id;
|
||||
}
|
||||
public function getShapeList() : DrawPathList{
|
||||
return $this->shapeList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static function fromXML(\DOMElement $element): ShapeDefinition {
|
||||
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
|
||||
//Utils::dump_element($element->getElementsByTagName("styles")->item(0));
|
||||
|
||||
$drawPathList = new DrawPathList([]);
|
||||
foreach ($element->getElementsByTagName("shapes")->item(0)->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement and $node->nodeName === "Shape") {
|
||||
$shapes = $shapes->merge(ShapeList::fromXML($node, $styles));
|
||||
$drawPathList = $drawPathList->merge(DrawPathList::fromXML($node, $styles));
|
||||
}
|
||||
}
|
||||
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $styles, $shapes);
|
||||
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
|
||||
}
|
||||
}
|
|
@ -1,281 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ShapeList {
|
||||
/** @var Shape[] */
|
||||
public $shapes;
|
||||
|
||||
public function __construct($shapes = []) {
|
||||
$this->shapes = $shapes;
|
||||
}
|
||||
|
||||
public function merge(ShapeList $b): ShapeList {
|
||||
return new ShapeList(array_merge($this->shapes, $b->shapes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Record[] $subPath
|
||||
* @param $lineStyleIdx
|
||||
* @param $fillStyleIdx
|
||||
* @param $fillStyleInsideIdx
|
||||
* @param Record[][] $currentFillEdgeMap
|
||||
* @param Record[][] $currentLineEdgeMap
|
||||
*/
|
||||
private static function processSubPath(array $subPath, $lineStyleIdx, $fillStyleIdx, $fillStyleInsideIdx, array &$currentFillEdgeMap, array &$currentLineEdgeMap) {
|
||||
if ($fillStyleIdx > 0) {
|
||||
if (!isset($currentFillEdgeMap[$fillStyleIdx])) {
|
||||
$currentFillEdgeMap[$fillStyleIdx] = [];
|
||||
}
|
||||
|
||||
foreach ($subPath as $n) {
|
||||
$currentFillEdgeMap[$fillStyleIdx][] = $n->reverse();
|
||||
}
|
||||
}
|
||||
if ($fillStyleInsideIdx > 0) {
|
||||
if (!isset($currentFillEdgeMap[$fillStyleInsideIdx])) {
|
||||
$currentFillEdgeMap[$fillStyleInsideIdx] = [];
|
||||
}
|
||||
|
||||
foreach ($subPath as $n) {
|
||||
$currentFillEdgeMap[$fillStyleInsideIdx][] = $n;
|
||||
}
|
||||
}
|
||||
if ($lineStyleIdx > 0) {
|
||||
if (!isset($currentLineEdgeMap[$lineStyleIdx])) {
|
||||
$currentLineEdgeMap[$lineStyleIdx] = [];
|
||||
}
|
||||
|
||||
foreach ($subPath as $n) {
|
||||
$currentLineEdgeMap[$lineStyleIdx][] = $n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Record[][] $edgeMap
|
||||
*/
|
||||
private static function cleanEdgeMap(array &$edgeMap) {
|
||||
foreach ($edgeMap as $styleIdx => $subPath) {
|
||||
if (count($subPath) > 0) {
|
||||
$idx = 0;
|
||||
/** @var Record $prevEdge */
|
||||
$prevEdge = null;
|
||||
/** @var Record $prevEdgeReverse */
|
||||
$prevEdgeReverse = null;
|
||||
$tmpPath = [];
|
||||
while (count($subPath) > 0) {
|
||||
$idx = 0;
|
||||
while ($idx < count($subPath)) {
|
||||
if ($prevEdge === null or $prevEdgeReverse->getStart()->equals($subPath[$idx]->getStart())) {
|
||||
$edge = array_splice($subPath, $idx, 1)[0];
|
||||
$tmpPath[] = $edge;
|
||||
$prevEdge = $edge;
|
||||
$prevEdgeReverse = $edge->reverse();
|
||||
} else {
|
||||
$found = false;
|
||||
for ($i = 0; $i < count($subPath); ++$i) {
|
||||
if ($prevEdgeReverse->getStart()->equals($subPath[$i]->getStart())) {
|
||||
$idx = $i;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$idx = 0;
|
||||
$prevEdge = null;
|
||||
$prevEdgeReverse = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$edgeMap[$styleIdx] = [];
|
||||
$pos = new Coordinate(0, 0);
|
||||
foreach ($tmpPath as $node) {
|
||||
if (!$pos->equals($node->getStart())) {
|
||||
$edgeMap[$styleIdx][] = new MoveRecord($node->getStart(), $pos);
|
||||
}
|
||||
$edgeMap[$styleIdx][] = $node;
|
||||
$pos = $node->reverse()->getStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, StyleList $currentStyles): ShapeList {
|
||||
/** @var Shape[] $shapes */
|
||||
$shapes = [];
|
||||
|
||||
|
||||
/** @var Record[] $edgeRecords */
|
||||
$edgeRecords = [];
|
||||
$styleRecord = new StyleRecord();
|
||||
$styleRecord->styleList = clone $currentStyles;
|
||||
$fillStyleIdxOffset = 0;
|
||||
$lineStyleIdxOffset = 0;
|
||||
|
||||
$cursorX = 0;
|
||||
$cursorY = 0;
|
||||
foreach ($element->getElementsByTagName("edges")->item(0)->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
if ($node->nodeName === "CurveTo") {
|
||||
$curve = QuadraticCurveRecord::fromXML($node, $cursorX, $cursorY);
|
||||
$cursorX = $curve->anchor->x;
|
||||
$cursorY = $curve->anchor->y;
|
||||
$edgeRecords[] = $curve;
|
||||
} else if ($node->nodeName === "LineTo") {
|
||||
$line = LineRecord::fromXML($node, $cursorX, $cursorY);
|
||||
$cursorX = $line->coord->x;
|
||||
$cursorY = $line->coord->y;
|
||||
$edgeRecords[] = $line;
|
||||
} else if ($node->nodeName === "ShapeSetup") {
|
||||
$record = StyleRecord::nullStyle();
|
||||
$styleChange = false;
|
||||
if ($node->hasChildNodes()) {
|
||||
$newStyles = StyleList::fromXML($node->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
|
||||
foreach ($newStyles->fillStyles as $s) {
|
||||
$styleRecord->styleList->fillStyles[] = $s;
|
||||
++$fillStyleIdxOffset;
|
||||
}
|
||||
foreach ($newStyles->lineStyles as $s) {
|
||||
$styleRecord->styleList->lineStyles[] = $s;
|
||||
++$lineStyleIdxOffset;
|
||||
}
|
||||
$styleChange = true;
|
||||
$record->styleList = $styleRecord->styleList;
|
||||
}
|
||||
|
||||
if ($node->hasAttribute("fillStyle0")) {
|
||||
$fillStyle = (int)$node->getAttribute("fillStyle0");
|
||||
$styleRecord->fillStyle = $fillStyle + ($fillStyle > 0 ? $fillStyleIdxOffset : 0);
|
||||
$styleChange = true;
|
||||
$record->fillStyle = $styleRecord->fillStyle;
|
||||
}
|
||||
if ($node->hasAttribute("fillStyle1")) {
|
||||
$fillStyleInside = (int)$node->getAttribute("fillStyle1");
|
||||
$styleRecord->fillStyleInside = $fillStyleInside + ($fillStyleInside > 0 ? $fillStyleIdxOffset : 0);
|
||||
$styleChange = true;
|
||||
$record->fillStyleInside = $styleRecord->fillStyleInside;
|
||||
}
|
||||
if ($node->hasAttribute("lineStyle")) {
|
||||
$lineStyle = (int)$node->getAttribute("lineStyle");
|
||||
$styleRecord->lineStyle = $lineStyle + ($lineStyle > 0 ? $lineStyleIdxOffset : 0);
|
||||
$styleChange = true;
|
||||
$record->lineStyle = $styleRecord->lineStyle;
|
||||
}
|
||||
|
||||
if ($styleChange) {
|
||||
$edgeRecords[] = $record;
|
||||
}
|
||||
|
||||
if ($node->hasAttribute("x")) {
|
||||
$move = MoveRecord::fromXML($node, $cursorX, $cursorY);
|
||||
$cursorX = $move->coord->x;
|
||||
$cursorY = $move->coord->y;
|
||||
$edgeRecords[] = $move;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Record[] $subPath */
|
||||
$subPath = [];
|
||||
|
||||
$currentFillStyleIdx = 0;
|
||||
$currentFillStyleInsideIdx = 0;
|
||||
$currentLineStyleIdx = 0;
|
||||
|
||||
/** @var Record[][] $currentFillEdgeMap */
|
||||
$currentFillEdgeMap = [];
|
||||
/** @var Record[] $currentLineEdgeMap */
|
||||
$currentLineEdgeMap = [];
|
||||
|
||||
|
||||
$fillEdgeMaps = [];
|
||||
$lineEdgeMaps = [];
|
||||
|
||||
$numGroups = 0;
|
||||
|
||||
|
||||
$lastNode = null;
|
||||
|
||||
foreach ($edgeRecords as $edge) {
|
||||
if ($edge instanceof LineRecord or $edge instanceof CubicCurveRecord or $edge instanceof QuadraticCurveRecord) {
|
||||
$subPath[] = $edge;
|
||||
} elseif ($edge instanceof MoveRecord) {
|
||||
//$subPath[] = $edge;
|
||||
} else if ($edge instanceof StyleRecord) {
|
||||
$changed = false;
|
||||
|
||||
if ($edge->lineStyle !== null or $edge->fillStyle !== null or $edge->fillStyleInside !== null) {
|
||||
self::processSubPath($subPath, $currentLineStyleIdx, $currentFillStyleIdx, $currentFillStyleInsideIdx, $currentFillEdgeMap, $currentLineEdgeMap);
|
||||
|
||||
/** @var Record[] $subPath */
|
||||
$subPath = [];
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
if ($changed and $edge->lineStyle === 0 and $edge->fillStyle === 0 and $edge->fillStyleInside === 0) {
|
||||
self::cleanEdgeMap($currentFillEdgeMap);
|
||||
self::cleanEdgeMap($currentLineEdgeMap);
|
||||
$fillEdgeMaps[] = $currentFillEdgeMap;
|
||||
$lineEdgeMaps[] = $currentLineEdgeMap;
|
||||
$currentFillEdgeMap = [];
|
||||
$currentLineEdgeMap = [];
|
||||
|
||||
$currentFillStyleIdx = 0;
|
||||
$currentFillStyleInsideIdx = 0;
|
||||
$currentLineStyleIdx = 0;
|
||||
|
||||
++$numGroups;
|
||||
} else {
|
||||
if ($edge->lineStyle !== null and $edge->lineStyle !== $currentLineStyleIdx) {
|
||||
$currentLineStyleIdx = $edge->lineStyle;
|
||||
}
|
||||
if ($edge->fillStyle !== null and $edge->fillStyle !== $currentFillStyleIdx) {
|
||||
$currentFillStyleIdx = $edge->fillStyle;
|
||||
}
|
||||
if ($edge->fillStyleInside !== null and $edge->fillStyleInside !== $currentFillStyleInsideIdx) {
|
||||
$currentFillStyleInsideIdx = $edge->fillStyleInside;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::processSubPath($subPath, $currentLineStyleIdx, $currentFillStyleIdx, $currentFillStyleInsideIdx, $currentFillEdgeMap, $currentLineEdgeMap);
|
||||
self::cleanEdgeMap($currentFillEdgeMap);
|
||||
self::cleanEdgeMap($currentLineEdgeMap);
|
||||
$fillEdgeMaps[] = $currentFillEdgeMap;
|
||||
$lineEdgeMaps[] = $currentLineEdgeMap;
|
||||
$currentFillEdgeMap = [];
|
||||
$currentLineEdgeMap = [];
|
||||
++$numGroups;
|
||||
|
||||
for ($i = 0; $i < $numGroups; ++$i) {
|
||||
//ksort($fillEdgeMaps[$i]);
|
||||
ksort($lineEdgeMaps[$i]);
|
||||
foreach ($fillEdgeMaps[$i] as $fillStyle => $records) {
|
||||
if (count($records) <= 1) {
|
||||
continue;
|
||||
}
|
||||
$record = new StyleRecord();
|
||||
$record->fillStyleInside = $fillStyle;
|
||||
$record->styleList = $styleRecord->styleList;
|
||||
$shapes[] = new Shape($records, $record);
|
||||
}
|
||||
foreach ($lineEdgeMaps[$i] as $lineStyle => $records) {
|
||||
if (count($records) <= 1) {
|
||||
continue;
|
||||
}
|
||||
$record = new StyleRecord();
|
||||
$record->lineStyle = $lineStyle;
|
||||
$record->styleList = $styleRecord->styleList;
|
||||
$shapes[] = $s = new Shape($records, $record);
|
||||
}
|
||||
}
|
||||
|
||||
return new ShapeList($shapes);
|
||||
}
|
||||
}
|
37
src/ShiftedRadialGradient.php
Normal file
37
src/ShiftedRadialGradient.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class ShiftedRadialGradient implements Gradient {
|
||||
/** @var GradientItem[] */
|
||||
public $colors;
|
||||
/** @var MatrixTransform|null */
|
||||
public ?MatrixTransform $transform;
|
||||
|
||||
public array $attributes;
|
||||
|
||||
public function __construct($colors = [], MatrixTransform $transform = null, array $attributes = []) {
|
||||
$this->colors = $colors;
|
||||
$this->transform = $transform;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
public function getItems() : array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): ShiftedRadialGradient {
|
||||
$colors = [];
|
||||
foreach ($element->getElementsByTagName("GradientItem") as $item) {
|
||||
$colors[] = GradientItem::fromXML($item);
|
||||
}
|
||||
$matrix = $element->getElementsByTagName("matrix");
|
||||
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, [
|
||||
"spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null,
|
||||
"interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null,
|
||||
"shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -7,15 +7,13 @@ class StyleContainer {
|
|||
public $frx = null, $fry = null;
|
||||
public $fscx = null, $fscy = null;
|
||||
public $fax = null, $fay = null;
|
||||
/** @var ?Coordinate */
|
||||
public $pos = null;
|
||||
|
||||
public ?Coordinate $pos = null;
|
||||
/** @var ?Coordinate[] */
|
||||
public $move = null;
|
||||
|
||||
/** @var ?Color */
|
||||
public $color1 = null;
|
||||
/** @var ?Color */
|
||||
public $color3 = null;
|
||||
public ?Color $color1 = null;
|
||||
public ?Color $color3 = null;
|
||||
|
||||
/** @var ?Color */
|
||||
public $original_color1 = null;
|
||||
|
@ -60,7 +58,7 @@ class StyleContainer {
|
|||
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd") {
|
||||
|
||||
} elseif ($k === "color1" or $k === "color3" or $k === "original_color1" or $k === "original_color3") {
|
||||
if (!(($container->{$k} !== null and $value !== null and $container->{$k} == $value) or $container->{$k} === $value)) {
|
||||
if (!(($container->{$k} !== null and $value !== null and $container->{$k}->equals($value)) or $container->{$k} === $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -98,7 +96,7 @@ class StyleContainer {
|
|||
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd" or $k === "original_color1" or $k === "original_color3") {
|
||||
|
||||
} elseif ($k === "color1") {
|
||||
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k} == $value) {
|
||||
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k}->equals($value)) {
|
||||
continue;
|
||||
}
|
||||
if ($value !== null) {
|
||||
|
@ -107,11 +105,11 @@ class StyleContainer {
|
|||
$a .= "\\1a&HFF&";
|
||||
}
|
||||
} elseif ($k === "color3") {
|
||||
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k} == $value) {
|
||||
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k}->equals($value)) {
|
||||
continue;
|
||||
}
|
||||
if ($value !== null) {
|
||||
$a .= "\\3c&H" . strtoupper(Utils::padHex(dechex($value->b ?? 0))) . strtoupper(Utils::padHex(dechex($value->g ?? 0))) . strtoupper(Utils::padHex(dechex($value->r ?? 0))) . "&\\1a&H" . strtoupper(Utils::padHex(dechex($value->alpha ?? 0))) . "&";
|
||||
$a .= "\\3c&H" . strtoupper(Utils::padHex(dechex($value->b ?? 0))) . strtoupper(Utils::padHex(dechex($value->g ?? 0))) . strtoupper(Utils::padHex(dechex($value->r ?? 0))) . "&\\3a&H" . strtoupper(Utils::padHex(dechex($value->alpha ?? 0))) . "&";
|
||||
} else if ($outputEmpty) {
|
||||
$a .= "\\3a&HFF&";
|
||||
}
|
||||
|
@ -203,7 +201,7 @@ class StyleContainer {
|
|||
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd" or $k === "original_color1" or $k === "original_color3") {
|
||||
|
||||
} elseif ($k === "color1" or $k === "color3") {
|
||||
if ($value !== null and $secondStyle->{$k} !== null and $value == $secondStyle->{$k}) {
|
||||
if ($value !== null and $secondStyle->{$k} !== null and $value->equals($secondStyle->{$k})) {
|
||||
$secondStyle->{$k} = null;
|
||||
}
|
||||
} elseif ($value === $secondStyle->{$k}) {
|
||||
|
|
|
@ -4,10 +4,10 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class StyleList {
|
||||
/** @var LinearGradient[]|Color[] */
|
||||
public $fillStyles;
|
||||
/** @var LineStyle */
|
||||
public $lineStyles;
|
||||
/** @var FillStyleRecord[] */
|
||||
public array $fillStyles;
|
||||
/** @var LineStyleRecord[] */
|
||||
public array $lineStyles;
|
||||
|
||||
public function __construct($fillStyles = [], $lineStyles = []) {
|
||||
$this->fillStyles = $fillStyles;
|
||||
|
@ -20,24 +20,52 @@ class StyleList {
|
|||
foreach ($element->getElementsByTagName("fillStyles")->item(0)->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
if ($node->nodeName === "Solid") {
|
||||
$fillStyles[] = Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0));
|
||||
$fillStyles[] = new FillStyleRecord(Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0)));
|
||||
} else if ($node->nodeName === "LinearGradient") {
|
||||
$fillStyles[] = LinearGradient::fromXML($node);
|
||||
$fillStyles[] = new FillStyleRecord(LinearGradient::fromXML($node));
|
||||
} else if ($node->nodeName === "RadialGradient") {
|
||||
$fillStyles[] = new FillStyleRecord(RadialGradient::fromXML($node));
|
||||
} else if ($node->nodeName === "ShiftedRadialGradient") {
|
||||
$fillStyles[] = new FillStyleRecord(RadialGradient::fromXML($node));
|
||||
} else if ($node->nodeName === "ClippedBitmap") {
|
||||
Utils::dump_element($node);
|
||||
//TODO
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
|
||||
} else if ($node->nodeName === "ClippedBitmap2") {
|
||||
Utils::dump_element($node);
|
||||
//TODO
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
|
||||
} else if ($node->nodeName === "TiledBitmap") {
|
||||
//TODO
|
||||
Utils::dump_element($node);
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
foreach ($element->getElementsByTagName("lineStyles")->item(0)->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
$ob = new LineStyle();
|
||||
$ob->width = max(TWIP_SIZE, (int)$node->getAttribute("width"));
|
||||
|
||||
//$fill = null;
|
||||
$colors = $node->getElementsByTagName("color");
|
||||
$colors2 = $node->getElementsByTagName("Color");
|
||||
$grads = $node->getElementsByTagName("coLinearGradientlor");
|
||||
$color = null;
|
||||
|
||||
if ($colors->count() > 0) {
|
||||
$ob->item = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
|
||||
$color = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
|
||||
}else if ($colors2->count() > 0) {
|
||||
$color = Color::fromXML($colors2->item(0));
|
||||
} else if ($grads->count() > 0) {
|
||||
$ob->item = LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0));
|
||||
//TODO: other gradients?
|
||||
//$fill = new FillStyleRecord(LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0)));
|
||||
}
|
||||
$lineStyles[] = $ob;
|
||||
|
||||
if($color === null){
|
||||
Utils::dump_element($node);
|
||||
}
|
||||
|
||||
$lineStyles[] = new LineStyleRecord(max(TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
|
||||
}
|
||||
}
|
||||
return new StyleList($fillStyles, $lineStyles);
|
||||
|
@ -45,17 +73,17 @@ class StyleList {
|
|||
|
||||
/**
|
||||
* @param $i
|
||||
* @return Color|LinearGradient|mixed|null
|
||||
* @return FillStyleRecord|null
|
||||
*/
|
||||
public function getFillStyle($i) {
|
||||
public function getFillStyle($i): ?FillStyleRecord {
|
||||
return $this->fillStyles[$i] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $i
|
||||
* @return Color|LinearGradient|mixed|null
|
||||
* @return LineStyleRecord|null
|
||||
*/
|
||||
public function getLineStyle($i) {
|
||||
public function getLineStyle($i): ?LineStyleRecord {
|
||||
return $this->lineStyles[$i] ?? null;
|
||||
}
|
||||
}
|
|
@ -2,19 +2,6 @@
|
|||
|
||||
namespace swf2ass;
|
||||
|
||||
class StyleRecord {
|
||||
public ?StyleList $styleList = null;
|
||||
public ?int $fillStyle = 0;
|
||||
public ?int $fillStyleInside = 0;
|
||||
public ?int $lineStyle = 0;
|
||||
interface StyleRecord {
|
||||
|
||||
public static function nullStyle(): StyleRecord {
|
||||
$record = new StyleRecord();
|
||||
$record->styleList = null;
|
||||
$record->lineStyle = null;
|
||||
$record->fillStyle = null;
|
||||
$record->fillStyleInside = null;
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
|
@ -91,6 +91,7 @@ abstract class Utils {
|
|||
$ob = new DisplayEntry();
|
||||
$objectID = $node->getAttribute("objectID");
|
||||
if (!isset($currentState->objects[$objectID])) {
|
||||
var_dump("Unknown object id $objectID");
|
||||
continue;
|
||||
throw new \Exception("Unknown object id $objectID");
|
||||
}
|
||||
|
@ -143,12 +144,21 @@ abstract class Utils {
|
|||
}
|
||||
|
||||
++$currentState->frameNumber;
|
||||
} else if ($node->nodeName === "SoundStreamBlock") {
|
||||
|
||||
} else if ($node->nodeName === "DefineSprite") {
|
||||
//TODO do actual animation
|
||||
var_dump("TODO DefineSprite");
|
||||
//Utils::dump_element($node);
|
||||
|
||||
//var_dump($node);
|
||||
|
||||
foreach ($node->getElementsByTagName("tags")->item(0)->childNodes as $node2) {
|
||||
if ($node2 instanceof \DOMElement) {
|
||||
if($node2->nodeName === "PlaceObject2" and isset($currentState->objects[$node2->getAttribute("objectID")])){
|
||||
//TODO
|
||||
$currentState->objects[$node->getAttribute("objectID")] = $currentState->objects[$node2->getAttribute("objectID")];
|
||||
var_dump($node->getAttribute("objectID") . " === " . $node2->getAttribute("objectID") . " " . get_class($currentState->objects[$node->getAttribute("objectID")]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($node->nodeName === "DefineBitsJPEG2" || $node->nodeName === "DefineBitsJPEG3") {
|
||||
$bitmapDef = JPEGBitmapDefinition::fromXML($node);
|
||||
$currentState->objects[$bitmapDef->id] = $bitmapDef;
|
||||
|
|
14
src/VisitedPoint.php
Normal file
14
src/VisitedPoint.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class VisitedPoint {
|
||||
public Coordinate $pos;
|
||||
public bool $is_bezier_control;
|
||||
|
||||
public function __construct(Coordinate $pos, bool $is_bezier_control = false) {
|
||||
$this->pos = $pos;
|
||||
$this->is_bezier_control = $is_bezier_control;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue