Icon Theme Specification(图标主题规范)
ssk-wh Lv4

原文链接:https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html

概述

图标主题是一种使应用程序的图标具有统一外观和感觉的机制。当用户选择一个特定的图标主题时,系统会使用该主题中的图标来渲染所有应用程序的图标。图标主题最初用于桌面文件规范中的图标字段,但在其他场景下也可以发挥作用,例如 mimetype 图标。

从程序员的角度来看,图标主题只是一种映射机制。给定一组目录和主题名称,它将从图标名称和标称图标大小映射到图标文件名。这样,应用程序可以使用相应的图标文件来显示其图标,而无需直接指定文件路径或图标文件名。

定义

图标主题:图标主题是一组命令的图标文件,它可以根据图标名和给定大小映射到具体的文件。主题也支持从其他主题继承,从而很好的扩展当前主题的功能。
图标文件:图标文件是可以加载并用作图标的图像。支持的图像文件格式为 PNG、XPM 和 SVG。PNG 是比较推荐的位图格式,SVG 用于矢量化图标。由于向后兼容的原因,支持 XPM 格式,不建议新主题使用 XPM 文件。(对 SVG 的支持是可选的。)
基础目录:在一组称为基本目录的目录中搜索图标和主题。主题存储在基本目录的子目录中。
图标比例:在非常高密度(高 dpi)的屏幕上,通常会缩放 UI 以避免 UI 太小以至于难以看到。为了支持这个图标可以有一个目标比例,描述他们被设计的比例因子。例如,目录大小为 48 但比例为 2x 的图标将是 96x96 像素,但设计为具有与比例为 1x 的 48x48 图标相同的细节级别。这可用于高密度显示器,其中 48x48 图标会太小(或放大后很难看),而普通的 96x96 图标会有很多细节很难看清。

目录布局

在一组目录中查找图标和主题。默认情况下,应用程序应顺序查找以下目录

$HOME/.icons(为了向后兼容)、
$XDG_DATA_DIRS/icons
/usr/share/pixmaps

应用程序可以进一步将它们自己的图标目录添加到此列表中,用户可以扩展或更改列表(以应用程序/桌面的特定方式)。在这些目录中的每一个中,主题都存储为子目录。通过具有相同名称的子目录,主题可以分布在多个基本目录中。这样用户就可以扩展和覆盖系统主题。

为了让第三方应用程序有地方安装它们的图标,应该始终存在一个名为“hicolor” [1]的主题。hicolor 主题的数据可从以下网址下载:http://www.freedesktop.org/software/icon-theme/

如果在当前主题中找不到图标,则需要在“hicolor”主题中查找实现

每个主题都存储为基本目录的子目录。尽管主题指定的用户可见名称可能不同,但主题的内部名称始终是子目录的名称。因此,主题名称区分大小写,并且仅限于 ASCII 字符。主题名称也可能不包含逗号或空格。

在至少一个主题目录中必须有一个名为 index.theme 的文件来描述主题。使用按顺序搜索基本目录时找到的第一个 index.theme。该文件描述了主题的一般属性。

在主题目录中还有一组包含图像文件的子目录。每个目录都包含为特定标称图标大小和比例设计的图标,如 index.theme 文件所述。子目录允许有几层深,例如主题“hicolor”中的子目录“48x48/apps”最终将位于 $basedir/hicolor/48x48/apps。

图像文件必须是以下类型之一:PNG、XPM 或 SVG,并且扩展名必须是“.png”、“.xpm”或“.svg”(小写)对 SVG 文件的支持是可选的。不支持 SVG 的实现应该忽略任何“.svg”文件。除此之外,可能还有一个附加文件,每个文件都有额外的图标数据。它应该与图像文件具有相同的基本名称,扩展名为“.icon”。例如,如果图标文件名为“mime_source_c.png”,相应的文件将被命名为“mime_source_c.icon”。

文件格式

图标主题描述文件和图标数据文件都是 ini 样式的文本文件,如桌面文件规范中所述。他们没有任何编码字段。相反,它们必须始终以 UTF-8 编码存储。

index.theme 文件必须以名为Icon Theme 的 部分开头,其内容根据下表 1 确定。所有列表均以逗号分隔。

标准 Keys

