inspect-ai 0.3.102__py3-none-any.whl → 0.3.104__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.
Files changed (112) hide show
  1. inspect_ai/_cli/common.py +2 -1
  2. inspect_ai/_cli/eval.py +2 -1
  3. inspect_ai/_display/core/active.py +3 -0
  4. inspect_ai/_display/core/config.py +1 -0
  5. inspect_ai/_display/core/panel.py +21 -13
  6. inspect_ai/_display/core/results.py +3 -7
  7. inspect_ai/_display/core/rich.py +3 -5
  8. inspect_ai/_display/log/__init__.py +0 -0
  9. inspect_ai/_display/log/display.py +173 -0
  10. inspect_ai/_display/plain/display.py +2 -2
  11. inspect_ai/_display/rich/display.py +2 -4
  12. inspect_ai/_display/textual/app.py +1 -6
  13. inspect_ai/_display/textual/widgets/task_detail.py +3 -14
  14. inspect_ai/_display/textual/widgets/tasks.py +1 -1
  15. inspect_ai/_eval/eval.py +14 -2
  16. inspect_ai/_eval/evalset.py +3 -2
  17. inspect_ai/_eval/registry.py +6 -1
  18. inspect_ai/_eval/run.py +7 -1
  19. inspect_ai/_eval/task/constants.py +1 -0
  20. inspect_ai/_eval/task/log.py +5 -1
  21. inspect_ai/_eval/task/run.py +1 -1
  22. inspect_ai/_util/citation.py +88 -0
  23. inspect_ai/_util/content.py +24 -2
  24. inspect_ai/_util/json.py +17 -2
  25. inspect_ai/_util/registry.py +19 -4
  26. inspect_ai/_view/schema.py +0 -6
  27. inspect_ai/_view/www/dist/assets/index.css +82 -24
  28. inspect_ai/_view/www/dist/assets/index.js +10124 -9808
  29. inspect_ai/_view/www/log-schema.json +418 -1
  30. inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
  31. inspect_ai/_view/www/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  32. inspect_ai/_view/www/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  33. inspect_ai/_view/www/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  34. inspect_ai/_view/www/node_modules/katex/src/metrics/format_json.py +28 -0
  35. inspect_ai/_view/www/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  36. inspect_ai/_view/www/package.json +2 -2
  37. inspect_ai/_view/www/src/@types/log.d.ts +140 -39
  38. inspect_ai/_view/www/src/app/content/RecordTree.tsx +13 -0
  39. inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -1
  40. inspect_ai/_view/www/src/app/routing/logNavigation.ts +31 -0
  41. inspect_ai/_view/www/src/app/routing/{navigationHooks.ts → sampleNavigation.ts} +39 -86
  42. inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +1 -1
  43. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +1 -1
  44. inspect_ai/_view/www/src/app/samples/chat/MessageCitations.module.css +16 -0
  45. inspect_ai/_view/www/src/app/samples/chat/MessageCitations.tsx +63 -0
  46. inspect_ai/_view/www/src/app/samples/chat/MessageContent.module.css +6 -0
  47. inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +174 -25
  48. inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +21 -3
  49. inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.module.css +7 -0
  50. inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.tsx +111 -0
  51. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.module.css +10 -0
  52. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.tsx +14 -0
  53. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.module.css +19 -0
  54. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.tsx +49 -0
  55. inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -1
  56. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +12 -2
  57. inspect_ai/_view/www/src/app/samples/chat/types.ts +4 -0
  58. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +1 -1
  59. inspect_ai/_view/www/src/app/samples/sampleLimit.ts +2 -2
  60. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
  61. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +4 -4
  62. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +1 -1
  63. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +15 -2
  64. inspect_ai/_view/www/src/tests/README.md +2 -2
  65. inspect_ai/_view/www/src/utils/git.ts +3 -1
  66. inspect_ai/_view/www/src/utils/html.ts +6 -0
  67. inspect_ai/agent/_handoff.py +3 -3
  68. inspect_ai/log/_condense.py +5 -0
  69. inspect_ai/log/_file.py +4 -1
  70. inspect_ai/log/_log.py +9 -4
  71. inspect_ai/log/_recorders/eval.py +4 -3
  72. inspect_ai/log/_recorders/json.py +5 -2
  73. inspect_ai/log/_recorders/recorder.py +1 -0
  74. inspect_ai/log/_util.py +2 -0
  75. inspect_ai/model/__init__.py +14 -0
  76. inspect_ai/model/_call_tools.py +13 -4
  77. inspect_ai/model/_chat_message.py +3 -0
  78. inspect_ai/model/_openai_responses.py +80 -34
  79. inspect_ai/model/_providers/_anthropic_citations.py +158 -0
  80. inspect_ai/model/_providers/_google_citations.py +100 -0
  81. inspect_ai/model/_providers/anthropic.py +196 -34
  82. inspect_ai/model/_providers/google.py +94 -22
  83. inspect_ai/model/_providers/mistral.py +20 -7
  84. inspect_ai/model/_providers/openai.py +11 -10
  85. inspect_ai/model/_providers/openai_compatible.py +3 -2
  86. inspect_ai/model/_providers/openai_responses.py +2 -5
  87. inspect_ai/model/_providers/perplexity.py +123 -0
  88. inspect_ai/model/_providers/providers.py +13 -2
  89. inspect_ai/model/_providers/vertex.py +3 -0
  90. inspect_ai/model/_trim.py +5 -0
  91. inspect_ai/tool/__init__.py +14 -0
  92. inspect_ai/tool/_mcp/_mcp.py +5 -2
  93. inspect_ai/tool/_mcp/sampling.py +19 -3
  94. inspect_ai/tool/_mcp/server.py +1 -1
  95. inspect_ai/tool/_tool.py +10 -1
  96. inspect_ai/tool/_tools/_web_search/_base_http_provider.py +104 -0
  97. inspect_ai/tool/_tools/_web_search/_exa.py +78 -0
  98. inspect_ai/tool/_tools/_web_search/_google.py +22 -25
  99. inspect_ai/tool/_tools/_web_search/_tavily.py +47 -65
  100. inspect_ai/tool/_tools/_web_search/_web_search.py +83 -36
  101. inspect_ai/tool/_tools/_web_search/_web_search_provider.py +7 -0
  102. inspect_ai/util/_display.py +11 -2
  103. inspect_ai/util/_sandbox/docker/compose.py +2 -2
  104. inspect_ai/util/_span.py +12 -1
  105. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.dist-info}/METADATA +2 -2
  106. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.dist-info}/RECORD +112 -88
  107. /inspect_ai/model/{_openai_computer_use.py → _providers/_openai_computer_use.py} +0 -0
  108. /inspect_ai/model/{_openai_web_search.py → _providers/_openai_web_search.py} +0 -0
  109. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.dist-info}/WHEEL +0 -0
  110. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.dist-info}/entry_points.txt +0 -0
  111. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.dist-info}/licenses/LICENSE +0 -0
  112. {inspect_ai-0.3.102.dist-info → inspect_ai-0.3.104.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)
@@ -91,7 +91,7 @@
91
91
  "react-popper": "^2.3.0",
92
92
  "react-router-dom": "^7.6.0",
93
93
  "react-virtuoso": "^4.12.7",
94
- "zustand": "^5.0.5",
95
- "use-resize-observer": "^9.1.0"
94
+ "use-resize-observer": "^9.1.0",
95
+ "zustand": "^5.0.5"
96
96
  }
97
97
  }