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.
- hovercraft/__init__.py +435 -0
- hovercraft/generate.py +473 -0
- hovercraft/parse.py +275 -0
- hovercraft/position.py +269 -0
- hovercraft/template.py +237 -0
- hovercraft/templates/default/css/highlight.css +61 -0
- hovercraft/templates/default/css/hovercraft.css +67 -0
- hovercraft/templates/default/js/MathJax/es5/a11y/assistive-mml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/a11y/complexity.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/a11y/explorer.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/a11y/semantic-enrich.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/a11y/sre.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/adaptors/liteDOM.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/core.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/asciimath.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/mml/entities.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/mml/extensions/mml3.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/mml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/action.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/all-packages.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/ams.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/amscd.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/autoload.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/bbox.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/boldsymbol.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/braket.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/bussproofs.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/cancel.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/cases.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/centernot.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/color.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/colortbl.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/colorv2.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/configmacros.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/empheq.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/enclose.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/extpfeil.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/gensymb.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/html.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/mathtools.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/mhchem.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/newcommand.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/noerrors.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/noundefined.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/physics.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/require.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/setoptions.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/tagformat.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/textcomp.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/textmacros.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/unicode.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/upgreek.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex/extensions/verb.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex-base.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex-full.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/input/tex.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/latest.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/loader.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/mml-chtml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/mml-svg.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/node-main.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/output/chtml/fonts/tex.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/output/chtml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/output/svg/fonts/tex.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/output/svg.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/startup.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/tex-chtml-full-speech.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/tex-chtml-full.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/tex-chtml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/tex-mml-chtml.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/tex-mml-svg.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/tex-svg-full.js +34 -0
- hovercraft/templates/default/js/MathJax/es5/tex-svg.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/ui/lazy.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/ui/menu.js +1 -0
- hovercraft/templates/default/js/MathJax/es5/ui/safe.js +1 -0
- hovercraft/templates/default/js/gotoSlide.js +51 -0
- hovercraft/templates/default/js/hovercraft.js +58 -0
- hovercraft/templates/default/js/impress.js +5009 -0
- hovercraft/templates/default/template.cfg +11 -0
- hovercraft/templates/default/template.xsl +161 -0
- hovercraft/templates/reST.xsl +535 -0
- hovercraft/templates/simple/css/highlight.css +61 -0
- hovercraft/templates/simple/css/hovercraft.css +67 -0
- hovercraft/templates/simple/js/hovercraft.js +58 -0
- hovercraft/templates/simple/js/impress.js +5009 -0
- hovercraft/templates/simple/template.cfg +10 -0
- hovercraft/templates/simple/template.xsl +162 -0
- prezentprogramo-3.1.dist-info/METADATA +143 -0
- prezentprogramo-3.1.dist-info/RECORD +94 -0
- prezentprogramo-3.1.dist-info/WHEEL +5 -0
- prezentprogramo-3.1.dist-info/entry_points.txt +5 -0
- prezentprogramo-3.1.dist-info/licenses/LICENSE.txt +254 -0
- 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)
|