Files
wordpress-kratos-yang/inc/avatar-styles/pixel_art_neutral.php
T
laoyang 3fcaf25c58 3.2.36 新增「自动头像」:按邮箱本地生成,14 套 CC0/MIT 风格可选 #5
为没有头像的访客按邮箱确定性地生成一张 SVG 头像,替代 Gravatar
那个灰底"神秘人"(cn.gravatar.com 在国内已超时,不勾加速时头像
干脆是空白)。全程纯 PHP,零外网请求、零落盘。

后台「主题设置」新增「自动头像」复选框 + 风格列表:勾上之后才
出现 14 套风格,点一下即时换 16 格预览,保存后全站生效。

实现要点(都是踩出来的):
- PRNG 必须与 DiceBear 9.x 逐位一致:xorshift + hashSeed 全程跑在
  JS 的 int32 语义里,PHP 64 位整数要逐步 i32() 归一;
  charCodeAt 按 UTF-16 码元取值,emoji 必须真做代理对拆分。
- pickComponent 有简繁两版:只有 glass/rings/shapes/thumbs 额外取
  三次整数(rotation/offsetX/offsetY)。给简版也补三次 ⇒ PRNG 多走
  3N 步 ⇒ 31 套里 27 套全错。编译期算 pc_has_ints 才治得住。
- 自带人类调色板:官方默认配色几乎全单值,30 个邮箱只出 2 种配色,
  那才是"默认头像丑"的真因,光换画风解决不了。
- 只收录 CC0/MIT(13 CC0 + 1 MIT),CC BY 那 13 套要求署名,一律不收——
  主题装给用户用,用户不会维护署名,收进来等于转嫁合规义务。

接入 get_avatar 的两个关键修正:
- 必须先看原 src 是不是 Gravatar 家族域名再决定是否替换。原先无条件
  替换会把媒体库头像、外链头像、别的插件处理过的 data: 头像一起盖掉。
- 必须删 srcset。只改 src 留着 srcset,浏览器优先用 srcset,
  生成的图根本不会被加载(现象是"代码明明换了,页面还是老样子"),
  且不能重建 <img> 标签(会丢掉评论头像 CSS 依赖的 class)。

后台三处必须这么做:
- body class 由 PHP 输出(admin_body_class),否则每次进设置页/保存后
  重载都会先显示风格列表再藏起来,闪一下。
- 事件监听 radio 的 change,不能监听 label 的 click —— label 会把
  click 转发给关联 input,冒泡回来再触发一次 ⇒ 同一风格发两次请求
  (实测 hits 2→4)。
- 首屏已渲染的 16 格写进 JS 缓存,否则点一圈回到默认风格会白请求一次。

不依赖任何可选扩展:不用 mbstring(未安装时 mb_substr 是 Fatal error,
整站白屏)、不用 gd/imagick。UTF-8 切分/截断/大写全部自己实现。
PHP 7.0 语法,PHP 8.5 下 E_ALL 无任何 warning。

验证:
- 14 套 × 6 种子 = 84 个用例与 DiceBear 官方 Node 输出逐字节一致,
  PHP 8.4 / 8.5 / 无 mbstring(php -n)三种环境均 84/0。
- 静态守卫 55/0;反向验证 17 项注入全部被捕获。
- 后台面板浏览器验证 26/0(显隐、几何、点选、缓存、连点、零报错)。
- 包内内容契约 24/0;包校验反向验证 6 项注入全部被捕获。
- 单次渲染稳态 0.047~0.105ms,峰值内存 2~4MB。
2026-10-25 18:38:11 +08:00

129 lines
11 KiB
PHP

