Abfahrtsanzeiger Display Basic Library
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.
 
 
 
libmonoformat/php/monoformat_structured.php

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 = $m_width * $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 = $m_width * $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("vvvvvvvvv",
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::AnimationElement) {
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 = $m_contentWidth * $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 = $m_width * $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_textFlags;
}
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
?>