Key 描述 值类型 是否必需
Name 图标主题的简称,例如在主题选择列表中显示 本地字符串 YES
Comment 描述主题的较长字符串 本地字符串 YES
Inherits 此主题继承自的主题的名称。如果在当前主题中找不到图标名称,则会在继承的主题中搜索(并在所有继承的主题中递归)。如果没有指定主题,则需要将“hicolor”主题添加到继承树中。
实现者(系统发行商或桌面环境维护团队)可以选择在最后指定的主题和 hicolor 主题之间添加其他默认主题。
字符串 NO
Directories 该主题的子目录列表。对于每个子目录,index.theme 文件中必须有一个部分描述该目录 字符串 YES
ScaledDirectories 除了目录中的子目录之外,该主题的其他子目录列表。这些目录只能由支持缩放目录的实现读取,并被添加以保持与不支持这些的旧实现的兼容性。 字符串 NO
Hidden 是否在主题选择用户界面中隐藏主题。这用于用户不应该看到的诸如后备主题之类的东西。 布尔值 NO
Example 应用作此主题外观示例的图标名称。 字符串 NO

目录 Keys

指定的每个目录都有一个与该目录同名的相应部分。

钥匙 描述 值类型 是否必需 类型
Size 此目录中图标的标称(未缩放)大小。 整数 是的
Scale 此目录中图标的目标比例。如果不存在则默认为值 1。为了向后兼容,任何比例不是 1 的目录都应该列在 ScaledDirectories 列表中,而不是目录中。 整数
Context 通常使用图标的上下文。这在名为“上下文”的部分中有详细讨论。 细绳
Type 此目录中图标的图标大小类型。有效类型为 Fixed、Scalable 和 Threshold。类型决定使用该部分中的其他键。如果未指定,则默认为阈值。 细绳
MaxSize 指定此目录中的图标可以缩放到的最大(未缩放)大小。如果不存在,则默认为 Size 的值。 整数 可扩展
MinSize 指定此目录中的图标可以缩放到的最小(未缩放)大小。如果不存在,则默认为 Size 的值。 整数 可扩展
Threshold 如果大小最多与所需(未缩放)大小相差这么多,则可以使用此目录中的图标。如果不存在则默认为 2。 整数 临界点

除了这些组之外,您还可以向 index.theme 文件添加额外的组以扩展它。这些扩展名必须以“X-”开头,可用于将桌面特定信息添加到主题文件中。示例组名称是“X-KDE Icon Theme”或“X-Gnome Icon Theme”。

图标 Keys

Key 描述 值类型 是否必需
DisplayName 一个翻译后的 UTF8 字符串,当图标在用户界面中列出时,可以用来代替图标名称。 本地字符串 NO
EmbeddedTextRectangle 如果存在,它指定显示图标的程序可以嵌入文本的矩形的四个角。这通常由想要在图标中显示文本文件内容预览的文件管理器使用。角由四个值的列表指定:x0、y0、x1、y1。这些值是从图标左上角开始的像素坐标,SVG 文件除外,它们在 1000x1000 坐标空间中指定,该空间缩放到图标的最终渲染大小。 整数 NO
AttachPoints 以“|”分隔的点列表 可用作标志/覆盖物的锚点。这些点是从图标左上角开始的像素坐标,SVG 文件除外,它们在 1000x1000 坐标空间中指定,该空间缩放到图标的最终渲染大小。 坐标点 NO

允许对 filename.icon 文件进行扩展,但键必须以“X-”开头,以避免与此格式的未来标准化扩展冲突。

Context

允许Context设计者在概念层面对图标进行分组。它不充当文件系统中的名称空间,因此图标可以具有相同的名称,但允许实现根据它进行分类和排序,例如。

这些是可用的上下文:

  • Actions. 表示用户启动的操作的图标,例如另存为。
  • Devices. 代表现实世界设备的图标,例如打印机和鼠标。它不适用于字符或块设备等文件系统节点。
  • FileSystems. 作为文件系统的一部分表示的对象的图标。例如,本地网络、“主页”和“桌面”文件夹。
  • MimeTypes. 表示 MIME 类型的图标。

image

图标查找

图标查找机制有两个全局设置,基本目录列表和当前主题的内部名称。鉴于这些,我们需要指定如何从图标名称、正常尺寸和比例中查找图标文件。

