boring_avatars 0.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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +62 -0
- data/THIRD_PARTY_NOTICES.md +30 -0
- data/docs/architecture.md +161 -0
- data/docs/core-library.md +257 -0
- data/docs/rails-binding.md +152 -0
- data/docs/testing-and-compatibility.md +201 -0
- data/lib/boring_avatars/bindings/rails/view_helper.rb +105 -0
- data/lib/boring_avatars/bindings/rails.rb +11 -0
- data/lib/boring_avatars/identifier.rb +25 -0
- data/lib/boring_avatars/input.rb +155 -0
- data/lib/boring_avatars/name_hash.rb +19 -0
- data/lib/boring_avatars/renderer.rb +91 -0
- data/lib/boring_avatars/svg/element.rb +16 -0
- data/lib/boring_avatars/svg/serializer.rb +67 -0
- data/lib/boring_avatars/svg/value.rb +17 -0
- data/lib/boring_avatars/utilities.rb +34 -0
- data/lib/boring_avatars/variants/bauhaus.rb +76 -0
- data/lib/boring_avatars/variants/beam.rb +130 -0
- data/lib/boring_avatars/variants/marble.rb +85 -0
- data/lib/boring_avatars/variants/pixel.rb +49 -0
- data/lib/boring_avatars/variants/ring.rb +42 -0
- data/lib/boring_avatars/variants/sunset.rb +57 -0
- data/lib/boring_avatars/variants.rb +32 -0
- data/lib/boring_avatars/version.rb +5 -0
- data/lib/boring_avatars.rb +34 -0
- data/rbi/boring_avatars.rbi +70 -0
- data/sig/boring_avatars.rbs +44 -0
- metadata +155 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0fe788777bdbe128ca571605e27acdbf767b23d5c036913f41acec0649f3a7d9
|
|
4
|
+
data.tar.gz: 2cf67f95d36958e6a87eb01ca31743963d6013f34321419df870cbeac2180d75
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9aecf93793fe46700d1493150fa9bd04955ad4384c6215953e85a3789170b3ee1f61e38e7f44815a9f740068a57c8283bfaeb14e36b4bff6e42010711ae23fee
|
|
7
|
+
data.tar.gz: d41d796bdc830f91644dce2c10db0011e935beacfeb295cd31d36f757109de0354b72917b0463dae0dc5459e820cd9457c224a1e9add2ea4237020c002f722c0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 boring_avatars contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# boring_avatars
|
|
2
|
+
|
|
3
|
+
`boring_avatars` is a Ruby port of [Boring Avatars](https://boringavatars.com/).
|
|
4
|
+
It provides a framework-independent SVG generator and an opt-in Rails View
|
|
5
|
+
Helper.
|
|
6
|
+
|
|
7
|
+
## Core
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require "boring_avatars"
|
|
11
|
+
|
|
12
|
+
svg = BoringAvatars.generate(
|
|
13
|
+
"Maria Mitchell",
|
|
14
|
+
variant: :beam,
|
|
15
|
+
size: 64,
|
|
16
|
+
colors: ["#264653", "#2A9D8F", "#E9C46A", "#F4A261", "#E76F51"]
|
|
17
|
+
)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Available variants are `marble`, `beam`, `pixel`, `sunset`, `ring`, and
|
|
21
|
+
`bauhaus`.
|
|
22
|
+
|
|
23
|
+
## Rails
|
|
24
|
+
|
|
25
|
+
Load the Rails binding explicitly:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
gem "boring_avatars", require: "boring_avatars/bindings/rails"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then use the helper from a view:
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<%= boring_avatar(
|
|
35
|
+
current_user.email,
|
|
36
|
+
variant: :bauhaus,
|
|
37
|
+
class: "avatar",
|
|
38
|
+
aria: { label: current_user.name }
|
|
39
|
+
) %>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See [the architecture documentation](docs/architecture.md) for the complete
|
|
43
|
+
API and compatibility policy.
|
|
44
|
+
|
|
45
|
+
## Type signatures
|
|
46
|
+
|
|
47
|
+
The gem ships both type signature formats:
|
|
48
|
+
|
|
49
|
+
- Sorbet RBI: `rbi/boring_avatars.rbi`
|
|
50
|
+
- RBS: `sig/boring_avatars.rbs`
|
|
51
|
+
|
|
52
|
+
Run `bundle exec rake typecheck` to validate both definitions. This runs Sorbet
|
|
53
|
+
and Steep contract checks and fails unless every public API is covered in both
|
|
54
|
+
formats with zero untyped Sorbet usages (100% public API type coverage). Tapioca
|
|
55
|
+
can import the RBI exported from the gem's `rbi/` directory, while RBS can load
|
|
56
|
+
the installed gem signature by library name.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
The gem is available under the MIT License. See
|
|
61
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for the upstream Boring
|
|
62
|
+
Avatars notice.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The avatar generation algorithms and SVG artwork are derived from
|
|
4
|
+
[boringdesigners/boring-avatars](https://github.com/boringdesigners/boring-avatars)
|
|
5
|
+
at commit `d0ff2582a8921b643a89de4a4912be28938a828b`.
|
|
6
|
+
|
|
7
|
+
## boring-avatars
|
|
8
|
+
|
|
9
|
+
MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2021 boringdesigners
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Boring Avatars Ruby アーキテクチャ
|
|
2
|
+
|
|
3
|
+
## この文書について
|
|
4
|
+
|
|
5
|
+
この文書は、Boring Avatars の Ruby 実装である `boring_avatars` gem の全体設計と実装順序を定義する。描画アルゴリズムの詳細は [Core ライブラリ設計](core-library.md)、Rails 固有部分は [Rails binding 設計](rails-binding.md)、互換性と検証方法は [テスト・互換性設計](testing-and-compatibility.md) を参照する。
|
|
6
|
+
|
|
7
|
+
移植元は `boringdesigners/boring-avatars` の commit [`d0ff2582a8921b643a89de4a4912be28938a828b`](https://github.com/boringdesigners/boring-avatars/tree/d0ff2582a8921b643a89de4a4912be28938a828b) に固定する。移植元の `package.json` は 2.0.4 だが、GitHub 上の最新リリースは v2.0.2 であるため、バージョン番号ではなく full SHA を互換性の基準とする。
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
- 名前とカラーパレットから、外部サービスを使わず決定的に SVG avatar を生成する。
|
|
12
|
+
- React 版の6 variantについて、ハッシュ、色選択、図形、transformの描画結果を移植する。
|
|
13
|
+
- Rails 非依存のCoreと、明示的に読み込むRails View Helperを単一gemで提供する。
|
|
14
|
+
- Core利用時にはRails、ActiveSupport、ActionViewをインストール・ロードさせない。
|
|
15
|
+
- 入力検証と一元化したXML serializerにより、生成結果をinline SVGとして安全に扱えるようにする。
|
|
16
|
+
|
|
17
|
+
## 対象外
|
|
18
|
+
|
|
19
|
+
- PNGなどSVG以外の画像生成
|
|
20
|
+
- Boring AvatarsのWeb API、Web UI、React component
|
|
21
|
+
- 設定DSL、global configuration、plugin API
|
|
22
|
+
- Rails 7以前の互換shim
|
|
23
|
+
- `geometric`、`abstract` など移植元のdeprecated alias
|
|
24
|
+
- Reactのcomponent treeに依存する `useId` や、SVG文字列のbyte-for-byte互換
|
|
25
|
+
- 移植元の未知variantから `marble` への暗黙fallback
|
|
26
|
+
|
|
27
|
+
## 公開インターフェース
|
|
28
|
+
|
|
29
|
+
gem名は `boring_avatars`、トップレベルnamespaceは `BoringAvatars` とする。Coreの公開メソッドは `BoringAvatars.generate` のみとし、variant rendererやhash utilityは実装詳細とする。
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require "boring_avatars"
|
|
33
|
+
|
|
34
|
+
svg = BoringAvatars.generate(
|
|
35
|
+
"Maria Mitchell",
|
|
36
|
+
variant: :beam,
|
|
37
|
+
colors: ["#264653", "#2A9D8F", "#E9C46A", "#F4A261", "#E76F51"],
|
|
38
|
+
size: "64px",
|
|
39
|
+
square: false,
|
|
40
|
+
title: true,
|
|
41
|
+
id_prefix: nil,
|
|
42
|
+
attributes: {
|
|
43
|
+
class: "avatar",
|
|
44
|
+
:"aria-label" => "Maria Mitchell"
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
戻り値はUTF-8の通常の `String` であり、Coreは `ActiveSupport::SafeBuffer` や `html_safe` を認識しない。
|
|
50
|
+
|
|
51
|
+
Railsでは次のentrypointとhelperだけを公開契約とする。
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require "boring_avatars/bindings/rails"
|
|
55
|
+
|
|
56
|
+
boring_avatar(
|
|
57
|
+
"Maria Mitchell",
|
|
58
|
+
variant: :beam,
|
|
59
|
+
size: 64,
|
|
60
|
+
class: "avatar",
|
|
61
|
+
aria: { label: "Maria Mitchell" },
|
|
62
|
+
data: { controller: "profile-avatar" }
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`boring_avatar` はCoreと同じSVGを `ActiveSupport::SafeBuffer` として返す。helper moduleの定数名は公開契約に含めない。
|
|
67
|
+
|
|
68
|
+
## コンポーネント境界
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
application
|
|
72
|
+
|
|
|
73
|
+
+-- require "boring_avatars"
|
|
74
|
+
| |
|
|
75
|
+
| +-- input validation
|
|
76
|
+
| +-- JavaScript-compatible name hash
|
|
77
|
+
| +-- variant renderer
|
|
78
|
+
| +-- SVG node builder / serializer
|
|
79
|
+
|
|
|
80
|
+
+-- require "boring_avatars/bindings/rails" # opt-in only
|
|
81
|
+
|
|
|
82
|
+
+-- requires Core
|
|
83
|
+
+-- ActiveSupport.on_load(:action_view)
|
|
84
|
+
+-- View Helper
|
|
85
|
+
|
|
|
86
|
+
+-- Rails-style attributes normalization
|
|
87
|
+
+-- random internal ID prefix
|
|
88
|
+
+-- delegates all rendering to Core
|
|
89
|
+
+-- final SafeBuffer conversion
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
依存方向は常にRails bindingからCoreへの一方向とする。Coreからbindingを探索する処理、`defined?(Rails)` による自動検出、Railsがない場合のfallbackは実装しない。
|
|
93
|
+
|
|
94
|
+
## 想定する内部構成
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
lib/
|
|
98
|
+
boring_avatars.rb
|
|
99
|
+
boring_avatars/
|
|
100
|
+
version.rb
|
|
101
|
+
input.rb
|
|
102
|
+
name_hash.rb
|
|
103
|
+
renderer.rb
|
|
104
|
+
svg/
|
|
105
|
+
element.rb
|
|
106
|
+
serializer.rb
|
|
107
|
+
variants/
|
|
108
|
+
marble.rb
|
|
109
|
+
beam.rb
|
|
110
|
+
pixel.rb
|
|
111
|
+
sunset.rb
|
|
112
|
+
ring.rb
|
|
113
|
+
bauhaus.rb
|
|
114
|
+
bindings/
|
|
115
|
+
rails.rb
|
|
116
|
+
rails/
|
|
117
|
+
view_helper.rb
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- `boring_avatars.rb` はCoreの公開facadeと必要なCoreファイルだけを読み込む。
|
|
121
|
+
- `Input` は型、値域、palette、追加属性を検証し、rendererが扱う正規化済みの不変データを作る。
|
|
122
|
+
- `NameHash` はJavaScript互換hashだけを担当する。
|
|
123
|
+
- `Renderer` は共通の `<svg>`、`<title>`、mask、IDを組み立て、選択したvariantへ内部要素の生成を委譲する。
|
|
124
|
+
- variantはSVG文字列を連結せず、serializer用のelement treeを返す。
|
|
125
|
+
- `Svg::Serializer` だけがXML文字列を生成する。variantやRails helperで手作業のescapeや文字列差し込みを行わない。
|
|
126
|
+
- `bindings/rails.rb` はCoreと必要最小限のActiveSupport機能を明示的にrequireし、load hookを登録する。
|
|
127
|
+
|
|
128
|
+
内部定数は公開APIとして文書化せず、可能なものは `private_constant` とする。将来のrefactorで内部ファイル構成を変更しても、公開メソッドとrequire pathは維持できる構造にする。
|
|
129
|
+
|
|
130
|
+
## 生成データフロー
|
|
131
|
+
|
|
132
|
+
1. `BoringAvatars.generate` が引数を受け取る。
|
|
133
|
+
2. `Input` がnameをUTF-8へtranscodeし、variant、palette、size、boolean、ID prefix、追加属性を検証する。
|
|
134
|
+
3. `NameHash` がUTF-16 code unit列からsigned 32-bit hashを計算する。
|
|
135
|
+
4. ID prefixが省略された場合、正規化済みのvariant、name、palette、squareから決定的なprefixを作る。
|
|
136
|
+
5. variant rendererがhashとpaletteから色・座標・transformを計算し、SVG element treeを返す。
|
|
137
|
+
6. 共通rendererがtitle、mask、defs、ルート属性を統合する。
|
|
138
|
+
7. serializerがtext nodeと属性値をescapeし、UTF-8のSVG `String` を返す。
|
|
139
|
+
8. Rails helperの場合に限り、完成済みの文字列を最後に一度だけSafeBufferへ変換する。
|
|
140
|
+
|
|
141
|
+
すべての計算は呼び出しごとのローカルデータで完結させる。Coreに連番、乱数、mutableなglobal configurationを持たせず、同じ入力から同じ文字列を生成する。
|
|
142
|
+
|
|
143
|
+
## エラー方針
|
|
144
|
+
|
|
145
|
+
- 公開引数の型・値・文字encoding・属性が契約に違反した場合は `ArgumentError` を送出する。
|
|
146
|
+
- 未知variantを別variantへ置き換えない。
|
|
147
|
+
- 不正な追加属性を削除・修正せず、呼び出し全体を失敗させる。
|
|
148
|
+
- Rails helperはCoreの例外をrescueしてfallback SVGへ置き換えない。
|
|
149
|
+
- Rails bindingをRails/ActiveSupportのない環境でrequireした場合は、依存元の通常の `LoadError` を伝播させる。
|
|
150
|
+
|
|
151
|
+
独自の例外classは初版では公開しない。利用者が修正すべき入力エラーと、依存が存在しないロードエラーをRuby標準の例外で区別する。
|
|
152
|
+
|
|
153
|
+
## 実装順序
|
|
154
|
+
|
|
155
|
+
1. gem metadataとCore entrypointを作り、Core-only requireがRailsをロードしないことを先に固定する。
|
|
156
|
+
2. 入力モデル、JavaScript互換hash、決定的ID、XML element/serializerを実装し、security testを通す。
|
|
157
|
+
3. 6 variantを1つずつ移植し、各variant追加時にpinned fixtureとの構造比較を通す。
|
|
158
|
+
4. Rails View Helperと `ActiveSupport.on_load(:action_view)` を追加し、require順序を別processで検証する。
|
|
159
|
+
5. Ruby/Rails matrix、built gem内容、ライセンスnoticeを検証して初版をリリースする。
|
|
160
|
+
|
|
161
|
+
各段階で一時的なfallbackや未検証variantを公開しない。6 variantとCore/Rails境界の受け入れ条件がすべて満たされた時点を初版完成とする。
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Core ライブラリ設計
|
|
2
|
+
|
|
3
|
+
## 責務
|
|
4
|
+
|
|
5
|
+
CoreはRailsに依存せず、正規化済みの入力からBoring Avatars互換のinline SVGを生成する。公開APIは `BoringAvatars.generate` だけとし、次の責務を内部で分離する。
|
|
6
|
+
|
|
7
|
+
- 入力検証と正規化
|
|
8
|
+
- JavaScript互換のname hash
|
|
9
|
+
- variant固有パラメーターとSVG要素の生成
|
|
10
|
+
- 内部IDの生成と参照整合性
|
|
11
|
+
- XML 1.0として安全な直列化
|
|
12
|
+
|
|
13
|
+
全体の依存関係とRailsとの境界は [アーキテクチャ](architecture.md)、検証方法は [テスト・互換性設計](testing-and-compatibility.md) を参照する。
|
|
14
|
+
|
|
15
|
+
## 公開API
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
BoringAvatars.generate(
|
|
19
|
+
name,
|
|
20
|
+
variant: :marble,
|
|
21
|
+
colors: ["#92A1C6", "#146A7C", "#F0AB3D", "#C271B4", "#C20D90"],
|
|
22
|
+
size: "40px",
|
|
23
|
+
square: false,
|
|
24
|
+
title: false,
|
|
25
|
+
id_prefix: nil,
|
|
26
|
+
attributes: {}
|
|
27
|
+
) # => String
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 入力契約
|
|
31
|
+
|
|
32
|
+
| 引数 | 契約 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `name` | 必須の `String`。空文字は許可する。有効な文字encodingからUTF-8へ変換できること。trim、case folding、Unicode normalizationは行わない。XML 1.0で使用できないcontrol characterは拒否する。 |
|
|
35
|
+
| `variant` | `Symbol` または `String`。`marble`、`beam`、`pixel`、`sunset`、`ring`、`bauhaus` の完全一致だけを許可する。case変換は行わない。 |
|
|
36
|
+
| `colors` | 1要素以上の `Array<String>`。各要素は `\A#[0-9A-Fa-f]{6}\z` に一致すること。順序と文字caseを保持する。 |
|
|
37
|
+
| `size` | 0より大きい `Integer`、有限の `Float`、または正の10進数に任意で `px`、`em`、`rem`、`%`、`vw`、`vh`、`vmin`、`vmax` を付けた `String`。符号、指数表現、空白、`calc()` は初版では許可しない。 |
|
|
38
|
+
| `square` | `true` または `false` のみ。truthy/falsy変換はしない。 |
|
|
39
|
+
| `title` | `true` または `false` のみ。`true` の場合はnameをtext nodeとする `<title>` を追加する。空nameなら空の `<title>` になる。 |
|
|
40
|
+
| `id_prefix` | `nil` または `\A[A-Za-z][A-Za-z0-9_-]{0,63}\z` に一致する `String`。`nil` は決定的prefixを生成する。 |
|
|
41
|
+
| `attributes` | flatな `Hash`。許可する名前と値型は後述する。 |
|
|
42
|
+
|
|
43
|
+
String形式のsizeは次のgrammarに固定する。
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
(digits ["." digits] | "." digits) [unit]
|
|
47
|
+
unit = "px" | "em" | "rem" | "%" | "vw" | "vh" | "vmin" | "vmax"
|
|
48
|
+
numeric value > 0
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
入力違反はすべて `ArgumentError` とする。配列や文字列を暗黙に変換せず、入力Hashや配列を破壊的に変更しない。
|
|
52
|
+
|
|
53
|
+
## JavaScript互換hash
|
|
54
|
+
|
|
55
|
+
移植元の [`src/lib/utilities.ts`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/utilities.ts) と同じ結果を得るため、Rubyの `String#bytes` や `String#codepoints` は使用しない。UTF-8へ変換済みのnameをUTF-16LEへencodeし、16-bit unsigned integer列として処理する。
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
hash = 0
|
|
59
|
+
|
|
60
|
+
for each UTF-16 code unit:
|
|
61
|
+
hash = 31 * hash + code_unit
|
|
62
|
+
hash = signed_int32(hash)
|
|
63
|
+
|
|
64
|
+
result = abs(hash)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`signed_int32` は各反復で下位32 bitを残し、最上位bitが立っている場合は `2^32` を引く。最後の値が `-2^31` の場合、JavaScriptの `Math.abs` と同様に `2^31` を返す。
|
|
68
|
+
|
|
69
|
+
基準vectorは次のとおり。
|
|
70
|
+
|
|
71
|
+
| name | hash |
|
|
72
|
+
|---|---:|
|
|
73
|
+
| `Clara Barton` | 645088871 |
|
|
74
|
+
| 空文字 | 0 |
|
|
75
|
+
| `日本語` | 25921943 |
|
|
76
|
+
| `😀` | 1772899 |
|
|
77
|
+
| `e\u0301` | 3900 |
|
|
78
|
+
| `é` | 233 |
|
|
79
|
+
|
|
80
|
+
合成文字と分解文字は異なる入力であり、同じhashへ正規化しない。
|
|
81
|
+
|
|
82
|
+
### 共通utility
|
|
83
|
+
|
|
84
|
+
variantの式では次の意味を使用する。
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
digit(number, n) = floor(number / 10^n) % 10
|
|
88
|
+
boolean(number, n) = digit(number, n) が偶数
|
|
89
|
+
unit(number, range, n = nil):
|
|
90
|
+
value = number % range
|
|
91
|
+
n が指定され、digit(number, n) が偶数なら -value、それ以外は value
|
|
92
|
+
random_color(number, colors) = colors[number % colors.length]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Beamのcontrast色は `#` を除いた6桁をRGBとして読み、次のYIQが128以上なら `#000000`、未満なら `#FFFFFF` とする。
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
yiq = (r * 299 + g * 587 + b * 114) / 1000
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
paletteを `#RRGGBB` に限定するため、移植元の無効CSS色に対する未定義挙動は持ち込まない。
|
|
102
|
+
|
|
103
|
+
## 内部ID
|
|
104
|
+
|
|
105
|
+
mask、filter、gradientのIDはnameを直接埋め込まない。Coreの `id_prefix: nil` は次のcanonical byte sequenceから決定的に生成する。
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
parts = ["v1", variant.to_s, square ? "1" : "0", utf8_name, colors.length.to_s, *colors]
|
|
109
|
+
canonical = parts.map { |part| decimal_byte_length(part) + ":" + part }.join
|
|
110
|
+
id_prefix = "ba-" + sha256(canonical).hex[0, 20]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
長さはUTF-8のbyte数で計算する。paletteとsquareを含めることで、同じnameでも異なる定義を持つSVG同士の衝突を避ける。size、title、root attributesは内部defsを変えないためdigest対象に含めない。
|
|
114
|
+
|
|
115
|
+
内部要素はprefixに固定suffixを付ける。
|
|
116
|
+
|
|
117
|
+
- mask: `<prefix>-mask`
|
|
118
|
+
- Marble filter: `<prefix>-filter`
|
|
119
|
+
- Sunset gradient: `<prefix>-gradient-0`、`<prefix>-gradient-1`
|
|
120
|
+
|
|
121
|
+
すべての `url(#...)` は同じelement tree内に存在するIDだけを参照する。明示された `id_prefix` も同じsuffix規則を使う。
|
|
122
|
+
|
|
123
|
+
Coreでは同じ入力から同じIDとSVG文字列を得る。同じSVGを1つのHTML文書へ複数埋め込む場合、定義自体は同一なので描画は混線しないが、厳密にIDを一意にしたい呼び出し側は異なる `id_prefix` を渡す。Rails helperはこの処理を自動化する。
|
|
124
|
+
|
|
125
|
+
## 共通SVG構造
|
|
126
|
+
|
|
127
|
+
すべてのvariantでルート要素は次の固定属性を持つ。
|
|
128
|
+
|
|
129
|
+
```xml
|
|
130
|
+
<svg
|
|
131
|
+
viewBox="0 0 SIZE SIZE"
|
|
132
|
+
fill="none"
|
|
133
|
+
role="img"
|
|
134
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
135
|
+
width="SIZE_OPTION"
|
|
136
|
+
height="SIZE_OPTION">
|
|
137
|
+
</svg>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- `SIZE` はvariant内部の座標系であり、`size` optionとは別物である。
|
|
141
|
+
- `marble`、`pixel`、`sunset`、`bauhaus` は80、`beam` は36、`ring` は90を使用する。
|
|
142
|
+
- `title: true` のとき、ルート直下の最初のchildとして `<title>` を置く。
|
|
143
|
+
- 共通maskは `maskUnits="userSpaceOnUse"` と座標・幅・高さをvariantの `SIZE` に合わせる。
|
|
144
|
+
- 丸型ではmask内rectの `rx` を `SIZE * 2`、squareでは `rx` 属性自体を省略する。
|
|
145
|
+
- root childの順序は、任意のtitle、mask、maskを参照する描画group、必要なdefsの順とする。MarbleとSunsetのdefsも移植元どおり描画groupの後へ置く。
|
|
146
|
+
- rootの固定属性は追加属性から上書きできない。
|
|
147
|
+
|
|
148
|
+
## Variant仕様
|
|
149
|
+
|
|
150
|
+
variantは移植元のelement順、path data、transform組み立て順を維持する。見た目が近い別実装への置換や、式の簡略化は互換とはみなさない。
|
|
151
|
+
|
|
152
|
+
### Marble
|
|
153
|
+
|
|
154
|
+
参照: [`avatar-marble.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-marble.tsx)
|
|
155
|
+
|
|
156
|
+
- `SIZE = 80`、3要素分のpropertiesを作る。
|
|
157
|
+
- iを0始まりとして、色は `hash + i`、X/Y移動は `unit(hash * (i + 1), 8, 1|2)`、scaleは `1.2 + unit(hash * (i + 1), 4) / 10`、回転は `unit(hash * (i + 1), 360, 1)`。
|
|
158
|
+
- 背景rectの後に固定pathを2枚重ねる。
|
|
159
|
+
- 1枚目のpathも移植元どおり `properties[2].scale` を使う。自然に見える `properties[1].scale` へ修正しない。
|
|
160
|
+
- 2枚目は `mix-blend-mode: overlay`。
|
|
161
|
+
- 両pathは同じfilterを参照し、`feGaussianBlur stdDeviation="7"` を適用する。
|
|
162
|
+
|
|
163
|
+
### Beam
|
|
164
|
+
|
|
165
|
+
参照: [`avatar-beam.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-beam.tsx)
|
|
166
|
+
|
|
167
|
+
- `SIZE = 36`。
|
|
168
|
+
- wrapper色は `color(hash)`、背景色は `color(hash + 13)`、顔色はwrapper色のYIQ contrast。
|
|
169
|
+
- X/Yの事前移動量はそれぞれ `unit(hash, 10, 1|2)`。5未満なら `SIZE / 9` を加える。
|
|
170
|
+
- 回転は `unit(hash, 360)`、scaleは `1 + unit(hash, SIZE / 12) / 10`。
|
|
171
|
+
- 口の開閉は百の位、wrapperの円/角丸は十の位で決める。
|
|
172
|
+
- eye spreadは `unit(hash, 5)`、mouth spreadは `unit(hash, 3)`、顔回転は `unit(hash, 10, 3)`。
|
|
173
|
+
- 顔移動の条件式、口の2種類のpath、目の `1.5 x 2` rectを移植元と同じ座標・順序で生成する。
|
|
174
|
+
|
|
175
|
+
### Pixel
|
|
176
|
+
|
|
177
|
+
参照: [`avatar-pixel.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-pixel.tsx)
|
|
178
|
+
|
|
179
|
+
- `SIZE = 80`、`10 x 10` のrectを64個生成する。
|
|
180
|
+
- i番目の色は `colors[(hash % (i + 1)) % colors.length]`。`hash + i` にはしない。
|
|
181
|
+
- maskには `mask-type="alpha"` を付ける。
|
|
182
|
+
- 描画順は単純なrow-majorではなく、移植元の順序を維持する。最初の行はX座標 `0, 20, 40, 60, 10, 30, 50, 70`、その後はX座標 `0, 20, 40, 60, 10, 30, 50, 70` ごとにY座標 `10..70` を並べる。
|
|
183
|
+
|
|
184
|
+
### Sunset
|
|
185
|
+
|
|
186
|
+
参照: [`avatar-sunset.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-sunset.tsx)
|
|
187
|
+
|
|
188
|
+
- `SIZE = 80`、色は `hash + 0..3` から4色を得る。
|
|
189
|
+
- 上半分と下半分を2つの縦方向linear gradientで塗る。
|
|
190
|
+
- 上はY座標0から40、下は40から80へ補間する。
|
|
191
|
+
- 移植元の空白除去nameによるgradient IDは使用せず、安全なprefixと `-gradient-0|1` を使う。
|
|
192
|
+
|
|
193
|
+
### Ring
|
|
194
|
+
|
|
195
|
+
参照: [`avatar-ring.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-ring.tsx)
|
|
196
|
+
|
|
197
|
+
- `SIZE = 90`、`hash + 0..4` から5色を得る。
|
|
198
|
+
- 9個のfillは `[c0, c1, c1, c2, c2, c3, c3, c0, c4]` とする。
|
|
199
|
+
- 上下背景、半径38・32・26の上下半円、半径23の中央円を移植元のpath dataと順序で重ねる。
|
|
200
|
+
|
|
201
|
+
### Bauhaus
|
|
202
|
+
|
|
203
|
+
参照: [`avatar-bauhaus.tsx`](https://github.com/boringdesigners/boring-avatars/blob/d0ff2582a8921b643a89de4a4912be28938a828b/src/lib/components/avatar-bauhaus.tsx)
|
|
204
|
+
|
|
205
|
+
- `SIZE = 80`、4要素分のpropertiesを作る。
|
|
206
|
+
- 色は `hash + i`。
|
|
207
|
+
- X/Y移動は `unit(hash * (i + 1), SIZE / 2 - (i + 17), 1|2)`、回転は `unit(hash * (i + 1), 360)`。
|
|
208
|
+
- 百の位が偶数ならrectの高さを80、奇数なら10にする。
|
|
209
|
+
- 背景rect、移動・回転するrect、移動するcircle、移動・回転するlineの順に描画する。
|
|
210
|
+
|
|
211
|
+
## 追加root属性
|
|
212
|
+
|
|
213
|
+
Coreの `attributes:` はflatなHashのみを受け取る。Rails形式のnested `data:` / `aria:` はbinding側でflat化してからCoreへ渡す。
|
|
214
|
+
|
|
215
|
+
許可する属性名は次だけとする。
|
|
216
|
+
|
|
217
|
+
- `id`
|
|
218
|
+
- `class`
|
|
219
|
+
- `lang`
|
|
220
|
+
- `tabindex`
|
|
221
|
+
- `focusable`
|
|
222
|
+
- `aria-` で始まり、後続が小文字英数字と `-` からなる名前
|
|
223
|
+
- `data-` で始まり、後続が小文字英数字と `-` からなる名前
|
|
224
|
+
|
|
225
|
+
Symbol keyは `_` を `-` に変換してから検証し、String keyはそのまま検証する。正規化後に同じ名前となるkeyが複数ある場合は `ArgumentError` とする。
|
|
226
|
+
|
|
227
|
+
値はString、有限のNumeric、`true`、`false`、`nil` のみを許可する。`nil` は属性を省略し、booleanは `"true"` / `"false"` として出力する。その他のobjectに `to_s` を暗黙適用しない。
|
|
228
|
+
|
|
229
|
+
許可外の属性は削除せず例外にする。このallowlistにより、`xmlns`、`viewBox`、`width`、`height`、`fill`、`role`、`style`、`href`、`xlink:href`、`on*` はすべて拒否される。
|
|
230
|
+
|
|
231
|
+
## XML serializer
|
|
232
|
+
|
|
233
|
+
SVGはテンプレートへの文字列挿入ではなく、内部element treeから直列化する。
|
|
234
|
+
|
|
235
|
+
- tag名と内部属性名はライブラリ定義の定数だけを使用する。
|
|
236
|
+
- text nodeと属性値は、入力が `html_safe?` を返すobjectであっても常に通常値として扱う。
|
|
237
|
+
- `&`、`<`、`>`、`"`、`'` をXML用にescapeする。
|
|
238
|
+
- XML 1.0で許可されないcode pointを事前に拒否する。
|
|
239
|
+
- `nil` の内部属性は省略する。
|
|
240
|
+
- 固定root属性を規定順で出力し、追加属性は正規化後の名前でsortして出力する。
|
|
241
|
+
- XML declarationは付けず、UTF-8の `<svg>...</svg>` を返す。
|
|
242
|
+
- 数値はlocale非依存で出力し、整数値のFloatは不要な `.0` を付けない。負のzeroは `0` とする。
|
|
243
|
+
|
|
244
|
+
variantやRails helperが生成後のSVGを正規表現で書き換えることは禁止する。ID、属性、titleを含むすべての値はelement treeを組み立てる段階で確定させる。
|
|
245
|
+
|
|
246
|
+
## Thread safetyと決定性
|
|
247
|
+
|
|
248
|
+
Coreは入力から出力を得る純粋な処理とし、mutableなmodule state、乱数、時刻、process ID、連番を使用しない。default paletteなどの内部定数はfreezeし、呼び出し元のArray/Stringを破壊しない。
|
|
249
|
+
|
|
250
|
+
次が同じであれば、Ruby processやthreadにかかわらず同じSVG文字列を返す。
|
|
251
|
+
|
|
252
|
+
- 正規化後のname
|
|
253
|
+
- variant
|
|
254
|
+
- paletteとその順序
|
|
255
|
+
- size、square、title
|
|
256
|
+
- 明示または決定的に生成されたID prefix
|
|
257
|
+
- 正規化後の追加属性
|