inspect-ai 0.3.103__py3-none-any.whl → 0.3.105__py3-none-any.whl
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.
- inspect_ai/_cli/common.py +2 -1
- inspect_ai/_cli/eval.py +2 -2
- inspect_ai/_display/core/active.py +3 -0
- inspect_ai/_display/core/config.py +1 -0
- inspect_ai/_display/core/panel.py +21 -13
- inspect_ai/_display/core/results.py +3 -7
- inspect_ai/_display/core/rich.py +3 -5
- inspect_ai/_display/log/__init__.py +0 -0
- inspect_ai/_display/log/display.py +173 -0
- inspect_ai/_display/plain/display.py +2 -2
- inspect_ai/_display/rich/display.py +2 -4
- inspect_ai/_display/textual/app.py +1 -6
- inspect_ai/_display/textual/widgets/task_detail.py +3 -14
- inspect_ai/_display/textual/widgets/tasks.py +1 -1
- inspect_ai/_eval/eval.py +1 -1
- inspect_ai/_eval/evalset.py +3 -3
- inspect_ai/_eval/registry.py +6 -1
- inspect_ai/_eval/run.py +5 -1
- inspect_ai/_eval/task/constants.py +1 -0
- inspect_ai/_eval/task/log.py +2 -0
- inspect_ai/_eval/task/run.py +65 -39
- inspect_ai/_util/citation.py +88 -0
- inspect_ai/_util/content.py +24 -2
- inspect_ai/_util/json.py +17 -2
- inspect_ai/_util/registry.py +19 -4
- inspect_ai/_view/schema.py +0 -6
- inspect_ai/_view/server.py +17 -0
- inspect_ai/_view/www/dist/assets/index.css +93 -31
- inspect_ai/_view/www/dist/assets/index.js +10639 -10011
- inspect_ai/_view/www/log-schema.json +418 -1
- inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
- inspect_ai/_view/www/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- inspect_ai/_view/www/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- inspect_ai/_view/www/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- inspect_ai/_view/www/node_modules/katex/src/metrics/format_json.py +28 -0
- inspect_ai/_view/www/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- inspect_ai/_view/www/package.json +2 -2
- inspect_ai/_view/www/src/@types/log.d.ts +140 -39
- inspect_ai/_view/www/src/app/content/RecordTree.tsx +13 -0
- inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -1
- inspect_ai/_view/www/src/app/routing/logNavigation.ts +31 -0
- inspect_ai/_view/www/src/app/routing/{navigationHooks.ts → sampleNavigation.ts} +39 -86
- inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/chat/ChatMessage.module.css +4 -0
- inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +17 -0
- inspect_ai/_view/www/src/app/samples/chat/MessageCitations.module.css +16 -0
- inspect_ai/_view/www/src/app/samples/chat/MessageCitations.tsx +63 -0
- inspect_ai/_view/www/src/app/samples/chat/MessageContent.module.css +6 -0
- inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +174 -25
- inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +21 -3
- inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.module.css +7 -0
- inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.tsx +111 -0
- inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.module.css +10 -0
- inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.tsx +14 -0
- inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.module.css +19 -0
- inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.tsx +49 -0
- inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -1
- inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +12 -2
- inspect_ai/_view/www/src/app/samples/chat/types.ts +4 -0
- inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/sample-tools/filters.ts +26 -0
- inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/SampleFilter.tsx +14 -3
- inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/completions.ts +359 -7
- inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/language.ts +6 -0
- inspect_ai/_view/www/src/app/samples/sampleLimit.ts +2 -2
- inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +4 -4
- inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +1 -1
- inspect_ai/_view/www/src/client/api/api-browser.ts +25 -0
- inspect_ai/_view/www/src/client/api/api-http.ts +3 -0
- inspect_ai/_view/www/src/client/api/api-vscode.ts +6 -0
- inspect_ai/_view/www/src/client/api/client-api.ts +3 -0
- inspect_ai/_view/www/src/client/api/jsonrpc.ts +1 -0
- inspect_ai/_view/www/src/client/api/types.ts +3 -0
- inspect_ai/_view/www/src/components/MarkdownDiv.tsx +15 -2
- inspect_ai/_view/www/src/state/samplePolling.ts +17 -1
- inspect_ai/_view/www/src/tests/README.md +2 -2
- inspect_ai/_view/www/src/utils/git.ts +3 -1
- inspect_ai/_view/www/src/utils/html.ts +6 -0
- inspect_ai/agent/_handoff.py +8 -5
- inspect_ai/agent/_react.py +5 -5
- inspect_ai/dataset/_dataset.py +1 -1
- inspect_ai/log/_condense.py +5 -0
- inspect_ai/log/_file.py +4 -1
- inspect_ai/log/_log.py +9 -4
- inspect_ai/log/_recorders/json.py +4 -2
- inspect_ai/log/_samples.py +5 -0
- inspect_ai/log/_util.py +2 -0
- inspect_ai/model/__init__.py +14 -0
- inspect_ai/model/_call_tools.py +17 -8
- inspect_ai/model/_chat_message.py +3 -0
- inspect_ai/model/_openai_responses.py +80 -34
- inspect_ai/model/_providers/_anthropic_citations.py +158 -0
- inspect_ai/model/_providers/_google_citations.py +100 -0
- inspect_ai/model/_providers/anthropic.py +219 -36
- inspect_ai/model/_providers/google.py +98 -22
- inspect_ai/model/_providers/mistral.py +20 -7
- inspect_ai/model/_providers/openai.py +11 -10
- inspect_ai/model/_providers/openai_compatible.py +3 -2
- inspect_ai/model/_providers/openai_responses.py +2 -5
- inspect_ai/model/_providers/perplexity.py +123 -0
- inspect_ai/model/_providers/providers.py +13 -2
- inspect_ai/model/_providers/vertex.py +3 -0
- inspect_ai/model/_trim.py +5 -0
- inspect_ai/tool/__init__.py +14 -0
- inspect_ai/tool/_mcp/_mcp.py +5 -2
- inspect_ai/tool/_mcp/sampling.py +19 -3
- inspect_ai/tool/_mcp/server.py +1 -1
- inspect_ai/tool/_tool.py +10 -1
- inspect_ai/tool/_tools/_web_search/_base_http_provider.py +104 -0
- inspect_ai/tool/_tools/_web_search/_exa.py +78 -0
- inspect_ai/tool/_tools/_web_search/_google.py +22 -25
- inspect_ai/tool/_tools/_web_search/_tavily.py +47 -65
- inspect_ai/tool/_tools/_web_search/_web_search.py +83 -36
- inspect_ai/tool/_tools/_web_search/_web_search_provider.py +7 -0
- inspect_ai/util/__init__.py +8 -0
- inspect_ai/util/_background.py +64 -0
- inspect_ai/util/_display.py +11 -2
- inspect_ai/util/_limit.py +72 -5
- inspect_ai/util/_sandbox/__init__.py +2 -0
- inspect_ai/util/_sandbox/docker/compose.py +2 -2
- inspect_ai/util/_sandbox/service.py +28 -7
- inspect_ai/util/_span.py +12 -1
- inspect_ai/util/_subprocess.py +51 -38
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/METADATA +2 -2
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/RECORD +134 -109
- /inspect_ai/model/{_openai_computer_use.py → _providers/_openai_computer_use.py} +0 -0
- /inspect_ai/model/{_openai_web_search.py → _providers/_openai_web_search.py} +0 -0
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/WHEEL +0 -0
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/licenses/LICENSE +0 -0
- {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import os
|
5
|
+
import json
|
6
|
+
|
7
|
+
from fontTools.ttLib import TTFont, sfnt
|
8
|
+
from fontTools.misc.timeTools import timestampNow
|
9
|
+
sfnt.USE_ZOPFLI = True
|
10
|
+
|
11
|
+
if len(sys.argv) < 2:
|
12
|
+
print("Usage: %s <font file>" % sys.argv[0])
|
13
|
+
sys.exit(1)
|
14
|
+
|
15
|
+
font_file = sys.argv[1]
|
16
|
+
font_name = os.path.splitext(os.path.basename(font_file))[0]
|
17
|
+
|
18
|
+
|
19
|
+
font = TTFont(font_file, recalcBBoxes=False, recalcTimestamp=False)
|
20
|
+
|
21
|
+
# fix timestamp to the epoch
|
22
|
+
font['head'].created = 0
|
23
|
+
font['head'].modified = 0
|
24
|
+
|
25
|
+
# remove fontforge timestamps
|
26
|
+
if 'FFTM' in font:
|
27
|
+
del font['FFTM']
|
28
|
+
|
29
|
+
# remove redundant GDEF table
|
30
|
+
if 'GDEF' in font:
|
31
|
+
del font['GDEF']
|
32
|
+
|
33
|
+
# remove Macintosh table
|
34
|
+
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
|
35
|
+
font['name'].names = [record for record in font['name'].names if record.platformID != 1]
|
36
|
+
font['cmap'].tables = [table for table in font['cmap'].tables if table.platformID != 1]
|
37
|
+
|
38
|
+
# fix OS/2 and hhea metrics
|
39
|
+
glyf = font['glyf']
|
40
|
+
ascent = int(max(glyf[c].yMax for c in font.getGlyphOrder() if hasattr(glyf[c], "yMax")))
|
41
|
+
descent = -int(min(glyf[c].yMin for c in font.getGlyphOrder() if hasattr(glyf[c], "yMin")))
|
42
|
+
|
43
|
+
font['OS/2'].usWinAscent = ascent
|
44
|
+
font['OS/2'].usWinDescent = descent
|
45
|
+
|
46
|
+
font['hhea'].ascent = ascent
|
47
|
+
font['hhea'].descent = -descent
|
48
|
+
|
49
|
+
# save TTF
|
50
|
+
font.save(font_file, reorderTables=None)
|
51
|
+
|
52
|
+
# save WOFF
|
53
|
+
font.flavor = 'woff'
|
54
|
+
font.save(os.path.join('woff', font_name + '.woff'), reorderTables=None)
|
55
|
+
|
56
|
+
# save WOFF2
|
57
|
+
font.flavor = 'woff2'
|
58
|
+
font.save(os.path.join('woff2', font_name + '.woff2'), reorderTables=None)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import collections
|
4
|
+
import json
|
5
|
+
import parse_tfm
|
6
|
+
import subprocess
|
7
|
+
import sys
|
8
|
+
|
9
|
+
|
10
|
+
def find_font_path(font_name):
|
11
|
+
try:
|
12
|
+
font_path = subprocess.check_output(['kpsewhich', font_name])
|
13
|
+
except OSError:
|
14
|
+
raise RuntimeError("Couldn't find kpsewhich program, make sure you" +
|
15
|
+
" have TeX installed")
|
16
|
+
except subprocess.CalledProcessError:
|
17
|
+
raise RuntimeError("Couldn't find font metrics: '%s'" % font_name)
|
18
|
+
return font_path.strip()
|
19
|
+
|
20
|
+
|
21
|
+
def main():
|
22
|
+
mapping = json.load(sys.stdin)
|
23
|
+
|
24
|
+
fonts = [
|
25
|
+
'cmbsy10.tfm',
|
26
|
+
'cmbx10.tfm',
|
27
|
+
'cmbxti10.tfm',
|
28
|
+
'cmex10.tfm',
|
29
|
+
'cmmi10.tfm',
|
30
|
+
'cmmib10.tfm',
|
31
|
+
'cmr10.tfm',
|
32
|
+
'cmsy10.tfm',
|
33
|
+
'cmti10.tfm',
|
34
|
+
'msam10.tfm',
|
35
|
+
'msbm10.tfm',
|
36
|
+
'eufm10.tfm',
|
37
|
+
'cmtt10.tfm',
|
38
|
+
'rsfs10.tfm',
|
39
|
+
'cmss10.tfm',
|
40
|
+
'cmssbx10.tfm',
|
41
|
+
'cmssi10.tfm',
|
42
|
+
]
|
43
|
+
|
44
|
+
# Extracted by running `\font\a=<font>` and then `\showthe\skewchar\a` in
|
45
|
+
# TeX, where `<font>` is the name of the font listed here. The skewchar
|
46
|
+
# will be printed out in the output. If it outputs `-1`, that means there
|
47
|
+
# is no skewchar, so we use `None` here.
|
48
|
+
font_skewchar = {
|
49
|
+
'cmbsy10': None,
|
50
|
+
'cmbx10': None,
|
51
|
+
'cmbxti10': None,
|
52
|
+
'cmex10': None,
|
53
|
+
'cmmi10': 127,
|
54
|
+
'cmmib10': None,
|
55
|
+
'cmr10': None,
|
56
|
+
'cmsy10': 48,
|
57
|
+
'cmti10': None,
|
58
|
+
'msam10': None,
|
59
|
+
'msbm10': None,
|
60
|
+
'eufm10': None,
|
61
|
+
'cmtt10': None,
|
62
|
+
'rsfs10': None,
|
63
|
+
'cmss10': None,
|
64
|
+
'cmssbx10': None,
|
65
|
+
'cmssi10': None,
|
66
|
+
}
|
67
|
+
|
68
|
+
font_name_to_tfm = {}
|
69
|
+
|
70
|
+
for font_name in fonts:
|
71
|
+
font_basename = font_name.split('.')[0]
|
72
|
+
font_path = find_font_path(font_name)
|
73
|
+
font_name_to_tfm[font_basename] = parse_tfm.read_tfm_file(font_path)
|
74
|
+
|
75
|
+
families = collections.defaultdict(dict)
|
76
|
+
|
77
|
+
for family, chars in mapping.items():
|
78
|
+
for char, char_data in chars.items():
|
79
|
+
char_num = int(char)
|
80
|
+
|
81
|
+
font = char_data['font']
|
82
|
+
tex_char_num = int(char_data['char'])
|
83
|
+
yshift = float(char_data['yshift'])
|
84
|
+
|
85
|
+
if family == "Script-Regular":
|
86
|
+
tfm_char = font_name_to_tfm[font].get_char_metrics(tex_char_num,
|
87
|
+
fix_rsfs=True)
|
88
|
+
else:
|
89
|
+
tfm_char = font_name_to_tfm[font].get_char_metrics(tex_char_num)
|
90
|
+
|
91
|
+
height = round(tfm_char.height + yshift / 1000.0, 5)
|
92
|
+
depth = round(tfm_char.depth - yshift / 1000.0, 5)
|
93
|
+
italic = round(tfm_char.italic_correction, 5)
|
94
|
+
width = round(tfm_char.width, 5)
|
95
|
+
|
96
|
+
skewkern = 0.0
|
97
|
+
if (font_skewchar[font] and
|
98
|
+
font_skewchar[font] in tfm_char.kern_table):
|
99
|
+
skewkern = round(
|
100
|
+
tfm_char.kern_table[font_skewchar[font]], 5)
|
101
|
+
|
102
|
+
families[family][char_num] = {
|
103
|
+
'height': height,
|
104
|
+
'depth': depth,
|
105
|
+
'italic': italic,
|
106
|
+
'skew': skewkern,
|
107
|
+
'width': width
|
108
|
+
}
|
109
|
+
|
110
|
+
sys.stdout.write(
|
111
|
+
json.dumps(families, separators=(',', ':'), sort_keys=True))
|
112
|
+
|
113
|
+
if __name__ == '__main__':
|
114
|
+
main()
|
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
from fontTools.ttLib import TTFont
|
4
|
+
import sys
|
5
|
+
import json
|
6
|
+
|
7
|
+
# map of characters to extract
|
8
|
+
metrics_to_extract = {
|
9
|
+
# Font name
|
10
|
+
"AMS-Regular": {
|
11
|
+
u"\u21e2": None, # \dashrightarrow
|
12
|
+
u"\u21e0": None, # \dashleftarrow
|
13
|
+
},
|
14
|
+
"Main-Regular": {
|
15
|
+
# Skew and italic metrics can't be easily parsed from the TTF. Instead,
|
16
|
+
# we map each character to a "base character", which is a character
|
17
|
+
# from the same font with correct italic and skew metrics. A character
|
18
|
+
# maps to None if it doesn't have a base.
|
19
|
+
|
20
|
+
#u"\u2209": None, # \notin
|
21
|
+
#u"\u2260": None, # \neq
|
22
|
+
u"\u2245": None, # \cong
|
23
|
+
u"\u2026": None, # \ldots
|
24
|
+
u"\u22ef": None, # \cdots
|
25
|
+
u"\u22f1": None, # \ddots
|
26
|
+
u"\u22ee": None, # \vdots
|
27
|
+
u"\u22ee": None, # \vdots
|
28
|
+
u"\u22a8": None, # \models
|
29
|
+
u"\u22c8": None, # \bowtie
|
30
|
+
u"\u2250": None, # \doteq
|
31
|
+
u"\u23b0": None, # \lmoustache
|
32
|
+
u"\u23b1": None, # \rmoustache
|
33
|
+
u"\u27ee": None, # \lgroup
|
34
|
+
u"\u27ef": None, # \rgroup
|
35
|
+
u"\u27f5": None, # \longleftarrow
|
36
|
+
u"\u27f8": None, # \Longleftarrow
|
37
|
+
u"\u27f6": None, # \longrightarrow
|
38
|
+
u"\u27f9": None, # \Longrightarrow
|
39
|
+
u"\u27f7": None, # \longleftrightarrow
|
40
|
+
u"\u27fa": None, # \Longleftrightarrow
|
41
|
+
u"\u21a6": None, # \mapsto
|
42
|
+
u"\u27fc": None, # \longmapsto
|
43
|
+
u"\u21a9": None, # \hookleftarrow
|
44
|
+
u"\u21aa": None, # \hookrightarrow
|
45
|
+
u"\u21cc": None, # \rightleftharpoons
|
46
|
+
},
|
47
|
+
"Main-Bold": {
|
48
|
+
u"\u2245": None, # \cong
|
49
|
+
},
|
50
|
+
"Size1-Regular": {
|
51
|
+
u"\u222c": u"\u222b", # \iint, based on \int
|
52
|
+
u"\u222d": u"\u222b", # \iiint, based on \int
|
53
|
+
},
|
54
|
+
"Size2-Regular": {
|
55
|
+
u"\u222c": u"\u222b", # \iint, based on \int
|
56
|
+
u"\u222d": u"\u222b", # \iiint, based on \int
|
57
|
+
},
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
def main():
|
62
|
+
start_json = json.load(sys.stdin)
|
63
|
+
|
64
|
+
for font in start_json:
|
65
|
+
fontInfo = TTFont("../../fonts/KaTeX_" + font + ".ttf")
|
66
|
+
glyf = fontInfo["glyf"]
|
67
|
+
widths = fontInfo.getGlyphSet()
|
68
|
+
unitsPerEm = float(fontInfo["head"].unitsPerEm)
|
69
|
+
|
70
|
+
# We keep ALL Unicode cmaps, not just fontInfo["cmap"].getcmap(3, 1).
|
71
|
+
# This is playing it extra safe, since it reports inconsistencies.
|
72
|
+
# Platform 0 is Unicode, platform 3 is Windows. For platform 3,
|
73
|
+
# encoding 1 is UCS-2 and encoding 10 is UCS-4.
|
74
|
+
cmap = [t.cmap for t in fontInfo["cmap"].tables
|
75
|
+
if (t.platformID == 0)
|
76
|
+
or (t.platformID == 3 and t.platEncID in (1, 10))]
|
77
|
+
|
78
|
+
chars = metrics_to_extract.get(font, {})
|
79
|
+
chars[u"\u0020"] = None # space
|
80
|
+
chars[u"\u00a0"] = None # nbsp
|
81
|
+
|
82
|
+
for char, base_char in chars.items():
|
83
|
+
code = ord(char)
|
84
|
+
names = set(t.get(code) for t in cmap)
|
85
|
+
if not names:
|
86
|
+
sys.stderr.write(
|
87
|
+
"Codepoint {} of font {} maps to no name\n"
|
88
|
+
.format(code, font))
|
89
|
+
continue
|
90
|
+
if len(names) != 1:
|
91
|
+
sys.stderr.write(
|
92
|
+
"Codepoint {} of font {} maps to multiple names: {}\n"
|
93
|
+
.format(code, font, ", ".join(sorted(names))))
|
94
|
+
continue
|
95
|
+
name = names.pop()
|
96
|
+
|
97
|
+
height = depth = italic = skew = width = 0
|
98
|
+
glyph = glyf[name]
|
99
|
+
if glyph.numberOfContours:
|
100
|
+
height = glyph.yMax / unitsPerEm
|
101
|
+
depth = -glyph.yMin / unitsPerEm
|
102
|
+
width = widths[name].width / unitsPerEm
|
103
|
+
if base_char:
|
104
|
+
base_char_str = str(ord(base_char))
|
105
|
+
base_metrics = start_json[font][base_char_str]
|
106
|
+
italic = base_metrics["italic"]
|
107
|
+
skew = base_metrics["skew"]
|
108
|
+
width = base_metrics["width"]
|
109
|
+
|
110
|
+
start_json[font][str(code)] = {
|
111
|
+
"height": height,
|
112
|
+
"depth": depth,
|
113
|
+
"italic": italic,
|
114
|
+
"skew": skew,
|
115
|
+
"width": width
|
116
|
+
}
|
117
|
+
|
118
|
+
sys.stdout.write(
|
119
|
+
json.dumps(start_json, separators=(',', ':'), sort_keys=True))
|
120
|
+
|
121
|
+
if __name__ == "__main__":
|
122
|
+
main()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import json
|
5
|
+
|
6
|
+
props = ['depth', 'height', 'italic', 'skew']
|
7
|
+
|
8
|
+
if len(sys.argv) > 1:
|
9
|
+
if sys.argv[1] == '--width':
|
10
|
+
props.append('width')
|
11
|
+
|
12
|
+
data = json.load(sys.stdin)
|
13
|
+
sys.stdout.write(
|
14
|
+
"// This file is GENERATED by buildMetrics.sh. DO NOT MODIFY.\n")
|
15
|
+
sep = "export default {\n "
|
16
|
+
for font in sorted(data):
|
17
|
+
sys.stdout.write(sep + json.dumps(font))
|
18
|
+
sep = ": {\n "
|
19
|
+
for glyph in sorted(data[font], key=int):
|
20
|
+
sys.stdout.write(sep + json.dumps(glyph) + ": ")
|
21
|
+
|
22
|
+
values = [value if value != 0.0 else 0 for value in
|
23
|
+
[data[font][glyph][key] for key in props]]
|
24
|
+
|
25
|
+
sys.stdout.write(json.dumps(values))
|
26
|
+
sep = ",\n "
|
27
|
+
sep = ",\n },\n "
|
28
|
+
sys.stdout.write(",\n },\n};\n")
|
@@ -0,0 +1,211 @@
|
|
1
|
+
class CharInfoWord(object):
|
2
|
+
def __init__(self, word):
|
3
|
+
b1, b2, b3, b4 = (word >> 24,
|
4
|
+
(word & 0xff0000) >> 16,
|
5
|
+
(word & 0xff00) >> 8,
|
6
|
+
word & 0xff)
|
7
|
+
|
8
|
+
self.width_index = b1
|
9
|
+
self.height_index = b2 >> 4
|
10
|
+
self.depth_index = b2 & 0x0f
|
11
|
+
self.italic_index = (b3 & 0b11111100) >> 2
|
12
|
+
self.tag = b3 & 0b11
|
13
|
+
self.remainder = b4
|
14
|
+
|
15
|
+
def has_ligkern(self):
|
16
|
+
return self.tag == 1
|
17
|
+
|
18
|
+
def ligkern_start(self):
|
19
|
+
return self.remainder
|
20
|
+
|
21
|
+
|
22
|
+
class LigKernProgram(object):
|
23
|
+
def __init__(self, program):
|
24
|
+
self.program = program
|
25
|
+
|
26
|
+
def execute(self, start, next_char):
|
27
|
+
curr_instruction = start
|
28
|
+
while True:
|
29
|
+
instruction = self.program[curr_instruction]
|
30
|
+
(skip, inst_next_char, op, remainder) = instruction
|
31
|
+
|
32
|
+
if inst_next_char == next_char:
|
33
|
+
if op < 128:
|
34
|
+
# Don't worry about ligatures for now, we only need kerns
|
35
|
+
return None
|
36
|
+
else:
|
37
|
+
return 256 * (op - 128) + remainder
|
38
|
+
elif skip >= 128:
|
39
|
+
return None
|
40
|
+
else:
|
41
|
+
curr_instruction += 1 + skip
|
42
|
+
|
43
|
+
|
44
|
+
class TfmCharMetrics(object):
|
45
|
+
def __init__(self, width, height, depth, italic, kern_table):
|
46
|
+
self.width = width
|
47
|
+
self.height = height
|
48
|
+
self.depth = depth
|
49
|
+
self.italic_correction = italic
|
50
|
+
self.kern_table = kern_table
|
51
|
+
|
52
|
+
|
53
|
+
class TfmFile(object):
|
54
|
+
def __init__(self, start_char, end_char, char_info, width_table,
|
55
|
+
height_table, depth_table, italic_table, ligkern_table,
|
56
|
+
kern_table):
|
57
|
+
self.start_char = start_char
|
58
|
+
self.end_char = end_char
|
59
|
+
self.char_info = char_info
|
60
|
+
self.width_table = width_table
|
61
|
+
self.height_table = height_table
|
62
|
+
self.depth_table = depth_table
|
63
|
+
self.italic_table = italic_table
|
64
|
+
self.ligkern_program = LigKernProgram(ligkern_table)
|
65
|
+
self.kern_table = kern_table
|
66
|
+
|
67
|
+
def get_char_metrics(self, char_num, fix_rsfs=False):
|
68
|
+
"""Return glyph metrics for a unicode code point.
|
69
|
+
|
70
|
+
Arguments:
|
71
|
+
char_num: a unicode code point
|
72
|
+
fix_rsfs: adjust for rsfs10.tfm's different indexing system
|
73
|
+
"""
|
74
|
+
if char_num < self.start_char or char_num > self.end_char:
|
75
|
+
raise RuntimeError("Invalid character number")
|
76
|
+
|
77
|
+
if fix_rsfs:
|
78
|
+
# all of the char_nums contained start from zero in rsfs10.tfm
|
79
|
+
info = self.char_info[char_num - self.start_char]
|
80
|
+
else:
|
81
|
+
info = self.char_info[char_num + self.start_char]
|
82
|
+
|
83
|
+
char_kern_table = {}
|
84
|
+
if info.has_ligkern():
|
85
|
+
for char in range(self.start_char, self.end_char + 1):
|
86
|
+
kern = self.ligkern_program.execute(info.ligkern_start(), char)
|
87
|
+
if kern:
|
88
|
+
char_kern_table[char] = self.kern_table[kern]
|
89
|
+
|
90
|
+
return TfmCharMetrics(
|
91
|
+
self.width_table[info.width_index],
|
92
|
+
self.height_table[info.height_index],
|
93
|
+
self.depth_table[info.depth_index],
|
94
|
+
self.italic_table[info.italic_index],
|
95
|
+
char_kern_table)
|
96
|
+
|
97
|
+
|
98
|
+
class TfmReader(object):
|
99
|
+
def __init__(self, f):
|
100
|
+
self.f = f
|
101
|
+
|
102
|
+
def read_byte(self):
|
103
|
+
return ord(self.f.read(1))
|
104
|
+
|
105
|
+
def read_halfword(self):
|
106
|
+
b1 = self.read_byte()
|
107
|
+
b2 = self.read_byte()
|
108
|
+
return (b1 << 8) | b2
|
109
|
+
|
110
|
+
def read_word(self):
|
111
|
+
b1 = self.read_byte()
|
112
|
+
b2 = self.read_byte()
|
113
|
+
b3 = self.read_byte()
|
114
|
+
b4 = self.read_byte()
|
115
|
+
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
|
116
|
+
|
117
|
+
def read_fixword(self):
|
118
|
+
word = self.read_word()
|
119
|
+
|
120
|
+
neg = False
|
121
|
+
if word & 0x80000000:
|
122
|
+
neg = True
|
123
|
+
word = (-word & 0xffffffff)
|
124
|
+
|
125
|
+
return (-1 if neg else 1) * word / float(1 << 20)
|
126
|
+
|
127
|
+
def read_bcpl(self, length):
|
128
|
+
str_length = self.read_byte()
|
129
|
+
data = self.f.read(length - 1)
|
130
|
+
return data[:str_length]
|
131
|
+
|
132
|
+
|
133
|
+
def read_tfm_file(file_name):
|
134
|
+
with open(file_name, 'rb') as f:
|
135
|
+
reader = TfmReader(f)
|
136
|
+
|
137
|
+
# file_size
|
138
|
+
reader.read_halfword()
|
139
|
+
header_size = reader.read_halfword()
|
140
|
+
|
141
|
+
start_char = reader.read_halfword()
|
142
|
+
end_char = reader.read_halfword()
|
143
|
+
|
144
|
+
width_table_size = reader.read_halfword()
|
145
|
+
height_table_size = reader.read_halfword()
|
146
|
+
depth_table_size = reader.read_halfword()
|
147
|
+
italic_table_size = reader.read_halfword()
|
148
|
+
|
149
|
+
ligkern_table_size = reader.read_halfword()
|
150
|
+
kern_table_size = reader.read_halfword()
|
151
|
+
|
152
|
+
# extensible_table_size
|
153
|
+
reader.read_halfword()
|
154
|
+
# parameter_table_size
|
155
|
+
reader.read_halfword()
|
156
|
+
|
157
|
+
# checksum
|
158
|
+
reader.read_word()
|
159
|
+
# design_size
|
160
|
+
reader.read_fixword()
|
161
|
+
|
162
|
+
if header_size > 2:
|
163
|
+
# coding_scheme
|
164
|
+
reader.read_bcpl(40)
|
165
|
+
|
166
|
+
if header_size > 12:
|
167
|
+
# font_family
|
168
|
+
reader.read_bcpl(20)
|
169
|
+
|
170
|
+
for i in range(header_size - 17):
|
171
|
+
reader.read_word()
|
172
|
+
|
173
|
+
char_info = []
|
174
|
+
for i in range(start_char, end_char + 1):
|
175
|
+
char_info.append(CharInfoWord(reader.read_word()))
|
176
|
+
|
177
|
+
width_table = []
|
178
|
+
for i in range(width_table_size):
|
179
|
+
width_table.append(reader.read_fixword())
|
180
|
+
|
181
|
+
height_table = []
|
182
|
+
for i in range(height_table_size):
|
183
|
+
height_table.append(reader.read_fixword())
|
184
|
+
|
185
|
+
depth_table = []
|
186
|
+
for i in range(depth_table_size):
|
187
|
+
depth_table.append(reader.read_fixword())
|
188
|
+
|
189
|
+
italic_table = []
|
190
|
+
for i in range(italic_table_size):
|
191
|
+
italic_table.append(reader.read_fixword())
|
192
|
+
|
193
|
+
ligkern_table = []
|
194
|
+
for i in range(ligkern_table_size):
|
195
|
+
skip = reader.read_byte()
|
196
|
+
next_char = reader.read_byte()
|
197
|
+
op = reader.read_byte()
|
198
|
+
remainder = reader.read_byte()
|
199
|
+
|
200
|
+
ligkern_table.append((skip, next_char, op, remainder))
|
201
|
+
|
202
|
+
kern_table = []
|
203
|
+
for i in range(kern_table_size):
|
204
|
+
kern_table.append(reader.read_fixword())
|
205
|
+
|
206
|
+
# There is more information, like the ligkern, kern, extensible, and
|
207
|
+
# param table, but we don't need these for now
|
208
|
+
|
209
|
+
return TfmFile(start_char, end_char, char_info, width_table,
|
210
|
+
height_table, depth_table, italic_table,
|
211
|
+
ligkern_table, kern_table)
|