查找首先在当前主题中完成,然后在当前主题的每个父主题中递归,最后在名为“hicolor”的默认主题中进行(实现可能会在“hicolor”之前添加更多默认主题,但“hicolor”必须是最后一个). 一旦有任何大小的图标与主题匹配,搜索就会停止。即使在继承的主题中可能有尺寸更接近正确图标的图标,我们也不想使用它。当您更改图标大小时(例如放大),这样做可能会导致图标发生不一致的变化。

主题内的查找分三个阶段完成。首先扫描所有目录以寻找完全匹配的目录,例如图标文件的允许大小与查找的内容相匹配的目录。然后扫描所有目录以查找与名称匹配的图标。如果失败,我们最终会退回到无主题图标。如果我们根本找不到任何图标,则由应用程序选择一个好的后备图标,因为正确的选择取决于上下文。

在主题中查找图标的确切算法(伪代码)(如果实现者支持 SVG):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
FindIcon(icon, size, scale) {
filename = FindIconHelper(icon, size, scale, user selected theme);
if filename != none
return filename

filename = FindIconHelper(icon, size, scale, "hicolor");
if filename != none
return filename

return LookupFallbackIcon (icon)
}
FindIconHelper(icon, size, scale, theme) {
filename = LookupIcon (icon, size, scale, theme)
if filename != none
return filename

if theme has parents
parents = theme.parents

for parent in parents {
filename = FindIconHelper (icon, size, scale, parent)
if filename != none
return filename
}
return none
}

使用以下辅助函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
LookupIcon (iconname, size, scale, theme) {
for each subdir in $(theme subdir list) {
for each directory in $(basename list) {
for extension in ("png", "svg", "xpm") {
if DirectoryMatchesSize(subdir, size, scale) {
filename = directory/$(themename)/subdir/iconname.extension
if exist filename
return filename
}
}
}
}
minimal_size = MAXINT
for each subdir in $(theme subdir list) {
for each directory in $(basename list) {
for extension in ("png", "svg", "xpm") {
filename = directory/$(themename)/subdir/iconname.extension
if exist filename and DirectorySizeDistance(subdir, size, scale) < minimal_size {
closest_filename = filename
minimal_size = DirectorySizeDistance(subdir, size, scale)
}
}
}
}
if closest_filename set
return closest_filename
return none
}

LookupFallbackIcon (iconname) {
for each directory in $(basename list) {
for extension in ("png", "svg", "xpm") {
if exists directory/iconname.extension
return directory/iconname.extension
}
}
return none
}

DirectoryMatchesSize(subdir, iconsize, iconscale) {
read Type and size data from subdir
if Scale != iconscale
return False;
if Type is Fixed
return Size == iconsize
if Type is Scaled
return MinSize <= iconsize <= MaxSize
if Type is Threshold
return Size - Threshold <= iconsize <= Size + Threshold
}

DirectorySizeDistance(subdir, iconsize, iconscale) {
read Type and size data from subdir
if Type is Fixed
return abs(Size*Scale - iconsize*iconscale)
if Type is Scaled
if iconsize*iconscale < MinSize*Scale
return MinSize*Scale - iconsize*iconscale
if iconsize*iconscale > MaxSize*Scale
return iconsize*iconscale - MaxSize*Scale
return 0
if Type is Threshold
if iconsize*iconscale < (Size - Threshold)*Scale
return MinSize*Scale - iconsize*iconscale
if iconsize*iconsize > (Size + Threshold)*Scale
return iconsize*iconsize - MaxSize*Scale
return 0
}

在某些情况下,您并不总是希望退回到继承主题中的图标。例如,有时您会寻找一组图标,在使用继承主题中的图标之前更喜欢其中的任何一个。为了支持此类操作,实现可以包含一个函数,该函数在继承层次结构中查找图标名称列表的第一个.它看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
FindBestIcon(iconList, size, scale) {
filename = FindBestIconHelper(iconList, size, scale, user selected theme);
if filename != none
return filename

filename = FindBestIconHelper(iconList, size, scale, "hicolor");
if filename != none
return filename

for icon in iconList {
filename = LookupFallbackIcon (icon)
if filename != none
return filename
}
return none;
}
FindBestIconHelper(iconList, size, scale, theme) {
for icon in iconList {
filename = LookupIcon (icon, size, theme)
if filename != none
return filename
}

if theme has parents
parents = theme.parents

for parent in parents {
filename = FindBestIconHelper (iconList, size, scale, parent)
if filename != none
return filename
}
return none
}

