@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 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. Both are equally usable from your own PHP.
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.0.0",
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 = 'palette_' . STR::shorthash("{$srcfile}:{$srcinfo->modifiedAt}:{$colors}");
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))->getRepresentativeColors($colors)) throw new Exception("Can't extract palette from image \"{$path}\".");
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
- return preg_replace_callback($pattern, function ($m) use ($clb) {
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