prefig 0.4.5.dev20250925053340__py3-none-any.whl → 0.4.6__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.
- prefig/core/annotations.py +107 -0
- prefig/core/diagram.py +29 -0
- prefig/core/label.py +14 -4
- prefig/resources/schema/pf_schema.rnc +4 -2
- prefig/resources/schema/pf_schema.rng +2250 -787
- {prefig-0.4.5.dev20250925053340.dist-info → prefig-0.4.6.dist-info}/METADATA +4 -4
- {prefig-0.4.5.dev20250925053340.dist-info → prefig-0.4.6.dist-info}/RECORD +10 -10
- {prefig-0.4.5.dev20250925053340.dist-info → prefig-0.4.6.dist-info}/LICENSE +0 -0
- {prefig-0.4.5.dev20250925053340.dist-info → prefig-0.4.6.dist-info}/WHEEL +0 -0
- {prefig-0.4.5.dev20250925053340.dist-info → prefig-0.4.6.dist-info}/entry_points.txt +0 -0
prefig/core/annotations.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import lxml.etree as ET
|
|
2
2
|
import logging
|
|
3
|
+
import copy
|
|
3
4
|
|
|
4
5
|
log = logging.getLogger('prefigure')
|
|
5
6
|
|
|
@@ -122,3 +123,109 @@ def annotate(element, diagram, parent = None):
|
|
|
122
123
|
ACTIVE = ET.Element('ACTIVE')
|
|
123
124
|
ACTIVE.text = element.get('id')
|
|
124
125
|
sonification.append(ACTIVE)
|
|
126
|
+
|
|
127
|
+
pronounciations = {
|
|
128
|
+
'de-solve': 'D E solve',
|
|
129
|
+
'define-shapes': 'define shapes',
|
|
130
|
+
'angle-marker': 'angle marker',
|
|
131
|
+
'area-between-curves': 'area between curves',
|
|
132
|
+
'area-under-curve': 'area under curve',
|
|
133
|
+
'grid-axes': 'grid axes',
|
|
134
|
+
'implicit-curve': 'implicit curve',
|
|
135
|
+
'parametric-curve': 'parametric curve',
|
|
136
|
+
'plot-de-solution': 'plot D E solution',
|
|
137
|
+
'riemann-sum': 'Riemann sum',
|
|
138
|
+
'slope-field': 'slope field',
|
|
139
|
+
'tick-mark': 'tick mark',
|
|
140
|
+
'tangent-line': 'tangent line',
|
|
141
|
+
'vector-field': 'vector field'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
labeled_elements = {
|
|
145
|
+
'label',
|
|
146
|
+
'point',
|
|
147
|
+
'xlabel',
|
|
148
|
+
'ylabel',
|
|
149
|
+
'angle-marker',
|
|
150
|
+
'tick-mark',
|
|
151
|
+
'item',
|
|
152
|
+
'node',
|
|
153
|
+
'edge'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
label_subelements = {
|
|
157
|
+
'm': 'math',
|
|
158
|
+
'b': 'bold',
|
|
159
|
+
'it': 'italics',
|
|
160
|
+
'plain': 'plain',
|
|
161
|
+
'newline': 'new line'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def diagram_to_speech(diagram):
|
|
165
|
+
diagram = copy.deepcopy(diagram)
|
|
166
|
+
|
|
167
|
+
element_num = 0
|
|
168
|
+
for element in diagram.getiterator():
|
|
169
|
+
if element.tag in label_subelements.keys():
|
|
170
|
+
element.getparent().remove(element)
|
|
171
|
+
continue
|
|
172
|
+
attribs = copy.deepcopy(element.attrib)
|
|
173
|
+
for attrib_name in list(element.attrib.keys()):
|
|
174
|
+
element.attrib.pop(attrib_name)
|
|
175
|
+
|
|
176
|
+
if element.tag == "diagram":
|
|
177
|
+
element.set('ref', 'figure')
|
|
178
|
+
intro = "This prefigure source file begins with a diagram having these attributes: "
|
|
179
|
+
elif element.tag == "definition":
|
|
180
|
+
element.set('ref', 'element-'+str(element_num))
|
|
181
|
+
tag_speech = 'definition'
|
|
182
|
+
intro = "A definition element defining " + element.text.strip()
|
|
183
|
+
elif element.tag in labeled_elements:
|
|
184
|
+
element.set('ref', 'element-'+str(element_num))
|
|
185
|
+
tag_speech = pronounciations.get(element.tag, element.tag)
|
|
186
|
+
label_text = label_to_speech(element)
|
|
187
|
+
if len(label_text) > 0:
|
|
188
|
+
if len(attribs) == 0:
|
|
189
|
+
intro = f"A {tag_speech} element with label {label_text}. The element has no attributes."
|
|
190
|
+
else:
|
|
191
|
+
intro = f"A {tag_speech} element with label {label_text}. There are these attributes: "
|
|
192
|
+
else:
|
|
193
|
+
if len(attribs) == 0:
|
|
194
|
+
intro = f"A {tag_speech} element with no attributes."
|
|
195
|
+
else:
|
|
196
|
+
intro = f"A {tag_speech} element with these attributes: "
|
|
197
|
+
element.text = None
|
|
198
|
+
else:
|
|
199
|
+
element.set('ref', 'element-'+str(element_num))
|
|
200
|
+
tag_speech = pronounciations.get(element.tag, element.tag)
|
|
201
|
+
if len(attribs) == 0:
|
|
202
|
+
intro = f"A {tag_speech} element with no attributes"
|
|
203
|
+
else:
|
|
204
|
+
intro = f"A {tag_speech} element with these attributes: "
|
|
205
|
+
element.set("text", intro + attributes_to_speech(attribs))
|
|
206
|
+
element_num += 1
|
|
207
|
+
element.tag = "annotation"
|
|
208
|
+
|
|
209
|
+
log.error(ET.tostring(diagram, pretty_print=True))
|
|
210
|
+
return diagram
|
|
211
|
+
|
|
212
|
+
def attributes_to_speech(attribs):
|
|
213
|
+
strings = []
|
|
214
|
+
for key, value in attribs.items():
|
|
215
|
+
strings.append(f"{key} has value {value}")
|
|
216
|
+
return ', '.join(strings)
|
|
217
|
+
|
|
218
|
+
def label_to_speech(element):
|
|
219
|
+
strings = []
|
|
220
|
+
if (element.text is not None and
|
|
221
|
+
len(element.text.strip()) > 0):
|
|
222
|
+
strings.append(element.text.strip())
|
|
223
|
+
for child in element:
|
|
224
|
+
child_speech = label_subelements.get(child.tag, child.tag)
|
|
225
|
+
strings.append('begin ' + child_speech)
|
|
226
|
+
strings.append(child.text.strip())
|
|
227
|
+
strings.append('end ' + child_speech)
|
|
228
|
+
if (child.tail is not None and
|
|
229
|
+
child.tail.strip() is not None):
|
|
230
|
+
strings.append(child.tail.strip())
|
|
231
|
+
return ' '.join(strings)
|
prefig/core/diagram.py
CHANGED
|
@@ -9,6 +9,7 @@ from . import utilities as util
|
|
|
9
9
|
from . import CTM
|
|
10
10
|
from . import label
|
|
11
11
|
from . import math_utilities as math_util
|
|
12
|
+
from . import annotations
|
|
12
13
|
|
|
13
14
|
log = logging.getLogger('prefigure')
|
|
14
15
|
|
|
@@ -31,6 +32,16 @@ class Diagram:
|
|
|
31
32
|
self.environment = environment
|
|
32
33
|
self.caption = ""
|
|
33
34
|
|
|
35
|
+
self.add_default_annotations = True
|
|
36
|
+
if (self.environment == 'pyodide' and
|
|
37
|
+
len(self.diagram_element.xpath('.//annotations')) == 0
|
|
38
|
+
):
|
|
39
|
+
diagram_annotations = annotations.diagram_to_speech(diagram_element)
|
|
40
|
+
annotations_tree = ET.SubElement(self.diagram_element,
|
|
41
|
+
'annotations')
|
|
42
|
+
annotations_tree.append(diagram_annotations)
|
|
43
|
+
self.add_default_annotations = False
|
|
44
|
+
|
|
34
45
|
math_util.set_diagram(self)
|
|
35
46
|
|
|
36
47
|
label.init(self.format, self.environment)
|
|
@@ -109,6 +120,15 @@ class Diagram:
|
|
|
109
120
|
if data_directory is not None:
|
|
110
121
|
self.external = data_directory
|
|
111
122
|
|
|
123
|
+
templates = self.diagram_element.xpath('.//templates')
|
|
124
|
+
if len(templates) > 0:
|
|
125
|
+
templates_element = templates[0]
|
|
126
|
+
for template in templates:
|
|
127
|
+
templates_parent = template.getparent()
|
|
128
|
+
templates_parent.remove(template)
|
|
129
|
+
for child in templates_element:
|
|
130
|
+
self.defaults[child.tag] = child
|
|
131
|
+
|
|
112
132
|
if self.defaults.get('macros', None) is not None:
|
|
113
133
|
label.add_macros(self.defaults.get('macros').text)
|
|
114
134
|
|
|
@@ -487,6 +507,13 @@ class Diagram:
|
|
|
487
507
|
def get_root(self):
|
|
488
508
|
return self.root
|
|
489
509
|
|
|
510
|
+
def apply_defaults(self, tag, element):
|
|
511
|
+
default = self.defaults.get(tag, None)
|
|
512
|
+
if default is not None:
|
|
513
|
+
for attr, value in default.attrib.items():
|
|
514
|
+
if element.get(attr, None) is None:
|
|
515
|
+
element.set(attr, value)
|
|
516
|
+
|
|
490
517
|
# when a graphical component is outlined, we first add the component's path
|
|
491
518
|
# to <defs> so that it can be reused, then we stroke it with a thick white
|
|
492
519
|
def add_outline(self, element, path, parent, outline_width = None):
|
|
@@ -566,6 +593,8 @@ class Diagram:
|
|
|
566
593
|
self.annotations_root = ET.Element('annotations')
|
|
567
594
|
|
|
568
595
|
def add_default_annotation(self, annotation):
|
|
596
|
+
if not self.add_default_annotations:
|
|
597
|
+
return
|
|
569
598
|
self.default_annotations.append(annotation)
|
|
570
599
|
|
|
571
600
|
def get_default_annotations(self):
|
prefig/core/label.py
CHANGED
|
@@ -15,6 +15,8 @@ import tempfile
|
|
|
15
15
|
|
|
16
16
|
log = logging.getLogger('prefigure')
|
|
17
17
|
|
|
18
|
+
allowed_fonts = {'serif','sans-serif','monospace'}
|
|
19
|
+
|
|
18
20
|
def init(format, environment):
|
|
19
21
|
global math_labels
|
|
20
22
|
global text_measurements
|
|
@@ -465,10 +467,18 @@ def position_svg_label(element, diagram, ctm, group):
|
|
|
465
467
|
# These lists contain: font, size, italics, bold, color
|
|
466
468
|
label_color = element.get("color", None)
|
|
467
469
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
diagram.apply_defaults('label', element)
|
|
471
|
+
|
|
472
|
+
font_family = element.get('font', 'sans-serif').lower().strip()
|
|
473
|
+
if font_family not in allowed_fonts:
|
|
474
|
+
font_family = 'sans-serif'
|
|
475
|
+
|
|
476
|
+
font_size = un.valid_eval(element.get('font-size', '14'))
|
|
477
|
+
|
|
478
|
+
std_font_face = [font_family, font_size, False, False, label_color]
|
|
479
|
+
it_font_face = [font_family, font_size, True, False, label_color]
|
|
480
|
+
b_font_face = [font_family, font_size, False, True, label_color]
|
|
481
|
+
it_b_font_face = [font_family, font_size, True, True, label_color]
|
|
472
482
|
|
|
473
483
|
label = element
|
|
474
484
|
|
|
@@ -48,7 +48,7 @@ DefinitionElements =
|
|
|
48
48
|
Derivative?,
|
|
49
49
|
DE-Solution?,
|
|
50
50
|
Define-Shapes?,
|
|
51
|
-
Read
|
|
51
|
+
Read?
|
|
52
52
|
|
|
53
53
|
Definition = element definition {
|
|
54
54
|
text,
|
|
@@ -177,7 +177,9 @@ LabelAttributes =
|
|
|
177
177
|
attribute rotate {text}?,
|
|
178
178
|
attribute clear-background {"yes"|"no"}?,
|
|
179
179
|
attribute background-margin {text}?,
|
|
180
|
-
attribute color {text}
|
|
180
|
+
attribute color {text}?,
|
|
181
|
+
attribute font {text}?,
|
|
182
|
+
attribute font-size {text}?
|
|
181
183
|
|
|
182
184
|
Math = element m {attribute color {text}? & text}
|
|
183
185
|
Bold = element b {attribute color {text}? & text & Italic*}
|