这可能非常有用,例如在处理 mimetype 图标时,其中多少都会有一些“特定”版本的图标。

例子

这是一个示例 index.theme 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[Icon Theme]
Name=Birch
Name[sv]=Björk
Comment=Icon theme with a wooden look
Comment[sv]=Träinspirerat ikontema
Inherits=wood,default
Directories=48x48/apps,48x48@2/apps48x48/mimetypes,32x32/apps,32x32@2/apps,scalable/apps,scalable/mimetypes

[scalable/apps]
Size=48
Type=Scalable
MinSize=1
MaxSize=256
Context=Applications

[scalable/mimetypes]
Size=48
Type=Scalable
MinSize=1
MaxSize=256
Context=MimeTypes

[32x32/apps]
Size=32
Type=Fixed
Context=Applications

[32x32@2/apps]
Size=32
Scale=2
Type=Fixed
Context=Applications

[48x48/apps]
Size=48
Type=Fixed
Context=Applications

[48x48@2/apps]
Size=48
Scale=2
Type=Fixed
Context=Applications

[48x48/mimetypes]
Size=48
Type=Fixed
Context=MimeTypes

/usr/share/icons 目录中相应的目录树可能如下所示:

birch/index.theme
birch/scalable/apps/mozilla.svg
birch/scalable/mimetypes/mime_text_plain.svg
birch/scalable/mimetypes/mime_text_plain.icon
birch/48x48/apps/mozilla.png
birch/48x48@2/apps/mozilla.png
birch/32x32/apps/mozilla.png
birch/32x32@2/apps/mozilla.png
birch/48x48/mimetypes/mime_text_plain.png
birch/48x48/mimetypes/mime_text_plain.icon

其中 birch/scalable/mimetypes/mime_text_plain.icon 包含:

1
2
3
4
[Icon Data]
DisplayName=Mime text/plain
EmbeddedTextRectangle=100,100,900,900
AttachPoints=200,200|800,200|500,500|200,800|800,800

birch/48x48/mimetypes/mime_text_plain.icon 包含:

1
2
3
4
[Icon Data]
DisplayName=Mime text/plain
EmbeddedTextRectangle=8,8,40,40
AttachPoints=20,20|40,40|50,10|10,50

在此示例中,由于目录的顺序,“mozilla”的查找将在 SVG 图标之前获取预渲染的 48x48 和 32x32 图标。(Icon Theme 中的 Directories 字段定义了查找的目录顺序)

安装应用程序图标

因此,您是应用程序作者,并且想要安装应用程序图标,以便它们在 KDE 和 Gnome 菜单中工作。至少你应该在 hicolor 主题中安装一个 48x48 的图标。这意味着在 $prefix/share/icons/hicolor/48x48/apps 中安装一个 PNG 文件。您可以选择安装不同大小的图标。例如,在 $prefix/share/icons/hicolor/scalable/apps 中安装一个 svg 图标意味着大多数桌面将有一个适用于所有尺寸的图标。您甚至可能希望安装外观与其他知名主题相匹配的图标,以便您的应用程序适合某些特定的桌面环境。

建议安装在 hicolor 主题中的图标看起来是中性的,因为它是一个后备主题,将与一些外观非常不同的主题结合使用。但是,如果您没有任何中性图标,请安装您所拥有的所有的 hicolor 主题图标,以便所有应用程序在所有主题中至少获得一些图标。

实现说明

本文档中描述的算法通过始终在目录中查找文件名(unix 术语中的 stat)来工作。一个好的实现应该只读取一次目录,并使用该信息在内存中进行所有查找(例如qt中QIcon的实现)。

这种缓存可能使用户无法在不重新启动应用程序的情况下添加图标。为了处理这个问题,任何执行缓存的实现都需要在执行缓存查找时查看顶级图标目录的最后修改时间,除非它在不到 5 秒前已经这样做了(意味着超过5秒再次获取图标时,应该重新刷新一遍内存中的信息,从而防止图标文件的更新无法及时获取到)。这意味着任何图标编辑器或主题安装程序只需要更改其更改主题的顶级目录的最后修改时间,就可以确保最终能使用到新图标。

 Comments