prefig 0.4.5.dev20250924053306__tar.gz → 0.4.6__tar.gz

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 (68) hide show
  1. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/PKG-INFO +4 -4
  2. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/annotations.py +107 -0
  3. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/circle.py +1 -1
  4. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/diagram.py +29 -0
  5. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/label.py +14 -4
  6. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/schema/pf_schema.rnc +4 -2
  7. prefig-0.4.6/prefig/resources/schema/pf_schema.rng +2250 -0
  8. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/pyproject.toml +4 -4
  9. prefig-0.4.5.dev20250924053306/prefig/resources/schema/pf_schema.rng +0 -787
  10. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/LICENSE +0 -0
  11. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/README.md +0 -0
  12. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/__init__.py +0 -0
  13. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/cli.py +0 -0
  14. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/CTM.py +0 -0
  15. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/__init__.py +0 -0
  16. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/area.py +0 -0
  17. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/arrow.py +0 -0
  18. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/axes.py +0 -0
  19. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/calculus.py +0 -0
  20. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/clip.py +0 -0
  21. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/compat.py +0 -0
  22. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/coordinates.py +0 -0
  23. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/definition.py +0 -0
  24. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/diffeqs.py +0 -0
  25. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/graph.py +0 -0
  26. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/grid_axes.py +0 -0
  27. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/group.py +0 -0
  28. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/image.py +0 -0
  29. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/implicit.py +0 -0
  30. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/label_tools.py +0 -0
  31. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/legend.py +0 -0
  32. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/line.py +0 -0
  33. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/math_utilities.py +0 -0
  34. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/network.py +0 -0
  35. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/parametric_curve.py +0 -0
  36. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/parse.py +0 -0
  37. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/path.py +0 -0
  38. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/point.py +0 -0
  39. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/polygon.py +0 -0
  40. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/read.py +0 -0
  41. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/rectangle.py +0 -0
  42. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/repeat.py +0 -0
  43. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/riemann_sum.py +0 -0
  44. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/shape.py +0 -0
  45. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/slope_field.py +0 -0
  46. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/statistics.py +0 -0
  47. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/tags.py +0 -0
  48. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/tangent_line.py +0 -0
  49. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/user_namespace.py +0 -0
  50. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/utilities.py +0 -0
  51. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/core/vector.py +0 -0
  52. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/engine.py +0 -0
  53. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/diagcess/diagcess.html +0 -0
  54. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/diagcess/diagcess.js +0 -0
  55. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/de-system.xml +0 -0
  56. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/derivatives.xml +0 -0
  57. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/diffeqs.xml +0 -0
  58. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/implicit.xml +0 -0
  59. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/projection.xml +0 -0
  60. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/riemann.xml +0 -0
  61. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/roots_of_unity.xml +0 -0
  62. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/examples/tangent.xml +0 -0
  63. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/fonts/Braille29.ttf +0 -0
  64. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/js/mj-sre-page.js +0 -0
  65. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/js/package.json +0 -0
  66. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/resources/template/pf_publication.xml +0 -0
  67. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/scripts/__init__.py +0 -0
  68. {prefig-0.4.5.dev20250924053306 → prefig-0.4.6}/prefig/scripts/install_mj.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefig
3
- Version: 0.4.5.dev20250924053306
3
+ Version: 0.4.6
4
4
  Summary: An authoring system for mathematical diagrams
5
5
  Home-page: https://prefigure.org
6
6
  License: GPL-3.0-or-later
@@ -15,12 +15,12 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Provides-Extra: text
16
16
  Requires-Dist: click (>=8.1.7,<9.0.0)
17
17
  Requires-Dist: click-log (>=0.4.0,<0.5.0)
18
- Requires-Dist: lxml (>=5.3.0,<6.0.0)
18
+ Requires-Dist: lxml (>=6,<7)
19
19
  Requires-Dist: networkx (>=2.5,<3.0)
20
20
  Requires-Dist: numpy (>=1.26,<2.0) ; python_version < "3.13"
21
- Requires-Dist: numpy (>=2.1.0,<3.0.0) ; python_version == "3.13"
21
+ Requires-Dist: numpy (>=2.1.0,<3.0.0) ; python_version >= "3.13"
22
22
  Requires-Dist: pycairo (>=1.20,<2.0) ; extra == "text"
23
- Requires-Dist: scipy (>=1.14.1,<2.0.0) ; python_version == "3.13"
23
+ Requires-Dist: scipy (>=1.14.1,<2.0.0) ; python_version >= "3.13"
24
24
  Requires-Dist: scipy (>=1.8,<2.0) ; python_version < "3.13"
25
25
  Requires-Dist: shapely (>=2.0.6,<3.0.0)
26
26
  Project-URL: Repository, https://github.com/davidaustinm/prefigure
@@ -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)
@@ -52,7 +52,7 @@ def circle(element, diagram, parent, outline_status):
52
52
  else:
53
53
  element.set('fill', 'none')
54
54
  else:
55
- element.set('stroke', element.get('stroke', 'none'))
55
+ element.set('stroke', element.get('stroke', 'black'))
56
56
  element.set('fill', element.get('fill', 'none'))
57
57
  element.set('thickness', element.get('thickness', '2'))
58
58
  util.add_attr(circle, util.get_2d_attr(element))
@@ -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):
@@ -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
- std_font_face = ['sans', 14, False, False, label_color]
469
- it_font_face = ['sans', 14, True, False, label_color]
470
- b_font_face = ['sans', 14, False, True, label_color]
471
- it_b_font_face = ['sans', 14, True, True, label_color]
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*}