<?php
return array(
'name' => 'pixel-art-neutral',
'meta' => array(
'title' => 'Pixel Art Neutral',
'creator' => 'DiceBear',
'source' => 'https://www.figma.com/community/file/1198754108850888330',
'license_name' => 'CC0 1.0',
'license_url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
),
'view_box' => '0 0 14 14',
'clip_default' => true,
'has_pre_create' => false,
'has_post_create' => false,
'is_initials' => false,
'attr_pairs' => array(
array('viewBox', '0 0 14 14'),
array('fill', 'none'),
array('shape-rendering', 'crispEdges'),
),
'bg_colors' => array('ffdbac', 'f5cfa0', 'eac393', 'e0b687', 'cb9e6e', 'b68655', 'a26d3d', '8d5524'),
'bg_type' => array('solid'),
'bg_rotation' => array('0', '360'),
'body_template' => '<g transform="translate(-1 -2)">@@k:eyes@@</g><g transform="translate(-1 -2)">@@k:glasses@@</g><g transform="translate(-1)">@@k:mouth@@</g>',
'pick_order' => array('eyes', 'glasses', 'mouth'),
'bool_order' => array('glasses'),
'color_order' => array(
'eyes' => 'eyesColor',
'glasses' => 'glassesColor',
'mouth' => 'mouthColor',
),
'pc_has_ints' => false,
'pc_meta' => array(
array(
'group' => 'eyes',
'values_opt' => 'eyes',
'width' => 0,
'height' => 0,
'rotation' => array('opt' => null, 'fallback' => array('0')),
'offsetX' => array('opt' => null, 'fallback' => array('0')),
'offsetY' => array('opt' => null, 'fallback' => array('0')),
),
array(
'group' => 'glasses',
'values_opt' => 'glasses',
'width' => 0,
'height' => 0,
'rotation' => array('opt' => null, 'fallback' => array('0')),
'offsetX' => array('opt' => null, 'fallback' => array('0')),
'offsetY' => array('opt' => null, 'fallback' => array('0')),
),
array(
'group' => 'mouth',
'values_opt' => 'mouth',
'width' => 0,
'height' => 0,
'rotation' => array('opt' => null, 'fallback' => array('0')),
'offsetX' => array('opt' => null, 'fallback' => array('0')),
'offsetY' => array('opt' => null, 'fallback' => array('0')),
),
),
'defaults' => array(
'backgroundColor' => array('ffdbac', 'f5cfa0', 'eac393', 'e0b687', 'cb9e6e', 'b68655', 'a26d3d', '8d5524'),
'eyes' => array('variant12', 'variant11', 'variant10', 'variant09', 'variant08', 'variant07', 'variant06', 'variant05', 'variant04', 'variant03', 'variant02', 'variant01'),
'eyesColor' => array('76778b', '697b94', '647b90', '5b7c8b', '588387', '876658'),
'glasses' => array('light07', 'light06', 'light05', 'light04', 'light03', 'light02', 'light01', 'dark07', 'dark06', 'dark05', 'dark04', 'dark03', 'dark02', 'dark01'),
'glassesColor' => array('4b4b4b', '323232', '191919', '43677d', '5f705c', 'a04b5d'),
'glassesProbability' => 10,
'mouth' => array('sad10', 'sad09', 'sad08', 'sad07', 'sad06', 'sad05', 'sad04', 'sad03', 'sad02', 'sad01', 'happy13', 'happy12', 'happy11', 'happy10', 'happy09', 'happy08', 'happy07', 'happy06', 'happy05', 'happy04', 'happy03', 'happy02', 'happy01'),
'mouthColor' => array('d29985', 'c98276', 'e35d6a', 'de0f0d'),
),
'groups' => array(
'eyes' => array(
'variant12' => '<g fill-rule="evenodd" clip-rule="evenodd"><path d="M5 6h1v2H4V7h1V6Zm5 0h1v2H9V7h1V6Z" fill="#fff"/><path d="M6 6v1H5v1h2V6H6Zm5 0v1h-1v1h2V6h-1Z" fill="@@c:eyes@@"/><path d="M6 6v1h1V6H6ZM5 7v1h1V7H5Zm6-1v1h1V6h-1Zm-1 1v1h1V7h-1Z" fill="#fff" fill-opacity=".5"/></g>',
'variant11' => '<path fill="#fff" d="M4 5h3v3H4zM9 5h3v3H9z"/><path fill="@@c:eyes@@" d="M4 6h2v1H4zM9 6h2v1H9z"/><path fill="#fff" fill-opacity=".7" d="M4 6h1v1H4zM9 6h1v1H9z"/>',
'variant10' => '<path fill="#fff" d="M4 5h3v3H4zM9 5h3v3H9z"/><path fill="@@c:eyes@@" d="M10 5h2v2h-2zM5 5h2v2H5z"/><path fill="#fff" fill-opacity=".4" d="M5 5h1v1H5zM6 6h1v1H6zM10 5h1v1h-1zM11 6h1v1h-1z"/><path fill="#fff" fill-opacity=".7" d="M11 5h1v1h-1zM6 5h1v1H6z"/>',
'variant09' => '<path fill="#fff" d="M4 5h3v3H4zM9 5h3v3H9z"/><path fill="@@c:eyes@@" d="M10 6h1v1h-1zM5 6h1v1H5z"/>',
'variant08' => '<path fill="#fff" d="M4 5h3v2H4zM9 5h3v2H9z"/><path fill="@@c:eyes@@" d="M9 6h1v1H9zM4 6h1v1H4z"/>',
'variant07' => '<path fill="#fff" d="M4 5h3v2H4zM9 5h3v2H9z"/><path fill="@@c:eyes@@" d="M11 6h1v1h-1zM6 6h1v1H6z"/>',
'variant06' => '<path fill="#fff" d="M4 5h3v2H4z"/><path fill="@@c:eyes@@" d="M5 6h1v1H5z"/><path fill="#fff" d="M9 5h3v2H9z"/><path fill="@@c:eyes@@" d="M10 6h1v1h-1z"/>',
'variant05' => '<path fill="#fff" d="M4 5h3v2H4zM9 5h3v2H9z"/><path d="M11 5h-1v1H9v1h3V6h-1V5Z" fill="@@c:eyes@@"/><path fill="#fff" fill-opacity=".5" d="M9 6h1v1H9zM10 5h1v1h-1zM11 6h1v1h-1z"/><path d="M6 5H5v1H4v1h3V6H6V5Z" fill="@@c:eyes@@"/><path fill="#fff" fill-opacity=".5" d="M4 6h1v1H4zM5 5h1v1H5zM6 6h1v1H6z"/>',
'variant04' => '<path d="M6 5H5v2h2V6H6V5ZM11 5h-1v2h2V6h-1V5Z" fill="#fff"/><path fill="@@c:eyes@@" d="M5 6h1v1H5zM10 6h1v1h-1z"/>',
'variant03' => '<path d="M10 5h1v2H9V6h1V5ZM5 5h1v2H4V6h1V5Z" fill="#fff"/><path fill="@@c:eyes@@" d="M11 6h-1v1h1zM6 6H5v1h1z"/>',
'variant02' => '<g fill-rule="evenodd" clip-rule="evenodd"><path d="M11 5h-1v2h2V6h-1V5ZM6 5H5v2h2V6H6V5Z" fill="#fff"/><path d="M10 5v1h1v1H9V5h1ZM5 5v1h1v1H4V5h1Z" fill="@@c:eyes@@"/><path d="M10 5v1H9V5h1Zm1 1v1h-1V6h1ZM5 5v1H4V5h1Zm1 1v1H5V6h1Z" fill="#fff" fill-opacity=".5"/></g>',
'variant01' => '<path fill="#fff" d="M12 5H9v3h3zM7 5H4v3h3z"/><path fill="@@c:eyes@@" d="M12 6h-2v1h2zM7 6H5v1h2z"/><path fill="#fff" fill-opacity=".7" d="M12 6h-1v1h1zM7 6H6v1h1z"/>',
),
'glasses' => array(
'light07' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4H9v1H7V4H4v1H2v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5h-2V4Zm0 1v3H9V5h3ZM7 8H4V5h3v3Z" fill="@@c:glasses@@"/><path fill="#fff" fill-opacity=".3" d="M9 5h3v3H9zM4 5h3v3H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM13 5h1v1h-1z"/>',
'light06' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5H2Zm10 3H9V6h3v2ZM7 8H4V6h3v2Z" fill="@@c:glasses@@"/><path fill="#fff" fill-opacity=".3" d="M9 6h3v2H9zM4 6h3v2H4z"/><path fill="#fff" fill-opacity=".2" d="M7 5h2v1H7zM2 5h2v1H2zM12 5h2v1h-2z"/>',
'light05' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5H9v1H7V5H2Zm5 1v2H4V6h3Zm2 0v2h3V6H9Z" fill="@@c:glasses@@"/><path fill="#fff" fill-opacity=".3" d="M4 6h3v2H4zM9 6h3v2H9z"/><path fill="#fff" fill-opacity=".2" d="M2 5h2v1H2zM12 5h2v1h-2z"/>',
'light04' => '<path fill="#fff" fill-opacity=".3" d="M4 5h3v3H4zM9 5h3v3H9z"/><path fill="@@c:glasses@@" d="M3 5h1v1H3zM7 5h2v1H7zM12 5h1v1h-1z"/>',
'light03' => '<path fill="@@c:glasses@@" d="M2 5h12v1H2zM3 7h10v1H3zM3 6h1v1H3zM12 6h1v1h-1z"/><path fill="#fff" fill-opacity=".3" d="M4 6h8v1H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM13 5h1v1h-1z"/><path fill="#000" fill-opacity=".3" d="M7 6h2v1H7z"/>',
'light02' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M14 5H2v1h1v2h4V7h2v1h4V6h1V5Zm-2 1H9v1h3V6ZM7 7H4V6h3v1Z" fill="@@c:glasses@@"/><path fill="#fff" fill-opacity=".3" d="M4 6h3v1H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM7 5h2v1H7zM13 5h1v1h-1z"/><path fill="#fff" fill-opacity=".3" d="M9 6h3v1H9z"/>',
'light01' => '<path d="M2 5h5v1H4v1H3V6H2V5ZM7 7v1H4V7h3ZM9 7H7V6h2v1ZM12 7H9v1h3V7ZM12 6H9V5h5v1h-1v1h-1V6Z" fill="@@c:glasses@@"/><path fill="#fff" fill-opacity=".3" d="M4 6h3v1H4zM9 6h3v1H9z"/><path fill="#fff" fill-opacity=".2" d="M12 5h2v1h-2zM2 5h2v1H2zM7 6h2v1H7z"/>',
'dark07' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4H9v1H7V4H4v1H2v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5h-2V4Zm0 1v3H9V5h3ZM7 8H4V5h3v3Z" fill="@@c:glasses@@"/><path fill="#000" fill-opacity=".3" d="M9 5h3v3H9zM4 5h3v3H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM13 5h1v1h-1z"/>',
'dark06' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5H2Zm10 3H9V6h3v2ZM7 8H4V6h3v2Z" fill="@@c:glasses@@"/><path fill="#000" fill-opacity=".3" d="M9 6h3v2H9zM4 6h3v2H4z"/><path fill="#fff" fill-opacity=".2" d="M7 5h2v1H7zM2 5h2v1H2zM12 5h2v1h-2z"/>',
'dark05' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5v1h1v2h1v1h3V8h2v1h3V8h1V6h1V5H9v1H7V5H2Zm5 1v2H4V6h3Zm2 0v2h3V6H9Z" fill="@@c:glasses@@"/><path fill="#000" fill-opacity=".3" d="M4 6h3v2H4zM9 6h3v2H9z"/><path fill="#fff" fill-opacity=".2" d="M2 5h2v1H2zM12 5h2v1h-2z"/>',
'dark04' => '<path fill="#000" fill-opacity=".3" d="M4 5h3v3H4zM9 5h3v3H9z"/><path fill="@@c:glasses@@" d="M3 5h1v1H3zM7 5h2v1H7zM12 5h1v1h-1z"/>',
'dark03' => '<path fill="@@c:glasses@@" d="M2 5h12v1H2zM3 7h10v1H3zM3 6h1v1H3zM12 6h1v1h-1z"/><path fill="#000" fill-opacity=".3" d="M4 6h8v1H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM13 5h1v1h-1z"/><path fill="#000" fill-opacity=".3" d="M7 6h2v1H7z"/>',
'dark02' => '<path fill-rule="evenodd" clip-rule="evenodd" d="M14 5H2v1h1v2h4V7h2v1h4V6h1V5Zm-2 1H9v1h3V6ZM7 7H4V6h3v1Z" fill="@@c:glasses@@"/><path fill="#000" fill-opacity=".3" d="M4 6h3v1H4z"/><path fill="#fff" fill-opacity=".2" d="M2 5h1v1H2zM7 5h2v1H7zM13 5h1v1h-1z"/><path fill="#000" fill-opacity=".3" d="M9 6h3v1H9z"/>',
'dark01' => '<path d="M2 5h5v1H4v1H3V6H2V5ZM7 7v1H4V7h3ZM9 7H7V6h2v1ZM12 7H9v1h3V7ZM12 6H9V5h5v1h-1v1h-1V6Z" fill="@@c:glasses@@"/><path fill="#000" fill-opacity=".3" d="M4 6h3v1H4zM9 6h3v1H9z"/><path fill="#fff" fill-opacity=".2" d="M12 5h2v1h-2zM2 5h2v1H2zM7 6h2v1H7z"/>',
),
'mouth' => array(
'sad10' => '<path d="M7 9v1h1v1h1V9H7Z" fill="@@c:mouth@@"/>',
'sad09' => '<path d="M6 10v1h1v-1h3V9H7v1H6Z" fill="@@c:mouth@@"/>',
'sad08' => '<path d="M6 9v1h3v1h1v-1H9V9H6Z" fill="@@c:mouth@@"/>',
'sad07' => '<path d="M7 9h2v1H7V9Z" fill="@@c:mouth@@"/>',
'sad06' => '<path d="M6 9h4v1H6V9Z" fill="@@c:mouth@@"/>',
'sad05' => '<path d="M7 9h3v1H7V9Z" fill="@@c:mouth@@"/>',
'sad04' => '<path d="M6 9h3v1H6V9Z" fill="@@c:mouth@@"/>',
'sad03' => '<path d="M9 9v1H7v1H6v-1h1V9h2Z" fill="@@c:mouth@@"/>',
'sad02' => '<path d="M7 9v1h2v1h1v-1H9V9H7Z" fill="@@c:mouth@@"/>',
'sad01' => '<path d="M7 9h2v1H7V9ZM7 10v1H6v-1h1ZM9 10v1h1v-1H9Z" fill="@@c:mouth@@"/>',
'happy13' => '<path d="M6 10v1h1v1h2v-1h1v-1H9V9H7v1H6Z" fill="@@c:mouth@@"/><path d="M7 10v1h2v-1H7Z" fill="#fff" fill-opacity=".2"/>',
'happy12' => '<path d="M6 10v1h1v1h2v-1h1v-1H9V9H7v1H6Z" fill="@@c:mouth@@"/><path d="M7 10v1h2v-1H7Z" fill="#fff"/>',
'happy11' => '<path d="M6 9v1h1v1h2v-1H7V9H6Z" fill="@@c:mouth@@"/>',
'happy10' => '<path d="M10 9v1H9v1H7v-1h2V9h1Z" fill="@@c:mouth@@"/>',
'happy09' => '<path d="M7 11h2v-1H7v1ZM7 10V9H6v1h1ZM9 10V9h1v1H9Z" fill="@@c:mouth@@"/>',
'happy08' => '<path d="M6 11v-1h3V9h1v1H9v1H6Z" fill="@@c:mouth@@"/>',
'happy07' => '<path d="M10 11v-1H7V9H6v1h1v1h3Z" fill="@@c:mouth@@"/>',
'happy06' => '<path d="M6 9v1h1v1h2v-1h1V9H6Z" fill="@@c:mouth@@"/>',
'happy05' => '<path d="M7 9v2h2v-1H8V9H7Z" fill="@@c:mouth@@"/><path d="M9 11v-1H8V9H7v1h1v1h1Z" fill="#fff" fill-opacity=".2"/>',
'happy04' => '<path d="M9 9v1H8v1h2V9H9Z" fill="@@c:mouth@@"/>',
'happy03' => '<path d="M7 9v1h1v1H6V9h1Z" fill="@@c:mouth@@"/>',
'happy02' => '<path d="M8 10v1h1v-1H8Z" fill="@@c:mouth@@"/>',
'happy01' => '<path d="M7 9v2h2V9H7Z" fill="@@c:mouth@@"/>',
),
),
);