Skip to content

feat: add basic reader and writer for keynote #860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"ext-xml": "*",
"ext-zip": "*",
"phpoffice/common": "^1",
"phpoffice/phpspreadsheet": "^1.9 || ^2.0 || ^3.0"
"phpoffice/phpspreadsheet": "^1.9 || ^2.0 || ^3.0",
"google/protobuf": "^4.30"
},
"require-dev": {
"phpunit/phpunit": ">=7.0",
Expand Down
48 changes: 48 additions & 0 deletions src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpPresentation\Common\Adapter\Protobuf;

class ProtobufAdapter
{
public function encode(array $data): string

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.1)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.2)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.4)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.0)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.4)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.1)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.

Check failure on line 9 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.3)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::encode() has parameter $data with no value type specified in iterable type array.
{
// Implement Protobuf encoding
// This is a placeholder
return serialize($data);
}

public function decode(string $data): array

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.1)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.2)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.4)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.0)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.4)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.1)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.

Check failure on line 16 in src/PhpPresentation/Common/Adapter/Protobuf/ProtobufAdapter.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.3)

Method PhpOffice\PhpPresentation\Common\Adapter\Protobuf\ProtobufAdapter::decode() return type has no value type specified in iterable type array.
{
// Implement Protobuf decoding
// This is a placeholder
return unserialize($data);
}

protected function encodeVarint(int $value): string
{
$bytes = '';
while ($value > 0x7F) {
$bytes .= chr(($value & 0x7F) | 0x80);
$value >>= 7;
}
$bytes .= chr($value & 0x7F);
return $bytes;
}

protected function decodeVarint(string $data, int &$offset): int
{
$value = 0;
$shift = 0;
while (true) {
$byte = ord($data[$offset++]);
$value |= ($byte & 0x7F) << $shift;
if (($byte & 0x80) === 0) {
break;
}
$shift += 7;
}
return $value;
}
}
38 changes: 38 additions & 0 deletions src/PhpPresentation/Common/Adapter/Snappy/SnappyAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpPresentation\Common\Adapter\Snappy;

class SnappyAdapter
{
public function compress(string $data): string
{
// Implement Snappy compression
// You might want to use a PHP extension or pure PHP implementation
// This is a placeholder
return $data;
}

public function decompress(string $data): string
{
// Implement Snappy decompression
// This is a placeholder
return $data;
}

protected function createChunk(string $data): string
{
$length = strlen($data);
$header = pack('V', $length);
return $header . $data;
}

protected function readChunk(string $data, int &$offset): string
{
$header = substr($data, $offset, 4);
$length = unpack('V', $header)[1];
$offset += 4;
return substr($data, $offset, $length);
}
}
219 changes: 219 additions & 0 deletions src/PhpPresentation/Common/Compression/Snappy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpPresentation\Common\Compression;

