feat(labels): add reader for global Labels file
Mirrors the Macros reader: LabelsFileReader -> LabelLibrary -> Label wraps the protobuf ProLabelsDocument and exposes each label's name (the proto 'text' field) plus optional RGBA color with a #RRGGBB hex helper. Includes CLI tool, PHPUnit suite against the bundled reference Labels sample, and api/labels.md docs.
This commit is contained in:
parent
b30918af41
commit
4e1ac9b7ea
|
|
@ -20,6 +20,7 @@ All project documentation lives in `doc/`. Load only what you need.
|
|||
| Parse/modify `.proplaylist` files | `doc/api/playlist.md` |
|
||||
| Parse/modify `.probundle` files | `doc/api/bundle.md` |
|
||||
| Read the global `Macros` file | `doc/api/macros.md` |
|
||||
| Read the global `Labels` file | `doc/api/labels.md` |
|
||||
| Understand `.pro` binary format | `doc/formats/pp_song_spec.md` |
|
||||
| Understand `.proplaylist` binary format | `doc/formats/pp_playlist_spec.md` |
|
||||
| Understand `.probundle` binary format | `doc/formats/pp_bundle_spec.md` |
|
||||
|
|
@ -40,7 +41,9 @@ doc/
|
|||
├── api/ ← PHP API docs (read/write/generate)
|
||||
│ ├── song.md
|
||||
│ ├── playlist.md
|
||||
│ └── bundle.md
|
||||
│ ├── bundle.md
|
||||
│ ├── macros.md
|
||||
│ └── labels.md
|
||||
└── internal/ ← Dev notes (learnings, decisions, issues)
|
||||
├── learnings.md
|
||||
├── decisions.md
|
||||
|
|
@ -55,6 +58,7 @@ PHP tools for parsing, modifying, and generating ProPresenter 7 files:
|
|||
- **Playlists** (`.proplaylist`) — ZIP64 archives containing playlist metadata and embedded songs
|
||||
- **Bundles** (`.probundle`) — ZIP archives containing a single presentation with embedded media assets
|
||||
- **Macros** (`Macros`, no extension) — Global protobuf-encoded macro library with collections
|
||||
- **Labels** (`Labels`, no extension) — Global protobuf-encoded label library (slide labels with optional UI colors)
|
||||
|
||||
### CLI Tools
|
||||
|
||||
|
|
@ -62,6 +66,7 @@ PHP tools for parsing, modifying, and generating ProPresenter 7 files:
|
|||
php bin/parse-song.php path/to/song.pro
|
||||
php bin/parse-playlist.php path/to/playlist.proplaylist
|
||||
php bin/parse-macros.php path/to/Macros
|
||||
php bin/parse-labels.php path/to/Labels
|
||||
```
|
||||
|
||||
### Key Source Files
|
||||
|
|
|
|||
47
bin/parse-labels.php
Executable file
47
bin/parse-labels.php
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use ProPresenter\Parser\LabelsFileReader;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: parse-labels.php <Labels>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$filePath = $argv[1];
|
||||
|
||||
try {
|
||||
$library = LabelsFileReader::read($filePath);
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$labels = $library->getLabels();
|
||||
|
||||
echo "Labels (" . count($labels) . "):\n";
|
||||
foreach ($labels as $index => $label) {
|
||||
$number = $index + 1;
|
||||
$name = $label->getName();
|
||||
$displayName = $name === '' ? '(unnamed)' : $name;
|
||||
|
||||
if ($label->hasColor()) {
|
||||
$color = $label->getColor();
|
||||
$colorPart = sprintf(
|
||||
'%s rgba(%.3f, %.3f, %.3f, %.3f)',
|
||||
$label->getColorHex(),
|
||||
$color['r'],
|
||||
$color['g'],
|
||||
$color['b'],
|
||||
$color['a'],
|
||||
);
|
||||
} else {
|
||||
$colorPart = '(no color)';
|
||||
}
|
||||
|
||||
echo sprintf(" [%d] %s :: %s\n", $number, $displayName, $colorPart);
|
||||
}
|
||||
10
doc/INDEX.md
10
doc/INDEX.md
|
|
@ -10,6 +10,7 @@
|
|||
| Parse/modify `.proplaylist` files | [api/playlist.md](api/playlist.md) |
|
||||
| Parse/modify `.probundle` files | [api/bundle.md](api/bundle.md) |
|
||||
| Read the global `Macros` file | [api/macros.md](api/macros.md) |
|
||||
| Read the global `Labels` file | [api/labels.md](api/labels.md) |
|
||||
| Understand `.pro` binary format | [formats/pp_song_spec.md](formats/pp_song_spec.md) |
|
||||
| Understand `.proplaylist` format | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) |
|
||||
| Understand `.probundle` format | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
- [api/playlist.md](api/playlist.md) — Playlist parser API (read, modify, generate `.proplaylist` files)
|
||||
- [api/bundle.md](api/bundle.md) — Bundle parser API (read, write `.probundle` files with media)
|
||||
- [api/macros.md](api/macros.md) — Macros library API (read the global `Macros` file)
|
||||
- [api/labels.md](api/labels.md) — Labels library API (read the global `Labels` file)
|
||||
|
||||
### Internal Reference
|
||||
- [internal/learnings.md](internal/learnings.md) — Development learnings and conventions discovered
|
||||
|
|
@ -57,7 +59,8 @@ doc/
|
|||
│ ├── song.md
|
||||
│ ├── playlist.md
|
||||
│ ├── bundle.md
|
||||
│ └── macros.md
|
||||
│ ├── macros.md
|
||||
│ └── labels.md
|
||||
└── internal/ ← Development notes (optional context)
|
||||
├── learnings.md
|
||||
├── decisions.md
|
||||
|
|
@ -128,6 +131,8 @@ This project provides PHP tools to parse, modify, and generate ProPresenter 7 fi
|
|||
| `src/ProBundleWriter.php` | Write `.probundle` files |
|
||||
| `src/MacroLibrary.php` | Macros library wrapper (read global `Macros` file) |
|
||||
| `src/MacrosFileReader.php` | Read global `Macros` file |
|
||||
| `src/LabelLibrary.php` | Labels library wrapper (read global `Labels` file) |
|
||||
| `src/LabelsFileReader.php` | Read global `Labels` file |
|
||||
|
||||
### CLI Tools
|
||||
|
||||
|
|
@ -140,4 +145,7 @@ php bin/parse-playlist.php path/to/playlist.proplaylist
|
|||
|
||||
# Parse and display the global Macros file
|
||||
php bin/parse-macros.php path/to/Macros
|
||||
|
||||
# Parse and display the global Labels file
|
||||
php bin/parse-labels.php path/to/Labels
|
||||
```
|
||||
|
|
|
|||
137
doc/api/labels.md
Normal file
137
doc/api/labels.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Labels Library API
|
||||
|
||||
> PHP module for reading the global ProPresenter `Labels` file (raw protobuf,
|
||||
> no extension) and exposing each label's name and UI color.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\LabelsFileReader;
|
||||
|
||||
$library = LabelsFileReader::read('/path/to/Labels');
|
||||
|
||||
foreach ($library->getLabels() as $label) {
|
||||
$label->getName(); // "KeyVisual Beamer"
|
||||
$label->hasColor(); // bool
|
||||
$label->getColor(); // ['r'=>0.0,'g'=>0.408,'b'=>0.702,'a'=>1.0] | null
|
||||
$label->getColorHex(); // "#0068B3" | null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Layout
|
||||
|
||||
The `Labels` file is the protobuf-serialised
|
||||
[`ProLabelsDocument`](../../proto/labels.proto):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `labels` | repeated `Action.Label` | Definitions: text + optional color |
|
||||
|
||||
Each `Action.Label` carries:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `text` | string | Display name (exposed as `getName()` on the wrapper) |
|
||||
| `color` | `Color` (optional) | RGBA float channels in 0..1; absent for system / "no color" labels |
|
||||
|
||||
Labels are identified by name only — there is no UUID. Slides reference
|
||||
labels by name from inside `.pro` files.
|
||||
|
||||
---
|
||||
|
||||
## Reading
|
||||
|
||||
```php
|
||||
use ProPresenter\Parser\LabelsFileReader;
|
||||
|
||||
$library = LabelsFileReader::read('/Users/me/.../Labels');
|
||||
```
|
||||
|
||||
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
|
||||
empty / unreadable files.
|
||||
|
||||
---
|
||||
|
||||
## LabelLibrary
|
||||
|
||||
Top-level wrapper around `Rv\Data\ProLabelsDocument`. Indexes labels by name
|
||||
for fast lookup.
|
||||
|
||||
```php
|
||||
$library->getLabels(); // Label[]
|
||||
$library->count(); // int
|
||||
$library->getLabelByName('Szene 1'); // ?Label (case-sensitive)
|
||||
$library->findLabelByName('szene 1'); // ?Label (case-insensitive)
|
||||
$library->getDocument(); // \Rv\Data\ProLabelsDocument
|
||||
```
|
||||
|
||||
If the same name appears more than once in the source document the first
|
||||
occurrence wins for both lookup helpers; every entry is preserved in
|
||||
`getLabels()` in document order.
|
||||
|
||||
---
|
||||
|
||||
## Label
|
||||
|
||||
```php
|
||||
$label->getName(); // "KeyVisual Beamer" (proto field is `text`)
|
||||
$label->hasColor(); // bool — was a Color message present?
|
||||
$label->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
|
||||
$label->getColorHex(); // "#RRGGBB" uppercase, alpha dropped, or null
|
||||
$label->getProto(); // \Rv\Data\Action\Label (raw protobuf)
|
||||
```
|
||||
|
||||
Color channels are floats in 0..1 as ProPresenter stores them. `getColorHex()`
|
||||
clamps and rounds each channel to 8 bits before formatting.
|
||||
|
||||
A label can legitimately exist without a `color` message. Treat that as
|
||||
"use the default UI color", not as black. The reference sample's first four
|
||||
labels (`Leere Folie`, `Instrumental`, `Wiederholen`, `Gesprochenes Wort`)
|
||||
hit this case.
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool
|
||||
|
||||
```bash
|
||||
php bin/parse-labels.php /path/to/Labels
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Labels (15):
|
||||
[1] Leere Folie :: (no color)
|
||||
[2] Instrumental :: (no color)
|
||||
[3] Wiederholen :: (no color)
|
||||
[4] Gesprochenes Wort :: (no color)
|
||||
[5] KeyVisual Stream & Beamer mit Countdown :: #CC298B rgba(0.800, 0.161, 0.545, 1.000)
|
||||
[6] KeyVisual Stream & Beamer mit Jingle :: #7600CC rgba(0.463, 0.000, 0.800, 1.000)
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/LabelLibrary.php` | Document-level wrapper with name lookups |
|
||||
| `src/Label.php` | Single label wrapper (name, color, hex) |
|
||||
| `src/LabelsFileReader.php` | Reads the `Labels` file |
|
||||
| `bin/parse-labels.php` | CLI tool |
|
||||
| `proto/labels.proto` | Protobuf schema (just imports `Action.Label`) |
|
||||
| `proto/action.proto` | Defines the inner `Action.Label` message |
|
||||
| `generated/Rv/Data/ProLabelsDocument.php` | Generated message class |
|
||||
| `generated/Rv/Data/Action/Label.php` | Generated label message class |
|
||||
|
||||
---
|
||||
|
||||
## Scope Notes
|
||||
|
||||
This module is read-only by design. Writing the `Labels` file back, editing
|
||||
slide-side label references on `.pro` files, or syncing labels across devices
|
||||
are not implemented here. Add them by mirroring the `ProFileWriter` /
|
||||
`ProFileGenerator` pattern when needed.
|
||||
|
|
@ -74,16 +74,20 @@
|
|||
| image | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 |
|
||||
| video | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 6 |
|
||||
| cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| label | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| label | [api/labels.md](api/labels.md), [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
|
||||
| Labels file | [api/labels.md](api/labels.md) |
|
||||
| LabelLibrary | [api/labels.md](api/labels.md) |
|
||||
| LabelsFileReader | [api/labels.md](api/labels.md) |
|
||||
| color | [api/labels.md](api/labels.md), [api/macros.md](api/macros.md) |
|
||||
|
||||
## PHP API
|
||||
|
||||
| Keyword | Document |
|
||||
|---------|----------|
|
||||
| read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md) |
|
||||
| read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| write | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md) |
|
||||
| generate | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md) |
|
||||
| parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md) |
|
||||
| parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| MacrosFileReader | [api/macros.md](api/macros.md) |
|
||||
| ProFileReader | [api/song.md](api/song.md) |
|
||||
| ProFileWriter | [api/song.md](api/song.md) |
|
||||
|
|
@ -96,8 +100,8 @@
|
|||
| PresentationBundle | [api/bundle.md](api/bundle.md) |
|
||||
| Song | [api/song.md](api/song.md) |
|
||||
| PlaylistArchive | [api/playlist.md](api/playlist.md) |
|
||||
| CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md) |
|
||||
| command line | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md) |
|
||||
| CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
| command line | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
|
||||
|
||||
## Protobuf
|
||||
|
||||
|
|
|
|||
BIN
doc/reference_samples/Labels
Normal file
BIN
doc/reference_samples/Labels
Normal file
Binary file not shown.
91
src/Label.php
Normal file
91
src/Label.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\Action\Label as LabelProto;
|
||||
|
||||
/**
|
||||
* Wraps a protobuf {@see LabelProto} from the global ProPresenter `Labels`
|
||||
* file. Surfaces the label's display name (the protobuf `text` field) and an
|
||||
* optional color used by the UI to tint slides / cues that carry the label.
|
||||
*
|
||||
* Labels do not have a UUID — identity is the name string.
|
||||
*/
|
||||
class Label
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LabelProto $label,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name (the protobuf `text` field; this is what the
|
||||
* ProPresenter UI renders next to the swatch).
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->label->getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the label carries an explicit color message (the absence of a
|
||||
* color is not the same as black: ProPresenter renders unset labels with
|
||||
* the default UI color).
|
||||
*/
|
||||
public function hasColor(): bool
|
||||
{
|
||||
return $this->label->hasColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label's color as an associative array, or null when unset.
|
||||
*
|
||||
* Channels are floats in the 0..1 range as stored by ProPresenter.
|
||||
*
|
||||
* @return array{r: float, g: float, b: float, a: float}|null
|
||||
*/
|
||||
public function getColor(): ?array
|
||||
{
|
||||
if (!$this->label->hasColor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$color = $this->label->getColor();
|
||||
|
||||
return [
|
||||
'r' => $color->getRed(),
|
||||
'g' => $color->getGreen(),
|
||||
'b' => $color->getBlue(),
|
||||
'a' => $color->getAlpha(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: render the color as a 6-digit `#RRGGBB` hex string,
|
||||
* dropping alpha. Returns null when the label has no color.
|
||||
*/
|
||||
public function getColorHex(): ?string
|
||||
{
|
||||
$color = $this->getColor();
|
||||
if ($color === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'#%02X%02X%02X',
|
||||
(int) round(max(0.0, min(1.0, $color['r'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['g'])) * 255),
|
||||
(int) round(max(0.0, min(1.0, $color['b'])) * 255),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf Label message.
|
||||
*/
|
||||
public function getProto(): LabelProto
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
}
|
||||
83
src/LabelLibrary.php
Normal file
83
src/LabelLibrary.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use Rv\Data\ProLabelsDocument;
|
||||
|
||||
/**
|
||||
* Wraps the protobuf {@see ProLabelsDocument} — the global ProPresenter
|
||||
* `Labels` file which lists every named label and its UI color.
|
||||
*
|
||||
* Labels are identified by name only (there is no UUID field). Names are
|
||||
* expected to be unique inside the document; if the source file violates
|
||||
* that, the first occurrence wins for {@see getLabelByName()} but every
|
||||
* label is preserved in {@see getLabels()} in document order.
|
||||
*/
|
||||
class LabelLibrary
|
||||
{
|
||||
/** @var Label[] */
|
||||
private array $labels = [];
|
||||
|
||||
/** @var array<string, Label> */
|
||||
private array $labelsByName = [];
|
||||
|
||||
/** @var array<string, Label> */
|
||||
private array $labelsByNameLower = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ProLabelsDocument $document,
|
||||
) {
|
||||
foreach ($this->document->getLabels() as $labelProto) {
|
||||
$label = new Label($labelProto);
|
||||
$this->labels[] = $label;
|
||||
|
||||
$name = $label->getName();
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->labelsByName[$name] ??= $label;
|
||||
$this->labelsByNameLower[strtolower($name)] ??= $label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Label[]
|
||||
*/
|
||||
public function getLabels(): array
|
||||
{
|
||||
return $this->labels;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->labels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exact-name lookup. Use {@see findLabelByName()} for a
|
||||
* case-insensitive variant.
|
||||
*/
|
||||
public function getLabelByName(string $name): ?Label
|
||||
{
|
||||
return $this->labelsByName[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case-insensitive name lookup.
|
||||
*/
|
||||
public function findLabelByName(string $name): ?Label
|
||||
{
|
||||
return $this->labelsByNameLower[strtolower($name)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying protobuf ProLabelsDocument.
|
||||
*/
|
||||
public function getDocument(): ProLabelsDocument
|
||||
{
|
||||
return $this->document;
|
||||
}
|
||||
}
|
||||
42
src/LabelsFileReader.php
Normal file
42
src/LabelsFileReader.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Rv\Data\ProLabelsDocument;
|
||||
|
||||
/**
|
||||
* Reader for the global ProPresenter `Labels` file (a raw protobuf
|
||||
* serialisation of {@see ProLabelsDocument}, no extension).
|
||||
*/
|
||||
final class LabelsFileReader
|
||||
{
|
||||
public static function read(string $filePath): LabelLibrary
|
||||
{
|
||||
if ($filePath === '' || !is_file($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('Labels file not found: %s', $filePath));
|
||||
}
|
||||
|
||||
$size = filesize($filePath);
|
||||
if ($size === false) {
|
||||
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
|
||||
}
|
||||
|
||||
if ($size === 0) {
|
||||
throw new RuntimeException(sprintf('Labels file is empty: %s', $filePath));
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException(sprintf('Unable to read labels file: %s', $filePath));
|
||||
}
|
||||
|
||||
$document = new ProLabelsDocument();
|
||||
$document->mergeFromString($data);
|
||||
|
||||
return new LabelLibrary($document);
|
||||
}
|
||||
}
|
||||
136
tests/LabelsFileReaderTest.php
Normal file
136
tests/LabelsFileReaderTest.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ProPresenter\Parser\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ProPresenter\Parser\Label;
|
||||
use ProPresenter\Parser\LabelLibrary;
|
||||
use ProPresenter\Parser\LabelsFileReader;
|
||||
|
||||
class LabelsFileReaderTest extends TestCase
|
||||
{
|
||||
private const REFERENCE_PATH = __DIR__ . '/../doc/reference_samples/Labels';
|
||||
|
||||
#[Test]
|
||||
public function readThrowsOnMissingFile(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
LabelsFileReader::read(__DIR__ . '/../doc/reference_samples/does-not-exist-labels');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function readReturnsLabelLibraryWithExpectedCount(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$this->assertInstanceOf(LabelLibrary::class, $library);
|
||||
$this->assertCount(15, $library->getLabels());
|
||||
$this->assertSame(15, $library->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function labelsExposeNames(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
$labels = $library->getLabels();
|
||||
|
||||
$this->assertInstanceOf(Label::class, $labels[0]);
|
||||
$this->assertSame('Leere Folie', $labels[0]->getName());
|
||||
$this->assertSame('Instrumental', $labels[1]->getName());
|
||||
$this->assertSame('KeyVisual Stream & Beamer mit Countdown', $labels[4]->getName());
|
||||
$this->assertSame('Szene 3', $labels[14]->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function labelsWithoutColorReportNullColor(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$leereFolie = $library->getLabelByName('Leere Folie');
|
||||
$this->assertNotNull($leereFolie);
|
||||
$this->assertFalse($leereFolie->hasColor());
|
||||
$this->assertNull($leereFolie->getColor());
|
||||
$this->assertNull($leereFolie->getColorHex());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function labelsWithColorReturnFloatChannels(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$beamer = $library->getLabelByName('KeyVisual Beamer');
|
||||
$this->assertNotNull($beamer);
|
||||
$this->assertTrue($beamer->hasColor());
|
||||
|
||||
$color = $beamer->getColor();
|
||||
$this->assertIsArray($color);
|
||||
$this->assertEqualsWithDelta(0.0, $color['r'], 0.001);
|
||||
$this->assertEqualsWithDelta(0.4078, $color['g'], 0.001);
|
||||
$this->assertEqualsWithDelta(0.7020, $color['b'], 0.001);
|
||||
$this->assertEqualsWithDelta(1.0, $color['a'], 0.001);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function colorHexIsSixDigitUppercase(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$beamer = $library->getLabelByName('KeyVisual Beamer');
|
||||
$this->assertNotNull($beamer);
|
||||
$this->assertSame('#0068B3', $beamer->getColorHex());
|
||||
|
||||
$countdown = $library->getLabelByName('KeyVisual Stream & Beamer mit Countdown');
|
||||
$this->assertNotNull($countdown);
|
||||
$this->assertSame('#CC298B', $countdown->getColorHex());
|
||||
|
||||
$kill = $library->getLabelByName('Namenseinblender Kill');
|
||||
$this->assertNotNull($kill);
|
||||
$this->assertSame('#000000', $kill->getColorHex());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getLabelByNameIsCaseSensitive(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$this->assertNotNull($library->getLabelByName('Szene 1'));
|
||||
$this->assertNull($library->getLabelByName('szene 1'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findLabelByNameIsCaseInsensitive(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$exact = $library->findLabelByName('Szene 1');
|
||||
$lower = $library->findLabelByName('szene 1');
|
||||
$upper = $library->findLabelByName('SZENE 1');
|
||||
|
||||
$this->assertNotNull($exact);
|
||||
$this->assertSame($exact, $lower);
|
||||
$this->assertSame($exact, $upper);
|
||||
$this->assertSame('Szene 1', $exact->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function unknownNameReturnsNull(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$this->assertNull($library->getLabelByName('does-not-exist'));
|
||||
$this->assertNull($library->findLabelByName('does-not-exist'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function documentIsExposedForRawAccess(): void
|
||||
{
|
||||
$library = LabelsFileReader::read(self::REFERENCE_PATH);
|
||||
|
||||
$document = $library->getDocument();
|
||||
$this->assertCount(15, $document->getLabels());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue