Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
49 lines
1.7 KiB
PHP
49 lines
1.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Integrations\Divi\Design;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* Names that resemble an unknown font name, for the hint of a validation error (ruling W5). Case-insensitive;
|
|
* ranked: the same name in another case, a name that starts with it, one that contains it, then a small
|
|
* Levenshtein distance (at most max(3, length / 3)). Within a rank the closer, then the shorter, then the
|
|
* alphabetically first name wins, so the order is deterministic. Pure PHP.
|
|
*/
|
|
final class FontSuggest
|
|
{
|
|
/**
|
|
* @param list<string> $names
|
|
* @return list<string> At most $limit names.
|
|
*/
|
|
public static function similar(string $name, array $names, int $limit = 5): array
|
|
{
|
|
$needle = mb_strtolower(trim($name));
|
|
if ($needle === '' || $limit < 1) {
|
|
return [];
|
|
}
|
|
$maxDistance = max(3, intdiv(mb_strlen($needle), 3));
|
|
$ranked = [];
|
|
foreach (array_unique($names) as $candidate) {
|
|
$lower = mb_strtolower($candidate);
|
|
$distance = levenshtein($needle, $lower);
|
|
$rank = match (true) {
|
|
$lower === $needle => 0,
|
|
str_starts_with($lower, $needle) => 1,
|
|
str_contains($lower, $needle) => 2,
|
|
$distance <= $maxDistance => 3,
|
|
default => null,
|
|
};
|
|
if ($rank !== null) {
|
|
$ranked[] = [$rank, $distance, mb_strlen($candidate), $candidate];
|
|
}
|
|
}
|
|
usort($ranked, static fn (array $a, array $b): int => $a[0] <=> $b[0] ?: $a[1] <=> $b[1] ?: $a[2] <=> $b[2]
|
|
?: strcmp($a[3], $b[3]));
|
|
|
|
return array_slice(array_column($ranked, 3), 0, $limit);
|
|
}
|
|
}
|