@planet-matrix/mobius-model 0.4.0 → 0.6.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/CHANGELOG.md +32 -0
- package/README.md +134 -21
- package/dist/index.js +45 -4
- package/dist/index.js.map +186 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -117
- package/src/basic/enhance.ts +10 -0
- package/src/basic/function.ts +81 -62
- package/src/basic/index.ts +2 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/object.ts +82 -0
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +92 -0
- package/src/encoding/base64.ts +107 -0
- package/src/encoding/index.ts +1 -0
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +18 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +78 -0
- package/src/random/index.ts +1 -0
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/object.spec.ts +32 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Color
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Color 模块提供围绕颜色值(color value)的构造、转换、解析、序列化、分析与派生能力,用于在明确颜色空间拓扑之上建立稳定、可解释且可扩展的公共颜色语义。
|
|
6
|
+
|
|
7
|
+
这个模块不把某一种显示层颜色表示当作事实核心,而是以 `XYZ` 作为唯一绝对参考层,再从 `XYZ` 发散出不同颜色空间支线。当前已稳定落地的是 `RGB` 支线,其中 `Linear RGB` 是计算核心,`sRGB` 是显示与字符串语义层,`HSL` 与 `HSV` 是交互语义层。
|
|
8
|
+
|
|
9
|
+
## For Understanding
|
|
10
|
+
|
|
11
|
+
理解 Color 模块时,最重要的不是记住有哪些颜色函数,而是先接受它的颜色空间拓扑。这个模块要解决的问题不是“让任意颜色表示之间都能方便互转”,而是“让颜色值沿着清楚、合法、长期稳定的路径被构造、移动和分析”。
|
|
12
|
+
|
|
13
|
+
当前拓扑的核心结论如下:
|
|
14
|
+
|
|
15
|
+
- `XYZ` 是唯一绝对核心,不需要额外加 `CIE` 前缀来区分语义。
|
|
16
|
+
- `RGB` 是从 `XYZ` 发散出去的一条独立支线。
|
|
17
|
+
- `Linear RGB` 是 `RGB` 支线的计算核心。
|
|
18
|
+
- `sRGB` 是显示层与字符串层的核心表示。
|
|
19
|
+
- `HSL` 与 `HSV` 是从 `sRGB` 派生出的交互层表示。
|
|
20
|
+
|
|
21
|
+
在当前阶段,合法主链应被理解为:
|
|
22
|
+
|
|
23
|
+
- `XYZ <-> Linear RGB`
|
|
24
|
+
- `Linear RGB <-> sRGB`
|
|
25
|
+
- `sRGB <-> HSL`
|
|
26
|
+
- `sRGB <-> HSV`
|
|
27
|
+
|
|
28
|
+
这套拓扑不是实现细节,而是模块语义的一部分。它意味着不应把跨级捷径视为独立公共能力,也不应为了方便而让解析、序列化、分析或派生偷偷依赖未声明的跨级路径。未来如果扩展 `Lab` 等其它支线,也必须继续直接从 `XYZ` 出发,而不是附着在现有 `RGB` 支线上。
|
|
29
|
+
|
|
30
|
+
## For Using
|
|
31
|
+
|
|
32
|
+
当应用需要围绕颜色值本身工作,并且希望这些工作建立在明确颜色空间语义之上时,可以使用这个模块,而不是在业务代码里混合使用一批语义不清的颜色工具。
|
|
33
|
+
|
|
34
|
+
当前公共能力大致可以按以下几类理解:
|
|
35
|
+
|
|
36
|
+
- 颜色值类型与构造:用于显式创建 `XYZ`、`Linear RGB`、`sRGB`、`HSL`、`HSV` 这些不同层级的颜色值。
|
|
37
|
+
- 合法颜色转换:用于沿着颜色空间树的合法边移动,例如 `XYZ` 与 `Linear RGB` 之间,或 `sRGB` 与 `HSL`、`HSV` 之间。
|
|
38
|
+
- 字符串解析与序列化:用于在十六进制颜色、`rgb(...)`、`rgba(...)`、`hsl(...)`、`hsla(...)`、`hsv(...)`、`hsva(...)` 等外部格式与其对应颜色值之间转换。
|
|
39
|
+
- 颜色分析:用于相对亮度、三刺激总量、`xy` 色度坐标、对比度、可读文本颜色选择等分析场景。
|
|
40
|
+
- 颜色派生:用于线性混色、透明度合成、fade、tint、shade、tone、lighten、darken 等建立在清楚层级上的稳定派生。
|
|
41
|
+
- 显示层归一化特例:如果你的目标就是把多种颜色字符串归一化为最终显示层颜色,可以使用专门返回 `sRGB` 的入口;这种入口属于有意接受语义折叠的特例,而不是默认解析原则。
|
|
42
|
+
|
|
43
|
+
这个模块不适合承载主题系统、设计令牌(design token)管理、品牌配色规则、组件语义色映射或其它与具体产品设计体系强耦合的内容。这些能力应建立在 Color 之上,而不是写入 Color 本身。
|
|
44
|
+
|
|
45
|
+
## For Contributing
|
|
46
|
+
|
|
47
|
+
贡献 Color 模块时,首要任务不是添加一个“颜色工具”,而是判断该能力在颜色空间树上的位置是否清楚、依赖哪条合法路径、以及它是否值得成为长期公共承诺。只要这三个问题里有一个回答不清,它通常就还不适合进入这个模块。
|
|
48
|
+
|
|
49
|
+
在扩展时,应优先遵守以下边界:
|
|
50
|
+
|
|
51
|
+
- `XYZ` 是唯一绝对核心,不需要再引入其它并列的绝对参考层。
|
|
52
|
+
- 任何新能力都应能在颜色空间树上找到清楚位置,而不是以便捷函数的形式漂浮在树外。
|
|
53
|
+
- 不要把跨级转换捷径公开成独立 API,即使它内部只是串联了多步合法转换。
|
|
54
|
+
- 解析、序列化、分析与派生也必须服从颜色空间拓扑,而不是只对转换函数保持克制。
|
|
55
|
+
- 当前稳定承诺的是 `XYZ` 核心层与 `RGB` 支线;未来方向可以说明,但不能在文档和导出层面写成既有事实。
|
|
56
|
+
|
|
57
|
+
### JSDoc 注释格式要求
|
|
58
|
+
|
|
59
|
+
- 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
|
|
60
|
+
- JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
|
|
61
|
+
- 如果描述后还有其他内容,应在描述后加一个空行。
|
|
62
|
+
- 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
|
|
63
|
+
- 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
|
|
64
|
+
- 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
|
|
65
|
+
- 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
|
|
66
|
+
- 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
|
|
67
|
+
- 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
|
|
68
|
+
- 如果函数返回结构化字符串,应展示其预期格式特征。
|
|
69
|
+
- 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
|
|
70
|
+
|
|
71
|
+
### 实现规范要求
|
|
72
|
+
|
|
73
|
+
- 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
|
|
74
|
+
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
|
|
75
|
+
- 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
|
|
76
|
+
- 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
|
|
77
|
+
- 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
|
|
78
|
+
- 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
|
|
79
|
+
- 辅助元素永远不要公开导出。
|
|
80
|
+
- 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/color/internal.ts`。
|
|
81
|
+
- 模块内可以包含子模块。Color 已经具有 `xyz` 与 `rgb` 这类稳定子问题域,后续扩展也应继续沿语义分层组织。
|
|
82
|
+
- 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
|
|
83
|
+
- 子模块不需要有自己的 `README.md`。
|
|
84
|
+
- 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
|
|
85
|
+
- 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
|
|
86
|
+
- 所有转换、解析、序列化、分析与派生实现都应服从颜色空间树,不允许通过跨级公式或包装函数引入语义捷径。
|
|
87
|
+
|
|
88
|
+
### 导出策略要求
|
|
89
|
+
|
|
90
|
+
- 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
|
|
91
|
+
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
|
|
92
|
+
- Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
|
|
93
|
+
- 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的颜色语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
|
|
94
|
+
|
|
95
|
+
### 测试要求
|
|
96
|
+
|
|
97
|
+
- 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
|
|
98
|
+
- 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
|
|
99
|
+
- 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
|
|
100
|
+
- 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
|
|
101
|
+
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
|
|
102
|
+
- 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
|
|
103
|
+
- 模块的单元测试文件目录是 `./tests/unit/color`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/color/<sub-module-name>`。
|
|
104
|
+
- 对颜色转换,应重点验证每一条合法边本身,而不是依赖跨级捷径拼结果。
|
|
105
|
+
- 对颜色解析、序列化、分析与派生,应重点验证它们所依赖的颜色层级是否明确,以及结果是否稳定、可解释。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 断言一个数值是有限数字。
|
|
3
|
+
*/
|
|
4
|
+
export const internalAssertFinite = (value: number, name: string): void => {
|
|
5
|
+
if (Number.isFinite(value) === false) {
|
|
6
|
+
throw new RangeError(`${name} must be a finite number`)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 断言一个数值是非负有限数字。
|
|
12
|
+
*/
|
|
13
|
+
export const internalAssertNonNegativeFinite = (value: number, name: string): void => {
|
|
14
|
+
internalAssertFinite(value, name)
|
|
15
|
+
|
|
16
|
+
if (value < 0) {
|
|
17
|
+
throw new RangeError(`${name} must be a non-negative finite number`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 断言一个颜色单位值位于 `0` 到 `1` 之间。
|
|
23
|
+
*/
|
|
24
|
+
export const internalAssertColorUnit = (value: number, unitName: string): void => {
|
|
25
|
+
if (Number.isFinite(value) === false || value < 0 || value > 1) {
|
|
26
|
+
throw new RangeError(`Color ${unitName} must be a finite number between 0 and 1`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 断言一个透明度值位于 `0` 到 `1` 之间。
|
|
32
|
+
*/
|
|
33
|
+
export const internalAssertAlpha = (value: number): void => {
|
|
34
|
+
internalAssertColorUnit(value, "alpha")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 将浮点数按颜色计算常用精度进行圆整。
|
|
39
|
+
*/
|
|
40
|
+
export const internalRoundColorFloat = (value: number): number => {
|
|
41
|
+
return Number(value.toFixed(6))
|
|
42
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createSrgbColorValue } from "./construct.ts"
|
|
2
|
+
import { internalAssertColorUnit } from "../internal.ts"
|
|
3
|
+
import { linearRgbColorValueToXyzColorValue } from "../xyz/convert.ts"
|
|
4
|
+
import { srgbColorValueToLinearRgbColorValue } from "./convert.ts"
|
|
5
|
+
import {
|
|
6
|
+
xyzColorValueToRelativeLuminance,
|
|
7
|
+
xyzColorValueToTristimulusSum,
|
|
8
|
+
xyzColorValueToXyChromaticityValue,
|
|
9
|
+
} from "../xyz/analyze.ts"
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
LinearRgbColorValue,
|
|
13
|
+
SrgbColorValue,
|
|
14
|
+
XyChromaticityValue,
|
|
15
|
+
} from "../types.ts"
|
|
16
|
+
|
|
17
|
+
const internalDefaultDarkSrgbColorValue: SrgbColorValue = { red: 0, green: 0, blue: 0, alpha: 1 }
|
|
18
|
+
const internalDefaultLightSrgbColorValue: SrgbColorValue = { red: 255, green: 255, blue: 255, alpha: 1 }
|
|
19
|
+
|
|
20
|
+
const internalNormalizeContrastRatio = (value: number): number => {
|
|
21
|
+
if (Number.isFinite(value) === false || value < 1) {
|
|
22
|
+
throw new RangeError("Contrast ratio must be a finite number greater than or equal to 1")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 读取 Linear RGB 颜色值的相对亮度。
|
|
30
|
+
*
|
|
31
|
+
* 该函数会沿合法路径 `Linear RGB -> XYZ -> luminance` 计算。
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```
|
|
35
|
+
* // Expect: 0.212673
|
|
36
|
+
* const example1 = linearRgbColorValueToRelativeLuminance({ red: 1, green: 0, blue: 0, alpha: 1 })
|
|
37
|
+
*
|
|
38
|
+
* // Expect: 1
|
|
39
|
+
* const example2 = linearRgbColorValueToRelativeLuminance({ red: 1, green: 1, blue: 1, alpha: 1 })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const linearRgbColorValueToRelativeLuminance = (color: LinearRgbColorValue): number => {
|
|
43
|
+
return xyzColorValueToRelativeLuminance(linearRgbColorValueToXyzColorValue(color))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 读取 sRGB 颜色值的相对亮度。
|
|
48
|
+
*
|
|
49
|
+
* 该函数会沿合法路径 `sRGB -> Linear RGB -> XYZ -> luminance` 计算。
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```
|
|
53
|
+
* // Expect: 0.125053
|
|
54
|
+
* const example1 = srgbColorValueToRelativeLuminance({ red: 51, green: 102, blue: 153, alpha: 1 })
|
|
55
|
+
*
|
|
56
|
+
* // Expect: 1
|
|
57
|
+
* const example2 = srgbColorValueToRelativeLuminance({ red: 255, green: 255, blue: 255, alpha: 1 })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const srgbColorValueToRelativeLuminance = (color: SrgbColorValue): number => {
|
|
61
|
+
return linearRgbColorValueToRelativeLuminance(srgbColorValueToLinearRgbColorValue(color))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 读取两个 sRGB 颜色值之间的对比度。
|
|
66
|
+
*
|
|
67
|
+
* 该函数假定输入已经是最终显示颜色;若存在透明叠加,应先完成合成。
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```
|
|
71
|
+
* // Expect: 21
|
|
72
|
+
* const example1 = srgbColorValuesToContrastRatio(
|
|
73
|
+
* { red: 0, green: 0, blue: 0, alpha: 1 },
|
|
74
|
+
* { red: 255, green: 255, blue: 255, alpha: 1 },
|
|
75
|
+
* )
|
|
76
|
+
*
|
|
77
|
+
* // Expect: 1
|
|
78
|
+
* const example2 = srgbColorValuesToContrastRatio(
|
|
79
|
+
* { red: 51, green: 102, blue: 153, alpha: 1 },
|
|
80
|
+
* { red: 51, green: 102, blue: 153, alpha: 1 },
|
|
81
|
+
* )
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const srgbColorValuesToContrastRatio = (first: SrgbColorValue, second: SrgbColorValue): number => {
|
|
85
|
+
const firstLuminance = srgbColorValueToRelativeLuminance(first)
|
|
86
|
+
const secondLuminance = srgbColorValueToRelativeLuminance(second)
|
|
87
|
+
const lighterLuminance = Math.max(firstLuminance, secondLuminance)
|
|
88
|
+
const darkerLuminance = Math.min(firstLuminance, secondLuminance)
|
|
89
|
+
|
|
90
|
+
return lighterLuminance === darkerLuminance
|
|
91
|
+
? 1
|
|
92
|
+
: Number((((lighterLuminance + 0.05) / (darkerLuminance + 0.05))).toFixed(6))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 判断两个 sRGB 颜色值是否达到指定对比度阈值。
|
|
97
|
+
*
|
|
98
|
+
* 该函数假定输入已经是最终显示颜色;若存在透明叠加,应先完成合成。
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```
|
|
102
|
+
* // Expect: true
|
|
103
|
+
* const example1 = doSrgbColorValuesMeetContrastRatio(
|
|
104
|
+
* { red: 0, green: 0, blue: 0, alpha: 1 },
|
|
105
|
+
* { red: 255, green: 255, blue: 255, alpha: 1 },
|
|
106
|
+
* 4.5,
|
|
107
|
+
* )
|
|
108
|
+
*
|
|
109
|
+
* // Expect: false
|
|
110
|
+
* const example2 = doSrgbColorValuesMeetContrastRatio(
|
|
111
|
+
* { red: 120, green: 120, blue: 120, alpha: 1 },
|
|
112
|
+
* { red: 255, green: 255, blue: 255, alpha: 1 },
|
|
113
|
+
* 4.5,
|
|
114
|
+
* )
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export const doSrgbColorValuesMeetContrastRatio = (first: SrgbColorValue, second: SrgbColorValue, minimumContrastRatio = 4.5): boolean => {
|
|
118
|
+
return srgbColorValuesToContrastRatio(first, second) >= internalNormalizeContrastRatio(minimumContrastRatio)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 为给定背景色选择更可读的黑色或白色文本颜色。
|
|
123
|
+
*
|
|
124
|
+
* 该函数假定背景色已经是最终显示颜色。
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```
|
|
128
|
+
* // Expect: { red: 255, green: 255, blue: 255, alpha: 1 }
|
|
129
|
+
* const example1 = pickReadableSrgbTextColorValue({ red: 0, green: 102, blue: 204, alpha: 1 })
|
|
130
|
+
*
|
|
131
|
+
* // Expect: { red: 0, green: 0, blue: 0, alpha: 1 }
|
|
132
|
+
* const example2 = pickReadableSrgbTextColorValue({ red: 250, green: 240, blue: 210, alpha: 1 })
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export const pickReadableSrgbTextColorValue = (
|
|
136
|
+
backgroundColor: SrgbColorValue,
|
|
137
|
+
options: { darkColor?: SrgbColorValue, lightColor?: SrgbColorValue } = {},
|
|
138
|
+
): SrgbColorValue => {
|
|
139
|
+
const normalizedDarkColor = createSrgbColorValue(options.darkColor ?? internalDefaultDarkSrgbColorValue)
|
|
140
|
+
const normalizedLightColor = createSrgbColorValue(options.lightColor ?? internalDefaultLightSrgbColorValue)
|
|
141
|
+
const darkContrastRatio = srgbColorValuesToContrastRatio(normalizedDarkColor, backgroundColor)
|
|
142
|
+
const lightContrastRatio = srgbColorValuesToContrastRatio(normalizedLightColor, backgroundColor)
|
|
143
|
+
|
|
144
|
+
return lightContrastRatio > darkContrastRatio ? normalizedLightColor : normalizedDarkColor
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 读取 Linear RGB 颜色值沿合法路径得到的三刺激总量。
|
|
149
|
+
*
|
|
150
|
+
* 该函数会沿合法路径 `Linear RGB -> XYZ -> tristimulus sum` 计算。
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```
|
|
154
|
+
* // Expect: 0.644463
|
|
155
|
+
* const example1 = linearRgbColorValueToTristimulusSum({ red: 1, green: 0, blue: 0, alpha: 1 })
|
|
156
|
+
*
|
|
157
|
+
* // Expect: 3.0393
|
|
158
|
+
* const example2 = linearRgbColorValueToTristimulusSum({ red: 1, green: 1, blue: 1, alpha: 1 })
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export const linearRgbColorValueToTristimulusSum = (color: LinearRgbColorValue): number => {
|
|
162
|
+
return xyzColorValueToTristimulusSum(linearRgbColorValueToXyzColorValue(color))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 读取 sRGB 颜色值沿合法路径得到的三刺激总量。
|
|
167
|
+
*
|
|
168
|
+
* 该函数会沿合法路径 `sRGB -> Linear RGB -> XYZ -> tristimulus sum` 计算。
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```
|
|
172
|
+
* // Expect: 0.562889
|
|
173
|
+
* const example1 = srgbColorValueToTristimulusSum({ red: 51, green: 102, blue: 153, alpha: 1 })
|
|
174
|
+
*
|
|
175
|
+
* // Expect: 3.0393
|
|
176
|
+
* const example2 = srgbColorValueToTristimulusSum({ red: 255, green: 255, blue: 255, alpha: 1 })
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export const srgbColorValueToTristimulusSum = (color: SrgbColorValue): number => {
|
|
180
|
+
return linearRgbColorValueToTristimulusSum(srgbColorValueToLinearRgbColorValue(color))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 读取 Linear RGB 颜色值沿合法路径得到的 `xy` 色度坐标。
|
|
185
|
+
*
|
|
186
|
+
* 该函数会沿合法路径 `Linear RGB -> XYZ -> xy chromaticity` 计算。
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```
|
|
190
|
+
* // Expect: { x: 0.64, y: 0.33 }
|
|
191
|
+
* const example1 = linearRgbColorValueToXyChromaticityValue({ red: 1, green: 0, blue: 0, alpha: 1 })
|
|
192
|
+
*
|
|
193
|
+
* // Expect: { x: 0.312727, y: 0.329023 }
|
|
194
|
+
* const example2 = linearRgbColorValueToXyChromaticityValue({ red: 1, green: 1, blue: 1, alpha: 1 })
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export const linearRgbColorValueToXyChromaticityValue = (color: LinearRgbColorValue): XyChromaticityValue => {
|
|
198
|
+
return xyzColorValueToXyChromaticityValue(linearRgbColorValueToXyzColorValue(color))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 读取 sRGB 颜色值沿合法路径得到的 `xy` 色度坐标。
|
|
203
|
+
*
|
|
204
|
+
* 该函数会沿合法路径 `sRGB -> Linear RGB -> XYZ -> xy chromaticity` 计算。
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```
|
|
208
|
+
* // Expect: { x: 0.210775, y: 0.222163 }
|
|
209
|
+
* const example1 = srgbColorValueToXyChromaticityValue({ red: 51, green: 102, blue: 153, alpha: 1 })
|
|
210
|
+
*
|
|
211
|
+
* // Expect: { x: 0.312727, y: 0.329023 }
|
|
212
|
+
* const example2 = srgbColorValueToXyChromaticityValue({ red: 255, green: 255, blue: 255, alpha: 1 })
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export const srgbColorValueToXyChromaticityValue = (color: SrgbColorValue): XyChromaticityValue => {
|
|
216
|
+
return linearRgbColorValueToXyChromaticityValue(srgbColorValueToLinearRgbColorValue(color))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 判断一个 sRGB 颜色值是否可视为亮色。
|
|
221
|
+
*
|
|
222
|
+
* 阈值位于 `0` 到 `1` 之间,判断依据是相对亮度。
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```
|
|
226
|
+
* // Expect: true
|
|
227
|
+
* const example1 = isLightSrgbColorValue({ red: 255, green: 255, blue: 255, alpha: 1 })
|
|
228
|
+
*
|
|
229
|
+
* // Expect: false
|
|
230
|
+
* const example2 = isLightSrgbColorValue({ red: 0, green: 0, blue: 0, alpha: 1 })
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export const isLightSrgbColorValue = (color: SrgbColorValue, threshold = 0.5): boolean => {
|
|
234
|
+
internalAssertColorUnit(threshold, "threshold")
|
|
235
|
+
return srgbColorValueToRelativeLuminance(createSrgbColorValue(color)) >= threshold
|
|
236
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
internalGetStableSrgbColorValueByString,
|
|
3
|
+
internalNormalizeHslColorValue,
|
|
4
|
+
internalNormalizeHsvColorValue,
|
|
5
|
+
internalNormalizeLinearRgbColorValue,
|
|
6
|
+
internalNormalizeSrgbColorValue,
|
|
7
|
+
} from "./internal.ts"
|
|
8
|
+
import {
|
|
9
|
+
srgbColorValueToHslColorValue,
|
|
10
|
+
srgbColorValueToHsvColorValue,
|
|
11
|
+
} from "./convert.ts"
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
HslColorValue,
|
|
15
|
+
HsvColorValue,
|
|
16
|
+
LinearRgbColorValue,
|
|
17
|
+
SrgbColorValue,
|
|
18
|
+
} from "../types.ts"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 构造一个 Linear RGB 颜色值对象。
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```
|
|
25
|
+
* // Expect: { red: 0.2, green: 0.4, blue: 0.6, alpha: 1 }
|
|
26
|
+
* const example1 = createLinearRgbColorValue({ red: 0.2, green: 0.4, blue: 0.6, alpha: 1 })
|
|
27
|
+
*
|
|
28
|
+
* // Expect: { red: -0.25, green: 0.4, blue: 1.2, alpha: 0.5 }
|
|
29
|
+
* const example2 = createLinearRgbColorValue({ red: -0.25, green: 0.4, blue: 1.2, alpha: 0.5 })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const createLinearRgbColorValue = (color: LinearRgbColorValue): LinearRgbColorValue => {
|
|
33
|
+
return internalNormalizeLinearRgbColorValue(color)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 构造一个 sRGB 颜色值对象。
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```
|
|
41
|
+
* // Expect: { red: 51, green: 102, blue: 153, alpha: 1 }
|
|
42
|
+
* const example1 = createSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 })
|
|
43
|
+
*
|
|
44
|
+
* // Expect: { red: 255, green: 255, blue: 255, alpha: 0.5 }
|
|
45
|
+
* const example2 = createSrgbColorValue({ red: 255, green: 255, blue: 255, alpha: 0.5 })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const createSrgbColorValue = (color: SrgbColorValue): SrgbColorValue => {
|
|
49
|
+
return internalNormalizeSrgbColorValue(color)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 构造一个 HSL 颜色值对象。
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```
|
|
57
|
+
* // Expect: { hue: 330, saturation: 0.5, lightness: 0.4, alpha: 1 }
|
|
58
|
+
* const example1 = createHslColorValue({ hue: -30, saturation: 0.5, lightness: 0.4, alpha: 1 })
|
|
59
|
+
*
|
|
60
|
+
* // Expect: { hue: 0, saturation: 0, lightness: 1, alpha: 0.5 }
|
|
61
|
+
* const example2 = createHslColorValue({ hue: 360, saturation: 0, lightness: 1, alpha: 0.5 })
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export const createHslColorValue = (color: HslColorValue): HslColorValue => {
|
|
65
|
+
return internalNormalizeHslColorValue(color)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 构造一个 HSV 颜色值对象。
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```
|
|
73
|
+
* // Expect: { hue: 330, saturation: 0.5, value: 0.6, alpha: 1 }
|
|
74
|
+
* const example1 = createHsvColorValue({ hue: -30, saturation: 0.5, value: 0.6, alpha: 1 })
|
|
75
|
+
*
|
|
76
|
+
* // Expect: { hue: 30, saturation: 0.5, value: 0.6, alpha: 1 }
|
|
77
|
+
* const example2 = createHsvColorValue({ hue: 390, saturation: 0.5, value: 0.6, alpha: 1 })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export const createHsvColorValue = (color: HsvColorValue): HsvColorValue => {
|
|
81
|
+
return internalNormalizeHsvColorValue(color)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 根据稳定字符串构造 sRGB 颜色值。
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```
|
|
89
|
+
* // Expect: { red: 254, green: 192, blue: 46, alpha: 1 }
|
|
90
|
+
* const example1 = createSrgbColorValueByString("mobius")
|
|
91
|
+
*
|
|
92
|
+
* // Expect: { red: 101, green: 227, blue: 144, alpha: 1 }
|
|
93
|
+
* const example2 = createSrgbColorValueByString("planet")
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export const createSrgbColorValueByString = (input: string): SrgbColorValue => {
|
|
97
|
+
return internalGetStableSrgbColorValueByString(input)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 根据稳定字符串构造 HSL 颜色值。
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```
|
|
105
|
+
* // Expect: { hue: 42.992126, saturation: 0.991597, lightness: 0.588235, alpha: 1 }
|
|
106
|
+
* const example1 = createHslColorValueByString("mobius")
|
|
107
|
+
*
|
|
108
|
+
* // Expect: { hue: 139.777778, saturation: 0.71831, lightness: 0.643137, alpha: 1 }
|
|
109
|
+
* const example2 = createHslColorValueByString("planet")
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export const createHslColorValueByString = (input: string): HslColorValue => {
|
|
113
|
+
return srgbColorValueToHslColorValue(createSrgbColorValueByString(input))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 根据稳定字符串构造 HSV 颜色值。
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```
|
|
121
|
+
* // Expect: { hue: 42.992126, saturation: 0.818898, value: 0.996078, alpha: 1 }
|
|
122
|
+
* const example1 = createHsvColorValueByString("mobius")
|
|
123
|
+
*
|
|
124
|
+
* // Expect: { hue: 139.777778, saturation: 0.555066, value: 0.890196, alpha: 1 }
|
|
125
|
+
* const example2 = createHsvColorValueByString("planet")
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export const createHsvColorValueByString = (input: string): HsvColorValue => {
|
|
129
|
+
return srgbColorValueToHsvColorValue(createSrgbColorValueByString(input))
|
|
130
|
+
}
|