You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1925 lines
69 KiB
1925 lines
69 KiB
<?php
|
|
|
|
namespace monoformat {
|
|
|
|
include_once("monoformat_bithelpers.php");
|
|
include_once("monoformat_schema.php");
|
|
|
|
function serializePixels(array $pixels): string {
|
|
$n = count($pixels);
|
|
$nBytes = intdiv($n + 7, 8);
|
|
$result = "";
|
|
for ($i = 0; $i < $nBytes; ++$i) {
|
|
$byte = 0;
|
|
for ($j = 0; $j < 8; ++$j) {
|
|
$k = $i * 8 + $j;
|
|
if ($k >= $n) {
|
|
break;
|
|
}
|
|
maybeSetBit($byte, $j, $pixels[$k]);
|
|
}
|
|
$result .= chr($byte);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function unserializePixels(int $n, string $serialized): array {
|
|
$nBytes = intdiv($n + 7, 8);
|
|
if (strlen($serialized) != $nBytes) {
|
|
$actual = strlen($serializd);
|
|
throw new \ValueError("Could not unserialize a pixel bitmap: the size $actual does not equal the expected $nBytes");
|
|
}
|
|
$result = array_fill(0, $n, false);
|
|
for ($i = 0; $i < $nBytes; ++$i) {
|
|
$byte = ord($serialized[$i]);
|
|
for ($j = 0; $j < 8; ++$j) {
|
|
$k = $i * 8 + $j;
|
|
if ($k >= $n) {
|
|
break;
|
|
}
|
|
$result[$k] = isBitSet($byte, $j);
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function packU24LE(int $value) {
|
|
return substr(pack("V", $value), 0, 3);
|
|
}
|
|
|
|
function unpackU24LE(string $parts) {
|
|
return array_values(unpack("V", $parts . "\x00"))[0];
|
|
}
|
|
|
|
function alignSerialized(string $data, int $alignment) {
|
|
$n = strlen($data);
|
|
if ($n % $alignment) {
|
|
$target = intdiv($n + $alignment - 1, $alignment) * $alignment;
|
|
$remaining = $target - $n;
|
|
$data .= str_repeat("\x00", $remaining);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
abstract class Section {
|
|
abstract public function sectionType(): SectionType;
|
|
abstract public function minimumFormatVersion(): int;
|
|
abstract public function serializeBinary(int $formatVersion): string;
|
|
abstract public function serializeJSON(int $formatVersion): array;
|
|
}
|
|
|
|
abstract class Element {
|
|
abstract public function elementType(): ElementType;
|
|
abstract public function minimumFormatVersion(): int;
|
|
abstract public function serializeBinary(int $formatVersion): string;
|
|
abstract public function serializeJSON(int $formatVersion): array;
|
|
}
|
|
|
|
class ImageElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private array $m_image = [];
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function image(): array {
|
|
return $this->m_image;
|
|
}
|
|
|
|
public function setImage(array $image) {
|
|
$n = $this->m_width * $this->m_height;
|
|
if (count($image) != $n) {
|
|
$actual = count($image);
|
|
throw new \ValueError("Cannot update the image of an ImageElement: the number of pixels supplied, $actual, is not the expected number, $n");
|
|
}
|
|
$this->m_image = $image;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$n = $width * $height;
|
|
$this->m_image = array_fill(0, $n, false);
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::Image;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 1;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvv",
|
|
ElementType::Image->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height,
|
|
0);
|
|
$result .= serializePixels($this->m_image);
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::Image->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
$result["image"] = $this->m_image;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): ImageElement {
|
|
[$type, $x, $y, $width, $height, $reserved] = array_values(unpack("v6", substr($data, 0, 12)));
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::Image) {
|
|
throw new \ValueError("Could not unserialize an image element: type mismatch");
|
|
}
|
|
$n = $width * $height;
|
|
$nBytes = intdiv($n + 7, 8);
|
|
$nAligned = intdiv($nBytes + 3, 4) * 4;
|
|
$pixels = unserializePixels($width * $height, substr($data, 12, $nBytes));
|
|
$result = new ImageElement($x, $y, $width, $height);
|
|
$result->setImage($pixels);
|
|
$data = substr($data, 12 + $nAligned);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): ImageElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::Image) {
|
|
throw new \ValueError("Could not unserialize an image element: type mismatch");
|
|
}
|
|
$result = new ImageElement($element["x"], $element["y"], $element["width"], $element["height"]);
|
|
$result->setImage($element["image"]);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class AnimationElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private int $m_updateInterval = 0;
|
|
private array $m_frames = [];
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function numberOfFrames(): int {
|
|
return count($this->m_frames);
|
|
}
|
|
|
|
public function updateInterval(): int {
|
|
return $this->m_updateInterval;
|
|
}
|
|
|
|
public function image(int $n): array {
|
|
if ($n >= count($this->m_frames)) {
|
|
$max = count($this->m_frames) - 1;
|
|
throw new \ValueError("Cannot retrieve a frame of an AnimationElement: the frame index $n is not in the range [0..$max]");
|
|
}
|
|
return $this->m_frames[$n];
|
|
}
|
|
|
|
public function setImage(int $n, array $image) {
|
|
if ($n >= count($this->m_frames)) {
|
|
$max = count($this->m_frames) - 1;
|
|
throw new \ValueError("Cannot retrieve a frame of an AnimationElement: the frame index $n is not in the range [0..$max]");
|
|
}
|
|
$nPixels = $this->m_width * $this->m_height;
|
|
if (count($image) != $nPixels) {
|
|
$actual = count($image);
|
|
throw new \ValueError("Cannot update a frame of an AnimationElement: the number of pixels supplied, $actual, is not the expected number, $nPixels");
|
|
}
|
|
$this->m_frames[$n] = $image;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, int $numberOfFrames, int $updateInterval) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_updateInterval = $updateInterval;
|
|
$n = $width * $height;
|
|
for ($i = 0; $i < $numberOfFrames; ++$i) {
|
|
array_push($this->m_frames, array_fill(0, $n, false));
|
|
}
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::Animation;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 1;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvvvv",
|
|
ElementType::Animation->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height,
|
|
count($this->m_frames),
|
|
$this->m_updateInterval,
|
|
0);
|
|
foreach ($this->m_frames as $frame) {
|
|
$result .= serializePixels($frame);
|
|
}
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::Animation->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
$result["updateInterval"] = $this->m_updateInterval;
|
|
$result["frames"] = $this->m_frames;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): AnimationElement {
|
|
[$type, $x, $y, $width, $height, $nFrames, $updateInterval, $reserved] = array_values(unpack("v8", substr($data, 0, 16)));
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::Animation) {
|
|
throw new \ValueError("Could not unserialize an animation element: type mismatch");
|
|
}
|
|
$n = $width * $height;
|
|
$nBytes = intdiv($n + 7, 8);
|
|
$nAligned = intdiv($nBytes * $nFrames + 3, 4) * 4;
|
|
$frames = [];
|
|
$result = new AnimationElement($x, $y, $width, $height, $nFrames, $updateInterval);
|
|
for ($i = 0; $i < $nFrames; ++$i) {
|
|
$pixels = unserializePixels($width * $height, substr($data, 16 + $i * $nBytes, $nBytes));
|
|
$result->setImage($i, $pixels);
|
|
}
|
|
$data = substr($data, 16 + $nAligned);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): AnimationElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::Animation) {
|
|
throw new \ValueError("Could not unserialize an animation element: type mismatch");
|
|
}
|
|
$result = new AnimationElement($element["x"], $element["y"], $element["width"], $element["height"], count($element["frames"]), $element["updateInterval"]);
|
|
for ($i = 0; $i < count($element["frames"]); ++$i) {
|
|
$result->setImage($i, $element["frames"][$i]);
|
|
}
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class HScrollImageElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private int $m_contentWidth = 0;
|
|
private ScrollFlags $m_flags;
|
|
private int $m_scrollSpeed = 0;
|
|
private array $m_image = [];
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function contentWidth(): int {
|
|
return $this->m_contentWidth;
|
|
}
|
|
|
|
public function flags(): ScrollFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function scrollSpeed(): int {
|
|
return $this->m_scrollSpeed;
|
|
}
|
|
|
|
public function image(): array {
|
|
return $this->m_image;
|
|
}
|
|
|
|
public function setImage(array $image) {
|
|
$n = $this->m_contentWidth * $this->m_height;
|
|
if (count($image) != $n) {
|
|
$actual = count($image);
|
|
throw new \ValueError("Cannot update the image of an HScrollImageElement: the number of pixels supplied, $actual, is not the expected number, $n");
|
|
}
|
|
$this->m_image = $image;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, int $contentWidth, ScrollFlags $flags, int $scrollSpeed) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_contentWidth = $contentWidth;
|
|
$this->m_flags = $flags;
|
|
$this->m_scrollSpeed = $scrollSpeed;
|
|
$n = $contentWidth * $height;
|
|
$this->m_image = array_fill(0, $n, false);
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::HScrollImage;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 1;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvvCCv",
|
|
ElementType::HScrollImage->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height,
|
|
$this->m_contentWidth,
|
|
$this->m_flags->toBitField(),
|
|
$this->m_scrollSpeed,
|
|
0);
|
|
$result .= serializePixels($this->m_image);
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::HScrollImage->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
$result["contentWidth"] = $this->m_contentWidth;
|
|
$result["flags"] = $this->m_flags->toJSON();
|
|
$result["scrollSpeed"] = $this->m_scrollSpeed;
|
|
$result["image"] = $this->m_image;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): HScrollImageElement {
|
|
[$type, $x, $y, $width, $height, $contentWidth] = array_values(unpack("v6", substr($data, 0, 12)));
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::HScrollImage) {
|
|
throw new \ValueError("Could not unserialize a horizontally scrolling image element: type mismatch");
|
|
}
|
|
[$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 12, 2)));
|
|
$flags = TextFlags::fromBitField($flags);
|
|
$n = $contentWidth * $height;
|
|
$nBytes = intdiv($n + 7, 8);
|
|
$nAligned = intdiv($nBytes + 3, 4) * 4;
|
|
$pixels = unserializePixels($n, substr($data, 16, $nBytes));
|
|
$result = new HScrollImageElement($x, $y, $width, $height, $contentWidth, $flags, $scrollSpeed);
|
|
$result->setImage($pixels);
|
|
$data = substr($data, 16 + $nAligned);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): HScrollImageElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::HScrollImage) {
|
|
throw new \ValueError("Could not unserialize a horizontally scrolling image element: type mismatch");
|
|
}
|
|
$result = new HScrollImageElement($element["x"], $element["y"], $element["width"], $element["height"], $element["contentWidth"],
|
|
ScrollFlags::fromJSON($element["flags"]), $element["scrollSpeed"]);
|
|
$result->setImage($element["image"]);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class VScrollImageElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private int $m_contentHeight = 0;
|
|
private ScrollFlags $m_flags;
|
|
private int $m_scrollSpeed = 0;
|
|
private array $m_image = [];
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function contentHeight(): int {
|
|
return $this->m_contentHeight;
|
|
}
|
|
|
|
public function flags(): ScrollFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function scrollSpeed(): int {
|
|
return $this->m_scrollSpeed;
|
|
}
|
|
|
|
public function image(): array {
|
|
return $this->m_image;
|
|
}
|
|
|
|
public function setImage(array $image) {
|
|
$n = $this->m_width * $this->m_contentHeight;
|
|
if (count($image) != $n) {
|
|
$actual = count($image);
|
|
throw new \ValueError("Cannot update the image of a VScrollImageElement: the number of pixels supplied, $actual, is not the expected number, $n");
|
|
}
|
|
$this->m_image = $image;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, int $contentHeight, ScrollFlags $flags, int $scrollSpeed) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_contentHeight = $contentHeight;
|
|
$this->m_flags = $flags;
|
|
$this->m_scrollSpeed = $scrollSpeed;
|
|
$n = $width * $contentHeight;
|
|
$this->m_image = array_fill(0, $n, false);
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::VScrollImage;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 1;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvvCCv",
|
|
ElementType::VScrollImage->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height,
|
|
$this->m_contentHeight,
|
|
$this->m_flags->toBitField(),
|
|
$this->m_scrollSpeed,
|
|
0);
|
|
$result .= serializePixels($this->m_image);
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::HScrollImage->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
$result["contentHeight"] = $this->m_contentHeight;
|
|
$result["flags"] = $this->m_flags->toJSON();
|
|
$result["scrollSpeed"] = $this->m_scrollSpeed;
|
|
$result["image"] = $this->m_image;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): VScrollImageElement {
|
|
[$type, $x, $y, $width, $height, $contentHeight] = array_values(unpack("v6", substr($data, 0, 12)));
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::VScrollImage) {
|
|
throw new \ValueError("Could not unserialize a vertically scrolling image element: type mismatch");
|
|
}
|
|
[$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 12, 2)));
|
|
$flags = TextFlags::fromBitField($flags);
|
|
$n = $width * $contentHeight;
|
|
$nBytes = intdiv($n + 7, 8);
|
|
$nAligned = intdiv($nBytes + 3, 4) * 4;
|
|
$pixels = unserializePixels($n, substr($data, 16, $nBytes));
|
|
$result = new VScrollImageElement($x, $y, $width, $height, $contentHeight, $flags, $scrollSpeed);
|
|
$result->setImage($pixels);
|
|
$data = substr($data, 16 + $nAligned);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): VScrollImageElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::VScrollImage) {
|
|
throw new \ValueError("Could not unserialize a vertically scrolling image element: type mismatch");
|
|
}
|
|
$result = new VScrollImageElement($element["x"], $element["y"], $element["width"], $element["height"], $element["contentHeight"],
|
|
ScrollFlags::fromJSON($element["flags"]), $element["scrollSpeed"]);
|
|
$result->setImage($element["image"]);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class LineElement extends Element {
|
|
private int $m_originX = 0;
|
|
private int $m_orignY = 0;
|
|
private int $m_targetX = 0;
|
|
private int $m_targetY = 0;
|
|
private LineStyle $m_lineStyle = LineStyle::Solid;
|
|
private LineFlags $m_flags;
|
|
|
|
public function originX(): int {
|
|
return $this->m_originX;
|
|
}
|
|
|
|
public function originY(): int {
|
|
return $this->m_originY;
|
|
}
|
|
|
|
public function targetX(): int {
|
|
return $this->m_targetX;
|
|
}
|
|
|
|
public function targetY(): int {
|
|
return $this->m_targetY;
|
|
}
|
|
|
|
public function lineStyle(): LineStyle {
|
|
return $this->m_lineStyle;
|
|
}
|
|
|
|
public function flags(): LineFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function __construct(int $originX, int $originY, int $targetX, int $targetY, LineStyle $lineStyle, LineFlags $flags) {
|
|
$this->m_originX = $originX;
|
|
$this->m_originY = $originY;
|
|
$this->m_targetX = $targetX;
|
|
$this->m_targetY = $targetY;
|
|
$this->m_lineStyle = $lineStyle;
|
|
$this->m_flags = $flags;
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::Line;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
if ($this->m_flags->toBitField() != 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvCC",
|
|
ElementType::Line->value,
|
|
$this->m_originX,
|
|
$this->m_originY,
|
|
$this->m_targetX,
|
|
$this->m_targetY,
|
|
$this->m_lineStyle->value,
|
|
$this->m_flags->toBitField());
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::Line->name;
|
|
$result["originX"] = $this->m_originX;
|
|
$result["originY"] = $this->m_originY;
|
|
$result["targetX"] = $this->m_targetX;
|
|
$result["targetY"] = $this->m_targetY;
|
|
$result["lineStyle"] = $this->m_lineStyle->toJSON();
|
|
$result["flags"] = $this->m_flags->toJSON();
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): LineElement {
|
|
[$type, $originX, $originY, $targetX, $targetY] = array_values(unpack("v5", substr($data, 0, 10)));
|
|
$data = substr($data, 10);
|
|
$pos = 10;
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::Line) {
|
|
throw new \ValueError("Could not unserialize a line element: type mismatch");
|
|
}
|
|
[$lineStyle, $flags] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
$lineStyle = LineStyle::from($lineStyle);
|
|
$flags = LineFlags::fromBitField($flags);
|
|
$alignedPos = intdiv($pos + 3, 4) * 4;
|
|
$data = substr($data, $alignedPos - $pos);
|
|
|
|
$result = new LineElement($originX, $originY, $targetX, $targetY, $lineStyle, $flags);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): LineElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::Line) {
|
|
throw new \ValueError("Could not unserialize a line element: type mismatch");
|
|
}
|
|
$flags = new LineFlags();
|
|
$lineStyle = LineStyle::fromName($element["lineStyle"]);
|
|
if ($formatVersion >= 2) {
|
|
$flags = LineFlags::fromJSON($element["flags"]);
|
|
}
|
|
$result = new LineElement($element["originX"], $element["originY"], $element["targetX"], $element["targetY"], $lineStyle, $flags);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class BoxElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private FillPattern $m_fillPattern = FillPattern::Solid;
|
|
private FillFlags $m_flags;
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function fillPattern(): FillPattern {
|
|
return $this->m_fillPattern;
|
|
}
|
|
|
|
public function flags(): FillFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, FillPattern $fillPattern, FillFlags $flags) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_fillPattern = $fillPattern;
|
|
$this->m_flags = $flags;
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::Box;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 2;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvvCC",
|
|
ElementType::Box->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height,
|
|
$this->m_fillPattern->value,
|
|
$this->m_flags->toBitField());
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::Box->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
$result["fillPattern"] = $this->m_fillPattern->toJSON();
|
|
$result["flags"] = $this->m_flags->toJSON();
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): BoxElement {
|
|
[$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10)));
|
|
$data = substr($data, 10);
|
|
$pos = 10;
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::Box) {
|
|
throw new \ValueError("Could not unserialize a box element: type mismatch");
|
|
}
|
|
[$fillPattern, $flags] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
$fillPattern = FillPattern::from($fillPattern);
|
|
$flags = FillFlags::fromBitField($flags);
|
|
|
|
$alignedPos = intdiv($pos + $len + 3, 4) * 4;
|
|
$data = substr($data, $alignedPos - $pos);
|
|
|
|
$result = new BoxElement($x, $y, $width, $height, $fillPattern, $flags);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): BoxElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::Box) {
|
|
throw new \ValueError("Could not unserialize a box element: type mismatch");
|
|
}
|
|
$fillPattern = FillPattern::fromName($element["fillPattern"]);
|
|
$flags = FillFlags::fromJSON($element["flags"]);
|
|
$result = new BoxElement($element["x"], $element["y"], $element["width"], $element["height"], $fillPattern, $flags);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class ClippedTextElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private TextFlags $m_textFlags;
|
|
private int $m_fontIndex = 0;
|
|
private string $m_text = "";
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function textFlags(): TextFlags {
|
|
return $this->m_textFlags;
|
|
}
|
|
|
|
public function fontIndex(): int {
|
|
return $this->m_fontIndex;
|
|
}
|
|
|
|
public function text(): string {
|
|
return $this->m_text;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, int $fontIndex, string $text) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_textFlags = $textFlags;
|
|
$this->m_fontIndex = $fontIndex;
|
|
$this->m_text = $text;
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::ClippedText;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
if ($this->m_textFlags->toBitField() != 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvv",
|
|
ElementType::ClippedText->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height);
|
|
if ($formatVersion >= 2) {
|
|
$result .= pack("CC",
|
|
$this->m_textFlags->toBitField(),
|
|
0);
|
|
}
|
|
$result .= pack("vv",
|
|
$this->m_fontIndex,
|
|
strlen($this->m_text));
|
|
$result .= $this->m_text;
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::ClippedText->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
if ($formatVersion >= 2) {
|
|
$result["textFlags"] = $this->m_textFlags->toJSON();
|
|
}
|
|
$result["fontIndex"] = $this->m_fontIndex;
|
|
$result["text"] = $this->m_text;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): ClippedTextElement {
|
|
[$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10)));
|
|
$data = substr($data, 10);
|
|
$pos = 10;
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::ClippedText) {
|
|
throw new \ValueError("Could not unserialize a clipped text element: type mismatch");
|
|
}
|
|
$textFlags = 0;
|
|
if ($formatVersion >= 2) {
|
|
[$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
}
|
|
$textFlags = TextFlags::fromBitField($textFlags);
|
|
[$fontIndex, $len] = array_values(unpack("v2", substr($data, 0, 4)));
|
|
$data = substr($data, 4);
|
|
$text = substr($data, 0, $len);
|
|
$pos += 4;
|
|
$alignedPos = intdiv($pos + $len + 3, 4) * 4;
|
|
$data = substr($data, $alignedPos - $pos);
|
|
|
|
$result = new ClippedTextElement($x, $y, $width, $height, $textFlags, $fontIndex, $text);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): ClippedTextElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::ClippedText) {
|
|
throw new \ValueError("Could not unserialize a clipped text element: type mismatch");
|
|
}
|
|
$textFlags = new TextFlags();
|
|
if ($formatVersion >= 2) {
|
|
$textFlags = TextFlags::fromJSON($element["textFlags"]);
|
|
}
|
|
$result = new ClippedTextElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $element["fontIndex"], $element["text"]);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class HScrollTextElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private TextFlags $m_textFlags;
|
|
private ScrollFlags $m_scrollFlags;
|
|
private int $m_scrollSpeed = 0;
|
|
private int $m_fontIndex = 0;
|
|
private string $m_text = "";
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function textFlags(): TextFlags {
|
|
return $this->m_textFlags;
|
|
}
|
|
|
|
public function flags(): ScrollFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function scrollSpeed(): int {
|
|
return $this->m_scrollSpeed;
|
|
}
|
|
|
|
public function fontIndex(): int {
|
|
return $this->m_fontIndex;
|
|
}
|
|
|
|
public function text(): string {
|
|
return $this->m_text;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, ScrollFlags $flags, int $scrollSpeed, int $fontIndex, string $text) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_textFlags = $textFlags;
|
|
$this->m_flags = $flags;
|
|
$this->m_scrollSpeed = $scrollSpeed;
|
|
$this->m_fontIndex = $fontIndex;
|
|
$this->m_text = $text;
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::HScrollText;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
if ($this->m_textFlags->toBitField() != 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvv",
|
|
ElementType::HScrollText->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height);
|
|
if ($formatVersion >= 2) {
|
|
$result .= pack("CC",
|
|
$this->m_textFlags->toBitField(),
|
|
0);
|
|
}
|
|
$result .= pack("CC",
|
|
$this->m_flags->toBitField(),
|
|
$this->m_scrollSpeed);
|
|
if ($formatVersion >= 2) {
|
|
$result .= pack("v", 0);
|
|
}
|
|
$result .= pack("vv",
|
|
$this->m_fontIndex,
|
|
strlen($this->m_text));
|
|
$result .= $this->m_text;
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::HScrollText->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
if ($formatVersion >= 2) {
|
|
$result["textFlags"] = $this->m_textFlags->toJSON();
|
|
}
|
|
$result["flags"] = $this->m_flags;
|
|
$result["scrollSpeed"] = $this->m_scrollSpeed;
|
|
$result["fontIndex"] = $this->m_fontIndex;
|
|
$result["text"] = $this->m_text;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): HScrollTextElement {
|
|
[$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10)));
|
|
$data = substr($data, 10);
|
|
$pos = 10;
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::HScrollText) {
|
|
throw new \ValueError("Could not unserialize a clipped text element: type mismatch");
|
|
}
|
|
$textFlags = 0;
|
|
if ($formatVersion >= 2) {
|
|
[$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
}
|
|
$textFlags = TextFlags::fromBitField($textFlags);
|
|
[$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$flags = ScrollFlags::fromBitField($flags);
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
if ($formatVersion >= 2) {
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
}
|
|
[$fontIndex, $len] = array_values(unpack("v2", substr($data, 0, 4)));
|
|
$data = substr($data, 4);
|
|
$text = substr($data, 0, $len);
|
|
$pos += 4;
|
|
$alignedPos = intdiv($pos + $len + 3, 4) * 4;
|
|
$data = substr($data, $alignedPos - $pos);
|
|
|
|
$result = new HScrollTextElement($x, $y, $width, $height, $textFlags, $flags, $scrollSpeed, $fontIndex, $text);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): HScrollTextElement {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::HScrollText) {
|
|
throw new \ValueError("Could not unserialize a horizontally scrolling text element: type mismatch");
|
|
}
|
|
$textFlags = new TextFlags();
|
|
if ($formatVersion >= 2) {
|
|
$textFlags = TextFlags::fromJSON($element["textFlags"]);
|
|
}
|
|
$flags = ScrollFlags::fromJSON($element["flags"]);
|
|
$result = new HScrollTextElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $flags, $element["scrollSpeed"], $element["fontIndex"], $element["text"]);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class CurrentTimeElement extends Element {
|
|
private int $m_x = 0;
|
|
private int $m_y = 0;
|
|
private int $m_width = 0;
|
|
private int $m_height = 0;
|
|
private TextFlags $m_textFlags;
|
|
private int $m_fontIndex = 0;
|
|
private int $m_utcOffset = 0;
|
|
private TimeDisplayFlags $m_flags;
|
|
|
|
public function x(): int {
|
|
return $this->m_x;
|
|
}
|
|
|
|
public function y(): int {
|
|
return $this->m_y;
|
|
}
|
|
|
|
public function width(): int {
|
|
return $this->m_width;
|
|
}
|
|
|
|
public function height(): int {
|
|
return $this->m_height;
|
|
}
|
|
|
|
public function textFlags(): TextFlags {
|
|
return $this->m_textFlags;
|
|
}
|
|
|
|
public function fontIndex(): int {
|
|
return $this->m_fontIndex;
|
|
}
|
|
|
|
public function utcOffset(): int {
|
|
return $this->m_utcOffset;
|
|
}
|
|
|
|
public function flags(): TimeDisplayFlags {
|
|
return $this->m_flags;
|
|
}
|
|
|
|
public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, int $fontIndex, int $utcOffset, TimeDisplayFlags $flags) {
|
|
$this->m_x = $x;
|
|
$this->m_y = $y;
|
|
$this->m_width = $width;
|
|
$this->m_height = $height;
|
|
$this->m_textFlags = $textFlags;
|
|
$this->m_fontIndex = $fontIndex;
|
|
$this->m_utcOffset = $utcOffset;
|
|
$this->m_flags = $flags;
|
|
}
|
|
|
|
public function elementType(): ElementType {
|
|
return ElementType::CurrentTime;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
if ($this->m_textFlags->toBitField() != 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("vvvvv",
|
|
ElementType::CurrentTime->value,
|
|
$this->m_x,
|
|
$this->m_y,
|
|
$this->m_width,
|
|
$this->m_height);
|
|
if ($formatVersion >= 2) {
|
|
$result .= pack("CC",
|
|
$this->m_textFlags->toBitField(),
|
|
0);
|
|
}
|
|
$result .= pack("vvv",
|
|
$this->m_fontIndex,
|
|
$this->m_utcOffset,
|
|
$this->m_flags->toBitField());
|
|
if ($formatVersion >= 2) {
|
|
$result .= pack("v", 0);
|
|
}
|
|
return alignSerialized($result, 4);
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = ElementType::CurrentTime->name;
|
|
$result["x"] = $this->m_x;
|
|
$result["y"] = $this->m_y;
|
|
$result["width"] = $this->m_width;
|
|
$result["height"] = $this->m_height;
|
|
if ($formatVersion >= 2) {
|
|
$result["textFlags"] = $this->m_textFlags->toJSON();
|
|
}
|
|
$result["fontIndex"] = $this->m_fontIndex;
|
|
$result["utcOffset"] = $this->m_utcOffset;
|
|
$result["flags"] = $this->m_flags->toJSON();
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string &$data, int $formatVersion): CurrentTimeElement {
|
|
[$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10)));
|
|
$data = substr($data, 10);
|
|
$pos = 10;
|
|
$type = ElementType::from($type);
|
|
if ($type != ElementType::CurrentTime) {
|
|
throw new \ValueError("Could not unserialize a current time element: type mismatch");
|
|
}
|
|
$textFlags = 0;
|
|
if ($formatVersion >= 2) {
|
|
[$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2)));
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
}
|
|
$textFlags = TextFlags::fromBitField($textFlags);
|
|
[$fontIndex, $utcOffset, $flags] = array_values(unpack("v3", substr($data, 0, 6)));
|
|
$flags = TimeDisplayFlags::fromBitField($flags);
|
|
$data = substr($data, 6);
|
|
$pos += 6;
|
|
if ($formatVersion >= 2) {
|
|
$data = substr($data, 2);
|
|
$pos += 2;
|
|
}
|
|
$alignedPos = intdiv($pos + 3, 4) * 4;
|
|
$data = substr($data, $alignedPos - $pos);
|
|
|
|
$result = new CurrentTimeElement($x, $y, $width, $height, $textFlags, $fontIndex, $utcOffset, $flags);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $element, int $formatVersion): CurrentTime {
|
|
$type = ElementType::fromString($element["type"]);
|
|
if ($type != ElementType::CurrentTime) {
|
|
throw new \ValueError("Could not unserialize a current time element: type mismatch");
|
|
}
|
|
$textFlags = new TextFlags();
|
|
if ($formatVersion >= 2) {
|
|
$textFlags = TextFlags::fromJSON($element["textFlags"]);
|
|
}
|
|
$flags = TimeDisplayFlags::fromJSON($element["flags"]);
|
|
$result = new CurrentTimeElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $element["fontIndex"], $element["utcOffset"], $flags);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
abstract class ElementContainingSection extends Section {
|
|
protected array $m_elements = [];
|
|
|
|
public function elementCount(): int {
|
|
return count($this->m_elements);
|
|
}
|
|
|
|
public function elementAt(int $i) {
|
|
if ($i >= count($this->m_elements)) {
|
|
$max = count($this->m_elements) - 1;
|
|
throw new \IndexError("Index $i out of range [0 ..$max]");
|
|
}
|
|
return $this->m_elements[$i];
|
|
}
|
|
|
|
public function appendElement(Element $element) {
|
|
array_push($this->m_elements, $element);
|
|
}
|
|
|
|
public function insertElement(int $index, Element $element) {
|
|
if ($index < count($this->m_elements)) {
|
|
array_splice($this->m_elements, $index, 0, $element);
|
|
} else {
|
|
array_push($this->m_elements, $element);
|
|
}
|
|
}
|
|
|
|
public function replaceElement(int $index, Element $element): Element {
|
|
$result = null;
|
|
if ($index < count($this->m_elements)) {
|
|
$result = $this->m_elements[$index];
|
|
$this->m_elements[$index] = $element;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function eraseElement(int $index): Element {
|
|
$result = null;
|
|
if ($index < count($this->m_elements)) {
|
|
$result = $this->m_elements[$index];
|
|
array_splice($this->m_elements, $index, 1);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
protected static function parseElementsBinary(string &$elementData, int $nElements, int $formatVersion): array {
|
|
$elements = [];
|
|
for ($i = 0; $i < $nElements; ++$i) {
|
|
$type = ElementType::from(unpack("v", substr($elementData, 0, 2))[1]);
|
|
$element = null;
|
|
switch ($type) {
|
|
case ElementType::Image: $element = ImageElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::Animation: $element = AnimationElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::HScrollImage: $element = HScrollImageElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::VScrollImage: $element = VScrollImageElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::Line: $element = LineElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::Box: $element = BoxElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::ClippedText: $element = ClippedTextElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::HScrollText: $element = HScrollTextElement::parseBinary($elementData, $formatVersion); break;
|
|
case ElementType::CurrentTime: $element = CurrenttimeElement::parseBinary($elementData, $formatVersion); break;
|
|
default: throw new \ValueError("Unsupported element type " . $type->name . " encountered in file");
|
|
}
|
|
array_push($elements, $element);
|
|
}
|
|
return $elements;
|
|
}
|
|
|
|
protected static function parseElementsJSON(array $elementData, int $formatVersion): array {
|
|
$elements = [];
|
|
foreach ($elementData as $serializedElement) {
|
|
$type = ElementType::fromName($serializedElement["type"]);
|
|
$element = null;
|
|
switch ($type) {
|
|
case ElementType::Image: $element = ImageElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::Animation: $element = AnimationElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::HScrollImage: $element = HScrollImageElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::VScrollImage: $element = VScrollImageElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::Line: $element = LineElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::Box: $element = BoxElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::ClippedText: $element = ClippedTextElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::HScrollText: $element = HScrollTextElement::parseJSON($serializedElement, $formatVersion); break;
|
|
case ElementType::CurrentTime: $element = CurrenttimeElement::parseJSON($serializedElement, $formatVersion); break;
|
|
default: throw new \ValueError("Unsupported element type " . $type->name . " encountered in file");
|
|
}
|
|
array_push($elements, $element);
|
|
}
|
|
return $elements;
|
|
}
|
|
}
|
|
|
|
class AlwaysDrawnSection extends ElementContainingSection {
|
|
private bool $m_drawOnFront = true;
|
|
private bool $m_drawOnBack = true;
|
|
private bool $m_clearBeforeDrawing = true;
|
|
|
|
public function doesDrawOnFront(): bool {
|
|
return $this->m_drawOnFront;
|
|
}
|
|
|
|
public function doesDrawOnBack(): bool {
|
|
return $this->m_drawOnBack;
|
|
}
|
|
|
|
public function doesClearBeforeDrawing(): bool {
|
|
return $this->m_clearBeforeDrawing;
|
|
}
|
|
|
|
public function setDrawOnFront(bool $value) {
|
|
$this->m_drawOnFront = $value;
|
|
}
|
|
|
|
public function setDrawOnBack(bool $value) {
|
|
$this->m_drawOnBack = $value;
|
|
}
|
|
|
|
public function setClearBeforeDrawing(bool $value) {
|
|
$this->m_clearBeforeDrawing = $value;
|
|
}
|
|
|
|
public function sectionType(): SectionType {
|
|
return SectionType::AlwaysDrawn;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
$result = 1;
|
|
foreach ($this->m_elements as $element) {
|
|
$result = max($result, $element->minimumFormatVersion());
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("C", SectionType::AlwaysDrawn->value);
|
|
// Will be replaced later
|
|
$result .= packU24LE(0);
|
|
$flags = 0;
|
|
maybeSetBit($flags, 0, $this->m_drawOnFront);
|
|
maybeSetBit($flags, 1, $this->m_drawOnBack);
|
|
maybeSetBit($flags, 2, $this->m_clearBeforeDrawing);
|
|
$result .= pack("vv", $flags, count($this->m_elements));
|
|
foreach ($this->m_elements as $element) {
|
|
$result .= $element->serializeBinary($formatVersion);
|
|
}
|
|
$result = alignSerialized($result, 4);
|
|
$len = strlen($result);
|
|
$len = packU24LE($len);
|
|
$result[1] = $len[0];
|
|
$result[2] = $len[1];
|
|
$result[3] = $len[2];
|
|
return $result;
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = SectionType::AlwaysDrawn->name;
|
|
$result["flags"] = [];
|
|
$result["flags"]["drawOnFront"] = $this->m_drawOnFront;
|
|
$result["flags"]["drawOnBack"] = $this->m_drawOnBack;
|
|
$result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing;
|
|
$result["elements"] = [];
|
|
foreach ($this->m_elements as $element) {
|
|
array_push($result["elements"], $element->serializeJSON($formatVersion));
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string $section, int $formatVersion): AlwaysDrawnSection {
|
|
$type = SectionType::from(unpack("C", substr($section, 0, 1))[1]);
|
|
if ($type != SectionType::AlwaysDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a AlwaysDrawnSection");
|
|
}
|
|
$len = unpackU24LE(substr($section, 1, 3));
|
|
[$flags, $nElements] = array_values(unpack("v2", substr($section, 4, 4)));
|
|
$elementData = substr($section, 8);
|
|
$elements = static::parseElementsBinary($elementData, $nElements, $formatVersion);
|
|
$result = new AlwaysDrawnSection();
|
|
$result->m_drawOnFront = isBitSet($flags, 0);
|
|
$result->m_drawOnBack = isBitSet($flags, 1);
|
|
$result->m_clearBeforeDrawing = isBitSet($flags, 2);
|
|
$result->m_elements = $elements;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $section, int $formatVersion): AlwaysDrawnSection {
|
|
$type = SectionType::fromString($section["type"]);
|
|
if ($type != SectionType::AlwaysDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a AlwaysDrawnSection");
|
|
}
|
|
$drawOnFront = $section["flags"]["drawOnFront"];
|
|
$drawOnBack = $section["flags"]["drawOnBack"];
|
|
$clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"];
|
|
$elements = static::parseElementsJSON($section["elements"], $formatVersion);
|
|
$result = new AlwaysDrawnSection();
|
|
$result->m_drawOnFront = $drawOnFront;
|
|
$result->m_drawOnBack = $drawOnBack;
|
|
$result->m_clearBeforeDrawing = $clearBeforeDrawing;
|
|
$result->m_elements = $elements;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class TimeBasedDrawnSection extends ElementContainingSection {
|
|
private int $m_startTimestamp = 0;
|
|
private int $m_endTimestamp = 0;
|
|
private bool $m_drawOnFront = true;
|
|
private bool $m_drawOnBack = true;
|
|
private bool $m_clearBeforeDrawing = true;
|
|
|
|
public function doesDrawOnFront(): bool {
|
|
return $this->m_drawOnFront;
|
|
}
|
|
|
|
public function doesDrawOnBack(): bool {
|
|
return $this->m_drawOnBack;
|
|
}
|
|
|
|
public function doesClearBeforeDrawing(): bool {
|
|
return $this->m_clearBeforeDrawing;
|
|
}
|
|
|
|
public function setDrawOnFront(bool $value) {
|
|
$this->m_drawOnFront = $value;
|
|
}
|
|
|
|
public function setDrawOnBack(bool $value) {
|
|
$this->m_drawOnBack = $value;
|
|
}
|
|
|
|
public function setClearBeforeDrawing(bool $value) {
|
|
$this->m_clearBeforeDrawing = $value;
|
|
}
|
|
|
|
public function startTimestamp(): int {
|
|
return $this->m_startTimestamp;
|
|
}
|
|
|
|
public function endTimestamp(): int {
|
|
return $this->m_endTimestamp;
|
|
}
|
|
|
|
public function setStartTimestamp(int $value) {
|
|
$this->m_startTimestamp = $value;
|
|
}
|
|
|
|
public function setEndTimestamp(int $value) {
|
|
$this->m_endTimestamp = $value;
|
|
}
|
|
|
|
public function sectionType(): SectionType {
|
|
return SectionType::TimeBasedDrawn;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
$result = 1;
|
|
foreach ($this->m_elements as $element) {
|
|
$result = max($result, $element->minimumFormatVersion());
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("C", SectionType::TimeBasedDrawn->value);
|
|
// Will be replaced later
|
|
$result .= packU24LE(0);
|
|
$flags = 0;
|
|
maybeSetBit($flags, 0, $this->m_drawOnFront);
|
|
maybeSetBit($flags, 1, $this->m_drawOnBack);
|
|
maybeSetBit($flags, 2, $this->m_clearBeforeDrawing);
|
|
$result .= pack("vv", $flags, count($this->m_elements));
|
|
$result .= pack("PP", $this->m_startTimestamp, $this->m_endTimestamp);
|
|
foreach ($this->m_elements as $element) {
|
|
$result .= $element->serializeBinary($formatVersion);
|
|
}
|
|
$result = alignSerialized($result, 4);
|
|
$len = strlen($result);
|
|
$len = packU24LE($len);
|
|
$result[1] = $len[0];
|
|
$result[2] = $len[1];
|
|
$result[3] = $len[2];
|
|
return $result;
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = SectionType::TimeBasedDrawn->name;
|
|
$result["flags"] = [];
|
|
$result["flags"]["drawOnFront"] = $this->m_drawOnFront;
|
|
$result["flags"]["drawOnBack"] = $this->m_drawOnBack;
|
|
$result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing;
|
|
$result["startTimestamp"] = $this->m_startTimestamp;
|
|
$result["endTimestamp"] = $this->m_endTimestamp;
|
|
$result["elements"] = [];
|
|
foreach ($this->m_elements as $element) {
|
|
array_push($result["elements"], $element->serializeJSON($formatVersion));
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string $section, int $formatVersion): TimeBasedDrawnSection {
|
|
$type = SectionType::from(unpack("C", substr($section, 0, 1))[1]);
|
|
if ($type != SectionType::TimeBasedDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a TimeBasedDrawnSection");
|
|
}
|
|
$len = unpackU24LE(substr($section, 1, 3));
|
|
[$flags, $nElements] = array_values(unpack("v2", substr($section, 4, 4)));
|
|
[$startTimestamp, $endTimestamp] = array_values(unpack("P2", substr($section, 8, 16)));
|
|
$elementData = substr($section, 24);
|
|
$elements = static::parseElementsBinary($elementData, $nElements, $formatVersion);
|
|
$result = new TimeBasedDrawnSection();
|
|
$result->m_drawOnFront = isBitSet($flags, 0);
|
|
$result->m_drawOnBack = isBitSet($flags, 1);
|
|
$result->m_clearBeforeDrawing = isBitSet($flags, 2);
|
|
$result->m_startTimestamp = $startTimestamp;
|
|
$result->m_endTimestamp = $endTimestamp;
|
|
$result->m_elements = $elements;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $section, int $formatVersion): TimeBasedDrawnSection {
|
|
$type = SectionType::fromString($section["type"]);
|
|
if ($type != SectionType::TimeBasedDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a TimeBasedDrawnSection");
|
|
}
|
|
$drawOnFront = $section["flags"]["drawOnFront"];
|
|
$drawOnBack = $section["flags"]["drawOnBack"];
|
|
$clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"];
|
|
$startTimestamp = $section["startTimestamp"];
|
|
$endTimestamp = $section["endTimestamp"];
|
|
$elements = static::parseElementsJSON($section["elements"], $formatVersion);
|
|
$result = new TimeBasedDrawnSection();
|
|
$result->m_drawOnFront = $drawOnFront;
|
|
$result->m_drawOnBack = $drawOnBack;
|
|
$result->m_clearBeforeDrawing = $clearBeforeDrawing;
|
|
$result->m_startTimestamp = $startTimestamp;
|
|
$result->m_endTimestamp = $endTimestamp;
|
|
$result->m_elements = $elements;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class MultiTimeBasedDrawnSection extends ElementContainingSection {
|
|
private array $m_timeRanges = [];
|
|
private bool $m_drawOnFront = true;
|
|
private bool $m_drawOnBack = true;
|
|
private bool $m_clearBeforeDrawing = true;
|
|
|
|
public function doesDrawOnFront(): bool {
|
|
return $this->m_drawOnFront;
|
|
}
|
|
|
|
public function doesDrawOnBack(): bool {
|
|
return $this->m_drawOnBack;
|
|
}
|
|
|
|
public function doesClearBeforeDrawing(): bool {
|
|
return $this->m_clearBeforeDrawing;
|
|
}
|
|
|
|
public function setDrawOnFront(bool $value) {
|
|
$this->m_drawOnFront = $value;
|
|
}
|
|
|
|
public function setDrawOnBack(bool $value) {
|
|
$this->m_drawOnBack = $value;
|
|
}
|
|
|
|
public function setClearBeforeDrawing(bool $value) {
|
|
$this->m_clearBeforeDrawing = $value;
|
|
}
|
|
|
|
public function timeRanges(): array {
|
|
return $this->m_timeRanges;
|
|
}
|
|
|
|
public function setTimeRanges(array $timeRanges) {
|
|
foreach ($timeRanges as $timeRange) {
|
|
if (!is_array($timeRange) || count($timeRange) != 2 || !isset($timeRange[0]) || !isset($timeRange[1])) {
|
|
throw new \ValueError("Invalid time range list supplied");
|
|
}
|
|
}
|
|
$this->m_timeRanges = $timeRanges;
|
|
}
|
|
|
|
public function sectionType(): SectionType {
|
|
return SectionType::MultiTimeBasedDrawn;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
$result = 2;
|
|
foreach ($this->m_elements as $element) {
|
|
$result = max($result, $element->minimumFormatVersion());
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("C", SectionType::MultiTimeBasedDrawn->value);
|
|
// Will be replaced later
|
|
$result .= packU24LE(0);
|
|
$flags = 0;
|
|
maybeSetBit($flags, 0, $this->m_drawOnFront);
|
|
maybeSetBit($flags, 1, $this->m_drawOnBack);
|
|
maybeSetBit($flags, 2, $this->m_clearBeforeDrawing);
|
|
$result .= pack("vvv", $flags, count($this->m_elements), count($this->m_timeRanges), 0);
|
|
foreach ($this->m_timeRanges as $timeRange) {
|
|
$result .= pack("PP", $timeRange[0], $timeRange[1]);
|
|
}
|
|
foreach ($this->m_elements as $element) {
|
|
$result .= $element->serializeBinary($formatVersion);
|
|
}
|
|
$result = alignSerialized($result, 4);
|
|
$len = strlen($result);
|
|
$len = packU24LE($len);
|
|
$result[1] = $len[0];
|
|
$result[2] = $len[1];
|
|
$result[3] = $len[2];
|
|
return $result;
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = SectionType::TimeBasedDrawn->name;
|
|
$result["flags"] = [];
|
|
$result["flags"]["drawOnFront"] = $this->m_drawOnFront;
|
|
$result["flags"]["drawOnBack"] = $this->m_drawOnBack;
|
|
$result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing;
|
|
$result["timeRanges"] = $this->m_timeRanges;
|
|
$result["elements"] = [];
|
|
foreach ($this->m_elements as $element) {
|
|
array_push($result["elements"], $element->serializeJSON($formatVersion));
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string $section, int $formatVersion): MultiTimeBasedDrawnSection {
|
|
$type = SectionType::from(unpack("C", substr($section, 0, 1))[1]);
|
|
if ($type != SectionType::MultiTimeBasedDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a MultiTimeBasedDrawnSection");
|
|
}
|
|
$len = unpackU24LE(substr($section, 1, 3));
|
|
[$flags, $nElements, $nRanges, $reserved] = array_values(unpack("v4", substr($section, 4, 8)));
|
|
$section = substr($section, 12);
|
|
$timeRanges = [];
|
|
for ($i = 0; $i < $nRanges; ++$i) {
|
|
[$startTimestamp, $endTimestamp] = array_values(unpack("P2", substr($section, 0, 16)));
|
|
$section = substr($section, 16);
|
|
array_push($timeRanges, [$startTimestamp, $endTimestamp]);
|
|
}
|
|
|
|
$elementData = $section;
|
|
$elements = static::parseElementsBinary($elementData, $nElements, $formatVersion);
|
|
$result = new MultiTimeBasedDrawnSection();
|
|
$result->m_drawOnFront = isBitSet($flags, 0);
|
|
$result->m_drawOnBack = isBitSet($flags, 1);
|
|
$result->m_clearBeforeDrawing = isBitSet($flags, 2);
|
|
$result->m_timeRanges = $timeRanges;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $section, int $formatVersion): MultiTimeBasedDrawnSection {
|
|
$type = SectionType::fromString($section["type"]);
|
|
if ($type != SectionType::MultiTimeBasedDrawn) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a MultiTimeBasedDrawnSection");
|
|
}
|
|
$drawOnFront = $section["flags"]["drawOnFront"];
|
|
$drawOnBack = $section["flags"]["drawOnBack"];
|
|
$clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"];
|
|
$timeRanges = $section["timeRanges"];
|
|
$elements = static::parseElementsJSON($section["elements"], $formatVersion);
|
|
$result = new MultiTimeBasedDrawnSection();
|
|
$result->m_drawOnFront = $drawOnFront;
|
|
$result->m_drawOnBack = $drawOnBack;
|
|
$result->m_clearBeforeDrawing = $clearBeforeDrawing;
|
|
// Use setter here to validate the structure
|
|
$result->setTimeRanges($timeRanges);
|
|
$result->m_elements = $elements;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class ExpiryDateSection extends Section {
|
|
private int $m_expiryDate = 0;
|
|
|
|
public function expiryDate(): int {
|
|
return $this->m_expiryDate;
|
|
}
|
|
|
|
public function setExpiryDate(int $value) {
|
|
$this->m_expiryDate = $value;
|
|
}
|
|
|
|
public function sectionType(): SectionType {
|
|
return SectionType::ExpiryDate;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 2;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("C", SectionType::ExpiryDate->value);
|
|
// Will be replaced later
|
|
$result .= packU24LE(0);
|
|
$result .= pack("P", $this->m_expiryDate);
|
|
$result = alignSerialized($result, 4);
|
|
$len = strlen($result);
|
|
$len = packU24LE($len);
|
|
$result[1] = $len[0];
|
|
$result[2] = $len[1];
|
|
$result[3] = $len[2];
|
|
return $result;
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = SectionType::ExpiryDate->name;
|
|
$result["expiryDate"] = $this->m_expiryDate;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string $section, int $formatVersion): ExpiryDateSection {
|
|
$type = SectionType::from(unpack("C", substr($section, 0, 1))[1]);
|
|
if ($type != SectionType::ExpiryDate) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a ExpiryDateSection");
|
|
}
|
|
$len = unpackU24LE(substr($section, 1, 3));
|
|
[$expiryDate] = array_values(unpack("P", substr($section, 4, 8)));
|
|
$result = new ExpiryDateSection();
|
|
$result->m_expiryDate = $expiryDate;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $section, int $formatVersion): ExpiryDateSection {
|
|
$type = SectionType::fromString($section["type"]);
|
|
if ($type != SectionType::ExpiryDate) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a ExpiryDateSection");
|
|
}
|
|
$expiryDate = $section["expiryDate"];
|
|
$result = new ExpiryDateSection();
|
|
$result->m_expiryDate = $expiryDate;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class CustomFontSection extends Section {
|
|
private string $m_fontData = "";
|
|
|
|
public function fontData(): string {
|
|
return $this->m_fontData;
|
|
}
|
|
|
|
public function setFontData(string $value) {
|
|
$this->m_fontData = $value;
|
|
}
|
|
|
|
public function sectionType(): SectionType {
|
|
return SectionType::CustomFont;
|
|
}
|
|
|
|
public function minimumFormatVersion(): int {
|
|
return 1;
|
|
}
|
|
|
|
public function serializeBinary(int $formatVersion): string {
|
|
$result = pack("C", SectionType::CustomFont->value);
|
|
// Will be replaced later
|
|
$result .= packU24LE(0);
|
|
$result .= packU24LE(strlen($this->m_fontData));
|
|
$result .= pack("C", 0);
|
|
$result .= $this->m_fontData;
|
|
$result = alignSerialized($result, 4);
|
|
$len = strlen($result);
|
|
$len = packU24LE($len);
|
|
$result[1] = $len[0];
|
|
$result[2] = $len[1];
|
|
$result[3] = $len[2];
|
|
return $result;
|
|
}
|
|
|
|
public function serializeJSON(int $formatVersion): array {
|
|
$result = [];
|
|
$result["type"] = SectionType::CustomFont->name;
|
|
$result["data"] = base64_encode($this->m_fontData);
|
|
return $result;
|
|
}
|
|
|
|
public static function parseBinary(string $section, int $formatVersion): CustomFontSection {
|
|
$type = SectionType::from(unpack("C", substr($section, 0, 1))[1]);
|
|
if ($type != SectionType::CustomFont) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a CustomFontSection");
|
|
}
|
|
$len = unpackU24LE(substr($section, 1, 3));
|
|
$actualLen = unpackU24LE(substr($section, 4, 3));
|
|
$fontData = substr($section, 8, $actualLen);
|
|
$result = new CustomFontSection();
|
|
$result->m_fontData = $fontData;
|
|
return $result;
|
|
}
|
|
|
|
public static function parseJSON(array $section, int $formatVersion): CustomFontSection {
|
|
$type = SectionType::fromString($section["type"]);
|
|
if ($type != SectionType::CustomFont) {
|
|
throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a CustomFontSection");
|
|
}
|
|
$fontData = base64_decode($section["data"]);
|
|
$result = new CustomFontSection();
|
|
$result->m_fontData = $fontData;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
class File {
|
|
public array $sections = [];
|
|
}
|
|
|
|
function serializeFile(File $file): string {
|
|
$result = "\xAF\x7E\x2B\x63";
|
|
$formatVersion = 1;
|
|
foreach ($file->sections as $section) {
|
|
$formatVersion = max($formatVersion, $section->minimumFormatVersion());
|
|
}
|
|
$result .= pack("Vvv", $formatVersion, count($file->sections), 0);
|
|
$result = alignSerialized($result, 4);
|
|
foreach ($file->sections as $section) {
|
|
$result .= $section->serializeBinary($formatVersion);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function serializeFileJSON(File $file): array {
|
|
$result = [];
|
|
$formatVersion = 1;
|
|
foreach ($file->sections as $section) {
|
|
$formatVersion = max($formatVersion, $section->minimumFormatVersion());
|
|
}
|
|
$result["version"] = $formatVersion;
|
|
$result["sections"] = [];
|
|
foreach ($file->sections as $section) {
|
|
array_push($result["sections"], $section->serializeJSON($formatVersion));
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function parseFile(string $data): File {
|
|
if (substr($data, 0, 4) != "\xAF\x7E\x2B\x63") {
|
|
throw new \ValueError("The file contains an invalid magic sequence");
|
|
}
|
|
$version = unpack("V", substr($data, 4, 4))[1];
|
|
[$nSections, $reserved] = array_values(unpack("v2", substr($data, 8, 4)));
|
|
$data = substr($data, 12);
|
|
$result = new File();
|
|
for ($i = 0; $i < $nSections; ++$i) {
|
|
$type = SectionType::from(unpack("C", substr($data, 0, 1))[1]);
|
|
$size = unpackU24LE(substr($data, 1, 3));
|
|
$sectionData = substr($data, 0, $size);
|
|
$data = substr($data, $size);
|
|
$section = null;
|
|
switch ($type) {
|
|
case SectionType::AlwaysDrawn: $section = AlwaysDrawnSection::parseBinary($sectionData, $version); break;
|
|
case SectionType::TimeBasedDrawn: $section = TimeBasedDrawnSection::parseBinary($sectionData, $version); break;
|
|
case SectionType::MultiTimeBasedDrawn: $section = MultiTimeBasedDrawnSection::parseBinary($sectionData, $version); break;
|
|
case SectionType::ExpiryDate: $section = ExpiryDateSection::parseBinary($sectionData, $version); break;
|
|
case SectionType::CustomFont: $section = CustomFontSection::parseBinary($sectionData, $version); break;
|
|
default: throw new \ValueError("Unsupported section type " . $type->name . " encountered in file");
|
|
}
|
|
array_push($result->sections, $section);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function parseFileJSON(array $data): File {
|
|
$version = $data["version"];
|
|
if ($version < 1 || $version > 2) {
|
|
throw new \ValueError("Unsupported file format version $version encountered while parsing a file");
|
|
}
|
|
$result = new File();
|
|
foreach ($data["sections"] as $serializedSection) {
|
|
$type = SectionType::fromString($serializedSection["type"]);
|
|
$section = null;
|
|
switch ($type) {
|
|
case SectionType::AlwaysDrawn: $section = AlwaysDrawnSection::parseJSON($serializedSection, $version); break;
|
|
case SectionType::TimeBasedDrawn: $section = TimeBasedDrawnSection::parseJSON($serializedSection, $version); break;
|
|
case SectionType::MultiTimeBasedDrawn: $section = MultiTimeBasedDrawnSection::parseJSON($serializedSection, $version); break;
|
|
case SectionType::ExpiryDate: $section = ExpiryDateSection::parseJSON($serializedSection, $version); break;
|
|
case SectionType::CustomFont: $section = CustomFontSection::parseJSON($serializedSection, $version); break;
|
|
default: throw new \ValueError("Unsupported section type " . $type->name . " encountered in file");
|
|
}
|
|
array_push($result->sections, $section);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
} // namespace monoformat
|
|
|
|
?>
|
|
|