@kirigami/php-prepros 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/package.json +1 -1
- package/src/libraries/img.class.php +58 -2
- package/src/libraries/str.class.php +23 -1
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ Part of the **Kirigami** project ecosystem.
|
|
|
34
34
|
|
|
35
35
|
- [@kirigami/php-prepros](#kirigamiphp-prepros)
|
|
36
36
|
- [Overview](#overview)
|
|
37
|
+
- [What's new in 3.0.1](#whats-new-in-301)
|
|
37
38
|
- [What's new in 3.0.0](#whats-new-in-300)
|
|
38
39
|
- [What's new in 2.0.0](#whats-new-in-200)
|
|
39
40
|
- [What's new in 1.9.3](#whats-new-in-193)
|
|
@@ -105,6 +106,12 @@ Part of the **Kirigami** project ecosystem.
|
|
|
105
106
|
|
|
106
107
|
---
|
|
107
108
|
|
|
109
|
+
## What's new in 3.0.1
|
|
110
|
+
|
|
111
|
+
A registered tag written inside Markdown code is no longer processed. `<markdown>`, `<img asset="...">` or a plugin tag shown in an inline code span (`` `<markdown prose>` ``) or a fenced block (` ``` ` / `~~~`) stays example text: before, a `<markdown>` in a code span paired with the real block's closing tag and broke the rest of the page, and an `<img asset>` in one became an empty image. Indented (four-space) code blocks are not recognized, since `<markdown>` bodies are indented; use a fence there.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
108
115
|
## What's new in 3.0.0
|
|
109
116
|
|
|
110
117
|
This release switches `YAML::` to the native YAML extension, `MD::` to native mdhtml, `SCHEMA` to native jsonk, and `Normalizer` to native norm. It also includes page types and request lifecycle hooks.
|
|
@@ -1514,11 +1521,12 @@ $img->height // int
|
|
|
1514
1521
|
// Instance methods (resize/save are chainable)
|
|
1515
1522
|
$img->resize(int $width, int $height = 0, bool $cover = false): self
|
|
1516
1523
|
$img->save(string $dest, ?int $quality = null): self // quality 0-100 for jpg/webp/avif; null = per-format default (82)
|
|
1517
|
-
$img->getRepresentativeColors(int $count = 5): string[] // ['#rrggbb', …]
|
|
1524
|
+
$img->getRepresentativeColors(int $count = 5): string[] // ['#rrggbb', …], median-cut + Lab merge
|
|
1525
|
+
$img->getAuraColors(int $count = 5): string[] // ['#rrggbb', …], up to 6, via the Aura extension
|
|
1518
1526
|
|
|
1519
1527
|
// Static helpers
|
|
1520
1528
|
IMG::asset(string $path, int $width = 0, int $height = 0, bool $cover = false): string // same feature as the <img asset> tag and the img-asset() Sass function
|
|
1521
|
-
IMG::palette(string $path, int $colors = 5): string[]
|
|
1529
|
+
IMG::palette(string $path, int $colors = 5): string[] // Aura-backed: at most 6 colours
|
|
1522
1530
|
```
|
|
1523
1531
|
|
|
1524
1532
|
`resize()` operates in *contain* mode by default (scales to fit within the target box while preserving aspect ratio). Pass `$cover = true` to crop and fill the exact target dimensions.
|
|
@@ -1536,7 +1544,10 @@ IMG::palette(string $path, int $colors = 5): string[]
|
|
|
1536
1544
|
and `colors()` Sass functions: they resolve `$path` against `image.source` from
|
|
1537
1545
|
`kirigami.yaml`, generate a resized/re-encoded file under `image.dest` (only
|
|
1538
1546
|
when missing or stale), or return a `CACHE`-backed list of representative
|
|
1539
|
-
colours.
|
|
1547
|
+
colours. `IMG::palette()` extracts them with the Aura extension (vibrant and
|
|
1548
|
+
muted swatches, each in a dark and a light variant), so it returns at most six
|
|
1549
|
+
colours, most populated first; asking for more returns what Aura found. Both
|
|
1550
|
+
are equally usable from your own PHP.
|
|
1540
1551
|
|
|
1541
1552
|
`IMG::asset()` is the single implementation behind the [`<img asset>` tag](#built-in-tags)
|
|
1542
1553
|
too — the tag is just a thin wrapper. Generated files are named after the source
|
|
@@ -1602,7 +1613,7 @@ STR::normalize(string $str): string
|
|
|
1602
1613
|
STR::slug(string $str, string $sep = ''): string
|
|
1603
1614
|
```
|
|
1604
1615
|
|
|
1605
|
-
`STR::replaceTags()` is the engine behind `PREPROS::registerTag()`. It finds all occurrences of `<tagname ...>...</tagname>` in an HTML string and replaces each with the return value of `$callback($fullMatch, $attrs, $body)`.
|
|
1616
|
+
`STR::replaceTags()` is the engine behind `PREPROS::registerTag()`. It finds all occurrences of `<tagname ...>...</tagname>` in an HTML string and replaces each with the return value of `$callback($fullMatch, $attrs, $body)`. Occurrences inside Markdown code (an inline code span or a fenced block) are left as written, so a tag shown as an example is not run.
|
|
1606
1617
|
|
|
1607
1618
|
`STR::trimIndent()` strips the common leading whitespace from a multi-line string — handy when pulling content out of indented `<markdown>` blocks.
|
|
1608
1619
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirigami/php-prepros",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kirigami",
|
|
@@ -332,6 +332,62 @@ class IMG
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Extracts a color palette with Aura (the static `aura` extension), which
|
|
337
|
+
* picks up to six named swatches (vibrant, muted, each in a dark and a
|
|
338
|
+
* light variant) instead of clustering N arbitrary colors.
|
|
339
|
+
*
|
|
340
|
+
* Swatches Aura had to fake (`was_ineligible`) are dropped, then colors
|
|
341
|
+
* are ordered by descending population and deduplicated.
|
|
342
|
+
*
|
|
343
|
+
* @param int $numColors Number of colors to return (at most 6).
|
|
344
|
+
* @return string[] Colors as "#rrggbb".
|
|
345
|
+
*/
|
|
346
|
+
public function getAuraColors(int $numColors = 5): array
|
|
347
|
+
{
|
|
348
|
+
if (!class_exists('Aura\Palette')) throw new Exception("The aura extension is not available.");
|
|
349
|
+
|
|
350
|
+
// Aura reads raw RGBA bytes. A small sample is plenty to find the
|
|
351
|
+
// dominant colors, and keeps the PHP pixel loop cheap.
|
|
352
|
+
$srcW = $this->width;
|
|
353
|
+
$srcH = $this->height;
|
|
354
|
+
$scale = min(1, 150 / max($srcW, $srcH));
|
|
355
|
+
$w = max(1, (int) round($srcW * $scale));
|
|
356
|
+
$h = max(1, (int) round($srcH * $scale));
|
|
357
|
+
|
|
358
|
+
$sample = imagecreatetruecolor($w, $h);
|
|
359
|
+
imagealphablending($sample, false);
|
|
360
|
+
imagesavealpha($sample, true);
|
|
361
|
+
imagefill($sample, 0, 0, imagecolorallocatealpha($sample, 0, 0, 0, 127));
|
|
362
|
+
imagecopyresampled($sample, $this->im, 0, 0, 0, 0, $w, $h, $srcW, $srcH);
|
|
363
|
+
|
|
364
|
+
$rgba = '';
|
|
365
|
+
for ($y = 0; $y < $h; $y++) {
|
|
366
|
+
for ($x = 0; $x < $w; $x++) {
|
|
367
|
+
$c = imagecolorat($sample, $x, $y);
|
|
368
|
+
$alpha = 255 - (($c >> 24) & 127) * 2; // GD alpha is 0 (opaque) to 127
|
|
369
|
+
$rgba .= chr(($c >> 16) & 255) . chr(($c >> 8) & 255) . chr($c & 255) . chr(min(255, $alpha));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
$palette = \Aura\Palette::generate($rgba);
|
|
374
|
+
|
|
375
|
+
$swatches = [];
|
|
376
|
+
foreach (['vibrant', 'vibrant_dark', 'vibrant_light', 'muted', 'muted_dark', 'muted_light'] as $name) {
|
|
377
|
+
$swatch = $palette->$name;
|
|
378
|
+
if ($swatch && !$swatch->was_ineligible) $swatches[] = $swatch;
|
|
379
|
+
}
|
|
380
|
+
usort($swatches, fn($a, $b) => $b->population <=> $a->population);
|
|
381
|
+
|
|
382
|
+
$colors = [];
|
|
383
|
+
foreach ($swatches as $swatch) {
|
|
384
|
+
$colors[] = sprintf('#%02x%02x%02x', $swatch->color->r, $swatch->color->g, $swatch->color->b);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return array_slice(array_values(array_unique($colors)), 0, $numColors);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
335
391
|
/**
|
|
336
392
|
* The AVIF encoder (libavif/aom) used by imageavif() can fail with
|
|
337
393
|
* "Encoding of color planes failed" in two common cases:
|
|
@@ -463,10 +519,10 @@ class IMG
|
|
|
463
519
|
{
|
|
464
520
|
$srcfile = FS::pathJoin(PREPROS::$config->image->source, $path);
|
|
465
521
|
if(!$srcinfo = PREPROS::fstat($srcfile)) throw new Exception("Invalid image file.");
|
|
466
|
-
$key = '
|
|
522
|
+
$key = 'aura_palette_' . STR::shorthash("{$srcfile}:{$srcinfo->modifiedAt}:{$colors}");
|
|
467
523
|
if($palette = CACHE::get($key)) return $palette;
|
|
468
524
|
if(!$localfile = current(PREPROS::mount($srcfile))) throw new Exception("Can't mount image.");
|
|
469
|
-
if(!$palette = (new self($localfile))->
|
|
525
|
+
if(!$palette = (new self($localfile))->getAuraColors($colors)) throw new Exception("Can't extract palette from image \"{$path}\".");
|
|
470
526
|
CACHE::set($key, $palette);
|
|
471
527
|
return $palette;
|
|
472
528
|
}
|
|
@@ -18,7 +18,27 @@ class STR
|
|
|
18
18
|
. '|<' . $t . '([^>]*)/>'
|
|
19
19
|
. '|<' . $t . '([^>]*)>#msi';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// A tag written inside Markdown code (a fenced block or an inline
|
|
22
|
+
// code span) is example text, not a tag: `<markdown>` shown in a
|
|
23
|
+
// code span must not close the real block around it, and
|
|
24
|
+
// `<img asset="...">` shown in one must not become an image. Mask
|
|
25
|
+
// that code before matching and put it back afterwards, both in the
|
|
26
|
+
// output and in what the callback receives.
|
|
27
|
+
$code = [];
|
|
28
|
+
if (!str_contains($contents, "\x1A")) $contents = preg_replace_callback(
|
|
29
|
+
'#^[ \t]*(`{3,}|~{3,})[^\n]*\n.*?^[ \t]*\1[ \t]*$'
|
|
30
|
+
. '|(?<!`)(`+)(?!`)[^\n]*?(?<!`)\2(?!`)#ms',
|
|
31
|
+
function ($m) use (&$code) {
|
|
32
|
+
$code[] = $m[0];
|
|
33
|
+
return "\x1A" . (count($code) - 1) . "\x1A";
|
|
34
|
+
},
|
|
35
|
+
$contents
|
|
36
|
+
);
|
|
37
|
+
$restore = fn(?string $s) => $s === null || !$code ? $s
|
|
38
|
+
: preg_replace_callback("#\x1A(\d+)\x1A#", fn($m) => $code[$m[1]], $s);
|
|
39
|
+
|
|
40
|
+
$result = preg_replace_callback($pattern, function ($m) use ($clb, $restore) {
|
|
41
|
+
$m = array_map($restore, $m);
|
|
22
42
|
if (isset($m[1]) || isset($m[2])) {
|
|
23
43
|
// paired form: <tag>content</tag>
|
|
24
44
|
$attrs = $m[1] ?? '';
|
|
@@ -34,6 +54,8 @@ class STR
|
|
|
34
54
|
}
|
|
35
55
|
return call_user_func($clb, $m[0], self::parseHtmlAttributes($attrs), $inner);
|
|
36
56
|
}, $contents, -1, $count, PREG_UNMATCHED_AS_NULL); // <-- the flag that fixes everything
|
|
57
|
+
|
|
58
|
+
return $restore($result);
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
|