Prezentprogramo 3.1__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 (94) hide show
  1. hovercraft/__init__.py +435 -0
  2. hovercraft/generate.py +473 -0
  3. hovercraft/parse.py +275 -0
  4. hovercraft/position.py +269 -0
  5. hovercraft/template.py +237 -0
  6. hovercraft/templates/default/css/highlight.css +61 -0
  7. hovercraft/templates/default/css/hovercraft.css +67 -0
  8. hovercraft/templates/default/js/MathJax/es5/a11y/assistive-mml.js +1 -0
  9. hovercraft/templates/default/js/MathJax/es5/a11y/complexity.js +1 -0
  10. hovercraft/templates/default/js/MathJax/es5/a11y/explorer.js +1 -0
  11. hovercraft/templates/default/js/MathJax/es5/a11y/semantic-enrich.js +1 -0
  12. hovercraft/templates/default/js/MathJax/es5/a11y/sre.js +1 -0
  13. hovercraft/templates/default/js/MathJax/es5/adaptors/liteDOM.js +1 -0
  14. hovercraft/templates/default/js/MathJax/es5/core.js +1 -0
  15. hovercraft/templates/default/js/MathJax/es5/input/asciimath.js +1 -0
  16. hovercraft/templates/default/js/MathJax/es5/input/mml/entities.js +1 -0
  17. hovercraft/templates/default/js/MathJax/es5/input/mml/extensions/mml3.js +1 -0
  18. hovercraft/templates/default/js/MathJax/es5/input/mml.js +1 -0
  19. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/action.js +1 -0
  20. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/all-packages.js +34 -0
  21. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/ams.js +1 -0
  22. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/amscd.js +1 -0
  23. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/autoload.js +1 -0
  24. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/bbox.js +1 -0
  25. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/boldsymbol.js +1 -0
  26. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/braket.js +1 -0
  27. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/bussproofs.js +1 -0
  28. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/cancel.js +1 -0
  29. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/cases.js +1 -0
  30. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/centernot.js +1 -0
  31. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/color.js +1 -0
  32. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/colortbl.js +1 -0
  33. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/colorv2.js +1 -0
  34. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/configmacros.js +1 -0
  35. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/empheq.js +1 -0
  36. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/enclose.js +1 -0
  37. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/extpfeil.js +1 -0
  38. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/gensymb.js +1 -0
  39. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/html.js +1 -0
  40. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/mathtools.js +1 -0
  41. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/mhchem.js +34 -0
  42. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/newcommand.js +1 -0
  43. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/noerrors.js +1 -0
  44. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/noundefined.js +1 -0
  45. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/physics.js +1 -0
  46. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/require.js +1 -0
  47. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/setoptions.js +1 -0
  48. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/tagformat.js +1 -0
  49. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/textcomp.js +1 -0
  50. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/textmacros.js +1 -0
  51. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/unicode.js +1 -0
  52. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/upgreek.js +1 -0
  53. hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/verb.js +1 -0
  54. hovercraft/templates/default/js/MathJax/es5/input/tex-base.js +1 -0
  55. hovercraft/templates/default/js/MathJax/es5/input/tex-full.js +34 -0
  56. hovercraft/templates/default/js/MathJax/es5/input/tex.js +1 -0
  57. hovercraft/templates/default/js/MathJax/es5/latest.js +1 -0
  58. hovercraft/templates/default/js/MathJax/es5/loader.js +1 -0
  59. hovercraft/templates/default/js/MathJax/es5/mml-chtml.js +1 -0
  60. hovercraft/templates/default/js/MathJax/es5/mml-svg.js +1 -0
  61. hovercraft/templates/default/js/MathJax/es5/node-main.js +1 -0
  62. hovercraft/templates/default/js/MathJax/es5/output/chtml/fonts/tex.js +1 -0
  63. hovercraft/templates/default/js/MathJax/es5/output/chtml.js +1 -0
  64. hovercraft/templates/default/js/MathJax/es5/output/svg/fonts/tex.js +1 -0
  65. hovercraft/templates/default/js/MathJax/es5/output/svg.js +1 -0
  66. hovercraft/templates/default/js/MathJax/es5/startup.js +1 -0
  67. hovercraft/templates/default/js/MathJax/es5/tex-chtml-full-speech.js +34 -0
  68. hovercraft/templates/default/js/MathJax/es5/tex-chtml-full.js +34 -0
  69. hovercraft/templates/default/js/MathJax/es5/tex-chtml.js +1 -0
  70. hovercraft/templates/default/js/MathJax/es5/tex-mml-chtml.js +1 -0
  71. hovercraft/templates/default/js/MathJax/es5/tex-mml-svg.js +1 -0
  72. hovercraft/templates/default/js/MathJax/es5/tex-svg-full.js +34 -0
  73. hovercraft/templates/default/js/MathJax/es5/tex-svg.js +1 -0
  74. hovercraft/templates/default/js/MathJax/es5/ui/lazy.js +1 -0
  75. hovercraft/templates/default/js/MathJax/es5/ui/menu.js +1 -0
  76. hovercraft/templates/default/js/MathJax/es5/ui/safe.js +1 -0
  77. hovercraft/templates/default/js/gotoSlide.js +51 -0
  78. hovercraft/templates/default/js/hovercraft.js +58 -0
  79. hovercraft/templates/default/js/impress.js +5009 -0
  80. hovercraft/templates/default/template.cfg +11 -0
  81. hovercraft/templates/default/template.xsl +161 -0
  82. hovercraft/templates/reST.xsl +535 -0
  83. hovercraft/templates/simple/css/highlight.css +61 -0
  84. hovercraft/templates/simple/css/hovercraft.css +67 -0
  85. hovercraft/templates/simple/js/hovercraft.js +58 -0
  86. hovercraft/templates/simple/js/impress.js +5009 -0
  87. hovercraft/templates/simple/template.cfg +10 -0
  88. hovercraft/templates/simple/template.xsl +162 -0
  89. prezentprogramo-3.1.dist-info/METADATA +143 -0
  90. prezentprogramo-3.1.dist-info/RECORD +94 -0
  91. prezentprogramo-3.1.dist-info/WHEEL +5 -0
  92. prezentprogramo-3.1.dist-info/entry_points.txt +5 -0
  93. prezentprogramo-3.1.dist-info/licenses/LICENSE.txt +254 -0
  94. prezentprogramo-3.1.dist-info/top_level.txt +1 -0