class Snappy
{
private const CHUNK_TYPE_COMPRESSED = 0x00;
private const CHUNK_TYPE_UNCOMPRESSED = 0x01;
private const MAX_BLOCK_SIZE = 65536;
private const MAX_OFFSET = 32768;

public function compress(string $data): string
{
if (empty($data)) {
return '';
}

$chunks = [];
$offset = 0;
$length = strlen($data);

while ($offset < $length) {
$blockSize = min(self::MAX_BLOCK_SIZE, $length - $offset);
$block = substr($data, $offset, $blockSize);

try {
$compressed = $this->compressBlock($block);
$chunks[] = $this->createChunk(
$compressed,
strlen($compressed) < strlen($block) ? self::CHUNK_TYPE_COMPRESSED : self::CHUNK_TYPE_UNCOMPRESSED
);
} catch (\Exception $e) {
// If compression fails, store uncompressed
$chunks[] = $this->createChunk($block, self::CHUNK_TYPE_UNCOMPRESSED);
}

$offset += $blockSize;
}

return implode('', $chunks);
}

public function decompress(string $data): string
{
if (empty($data)) {
return '';
}

$result = '';
$offset = 0;
$length = strlen($data);

try {
while ($offset < $length) {
if ($offset + 5 > $length) {
throw new \RuntimeException('Invalid chunk header');
}

$header = unpack('Ctype/Vsize', substr($data, $offset, 5));
if (!$header) {
throw new \RuntimeException('Failed to unpack chunk header');
}

$offset += 5;
if ($offset + $header['size'] > $length) {
throw new \RuntimeException('Invalid chunk size');
}

$chunk = substr($data, $offset, $header['size']);
$offset += $header['size'];

$result .= ($header['type'] === self::CHUNK_TYPE_COMPRESSED)
? $this->decompressBlock($chunk)
: $chunk;
}
} catch (\Exception $e) {
throw new \RuntimeException('Decompression failed: ' . $e->getMessage());
}

return $result;
}

private function compressBlock(string $data): string
{
if (empty($data)) {
return '';
}

$result = '';
$length = strlen($data);
$pos = 0;
$hashTable = [];

while ($pos < $length) {
// Look for matches in the last MAX_OFFSET bytes
$maxLookback = max(0, $pos - self::MAX_OFFSET);
$match = $this->findLongestMatch($data, $pos, $maxLookback, $hashTable);

if ($match && $match['length'] > 3) {
// Encode match
$result .= $this->encodeMatch($match['offset'], $match['length']);
$pos += $match['length'];
} else {
// Encode literal
$literalLength = min(self::MAX_BLOCK_SIZE, $length - $pos);
$result .= $this->encodeLiteral(substr($data, $pos, $literalLength));
$pos += $literalLength;
}

// Update hash table
$hashTable[$this->hash(substr($data, $pos, 4))] = $pos;
}

return $result;
}

private function decompressBlock(string $data): string
{
if (empty($data)) {
return '';
}

$result = '';
$pos = 0;
$length = strlen($data);

while ($pos < $length) {
$tag = ord($data[$pos++]);

if ($tag & 0x80) {
// Match
if ($pos + 1 > $length) {
throw new \RuntimeException('Invalid match data');
}

$matchLength = (($tag >> 2) & 0x1F) + 4;
$matchOffset = (ord($data[$pos++]) << 3) | ($tag >> 5);

if ($matchOffset > strlen($result)) {
throw new \RuntimeException('Invalid match offset');
}

// Copy from back reference
$start = strlen($result) - $matchOffset;
for ($i = 0; $i < $matchLength; $i++) {
$result .= $result[$start + $i];
}
} else {
// Literal
$literalLength = ($tag & 0x7F) + 1;
if ($pos + $literalLength > $length) {
throw new \RuntimeException('Invalid literal length');
}

$result .= substr($data, $pos, $literalLength);
$pos += $literalLength;
}
}

return $result;
}

private function createChunk(string $data, int $type): string
{
return pack('CV', $type, strlen($data)) . $data;
}

private function hash(string $data): int
{
// Simple rolling hash function
$hash = 0;
for ($i = 0; $i < min(4, strlen($data)); $i++) {
$hash = ($hash * 33) + ord($data[$i]);
}
return $hash & 0xFFFFFFFF;
}

private function findLongestMatch(string $data, int $pos, int $maxLookback, array $hashTable): ?array

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.1)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.1)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.2)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.2)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.4)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.4)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.0)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.0)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.4)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.4)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.1)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (7.1)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.3)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() return type has no value type specified in iterable type array.

Check failure on line 180 in src/PhpPresentation/Common/Compression/Snappy.php

View workflow job for this annotation

GitHub Actions / PHP Static Analysis (8.3)

Method PhpOffice\PhpPresentation\Common\Compression\Snappy::findLongestMatch() has parameter $hashTable with no value type specified in iterable type array.
{
$length = strlen($data);
if ($pos + 4 > $length) {
return null;
}

$hash = $this->hash(substr($data, $pos, 4));
if (!isset($hashTable[$hash]) || $hashTable[$hash] < $maxLookback) {
return null;
}

$matchPos = $hashTable[$hash];
$matchLength = 0;
while (
$pos + $matchLength < $length &&
$matchLength < 255 &&
$data[$matchPos + $matchLength] === $data[$pos + $matchLength]
) {
$matchLength++;
}

return $matchLength >= 4 ? [
'offset' => $pos - $matchPos,
'length' => $matchLength
] : null;
}

private function encodeLiteral(string $literal): string
{
$length = strlen($literal) - 1;
return chr($length) . $literal;
}

private function encodeMatch(int $offset, int $length): string
{
$tag = 0x80 | (($length - 4) << 2) | ($offset >> 3);
return chr($tag) . chr($offset & 0xFF);
}
}
Loading
Loading