2026-01-09 07:13:59 +00:00

158 lines
5.5 KiB
PHP

<?php declare(strict_types=1);
namespace PrinsFrank\PdfParser\Stream;
use Override;
use PrinsFrank\PdfParser\Document\CMap\ToUnicode\ToUnicodeCMapOperator;
use PrinsFrank\PdfParser\Document\Generic\Character\DelimiterCharacter;
use PrinsFrank\PdfParser\Document\Generic\Character\WhitespaceCharacter;
use PrinsFrank\PdfParser\Document\Generic\Marker;
use PrinsFrank\PdfParser\Document\Generic\Parsing\RollingCharBuffer;
use PrinsFrank\PdfParser\Exception\InvalidArgumentException;
use PrinsFrank\PdfParser\Exception\RuntimeException;
class FileStream extends AbstractStream {
/** @var resource */
private readonly mixed $handle;
/** @param resource $handle */
final private function __construct(mixed $handle) {
if (is_resource($handle) === false || in_array(get_resource_type($handle), ['stream'], true) === false) {
throw new InvalidArgumentException(sprintf('$handle should be a stream, %s given', is_resource($handle) ? get_resource_type($handle) : gettype($handle)));
}
$this->handle = $handle;
}
public static function openFile(string $path): self {
$handle = fopen($path, 'rb');
if ($handle === false) {
throw new InvalidArgumentException(sprintf('Failed to open file at path "%s"', $path));
}
return new self($handle);
}
public static function fromString(string $content): self {
$handle = fopen('php://temp', 'rb+');
if ($handle === false) {
throw new RuntimeException('Unable to create file handle to temp');
}
fwrite($handle, $content);
rewind($handle);
return new self($handle);
}
#[Override]
public function getSizeInBytes(): int {
$stats = fstat($this->handle);
if ($stats === false) {
throw new RuntimeException('Unable to retrieve file information');
}
return $stats['size'];
}
#[Override]
public function read(int $from, int $nrOfBytes): string {
if ($nrOfBytes <= 0) {
throw new InvalidArgumentException(sprintf('$nrOfBytes must be greater than 0, %d given', $nrOfBytes));
}
fseek($this->handle, $from);
$bytes = fread($this->handle, $nrOfBytes);
if ($bytes === false) {
throw new RuntimeException('Unable to read from handle');
}
return $bytes;
}
#[Override]
public function slice(int $startByteOffset, int $endByteOffset): string {
if ($startByteOffset <= 0) {
throw new InvalidArgumentException(sprintf('$startByteOffset must be greater than 0, %d given', $startByteOffset));
}
if ($endByteOffset - $startByteOffset < 1) {
throw new InvalidArgumentException(sprintf('End byte offset %d should be bigger than start byte offset %d', $endByteOffset, $startByteOffset));
}
fseek($this->handle, $startByteOffset);
$bytes = fread($this->handle, $endByteOffset - $startByteOffset);
if ($bytes === false) {
throw new RuntimeException('Unable to read bytes from handle');
}
return $bytes;
}
#[Override]
public function chars(int $from, int $nrOfBytes): iterable {
if ($from < 0) {
throw new InvalidArgumentException(sprintf('StartOffset should be greater than zero, %d given', $from));
}
if ($nrOfBytes <= 0) {
throw new InvalidArgumentException(sprintf('$nrOfBytes to read must be greater than 0, %d given', $nrOfBytes));
}
$bytesRead = 0;
while ($bytesRead < $nrOfBytes) {
fseek($this->handle, $from + $bytesRead);
$bytes = fread($this->handle, 1);
if ($bytes === false) {
throw new RuntimeException('Unable to read bytes from stream');
}
yield $bytes;
$bytesRead++;
}
}
#[Override]
public function firstPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromStart, int $before): ?int {
$rollingCharBuffer = new RollingCharBuffer($needleLength = strlen($needle->value));
while ($offsetFromStart < $before) {
fseek($this->handle, $offsetFromStart);
$character = fgetc($this->handle);
if ($character === false) {
throw new RuntimeException('Unable to get char from stream');
}
$rollingCharBuffer->next($character);
$offsetFromStart++;
if ($rollingCharBuffer->seenString($needle->value)) {
return $offsetFromStart - $needleLength;
}
}
return null;
}
#[Override]
public function lastPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromEnd): ?int {
$rollingCharBuffer = new RollingCharBuffer(strlen($needle->value));
$offsetFromEnd++;
while (fseek($this->handle, - $offsetFromEnd, SEEK_END) !== -1) {
$character = fgetc($this->handle);
if ($character === false) {
throw new RuntimeException('Unable to get character from stream');
}
$rollingCharBuffer->next($character);
$offsetFromEnd++;
if ($rollingCharBuffer->seenReverseString($needle->value)) {
return $this->getSizeInBytes() - $offsetFromEnd + 1;
}
}
return null;
}
public function __destruct() {
fclose($this->handle);
}
}