hovercraft/parse.py ADDED
@@ -0,0 +1,275 @@
1
+ from docutils import nodes
2
+ from docutils.core import publish_string
3
+ from docutils.readers.standalone import Reader
4
+ from docutils.transforms.misc import Transitions
5
+ from docutils.writers.docutils_xml import Writer
6
+ from lxml import etree
7
+
8
+
9
+ class HovercraftTransitions(Transitions):
10
+ @property
11
+ def _doc_transitions(self):
12
+ if not hasattr(self.document, "_transitions"):
13
+ self.document._transitions = []
14
+ return self.document._transitions
15
+
16
+ def visit_transition(self, node):
17
+ # First add the level of the transition as a class
18
+ char = node.rawsource[0]
19
+ if char not in self._doc_transitions:
20
+ self._doc_transitions.append(char)
21
+ level = self._doc_transitions.index(char) + 1
22
+ node.attributes["level"] = str(level)
23
+
24
+ index = node.parent.index(node)
25
+ error = None
26
+ # Here the default transformer complained if you had a transition as
27
+ # the first thing in a section. I don't see why.
28
+ if isinstance(node.parent[index - 1], nodes.transition):
29
+ error = self.document.reporter.error(
30
+ "At least one body element must separate transitions; "
31
+ "adjacent transitions are not allowed.",
32
+ source=node.source,
33
+ line=node.line,
34
+ )
35
+ if error:
36
+ # Insert before node and update index.
37
+ node.parent.insert(index, error)
38
+ index += 1
39
+ assert index < len(node.parent)
40
+ if index != len(node.parent) - 1:
41
+ # No need to move the node.
42
+ return
43
+ # Node behind which the transition is to be moved.
44
+ sibling = node
45
+ # While sibling is the last node of its parent.
46
+ while index == len(sibling.parent) - 1:
47
+ sibling = sibling.parent
48
+ # If sibling is the whole document (i.e. it has no parent).
49
+ if sibling.parent is None:
50
+ # Transition at the end of document. Do not move the
51
+ # transition up, and place an error behind.
52
+ error = self.document.reporter.error(
53
+ "Document may not end with a transition.", line=node.line
54
+ )
55
+ node.parent.insert(node.parent.index(node) + 1, error)
56
+ return
57
+ index = sibling.parent.index(sibling)
58
+ # Remove the original transition node.
59
+ node.parent.remove(node)
60
+ # Insert the transition after the sibling.
61
+ sibling.parent.insert(index + 1, node)
62
+
63
+
64
+ class HovercraftReader(Reader):
65
+ def get_transforms(self):
66
+ transforms = Reader().get_transforms()
67
+ transforms.remove(Transitions)
68
+ transforms.append(HovercraftTransitions)
69
+ return transforms
70
+
71
+
72
+ def rst2xml(rststring, source_path=None):
73
+ reader = HovercraftReader()
74
+ writer = Writer()
75
+ result = publish_string(
76
+ rststring,
77
+ source_path=source_path,
78
+ reader=reader,
79
+ writer=writer,
80
+ settings_overrides={"syntax_highlight": "short"},
81
+ )
82
+ dependencies = reader.settings.record_dependencies.list
83
+ return result, dependencies
84
+
85
+
86
+ def copy_node(node):
87
+ """Makes a copy of a node with the same attributes and text, but no children."""
88
+ element = node.makeelement(node.tag)
89
+ element.text = node.text
90
+ element.tail = node.tail
91
+ for key, value in node.items():
92
+ element.set(key, value)
93
+
94
+ return element
95
+
96
+
97
+ class SlideMaker(object):
98
+ """A docutils XML walker that will organize the XML into slides"""
99
+
100
+ def __init__(self, intree, skip_notes=False):
101
+ self.intree = intree
102
+ self.result = None
103
+ self.curnode = None
104
+ self.steps = 0
105
+ self.skip_notes = skip_notes
106
+ self.skip_nodes = (
107
+ "docinfo",
108
+ "field_list",
109
+ "field",
110
+ "field_body",
111
+ )
112
+ self.need_mathjax = False
113
+ self.lists_with_substeps = []
114
+
115
+ def _newstep(self, level):
116
+ step = etree.Element(
117
+ "step",
118
+ attrib={
119
+ "step": str(self.steps),
120
+ "class": "step step-level-%s" % level,
121
+ },
122
+ )
123
+ self.steps += 1
124
+ return step
125
+
126
+ def walk(self):
127
+ walken = etree.iterwalk(self.intree, events=("start", "end"))
128
+ for event, node in walken:
129
+ if node.tag in self.skip_nodes:
130
+ continue
131
+ method = getattr(self, "%s_%s" % (event, node.tag), None)
132
+ if method is None:
133
+ if event == "start":
134
+ self.default_start(node)
135
+ else:
136
+ if event == "end":
137
+ self.default_end(node)
138
+ else:
139
+ method(node)
140
+
141
+ return self.result
142
+
143
+ def default_start(self, node):
144
+ new = copy_node(node)
145
+ self.curnode.append(new)
146
+ self.curnode = new
147
+
148
+ def default_end(self, node):
149
+ if self.curnode.tag != "step":
150
+ self.curnode = self.curnode.getparent()
151
+
152
+ def start_document(self, node):
153
+ self.curnode = self.result = copy_node(node)
154
+
155
+ def start_section(self, node):
156
+ # If there has been no transition by now, start a new step:
157
+ if self.steps == 0:
158
+ step = self._newstep(1)
159
+ self.result.append(step)
160
+ self.curnode = step
161
+
162
+ # Then carry on as normal
163
+ self.default_start(node)
164
+
165
+ def start_transition(self, node):
166
+ level = int(node.attrib["level"])
167
+ step = self._newstep(level)
168
+ # Walk back up to a transition of the same level
169
+ curnode = self.curnode
170
+
171
+ def get_level(clss):
172
+ for cls in clss.split():
173
+ # Skip unrelated classes, such as those added by the user.
174
+ if cls.startswith("step-level-"):
175
+ return int(cls.split("-")[-1])
176
+
177
+ while curnode.tag != "step" or get_level(curnode.attrib["class"]) >= level:
178
+ parent = curnode.getparent()
179
+ if parent is None:
180
+ # Top of tree
181
+ break
182
+ curnode = parent
183
+
184
+ # This is the transition above the new transition.
185
+ # Add the new transition to the end.
186
+ curnode.append(step)
187
+ self.curnode = step
188
+
189
+ def end_transition(self, node):
190
+ self.default_end(node)
191
+
192
+ def start_field_name(self, node):
193
+ # Fields are made into attribute, nothing to do here:.
194
+ pass
195
+
196
+ def end_field_name(self, node):
197
+ # Fields are made into attributes
198
+ pass
199
+
200
+ def start_paragraph(self, node):
201
+ # Fields are made into attributes.
202
+ parent = node.getparent()
203
+ if parent.tag == "field_body":
204
+ fieldname = parent.getprevious().text
205
+ current = self.curnode.get(fieldname)
206
+ if current:
207
+ value = current + " " + node.text
208
+ else:
209
+ value = node.text
210
+ self.curnode.set(fieldname, value)
211
+ else:
212
+ self.default_start(node)
213
+
214
+ def end_paragraph(self, node):
215
+ parent = node.getparent()
216
+ if parent.tag != "field_body":
217
+ self.default_end(node)
218
+
219
+ def start_note(self, node):
220
+ if not self.skip_notes:
221
+ return self.default_start(node)
222
+ # Skip this node completely, including children:
223
+ while len(node) > 0:
224
+ del node[0]
225
+
226
+ def start_math_block(self, node):
227
+ self.need_mathjax = True
228
+ self.default_start(node)
229
+
230
+ def start_math(self, node):
231
+ self.need_mathjax = True
232
+ self.default_start(node)
233
+
234
+ def start_list_item(self, node):
235
+ if self.lists_with_substeps:
236
+ # We are currently in a list hierarchy that should have
237
+ # substeps
238
+ classes = node.attrib.get("classes", "")
239
+ if "substep" not in classes:
240
+ classes += " substep"
241
+ node.attrib["classes"] = classes.strip()
242
+
243
+ self.default_start(node)
244
+
245
+ def _fix_substep_list(self, node):
246
+ # The list istelf should not have the substep class.
247
+ # For some reason we can't modify nodes in the end_...() functions,
248
+ # only in the start_...() functions, so we drop the substep class
249
+ # but add the node to a stack of nodes with the class
250
+ classes = node.attrib.get("classes", "")
251
+ if "substep" in classes:
252
+ self.lists_with_substeps.append(node)
253
+ classes = classes.replace("substep", "").strip()
254
+ if not classes:
255
+ del node.attrib["classes"]
256
+ else:
257
+ node.attrib["classes"] = classes
258
+
259
+ def start_enumerated_list(self, node):
260
+ self._fix_substep_list(node)
261
+ self.default_start(node)
262
+
263
+ def end_enumerated_list(self, node):
264
+ if node in self.lists_with_substeps:
265
+ self.lists_with_substeps.remove(node)
266
+ self.default_end(node)
267
+
268
+ def start_bullet_list(self, node):
269
+ self._fix_substep_list(node)
270
+ self.default_start(node)
271
+
272
+ def end_bullet_list(self, node):
273
+ if node in self.lists_with_substeps:
274
+ self.lists_with_substeps.remove(node)
275
+ self.default_end(node)
hovercraft/position.py ADDED
@@ -0,0 +1,269 @@
1
+ import math
2
+
3
+ from svg.path import parse_path
4
+
5
+ DEFAULT_MOVEMENT = 1600 # If no other movement is specified, go 1600px to the right.
6
+ POSITION_ATTRIBS = [
7
+ "data-x",
8
+ "data-y",
9
+ "data-z",
10
+ "data-rotate-x",
11
+ "data-rotate-y",
12
+ "data-rotate-z",
13
+ "data-scale",
14
+ ]
15
+
16
+
17
+ def gather_positions(
18
+ tree, default_movement_from_args, default_movement_from_data_width
19
+ ):
20
+ """Makes a list of positions and position commands from the tree"""
21
+ pos = {
22
+ "data-x": "r0",
23
+ "data-y": "r0",
24
+ "data-z": "r0",
25
+ "data-rotate-x": "r0",
26
+ "data-rotate-y": "r0",
27
+ "data-rotate-z": "r0",
28
+ "data-scale": "r0",
29
+ "is_path": False,
30
+ }
31
+
32
+ steps = 0
33
+ default_movement = True
34
+
35
+ for step in tree.findall("step"):
36
+ steps += 1
37
+
38
+ for key in POSITION_ATTRIBS:
39
+ value = step.get(key)
40
+
41
+ if value is not None:
42
+ # We have a new value
43
+ default_movement = False # No longer use the default movement
44
+ pos[key] = value
45
+ elif pos[key] and not pos[key].startswith("r"):
46
+ # The old value was absolute and no new value, so stop
47
+ pos[key] = "r0"
48
+ # We had no new value, and the old value was a relative
49
+ # movement, so we just keep moving.
50
+
51
+ if steps == 1 and pos["data-scale"] == "r0":
52
+ # No scale given for first slide, it needs to start at 1
53
+ pos["data-scale"] = "1"
54
+
55
+ if default_movement and steps != 1:
56
+ # No positioning has been given, use args:
57
+ if default_movement_from_args:
58
+ pos["data-x"] = "r%s" % default_movement_from_args
59
+ else:
60
+ if (
61
+ default_movement_from_data_width
62
+ and int(default_movement_from_data_width) > DEFAULT_MOVEMENT
63
+ ):
64
+ pos["data-x"] = "r%s" % default_movement_from_data_width
65
+ else:
66
+ # No positioning has been given, use default:
67
+ pos["data-x"] = "r%s" % DEFAULT_MOVEMENT
68
+
69
+ if "data-rotate" in step.attrib:
70
+ # data-rotate is an alias for data-rotate-z
71
+ pos["data-rotate-z"] = step.get("data-rotate")
72
+ del step.attrib["data-rotate"]
73
+
74
+ if "hovercraft-path" in step.attrib:
75
+ # Path given x and y will be calculated from the path
76
+ default_movement = False # No longer use the default movement
77
+ pos["is_path"] = True
78
+ # Add the path spec
79
+ pos["path"] = step.attrib["hovercraft-path"]
80
+ yield pos.copy()
81
+ # And get rid of it for the next step
82
+ del pos["path"]
83
+ else:
84
+ if "data-x" in step.attrib or "data-y" in step.attrib:
85
+ # No longer using a path
86
+ pos["is_path"] = False
87
+ yield pos.copy()
88
+
89
+
90
+ def _coord_to_pos(coord):
91
+ return {"data-x": int(coord.real), "data-y": int(coord.imag)}
92
+
93
+
94
+ def _pos_to_cord(coord):
95
+ return coord["data-x"] + coord["data-y"] * 1j
96
+
97
+
98
+ def _path_angle(path, point):
99
+ start = point - 0.01
100
+ end = point + 0.01
101
+ if start < 0:
102
+ start = 0
103
+ end += 0.01
104
+ elif end > 1:
105
+ end = 1
106
+ start -= 0.01
107
+
108
+ distance = path.point(end) - path.point(start)
109
+ hyp = math.hypot(distance.real, distance.imag)
110
+ result = math.degrees(math.asin(distance.imag / hyp))
111
+
112
+ if distance.real < 0:
113
+ result = -180 - result
114
+
115
+ if abs(result) < 0.1:
116
+ result = 0
117
+
118
+ return result
119
+
120
+
121
+ def num(s):
122
+ try:
123
+ return int(s)
124
+ except ValueError:
125
+ return float(s)
126
+
127
+
128
+ def _update_position(pos1, pos2):
129
+
130
+ for key in POSITION_ATTRIBS:
131
+ val = pos2.get(key)
132
+ if val is not None:
133
+ plus = val.find("+")
134
+ minus = val.find("-")
135
+ if plus > -1:
136
+ newval = num(val[plus + 1 :])
137
+ pos1[key + "-rel"] = val[0:plus]
138
+ elif minus > -1 and not val.startswith("r-"):
139
+ newval = num(val[minus:])
140
+ pos1[key + "-rel"] = val[0:minus]
141
+ else:
142
+ if val[0] == "r":
143
+ # Relative movement
144
+ newval = pos1[key] + num(val[1:])
145
+ else:
146
+ newval = num(val)
147
+ pos1.pop(key + "-rel", None)
148
+ pos1[key] = newval
149
+
150
+
151
+ def calculate_positions(positions):
152
+ """Calculates position information"""
153
+ current_position = {
154
+ "data-x": 0,
155
+ "data-y": 0,
156
+ "data-z": 0,
157
+ "data-rotate-x": 0,
158
+ "data-rotate-y": 0,
159
+ "data-rotate-z": 0,
160
+ "data-scale": 1,
161
+ }
162
+
163
+ positer = iter(positions)
164
+ position = next(positer)
165
+ _update_position(current_position, position)
166
+
167
+ while True:
168
+
169
+ if "path" in position:
170
+ # Start of a new path!
171
+ path = position["path"]
172
+ # Follow the path specification
173
+ first_point = _pos_to_cord(current_position)
174
+
175
+ # Paths that end in Z or z are closed.
176
+ closed_path = path.strip()[-1].upper() == "Z"
177
+ path = parse_path(path)
178
+
179
+ # Find out how many positions should be calculated:
180
+ count = 1
181
+ last = False
182
+ deferred_positions = []
183
+ while True:
184
+ try:
185
+ position = next(positer)
186
+ deferred_positions.append(position)
187
+ except StopIteration:
188
+ last = True # This path goes to the end
189
+ break
190
+ if not position.get("is_path") or "path" in position:
191
+ # The end of the path, or the start of a new one
192
+ break
193
+ count += 1
194
+
195
+ if count < 2:
196
+ raise AssertionError(
197
+ "The path specification is only used for "
198
+ "one slide, which makes it pointless."
199
+ )
200
+
201
+ if closed_path:
202
+ # This path closes in on itself. Skip the last part, so that
203
+ # the first and last step doesn't overlap.
204
+ endcount = count + 1
205
+ else:
206
+ endcount = count
207
+
208
+ multiplier = (endcount * DEFAULT_MOVEMENT) / path.length()
209
+ offset = path.point(0)
210
+
211
+ path_iter = iter(deferred_positions)
212
+ for x in range(count):
213
+
214
+ point = path.point(x / (endcount - 1))
215
+ point = ((point - offset) * multiplier) + first_point
216
+
217
+ current_position.update(_coord_to_pos(point))
218
+
219
+ rotation = _path_angle(path, x / (endcount - 1))
220
+ current_position["data-rotate-z"] = rotation
221
+ yield current_position.copy()
222
+ try:
223
+ position = next(path_iter)
224
+ except StopIteration:
225
+ last = True
226
+ break
227
+ _update_position(current_position, position)
228
+
229
+ if last:
230
+ break
231
+
232
+ continue
233
+
234
+ yield current_position.copy()
235
+ try:
236
+ position = next(positer)
237
+ except StopIteration:
238
+ break
239
+ _update_position(current_position, position)
240
+
241
+
242
+ def update_positions(tree, positions):
243
+ """Updates the tree with new positions"""
244
+
245
+ for step, pos in zip(tree.findall("step"), positions):
246
+ for key in sorted(pos):
247
+ value = pos.get(key)
248
+ if key.endswith("-rel"):
249
+ abs_key = key[: key.index("-rel")]
250
+ if value is not None:
251
+ els = tree.findall(".//*[@id='" + value + "']")
252
+ for el in els:
253
+ pos[abs_key] = num(el.get(abs_key)) + pos.get(abs_key)
254
+ step.attrib[abs_key] = str(pos.get(abs_key))
255
+ else:
256
+ step.attrib[key] = str(pos[key])
257
+
258
+ if "hovercraft-path" in step.attrib:
259
+ del step.attrib["hovercraft-path"]
260
+
261
+
262
+ def position_slides(tree, default_movement_from_args, default_movement_from_data_width):
263
+ """Position the slides in the tree"""
264
+
265
+ positions = gather_positions(
266
+ tree, default_movement_from_args, default_movement_from_data_width
267
+ )
268
+ positions = calculate_positions(positions)
269
+ update_positions(tree, positions)