xref: /trunk/test/odf-validate/fixtures/draw/make.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21# usage: runpy.sh fixtures/draw/make.py <port> <outdir>
22# Draw and Impress features the corpus lacks: animations, transitions, presentation settings, click actions, image maps,
23# 3D scenes, custom shapes, fontwork, connectors, glue points, measure, captions, plugin/applet/frame, page anchors.
24# Saves with the office's configured ODF version: run it against the strict and extended profiles.
25import sys, os, math, wave, struct, tempfile, threading, uno
26from unohelp import connect, desktop, pv, odf_version
27
28port, out = int(sys.argv[1]), os.path.abspath(sys.argv[2])
29os.makedirs(out, exist_ok=True)
30ctx = connect(port)
31smgr = ctx.ServiceManager
32desk = desktop(ctx)
33failed = []
34STEP_SECONDS = 90
35crashes = []
36ONLY = set(sys.argv[3:]) | set(os.environ.get("DRAW_ONLY", "").split())
37tmp = tempfile.mkdtemp(prefix="t12_draw_")
38SOUND = os.path.join(tmp, "beep.wav")
39w = wave.open(SOUND, "wb")
40w.setnchannels(1); w.setsampwidth(2); w.setframerate(8000)
41w.writeframes(b"".join(struct.pack("<h", int(8000 * math.sin(i / 5.0))) for i in range(4000)))
42w.close()
43SOUND_URL = uno.systemPathToFileUrl(SOUND)
44
45# online spelling runs on a timer and was seen crashing the office on closed documents (ImpEditEngine::DoOnlineSpelling)
46def quiet_spelling():
47    try:
48        smgr.createInstanceWithContext("com.sun.star.linguistic2.LinguProperties", ctx).IsSpellAuto = False
49    except Exception as e:
50        print("  cannot disable online spelling", repr(e)[:100])
51
52
53quiet_spelling()
54PROFILE = "t12_draw_" + ("strict" if odf_version(ctx) == 4 else "ext")
55
56CLIP = {"odp": "impress8", "odg": "draw8", "otp": "impress8_template", "otg": "draw8_template",
57        "odt": "writer8", "ods": "calc8"}
58FACTORY = {"odp": "simpress", "odg": "sdraw", "odt": "swriter", "ods": "scalc"}
59
60
61def new(kind):
62    return desk.loadComponentFromURL("private:factory/" + FACTORY[kind], "_blank", 0, (pv("Hidden", True),))
63
64
65KEEP = []
66
67
68def done(doc):
69    # closing documents or releasing annotation/table proxies after a close crashed the office (use after free); leak them and _exit
70    KEEP.append(doc)
71
72
73def save(doc, name):
74    try:
75        doc.storeToURL(uno.systemPathToFileUrl(os.path.join(out, name)), (pv("FilterName", CLIP[name.rsplit(".", 1)[1]]),))
76        print("saved", name)
77    except Exception as e:
78        failed.append(name)
79        print("FAIL save", name, repr(e)[:300])
80
81
82def step(label, fn, *a, **kw):
83    print('  step', label, flush=True)
84    try:
85        return fn(*a, **kw)
86    except Exception as e:
87        failed.append(label)
88        import traceback
89        print("FAIL", label, type(e).__name__, repr(e)[:300], traceback.format_exc().splitlines()[-3:-1])
90
91
92def office_alive():
93    try:
94        desk.getImplementationName()
95        return True
96    except Exception:
97        return False
98
99
100def kill_office():
101    import subprocess
102    print("WATCHDOG: step exceeded %ds, killing the office" % STEP_SECONDS, flush=True)
103    subprocess.run(["pkill", "-f", "[p]rofile_" + PROFILE])
104
105
106def restart_office():
107    global ctx, smgr, desk
108    import subprocess
109    harness = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
110    prof = PROFILE
111    subprocess.run(["pkill", "-f", "[p]rofile_" + prof])
112    subprocess.run([os.path.join(harness, "start.sh"), prof, str(port)], check=True)
113    ctx = connect(port)
114    smgr = ctx.ServiceManager
115    desk = desktop(ctx)
116    quiet_spelling()
117
118
119def top(label, fn, *a):
120    if ONLY and label not in ONLY:
121        return
122    print('step', label, flush=True)
123    for attempt in (1, 2):
124        nfailed = len(failed)
125        dog = threading.Timer(STEP_SECONDS, kill_office)
126        dog.start()
127        try:
128            fn(*a)
129            return
130        except Exception as e:
131            if office_alive():
132                failed.append(label)
133                print("FAIL", label, type(e).__name__, repr(e)[:300])
134                return
135            del failed[nfailed:]
136            crashes.append(label)
137            print("CRASH", label, "attempt", attempt, repr(e)[:150])
138            restart_office()
139        finally:
140            dog.cancel()
141    failed.append(label)
142
143
144def struct_(name, *a):
145    return uno.createUnoStruct(name, *a)
146
147
148def pt(x, y):
149    return struct_("com.sun.star.awt.Point", x, y)
150
151
152def sz(w, h):
153    return struct_("com.sun.star.awt.Size", w, h)
154
155
156def mk(doc, page, service, x=1000, y=1000, w=3000, h=2000, text=None, **props):
157    s = doc.createInstance("com.sun.star.drawing." + service)
158    page.add(s)
159    if w is not None:
160        s.Position = pt(x, y)
161        s.Size = sz(w, h)
162    if text is not None:
163        s.String = text
164    for k, v in props.items():
165        try:
166            setattr(s, k, v)
167        except Exception as e:
168            print("  prop", service, k, repr(e)[:120])
169    return s
170
171
172def setp(obj, **props):
173    for k, v in props.items():
174        try:
175            setattr(obj, k, v)
176        except Exception as e:
177            print("  prop", k, repr(e)[:120])
178
179
180def spec(name):
181    return smgr.createInstanceWithContext("com.sun.star.animations." + name, ctx)
182
183
184def add_page(doc):
185    doc.DrawPages.insertNewByIndex(doc.DrawPages.Count)
186    return doc.DrawPages.getByIndex(doc.DrawPages.Count - 1)
187
188
189def presentation_settings():
190    doc = new("odp")
191    pages = [doc.DrawPages.getByIndex(0)] + [add_page(doc) for _ in range(3)]
192    for i, p in enumerate(pages):
193        p.Name = "Slide%d" % (i + 1)
194        mk(doc, p, "RectangleShape", text="slide %d" % (i + 1))
195    shows = doc.CustomPresentations
196    for nm, idx in (("ShowA", (0, 2)), ("ShowB", (1, 3, 0))):
197        show = shows.createInstance()
198        for i in idx:
199            show.insertByIndex(show.Count, pages[i])
200        shows.insertByName(nm, show)
201    pres = doc.Presentation
202    pres.IsShowAll = False
203    pres.CustomShow = "ShowB"
204    pres.IsEndless = True
205    pres.Pause = 7
206    pres.AllowAnimations = False
207    pres.IsAlwaysOnTop = True
208    pres.IsAutomatic = True
209    pres.IsFullScreen = False
210    pres.IsMouseVisible = False
211    pres.StartWithNavigator = True
212    pres.UsePen = True
213    pres.IsTransitionOnClick = False
214    pres.IsShowLogo = True
215    save(doc, "pres-settings.odp")
216    pres.CustomShow = ""
217    pres.FirstPage = "Slide3"
218    save(doc, "pres-settings-start.odp")
219    done(doc)
220
221
222def header_footer():
223    doc = new("odp")
224    p1 = doc.DrawPages.getByIndex(0)
225    p2 = add_page(doc)
226    for i, p in enumerate((p1, p2)):
227        mk(doc, p, "RectangleShape", text="hf %d" % i)
228        setp(p, IsFooterVisible=True, FooterText="footer %d" % i, IsPageNumberVisible=True, IsDateTimeVisible=True)
229    setp(p1, IsDateTimeFixed=True, DateTimeText="fixed date")
230    setp(p2, IsDateTimeFixed=False, DateTimeFormat=0x2002)
231    for p in (p1, p2):
232        setp(p.NotesPage, IsHeaderVisible=True, HeaderText="notes header", IsFooterVisible=True, FooterText="notes footer",
233             IsDateTimeVisible=True, IsDateTimeFixed=True, DateTimeText="notes date", IsPageNumberVisible=True)
234    setp(doc.MasterPages.getByIndex(0), IsFooterVisible=True, FooterText="master footer", IsPageNumberVisible=True)
235    try:
236        hm = doc.getHandoutMasterPage()
237    except Exception as e:
238        hm = None
239        print("  handout master", repr(e)[:100])
240    hm is not None and setp(hm, IsHeaderVisible=True, HeaderText="handout header", IsFooterVisible=True,
241         FooterText="handout footer", IsDateTimeVisible=True, IsDateTimeFixed=True, DateTimeText="handout date")
242    save(doc, "header-footer.odp")
243    done(doc)
244
245
246def layouts():
247    doc = new("odp")
248    first = True
249    for n in range(0, 40):
250        try:
251            p = doc.DrawPages.getByIndex(0) if first else add_page(doc)
252            first = False
253            p.Layout = n
254        except Exception as e:
255            print("  layout", n, repr(e)[:100])
256    save(doc, "layouts.odp")
257    done(doc)
258
259
260def const(prefix, name):
261    mod = "presentation" if prefix in ("EffectNodeType", "EffectPresetClass", "ShapeAnimationSubType", "EffectCommands") else "animations"
262    return uno.getConstantByName("com.sun.star.%s.%s.%s" % (mod, prefix, name))
263
264
265def transitions():
266    doc = new("odp")
267    first = True
268    types = [uno.getConstantByName("com.sun.star.animations.TransitionType.BARWIPE") + i for i in range(43)]
269    subs = list(range(0, 120))
270    combos = [(t, 0) for t in types] + [(1, st) for st in subs if st]
271    for i, (t, st) in enumerate(combos):
272        p = doc.DrawPages.getByIndex(0) if first else add_page(doc)
273        first = False
274        setp(p, TransitionType=t, TransitionSubtype=st, TransitionDuration=1.5 + (i % 3), TransitionDirection=bool(i % 2),
275             TransitionFadeColor=0x336699 + i)
276    p = add_page(doc)
277    setp(p, TransitionType=37, TransitionSubtype=const("TransitionSubType", "FADETOCOLOR"), TransitionDirection=False,
278         TransitionFadeColor=0xFF8800)
279    p = add_page(doc)
280    setp(p, TransitionType=37, TransitionSubtype=const("TransitionSubType", "FADEFROMCOLOR"), TransitionFadeColor=0x0088FF)
281    p = add_page(doc)
282    setp(p, TransitionType=0, Sound=SOUND_URL, LoopSound=True)
283    p = add_page(doc)
284    setp(p, TransitionType=4, TransitionSubtype=3, Sound=SOUND_URL)
285    p = add_page(doc)
286    setp(p, TransitionType=0, Sound=True)
287    p = add_page(doc)
288    setp(p, TransitionType=36, Sound=False, Change=1, Duration=5)
289    save(doc, "transitions.odp")
290    done(doc)
291
292
293def nv(name, value):
294    return struct_("com.sun.star.beans.NamedValue", name, value)
295
296
297def node(name, **props):
298    n = spec(name)
299    for k, v in props.items():
300        setattr(n, k, v)
301    return n
302
303
304def userdata(**kw):
305    return tuple(nv(k.replace("_", "-"), v) for k, v in kw.items())
306
307
308def event(trigger, source=None, offset=None):
309    e = struct_("com.sun.star.animations.Event")
310    e.Trigger = trigger
311    if source is not None:
312        e.Source = source
313    if offset is not None:
314        e.Offset = offset
315    return e
316
317
318def animations_one(doc, p, k):
319    sh = mk(doc, p, "RectangleShape", text="target one\ntwo\nthree")
320    sh2 = mk(doc, p, "EllipseShape", x=6000, y=1000, text="second")
321    sh3 = mk(doc, p, "RectangleShape", x=1000, y=5000, text="third")
322    root = p.AnimationNode
323    seq = node("SequenceTimeContainer", UserData=userdata(node_type=const("EffectNodeType", "MAIN_SEQUENCE")))
324    root.appendChild(seq)
325    EN = lambda n: const("EffectNodeType", n)
326
327    def effect(nodetype, preset, cls, *children, **kw):
328        par = node("ParallelTimeContainer", Begin=kw.pop("Begin", 0.0),
329                   UserData=userdata(node_type=EN(nodetype), preset_id=preset, preset_class=const("EffectPresetClass", cls),
330                                     preset_sub_type="", group_id=1))
331        for c in children:
332            par.appendChild(c)
333        for a, b in kw.items():
334            setattr(par, a, b)
335        return par
336
337    def click(*effects, trig=True):
338        outer = node("ParallelTimeContainer", Begin=event(const("EventTrigger", "ON_NEXT")) if trig else 0.0)
339        inner = node("ParallelTimeContainer", Begin=0.0)
340        for e in effects:
341            inner.appendChild(e)
342        outer.appendChild(inner)
343        seq.appendChild(outer)
344
345    st = node("AnimateSet", Target=sh, AttributeName="Visibility", To=True, Begin=0.0, Duration=0.001, Fill=const("AnimationFill", "HOLD"))
346    click(effect("ON_CLICK", "ooo-entrance-appear", "ENTRANCE", st))
347    an = node("Animate", Target=sh2, AttributeName="X", Values=(0.0, 0.5), KeyTimes=(0.0, 1.0), Duration=2.0,
348              Formula="$X+#x", CalcMode=const("AnimationCalcMode", "DISCRETE"), Accumulate=True,
349              Additive=const("AnimationAdditiveMode", "SUM"), TimeFilter=(struct_("com.sun.star.animations.TimeFilterPair", 0.0, 0.0), struct_("com.sun.star.animations.TimeFilterPair", 1.0, 1.0)))
350    an2 = node("Animate", Target=sh2, AttributeName="Opacity", From=0.0, By=0.1, To=1.0, Duration=1.0,
351               CalcMode=const("AnimationCalcMode", "PACED"), Additive=const("AnimationAdditiveMode", "MULTIPLY"))
352    an3 = node("Animate", Target=sh2, AttributeName="Y", From=0.0, To=0.5, Duration=1.0, Additive=const("AnimationAdditiveMode", "BASE"))
353    an4 = node("Animate", Target=sh2, AttributeName="Height", From=0.0, To=0.5, Duration=1.0, Additive=const("AnimationAdditiveMode", "NONE"))
354    click(effect("WITH_PREVIOUS", "ooo-entrance-fly-in", "ENTRANCE", an, an2, an3, an4))
355    ac = node("AnimateColor", Target=sh, AttributeName="FillColor", From=0xFF0000, To=0x00FF00, Duration=1.0,
356              ColorInterpolation=const("AnimationColorSpace", "HSL"), Direction=False)
357    ac2 = node("AnimateColor", Target=sh3, AttributeName="LineColor", By=0x101010, Duration=1.0,
358               ColorInterpolation=const("AnimationColorSpace", "RGB"), Direction=True)
359    click(effect("AFTER_PREVIOUS", "ooo-emphasis-change-fill-color", "EMPHASIS", ac, ac2))
360    am = node("AnimateMotion", Target=sh3, Path="M 0 0 L 0.25 0.25 C 0.3 0.3 0.4 0.3 0.5 0.5", Duration=2.0,
361              CalcMode=const("AnimationCalcMode", "PACED"))
362    click(effect("ON_CLICK", "ooo-motionpath-user", "MOTIONPATH", am))
363    ats = [node("AnimateTransform", Target=sh2, TransformType=tt, Values=("1", "2"), Duration=1.0, SubItem=sub)
364           for tt, sub in ((0, 0), (1, 1), (2, 2), (3, 0), (4, 1))]
365    click(effect("ON_CLICK", "ooo-emphasis-spin", "EMPHASIS", *ats))
366    attrs = [("Width", "0", "width"), ("Height", "0", "height"), ("Y", "0", "y"), ("Rotate", 0.0, 90.0), ("SkewX", 0.0, 30.0),
367             ("CharColor", 0, 0xFF0000), ("CharRotation", 0, 90), ("CharWeight", 100.0, 150.0), ("CharUnderline", 0, 1),
368             ("CharFontName", "Arial", "Times"), ("CharHeight", 1.0, 2.0), ("CharPosture", 0, 2),
369             ("LineStyle", 0, 1), ("FillStyle", 0, 1), ("DimColor", 0, 0xAAAAAA), ("Visibility", False, True)]
370    kids = []
371    for an_name, fr, to in attrs:
372        try:
373            kids.append(node("Animate", Target=sh3, AttributeName=an_name, From=fr, To=to, Duration=0.5,
374                             Restart=const("AnimationRestart", "ALWAYS"), RestartDefault=const("AnimationRestart", "NEVER"),
375                             FillDefault=const("AnimationFill", "FREEZE"), Acceleration=0.2, Decelerate=0.3,
376                             AutoReverse=True, RepeatCount=3.0, RepeatDuration=9.0, EndSync=const("AnimationEndSync", "FIRST"),
377                             End=5.0))
378        except Exception as e:
379            print("  animate", an_name, repr(e)[:100])
380    click(effect("ON_CLICK", "ooo-emphasis-custom", "CUSTOM", *kids))
381    tf = node("TransitionFilter", Target=sh, Transition=1, Subtype=2, Mode=False, Direction=False, FadeColor=0x123456, Duration=1.0)
382    tf2 = node("TransitionFilter", Target=sh2, Transition=37, Subtype=const("TransitionSubType", "FADETOCOLOR"), Mode=True,
383               Direction=True, FadeColor=0x654321, Duration=1.0)
384    click(effect("ON_CLICK", "ooo-entrance-wipe", "ENTRANCE", tf, tf2))
385    au = node("Audio", Source=SOUND_URL, Volume=0.5, Begin=0.0)
386    au2 = node("Audio", Source=SOUND_URL, Volume=1.0)
387    cm = [node("Command", Target=sh, Command=c, Parameter=None) for c in range(6)]
388    click(effect("ON_CLICK", "media-call", "MEDIACALL", au, au2, *cm))
389    it = node("IterateContainer", Target=sh, SubItem=const("ShapeAnimationSubType", "ONLY_TEXT"),
390              IterateType=1, IterateInterval=0.25,
391              UserData=userdata(node_type=EN("ON_CLICK"), preset_id="ooo-entrance-fade-in", preset_class=const("EffectPresetClass", "ENTRANCE")))
392    it.appendChild(node("AnimateSet", Target=sh, AttributeName="Visibility", To=True, Begin=0.0, Duration=0.001))
393    click(it)
394    for sub, itype in ((1, 0), (0, 2)):
395        it2 = node("IterateContainer", Target=ParagraphTarget(sh, 1), SubItem=sub, IterateType=itype, IterateInterval=1.5)
396        it2.appendChild(node("AnimateSet", Target=ParagraphTarget(sh, 1), AttributeName="Visibility", To=True))
397        click(it2)
398    seq2 = node("SequenceTimeContainer", UserData=userdata(node_type=EN("INTERACTIVE_SEQUENCE")),
399                Begin=event(const("EventTrigger", "ON_CLICK"), sh3), End=(event(const("EventTrigger", "ON_END"), sh), 5.0))
400    seq2.appendChild(node("AnimateSet", Target=sh2, AttributeName="Visibility", To=False))
401    root.appendChild(seq2)
402    for kind in range(12):
403        trig = kind + 1
404        e = event(trig, sh, 0.5 if kind % 2 else None)
405        seq3 = node("SequenceTimeContainer", UserData=userdata(node_type=EN("INTERACTIVE_SEQUENCE")), Begin=e)
406        seq3.appendChild(node("AnimateSet", Target=sh2, AttributeName="Visibility", To=True))
407        root.appendChild(seq3)
408    for fill, rest in ((1, 1), (2, 2), (3, 3), (4, 0), (5, 1)):
409        par = node("ParallelTimeContainer", Fill=fill, Restart=rest, Begin=0.0)
410        par.appendChild(node("AnimateSet", Target=sh, AttributeName="Visibility", To=True))
411        root.appendChild(par)
412
413
414def ParagraphTarget(shape, n):
415    return struct_("com.sun.star.presentation.ParagraphTarget", shape, n)
416
417
418def animations():
419    doc = new("odp")
420    p = doc.DrawPages.getByIndex(0)
421    step("anim-tree", animations_one, doc, p, 0)
422    save(doc, "animations.odp")
423    done(doc)
424
425
426def enum(t, v):
427    return uno.Enum("com.sun.star.presentation." + t, v)
428
429
430ANIM_EFFECTS = """FADE_FROM_LEFT FADE_FROM_TOP FADE_FROM_RIGHT FADE_FROM_BOTTOM FADE_TO_CENTER FADE_FROM_CENTER MOVE_FROM_LEFT MOVE_FROM_TOP
431MOVE_FROM_RIGHT MOVE_FROM_BOTTOM VERTICAL_STRIPES HORIZONTAL_STRIPES CLOCKWISE COUNTERCLOCKWISE FADE_FROM_UPPERLEFT FADE_FROM_UPPERRIGHT
432FADE_FROM_LOWERLEFT FADE_FROM_LOWERRIGHT CLOSE_VERTICAL CLOSE_HORIZONTAL OPEN_VERTICAL OPEN_HORIZONTAL PATH MOVE_TO_LEFT MOVE_TO_TOP
433MOVE_TO_RIGHT MOVE_TO_BOTTOM SPIRALIN_LEFT SPIRALIN_RIGHT SPIRALOUT_LEFT SPIRALOUT_RIGHT DISSOLVE WAVYLINE_FROM_LEFT WAVYLINE_FROM_TOP
434WAVYLINE_FROM_RIGHT WAVYLINE_FROM_BOTTOM RANDOM VERTICAL_LINES HORIZONTAL_LINES LASER_FROM_LEFT LASER_FROM_TOP LASER_FROM_RIGHT
435LASER_FROM_BOTTOM LASER_FROM_UPPERLEFT LASER_FROM_UPPERRIGHT LASER_FROM_LOWERLEFT LASER_FROM_LOWERRIGHT APPEAR HIDE MOVE_FROM_UPPERLEFT
436MOVE_FROM_UPPERRIGHT MOVE_FROM_LOWERRIGHT MOVE_FROM_LOWERLEFT MOVE_TO_UPPERLEFT MOVE_TO_UPPERRIGHT MOVE_TO_LOWERRIGHT MOVE_TO_LOWERLEFT
437MOVE_SHORT_FROM_LEFT MOVE_SHORT_FROM_UPPERLEFT MOVE_SHORT_FROM_TOP MOVE_SHORT_FROM_UPPERRIGHT MOVE_SHORT_FROM_RIGHT
438MOVE_SHORT_FROM_LOWERRIGHT MOVE_SHORT_FROM_BOTTOM MOVE_SHORT_FROM_LOWERLEFT MOVE_SHORT_TO_LEFT MOVE_SHORT_TO_UPPERLEFT MOVE_SHORT_TO_TOP
439MOVE_SHORT_TO_UPPERRIGHT MOVE_SHORT_TO_RIGHT MOVE_SHORT_TO_LOWERRIGHT MOVE_SHORT_TO_BOTTOM MOVE_SHORT_TO_LOWERLEFT VERTICAL_CHECKERBOARD
440HORIZONTAL_CHECKERBOARD HORIZONTAL_ROTATE VERTICAL_ROTATE HORIZONTAL_STRETCH VERTICAL_STRETCH STRETCH_FROM_LEFT STRETCH_FROM_UPPERLEFT
441STRETCH_FROM_TOP STRETCH_FROM_UPPERRIGHT STRETCH_FROM_RIGHT STRETCH_FROM_LOWERRIGHT STRETCH_FROM_BOTTOM STRETCH_FROM_LOWERLEFT ZOOM_IN
442ZOOM_IN_SMALL ZOOM_IN_SPIRAL ZOOM_OUT ZOOM_OUT_SMALL ZOOM_OUT_SPIRAL ZOOM_IN_FROM_LEFT ZOOM_IN_FROM_UPPERLEFT ZOOM_IN_FROM_TOP
443ZOOM_IN_FROM_UPPERRIGHT ZOOM_IN_FROM_RIGHT ZOOM_IN_FROM_LOWERRIGHT ZOOM_IN_FROM_BOTTOM ZOOM_IN_FROM_LOWERLEFT ZOOM_IN_FROM_CENTER
444ZOOM_OUT_FROM_LEFT ZOOM_OUT_FROM_UPPERLEFT ZOOM_OUT_FROM_TOP ZOOM_OUT_FROM_UPPERRIGHT ZOOM_OUT_FROM_RIGHT ZOOM_OUT_FROM_LOWERRIGHT
445ZOOM_OUT_FROM_BOTTOM ZOOM_OUT_FROM_LOWERLEFT ZOOM_OUT_FROM_CENTER""".split()
446CLICK_ACTIONS = "NONE PREVPAGE NEXTPAGE FIRSTPAGE LASTPAGE BOOKMARK DOCUMENT INVISIBLE SOUND VERB VANISH PROGRAM MACRO STOPPRESENTATION".split()
447
448
449def png(path, w=8, h=8):
450    import zlib
451    raw = b"".join(b"\x00" + b"".join(bytes((x * 30 % 256, y * 30 % 256, 128)) for x in range(w)) for y in range(h))
452    def chunk(t, d):
453        c = struct.pack(">I", len(d)) + t + d
454        return c + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
455    data = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(raw)) + chunk(b"IEND", b"")
456    open(path, "wb").write(data)
457
458
459PNG = os.path.join(tmp, "pix.png")
460png(PNG)
461PNG_URL = uno.systemPathToFileUrl(PNG)
462
463
464def actions():
465    doc = new("odp")
466    page = doc.DrawPages.getByIndex(0)
467    pg2 = add_page(doc)
468    pg2.Name = "Target"
469    x = 500
470    y = 500
471    for i, a in enumerate(CLICK_ACTIONS):
472        sh = mk(doc, page, "RectangleShape", x=x, y=y, w=2000, h=1000, text=a)
473        x += 2200
474        if x > 22000:
475            x = 500; y += 1200
476        props = dict(OnClick=enum("ClickAction", a))
477        if a in ("BOOKMARK",):
478            props["Bookmark"] = "Target"
479        if a in ("DOCUMENT",):
480            props["Bookmark"] = "file:///tmp/other.odp#Slide1"
481        if a == "PROGRAM":
482            props["Bookmark"] = "file:///usr/bin/true"
483        if a == "SOUND":
484            props.update(Bookmark=SOUND_URL, PlayFull=True)
485        if a == "VERB":
486            props["Verb"] = 1
487        if a == "MACRO":
488            props["Bookmark"] = "macro:///Standard.Module1.Main"
489        setp(sh, **props)
490    for i, eff in enumerate(ANIM_EFFECTS):
491        sh = mk(doc, page, "RectangleShape", x=500 + (i % 10) * 2200, y=6000 + (i // 10) * 1100, w=2000, h=900, text=eff[:14])
492        setp(sh, OnClick=enum("ClickAction", "VANISH"), Effect=enum("AnimationEffect", eff),
493             Speed=enum("AnimationSpeed", ("SLOW", "MEDIUM", "FAST")[i % 3]), SoundOn=bool(i % 2), Sound=SOUND_URL, PlayFull=bool(i % 3 == 0),
494             DimPrevious=False, DimHide=False)
495    save(doc, "click-actions.odp")
496    done(doc)
497
498
499def draw_links():
500    doc = new("odg")
501    pages = [doc.DrawPages.getByIndex(0), add_page(doc)]
502    pages[1].Name = "Second"
503    for i, a in enumerate(("BOOKMARK", "DOCUMENT")):
504        sh = mk(doc, pages[0], "RectangleShape", x=500 + i * 3500, text=a)
505        setp(sh, OnClick=enum("ClickAction", a), Bookmark="Second" if i == 0 else "file:///tmp/other.odg#Page1")
506    save(doc, "draw-links.odg")
507    done(doc)
508
509
510def image_maps():
511    doc = new("odg")
512    page = doc.DrawPages.getByIndex(0)
513    g = mk(doc, page, "GraphicObjectShape", x=1000, y=1000, w=8000, h=6000)
514    g.GraphicURL = PNG_URL
515    imap = g.ImageMap
516    r = doc.createInstance("com.sun.star.image.ImageMapRectangleObject")
517    r.Boundary = struct_("com.sun.star.awt.Rectangle", 0, 0, 3000, 2000)
518    r.URL = "http://example.org/rect"
519    r.Target = "_blank"
520    r.Description = "rect area"
521    r.IsActive = True
522    r.Name = "r1"
523    imap.insertByIndex(0, r)
524    c = doc.createInstance("com.sun.star.image.ImageMapCircleObject")
525    c.Center = pt(5000, 3000)
526    c.Radius = 1000
527    c.URL = "http://example.org/circle"
528    c.Title = "circle title"
529    c.IsActive = False
530    imap.insertByIndex(1, c)
531    po = doc.createInstance("com.sun.star.image.ImageMapPolygonObject")
532    po.Polygon = (pt(0, 0), pt(100, 0), pt(100, 100), pt(0, 100))
533    po.URL = "#target"
534    po.Description = "poly area"
535    imap.insertByIndex(2, po)
536    g.ImageMap = imap
537    gr2 = mk(doc, page, "GraphicObjectShape", x=10000, y=1000, w=3000, h=3000)
538    gr2.GraphicURL = PNG_URL
539    setp(gr2, Title="a picture", Description="a described picture")
540    save(doc, "image-map.odg")
541    done(doc)
542
543
544def pts(*xy):
545    return tuple(pt(x, y) for x, y in xy)
546
547
548def poly(doc, page, service, points, **props):
549    s_ = doc.createInstance("com.sun.star.drawing." + service)
550    page.add(s_)
551    s_.PolyPolygon = (pts(*points),)
552    setp(s_, **props)
553    return s_
554
555
556def bezier(doc, page, service, **props):
557    s_ = doc.createInstance("com.sun.star.drawing." + service)
558    page.add(s_)
559    C = uno.Enum("com.sun.star.drawing.PolygonFlags", "NORMAL")
560    K = uno.Enum("com.sun.star.drawing.PolygonFlags", "CONTROL")
561    coords = struct_("com.sun.star.drawing.PolyPolygonBezierCoords")
562    coords.Coordinates = (pts((1000, 1000), (2000, 0), (3000, 2000), (4000, 1000), (5000, 3000), (1000, 3000)),)
563    coords.Flags = ((C, K, K, C, C, C),)
564    s_.PolyPolygonBezier = coords
565    setp(s_, **props)
566    return s_
567
568
569def shapes_core(doc, page):
570    mk(doc, page, "RectangleShape", x=500, y=500, text="plain", Name="rect1")
571    mk(doc, page, "RectangleShape", x=4000, y=500, text="round", CornerRadius=400, RotateAngle=3000, ShearAngle=500, Name="rect2")
572    mk(doc, page, "RectangleShape", x=8000, y=500, text="rot", RotateAngle=9000)
573    for i, kind in enumerate(("FULL", "CUT", "SECTION", "ARC")):
574        ck = uno.Enum("com.sun.star.drawing.CircleKind", kind)
575        mk(doc, page, "EllipseShape", x=500 + i * 2600, y=3500, w=2400, h=2400, CircleKind=ck,
576           CircleStartAngle=3000, CircleEndAngle=24000, Name="circle-" + kind)
577        mk(doc, page, "EllipseShape", x=500 + i * 3200, y=6500, w=3000, h=2000, CircleKind=ck,
578           CircleStartAngle=3000, CircleEndAngle=24000, Name="ellipse-" + kind)
579        mk(doc, page, "EllipseShape", x=500 + i * 3200, y=9000, w=2400, h=2400, CircleKind=ck, RotateAngle=4500,
580           CircleStartAngle=3000, CircleEndAngle=24000)
581    mk(doc, page, "EllipseShape", x=22000, y=9000, w=3000, h=2000, RotateAngle=2500, Name="ellipse-rot")
582    poly(doc, page, "LineShape", ((500, 12000), (4000, 13000)), Name="line1")
583    poly(doc, page, "LineShape", ((4500, 12000), (8000, 13000)), RotateAngle=2000)
584    poly(doc, page, "PolyLineShape", ((9000, 12000), (10000, 14000), (11000, 12000), (12000, 14000)), Name="polyline1")
585    poly(doc, page, "PolyLineShape", ((13000, 12000), (14000, 14000), (15000, 12000)), RotateAngle=3000)
586    poly(doc, page, "PolyPolygonShape", ((16000, 12000), (18000, 12000), (17000, 14000)), Name="polygon1")
587    poly(doc, page, "PolyPolygonShape", ((19000, 12000), (21000, 12000), (20000, 14000)), RotateAngle=1500)
588    bezier(doc, page, "ClosedBezierShape", Name="path-closed")
589    bezier(doc, page, "OpenBezierShape", RotateAngle=1000)
590    bezier(doc, page, "ClosedFreeHandShape")
591    bezier(doc, page, "OpenFreeHandShape")
592    poly(doc, page, "PolyLinePathShape", ((1000, 16000), (2000, 17000), (3000, 16000)))
593    poly(doc, page, "PolyPolygonPathShape", ((4000, 16000), (6000, 16000), (5000, 17500)))
594    mk(doc, page, "TextShape", x=7000, y=16000, w=4000, h=1000, text="text box", TextAutoGrowHeight=True,
595       TextMinimumFrameWidth=2000, TextMaximumFrameWidth=6000, TextMinimumFrameHeight=500, TextMaximumFrameHeight=3000, Name="text1")
596    t = mk(doc, page, "TextShape", x=12000, y=16000, w=4000, h=1000, text="titled")
597    setp(t, Title="text title", Description="text description")
598    g1 = mk(doc, page, "RectangleShape", x=500, y=18000, text="g1")
599    g2 = mk(doc, page, "EllipseShape", x=4000, y=18000, text="g2")
600    grp = doc.createInstance("com.sun.star.drawing.GroupShape")
601    page.add(grp)
602    grp.add(g1)
603    grp.add(g2)
604    setp(grp, Name="group1", Title="group title", Description="group desc")
605    inner = doc.createInstance("com.sun.star.drawing.GroupShape")
606    page.add(inner)
607    inner.add(mk(doc, page, "RectangleShape", x=8000, y=18000, text="n1"))
608    inner.add(mk(doc, page, "RectangleShape", x=11000, y=18000, text="n2"))
609    outer = doc.createInstance("com.sun.star.drawing.GroupShape")
610    page.add(outer)
611    outer.add(inner)
612    outer.add(mk(doc, page, "RectangleShape", x=14000, y=18000, text="n3"))
613    setp(outer, Name="group2")
614
615
616def connectors(doc, page):
617    r = [mk(doc, page, "RectangleShape", x=1000 + (i % 3) * 6000, y=1000 + (i // 3) * 5000, w=2500, h=1500, text="n%d" % i, Name="node%d" % i)
618         for i in range(9)]
619    for i in range(4):
620        gp = struct_("com.sun.star.drawing.GluePoint2")
621        gp.Position = pt(500 * i, 200 * i)
622        gp.IsRelative = bool(i % 2)
623        gp.PositionAlignment = uno.Enum("com.sun.star.drawing.Alignment", ("TOP_LEFT", "CENTER", "BOTTOM_RIGHT", "LEFT")[i])
624        gp.Escape = uno.Enum("com.sun.star.drawing.EscapeDirection", ("SMART", "LEFT", "UP", "DOWN")[i])
625        gp.IsUserDefined = True
626        r[0].GluePoints.insert(gp)
627    escs = ("SMART", "LEFT", "RIGHT", "UP", "DOWN", "HORIZONTAL", "VERTICAL")
628    for i, esc in enumerate(escs):
629        gp = struct_("com.sun.star.drawing.GluePoint2")
630        gp.Position = pt(100 * i, 100 * i)
631        gp.IsRelative = False
632        gp.PositionAlignment = uno.Enum("com.sun.star.drawing.Alignment", ("TOP_LEFT", "TOP", "TOP_RIGHT", "LEFT", "CENTER", "RIGHT", "BOTTOM_LEFT")[i])
633        gp.Escape = uno.Enum("com.sun.star.drawing.EscapeDirection", esc)
634        gp.IsUserDefined = True
635        r[1].GluePoints.insert(gp)
636    kinds = ("STANDARD", "CURVE", "LINE", "LINES")
637    n = 0
638    for k in kinds:
639        c = doc.createInstance("com.sun.star.drawing.ConnectorShape")
640        page.add(c)
641        c.EdgeKind = uno.Enum("com.sun.star.drawing.ConnectorType", k)
642        c.StartShape = r[n % 9]
643        c.EndShape = r[(n + 4) % 9]
644        c.StartGluePointIndex = 0 if n % 2 else 4
645        c.EndGluePointIndex = 5 + n
646        setp(c, Title="conn " + k, Description="connector " + k, EdgeLine1Delta=300, EdgeLine2Delta=-300, EdgeNode1HorzDist=100, EdgeNode2VertDist=200)
647        n += 1
648        c2 = doc.createInstance("com.sun.star.drawing.ConnectorShape")
649        page.add(c2)
650        c2.EdgeKind = uno.Enum("com.sun.star.drawing.ConnectorType", k)
651        c2.StartPosition = pt(1000, 12000 + n * 700)
652        c2.EndPosition = pt(9000, 13000 + n * 900)
653        c2.String = "free " + k
654
655
656def measure_caption(doc, page):
657    for i, (a, b) in enumerate((((1000, 1000), (9000, 1000)), ((1000, 3000), (9000, 6000)), ((11000, 1000), (11000, 8000)))):
658        m = doc.createInstance("com.sun.star.drawing.MeasureShape")
659        page.add(m)
660        m.StartPosition = pt(*a)
661        m.EndPosition = pt(*b)
662        setp(m, Name="measure%d" % i, MeasureTextHorizontalPosition=i % 4, MeasureTextVerticalPosition=i % 4,
663             MeasureLineDistance=500, MeasureHelpLine1Length=200, MeasureHelpLine2Length=200, MeasureOverhang=100,
664             MeasureUnit=i, MeasureShowUnit=True, MeasureBelowReferenceEdge=bool(i % 2), MeasureTextRotate90=bool(i % 2),
665             MeasureTextIsFixedAngle=True, MeasureTextFixedAngle=1500 * i, MeasureDecimalPlaces=2,
666             MeasureTextAutoAngle=False, MeasureTextAutoAngleView=i, Title="m", Description="measure")
667        m.String = "measure %d" % i
668    for i in range(3):
669        c = doc.createInstance("com.sun.star.drawing.CaptionShape")
670        page.add(c)
671        c.Position = pt(1000 + i * 7000, 10000)
672        c.Size = sz(4000, 2000)
673        c.String = "caption %d" % i
674        setp(c, CaptionPoint=pt(500 + i * 7000, 14000), CaptionType=i, CaptionIsFitLineLength=bool(i % 2), CaptionIsEscapeRelative=bool(i % 2),
675             CaptionEscapeDirection=i, CaptionEscapeRelative=500, CaptionEscapeAbsolute=300, CaptionLineLength=700,
676             CaptionGap=100, CaptionAngle=300 * i, CornerRadius=300 * i, Name="caption%d" % i)
677
678
679def layers_doc():
680    doc = new("odg")
681    page = doc.DrawPages.getByIndex(0)
682    lm = doc.LayerManager
683    for i, nm in enumerate(("layerA", "layerB")):
684        ly = lm.insertNewByIndex(lm.Count)
685        ly.Name = nm
686        setp(ly, IsVisible=bool(i % 2 == 0), IsPrintable=bool(i == 0), IsLocked=bool(i == 1), Title="t" + nm, Description="d" + nm)
687        sh = mk(doc, page, "RectangleShape", x=500 + 3000 * i, text=nm)
688        sh.LayerName = nm
689    for k, v in enumerate(("a", "b")):
690        sh = mk(doc, page, "EllipseShape", x=500 + 3000 * k, y=4000, text="nav%d" % k)
691    try:
692        order = smgr.createInstance("com.sun.star.drawing.ShapeCollection")
693        for i in reversed(range(page.Count)):
694            order.add(page.getByIndex(i))
695        page.NavigationOrder = order
696    except Exception as e:
697        print("  navorder", repr(e)[:150])
698    save(doc, "layers.odg")
699    done(doc)
700
701
702def shapes_docs():
703    for kind in ("odg", "odp"):
704        for label, fn in (("core", shapes_core), ("connectors", connectors), ("measure", measure_caption)):
705            doc = new(kind)
706            page = doc.DrawPages.getByIndex(0)
707            step("%s-%s" % (label, kind), fn, doc, page)
708            save(doc, "%s.%s" % (label, kind))
709            done(doc)
710
711
712def p3(x, y, z):
713    return struct_("com.sun.star.drawing.Position3D", x, y, z)
714
715
716def d3(x, y, z):
717    return struct_("com.sun.star.drawing.Direction3D", x, y, z)
718
719
720def scene_3d(doc, page, x, y, nm, mode="SMOOTH", proj="PERSPECTIVE", extra=None):
721    sc = doc.createInstance("com.sun.star.drawing.Shape3DSceneObject")
722    page.add(sc)
723    sc.Position = pt(x, y)
724    sc.Size = sz(5000, 5000)
725    cube = doc.createInstance("com.sun.star.drawing.Shape3DCubeObject")
726    sc.add(cube)
727    setp(cube, D3DPosition=p3(-1000, -1000, -1000), D3DSize=d3(2000, 3000, 2500))
728    sph = doc.createInstance("com.sun.star.drawing.Shape3DSphereObject")
729    sc.add(sph)
730    setp(sph, D3DPosition=p3(500, 500, 0), D3DSize=d3(1500, 1500, 1500), D3DHorizontalSegments=12, D3DVerticalSegments=10)
731    ex = doc.createInstance("com.sun.star.drawing.Shape3DExtrudeObject")
732    sc.add(ex)
733    poly3 = struct_("com.sun.star.drawing.PolyPolygonShape3D")
734    poly3.SequenceX = ((0.0, 1000.0, 1000.0, 0.0),)
735    poly3.SequenceY = ((0.0, 0.0, 1000.0, 1000.0),)
736    poly3.SequenceZ = ((0.0, 0.0, 0.0, 0.0),)
737    setp(ex, D3DPolyPolygon3D=poly3, D3DDepth=700, D3DPercentDiagonal=5, D3DDoubleSided=True,
738         D3DCloseFront=True, D3DCloseBack=False, D3DNormalsKind=uno.Enum("com.sun.star.drawing.NormalsKind", "SPECIFIC"),
739         D3DNormalsInvert=True, D3DTextureProjectionX=uno.Enum("com.sun.star.drawing.TextureProjectionMode", "SPHERE"),
740         D3DTextureProjectionY=uno.Enum("com.sun.star.drawing.TextureProjectionMode", "OBJECTSPECIFIC"),
741         D3DTextureKind=uno.Enum("com.sun.star.drawing.TextureKind", "COLOR"),
742         D3DTextureMode=uno.Enum("com.sun.star.drawing.TextureMode", "BLEND"), D3DTextureFilter=True,
743         D3DMaterialColor=0x336699, D3DMaterialEmission=0x101010, D3DMaterialSpecular=0xAAAAAA, D3DMaterialSpecularIntensity=40,
744         D3DShadow3D=True)
745    la = doc.createInstance("com.sun.star.drawing.Shape3DLatheObject")
746    sc.add(la)
747    poly4 = struct_("com.sun.star.drawing.PolyPolygonShape3D")
748    poly4.SequenceX = ((500.0, 1500.0, 1000.0),)
749    poly4.SequenceY = ((0.0, 0.0, 1500.0),)
750    poly4.SequenceZ = ((0.0, 0.0, 0.0),)
751    setp(la, D3DPolyPolygon3D=poly4, D3DHorizontalSegments=16, D3DEndAngle=2700, D3DPercentDiagonal=10)
752    cg = struct_("com.sun.star.drawing.CameraGeometry")
753    cg.vrp = p3(0.0, 0.0, 6000.0)
754    cg.vpn = d3(1.0, 1.0, 5.0)
755    cg.vup = d3(0.0, 1.0, 0.2)
756    setp(sc, Name=nm, D3DCameraGeometry=cg, D3DScenePerspective=uno.Enum("com.sun.star.drawing.ProjectionMode", proj),
757         D3DSceneShadeMode=uno.Enum("com.sun.star.drawing.ShadeMode", mode), D3DSceneDistance=3000, D3DSceneFocalLength=40,
758         D3DSceneShadowSlant=15, D3DSceneAmbientColor=0x202040, D3DSceneTwoSidedLighting=True,
759         D3DSceneLightOn1=True, D3DSceneLightOn2=True, D3DSceneLightOn3=False, D3DSceneLightColor1=0xFFFFFF,
760         D3DSceneLightColor2=0xFF0000, D3DSceneLightColor5=0x00FF00, D3DSceneLightOn5=True,
761         D3DSceneLightDirection1=d3(1.0, 1.0, 1.0), D3DSceneLightDirection2=d3(-1.0, 0.0, 1.0), RotateAngle=1000 if extra else 0,
762         Title="3d scene", Description="a scene")
763    if extra:
764        setp(sc, **extra)
765    return sc
766
767
768def threed(kind):
769    doc = new(kind)
770    page = doc.DrawPages.getByIndex(0)
771    n = 0
772    for mode in ("FLAT", "PHONG", "SMOOTH", "DRAFT"):
773        for proj in ("PERSPECTIVE", "PARALLEL"):
774            step("scene-%s-%s" % (mode, proj), scene_3d, doc, page, 500 + (n % 4) * 5500, 500 + (n // 4) * 5500, "scene%d" % n, mode, proj)
775            n += 1
776    save(doc, "scenes.%s" % kind)
777    done(doc)
778
779
780def pv_(name, value):
781    return pv(name, value)
782
783
784def param(value, kind=0):
785    p_ = struct_("com.sun.star.drawing.EnhancedCustomShapeParameter")
786    p_.Value = value
787    p_.Type = kind
788    return p_
789
790
791def pair(a, b, ka=0, kb=0):
792    pp = struct_("com.sun.star.drawing.EnhancedCustomShapeParameterPair")
793    pp.First = param(a, ka)
794    pp.Second = param(b, kb)
795    return pp
796
797
798def custom(doc, page, x, y, w, h, geometry, text=None, **props):
799    c = doc.createInstance("com.sun.star.drawing.CustomShape")
800    page.add(c)
801    c.Position = pt(x, y)
802    c.Size = sz(w, h)
803    if text:
804        c.String = text
805    c.CustomShapeGeometry = tuple(pv(k, v) for k, v in geometry)
806    setp(c, **props)
807    return c
808
809
810PRESETS = """rectangle round-rectangle ellipse diamond isosceles-triangle right-triangle parallelogram trapezoid octagon hexagon pentagon cross
811ring heart sun moon smiley-face lightning flower-1 cloud-callout star4 star5 star6 star8 star12 star24 right-arrow left-arrow up-arrow
812down-arrow left-right-arrow quad-arrow bent-arrow u-turn-arrow circular-arrow notched-right-arrow striped-right-arrow pentagon-right
813chevron flowchart-process flowchart-decision flowchart-terminator flowchart-document flowchart-data flowchart-connector
814flowchart-card flowchart-delay flowchart-sort mso-spt100 mso-spt21 round-callout rectangular-callout line-callout-1 bracket-pair
815brace-pair left-bracket right-brace wave double-wave horizontal-scroll vertical-scroll cube can bevel frame folded-corner
816block-arc forbidden down-ribbon up-ribbon quad-bevel octagon-bevel diamond-bevel left-right-up-arrow
817ellipse-ribbon ellipse-ribbon-2 irregular-seal1 irregular-seal2 doorplate
818fontwork-plain-text fontwork-slant-up fontwork-wave fontwork-fade-up-and-right fontwork-arch-up-curve fontwork-circle-curve
819fontwork-open-circle-curve fontwork-arch-up-pour fontwork-circle-pour fontwork-triangle-up fontwork-stop fontwork-inflate""".split()
820
821
822def custom_shapes():
823    doc = new("odg")
824    page = doc.DrawPages.getByIndex(0)
825    for i, name in enumerate(PRESETS):
826        step("preset " + name, custom, doc, page, 500 + (i % 8) * 2700, 500 + (i // 8) * 2300, 2400, 2000, [("Type", name)], text=name[:8])
827    save(doc, "custom-presets.odg")
828    done(doc)
829
830
831def custom_full():
832    doc = new("odg")
833    page = doc.DrawPages.getByIndex(0)
834    PV = "[]com.sun.star.beans.PropertyValue"
835    ECS = "com.sun.star.drawing.EnhancedCustomShape"
836    pairs = lambda seq: uno.Any("[]" + ECS + "ParameterPair", tuple(seq))
837    shade = lambda n: uno.Enum("com.sun.star.drawing.ShadeMode", n)
838    proj = lambda n: uno.Enum("com.sun.star.drawing.ProjectionMode", n)
839    for i, (sm, pm) in enumerate((("FLAT", "PARALLEL"), ("PHONG", "PERSPECTIVE"), ("SMOOTH", "PARALLEL"), ("DRAFT", "PERSPECTIVE"))):
840        ext = uno.Any(PV, (pv("Extrusion", True), pv("Brightness", 33.0), pv("Depth", pair(900.0, 0.5)), pv("Diffusion", 20.0),
841               pv("NumberOfLineSegments", 12), pv("LightFace", bool(i % 2)), pv("FirstLightHarsh", True), pv("SecondLightHarsh", False),
842               pv("FirstLightLevel", 60.0), pv("SecondLightLevel", 40.0), pv("FirstLightDirection", d3(10.0, 20.0, 30.0)),
843               pv("SecondLightDirection", d3(-1.0, 2.0, 3.0)), pv("Metal", bool(i % 2)), pv("ShadeMode", shade(sm)),
844               pv("RotateAngle", pair(10.0, 20.0)), pv("RotationCenter", d3(0.1, 0.2, 0.3)), pv("Shininess", 50.0),
845               pv("Skew", pair(50.0, 135.0)), pv("Specularity", 70.0), pv("ProjectionMode", proj(pm)),
846               pv("ViewPoint", p3(3500.0, -3500.0, 25000.0)), pv("Origin", pair(0.5, -0.5)), pv("Color", True)))
847        tp = uno.Any(PV, (pv("TextPath", True), pv("TextPathMode", uno.Enum("com.sun.star.drawing.EnhancedCustomShapeTextPathMode", ("NORMAL", "PATH", "SHAPE", "NORMAL")[i])),
848              pv("ScaleX", bool(i % 2)), pv("SameLetterHeights", bool(i // 2))))
849        path = uno.Any(PV, (pv("ExtrusionAllowed", True), pv("ConcentricGradientFillAllowed", True), pv("TextPathAllowed", True),
850                pv("GluePointType", i % 3), pv("StretchX", 100 * i), pv("StretchY", 50 * i),
851                pv("GluePoints", pairs((pair(0, 0), pair(10800, 0), pair(21600, 21600)))),
852                pv("Coordinates", pairs((pair(0, 0), pair(21600, 0), pair(21600, 21600), pair(0, 21600)))),
853                pv("Segments", uno.Any("[]" + ECS + "Segment", (struct_(ECS + "Segment", 1, 1), struct_(ECS + "Segment", 2, 3), struct_(ECS + "Segment", 4, 1)))),
854                pv("TextFrames", uno.Any("[]" + ECS + "TextFrame", (struct_(ECS + "TextFrame", pair(0, 0), pair(21600, 21600)),)))))
855        h = uno.Any(PV, (pv("Position", pair(10800, 0)), pv("MirroredX", bool(i % 2)), pv("MirroredY", bool(i // 2)), pv("Switched", bool(i % 2)),
856             pv("RangeXMinimum", param(0)), pv("RangeXMaximum", param(21600)), pv("RangeYMinimum", param(0)), pv("RangeYMaximum", param(21600))))
857        hp = uno.Any(PV, (pv("Position", pair(10800, 10800)), pv("Polar", pair(10800, 10800)), pv("RadiusRangeMinimum", param(0)),
858              pv("RadiusRangeMaximum", param(10800))))
859        adj = struct_(ECS + "AdjustmentValue")
860        adj.Value = 5400
861        adj.State = uno.Enum("com.sun.star.beans.PropertyState", "DIRECT_VALUE")
862        geo = [("Type", "non-primitive"), ("MirroredX", bool(i % 2)), ("MirroredY", bool(i // 2)),
863               ("ViewBox", struct_("com.sun.star.awt.Rectangle", 0, 0, 21600, 21600)), ("TextRotateAngle", 15.0 * i),
864               ("Extrusion", ext), ("TextPath", tp), ("Path", path), ("Equations", uno.Any("[]string", ("x*2", "?0 -3", "$0 +1"))),
865               ("Handles", uno.Any("[][]com.sun.star.beans.PropertyValue", (h.value, hp.value))),
866               ("AdjustmentValues", uno.Any("[]" + ECS + "AdjustmentValue", (adj,)))]
867        step("custom-full-%d" % i, custom, doc, page, 500 + (i % 2) * 8000, 500 + (i // 2) * 7000, 7000, 6000, geo, text="fw%d" % i,
868             CustomShapeEngine="com.sun.star.drawing.EnhancedCustomShapeEngine", CustomShapeData="data%d" % i)
869    save(doc, "custom-full.odg")
870    done(doc)
871
872
873def fontwork():
874    doc = new("odg")
875    page = doc.DrawPages.getByIndex(0)
876    for i, name in enumerate(("fontwork-plain-text", "fontwork-wave", "fontwork-slant-up", "fontwork-arch-up-curve", "fontwork-circle-pour")):
877        geo = [("Type", name), ("TextPath", uno.Any("[]com.sun.star.beans.PropertyValue", (pv("TextPath", True), pv("TextPathMode", uno.Enum("com.sun.star.drawing.EnhancedCustomShapeTextPathMode", "SHAPE")),
878                                              pv("ScaleX", False), pv("SameLetterHeights", bool(i % 2))))),
879               ("Path", uno.Any("[]com.sun.star.beans.PropertyValue", (pv("TextPathAllowed", True), pv("ExtrusionAllowed", True)))),
880               ("Extrusion", uno.Any("[]com.sun.star.beans.PropertyValue", (pv("Extrusion", bool(i % 2)),)))]
881        c = step("fw-" + name, custom, doc, page, 500, 500 + i * 3200, 12000, 3000, geo, text="Fontwork " + name[9:])
882        if c is not None:
883            setp(c, TextAutoGrowHeight=False)
884    save(doc, "fontwork.odg")
885    done(doc)
886
887
888def cmds(**kw):
889    return uno.Any("[]com.sun.star.beans.PropertyValue", tuple(pv(k, v) for k, v in kw.items()))
890
891
892def special_shapes(doc, page, impress):
893    step("plugin", lambda: mk(doc, page, "PluginShape", x=500, y=500, w=4000, h=3000, PluginURL="file:///tmp/clip.mov",
894                              PluginMimeType="video/quicktime", PluginCommands=cmds(autoplay="true", loop="false")))
895    step("applet", lambda: mk(doc, page, "AppletShape", x=5000, y=500, w=4000, h=3000, AppletCodeBase="file:///tmp/",
896                              AppletName="myapplet", AppletCode="Demo.class", AppletIsScript=True,
897                              AppletCommands=cmds(speed="5", color="red")))
898    step("frame", lambda: mk(doc, page, "FrameShape", x=10000, y=500, w=4000, h=3000, FrameURL="http://example.org/",
899                             FrameName="myframe", FrameIsAutoScroll=True, FrameIsBorder=True, FrameMarginWidth=100, FrameMarginHeight=200))
900    step("frame2", lambda: mk(doc, page, "FrameShape", x=15000, y=500, w=4000, h=3000, FrameURL="file:///tmp/x.html", FrameIsAutoScroll=False,
901                              FrameIsBorder=False))
902
903    def media():
904        m = mk(doc, page, "MediaShape", x=500, y=4000, w=4000, h=3000)
905        setp(m, MediaURL=SOUND_URL, Loop=True, Mute=True, VolumeDB=-6,
906             Zoom=uno.Enum("com.sun.star.media.ZoomLevel", "FIT_TO_WINDOW"))
907        for i, z in enumerate(("ZOOM_1_TO_4", "ZOOM_1_TO_2", "ORIGINAL", "ZOOM_2_TO_1", "ZOOM_4_TO_1", "FIT_TO_WINDOW_FIXED_ASPECT", "FULLSCREEN")):
908            m2 = mk(doc, page, "MediaShape", x=5000 + i * 2000, y=4000, w=1800, h=1500)
909            setp(m2, MediaURL=SOUND_URL, Zoom=uno.Enum("com.sun.star.media.ZoomLevel", z))
910    step("media", media)
911    step("page-thumb", lambda: mk(doc, page, "PageShape", x=500, y=8000, w=4000, h=3000, PageNumber=1, Name="thumb"))
912    step("page-thumb-rot", lambda: mk(doc, page, "PageShape", x=5000, y=8000, w=4000, h=3000, PageNumber=1, RotateAngle=2000,
913                                      Title="thumb title", Description="thumb desc"))
914    for i, (clsid, nm) in enumerate((("078B7ABA-54FC-457F-8551-6147E776A997", "math"), ("12DCAE26-281F-416F-A234-C3086127382E", "chart"),
915                                     ("47BBB4CB-CE4C-4E80-A591-42D9AE74950F", "calc"))):
916        def ole(clsid=clsid, nm=nm, i=i):
917            o = doc.createInstance("com.sun.star.drawing.OLE2Shape")
918            page.add(o)
919            o.CLSID = clsid
920            o.Position = pt(500 + i * 5000, 12000)
921            o.Size = sz(4000, 3000)
922            o.Name = "ole-" + nm
923            o.Title = "ole"
924            o.Description = nm
925        step("ole-" + nm, ole)
926
927    def control():
928        form = doc.createInstance("com.sun.star.form.component.Form")
929        page.Forms.insertByIndex(0, form)
930        for i, svc in enumerate(("CommandButton", "TextField", "CheckBox")):
931            model = doc.createInstance("com.sun.star.form.component." + svc)
932            model.Name = "ctl%d" % i
933            form.insertByIndex(i, model)
934            cs = doc.createInstance("com.sun.star.drawing.ControlShape")
935            page.add(cs)
936            cs.Position = pt(500 + i * 4000, 16000)
937            cs.Size = sz(3000, 800)
938            cs.Control = model
939            setp(cs, Title="control", Description="ctl", Name="shape-ctl%d" % i, RotateAngle=0)
940    step("control", control)
941
942
943def special_docs():
944    for kind in ("odg", "odp"):
945        doc = new(kind)
946        page = doc.DrawPages.getByIndex(0)
947        step("special-" + kind, special_shapes, doc, page, kind == "odp")
948        save(doc, "special.%s" % kind)
949        done(doc)
950
951
952def masters_and_notes():
953    doc = new("odp")
954    mp = doc.MasterPages.getByIndex(0)
955    mp.Name = "MasterOne"
956    mk(doc, mp, "RectangleShape", x=500, y=500, text="on master")
957    mp2 = doc.MasterPages.insertNewByIndex(doc.MasterPages.Count)
958    mp2.Name = "MasterTwo"
959    mk(doc, mp2, "EllipseShape", x=500, y=500, text="master two")
960    pages = [doc.DrawPages.getByIndex(0), add_page(doc)]
961    pages[0].MasterPage = mp
962    pages[1].MasterPage = mp2
963    for i, p in enumerate(pages):
964        mk(doc, p, "RectangleShape", text="slide %d" % i)
965        np_ = p.NotesPage
966        mk(doc, np_, "RectangleShape", x=500, y=9000, text="note shape")
967        mk(doc, np_, "PageShape", x=500, y=500, w=4000, h=3000, PageNumber=i + 1)
968    try:
969        mk(doc, doc.getHandoutMasterPage(), "RectangleShape", x=500, y=500, text="handout")
970    except Exception as e:
971        print("  handout master", repr(e)[:100])
972    save(doc, "masters-notes.odp")
973    doc.storeToURL(uno.systemPathToFileUrl(os.path.join(out, "masters-notes.otp")), (pv("FilterName", "impress8_template"),))
974    done(doc)
975    doc = new("odg")
976    page = doc.DrawPages.getByIndex(0)
977    mp = doc.MasterPages.getByIndex(0)
978    mk(doc, mp, "RectangleShape", x=500, y=500, text="draw master")
979    page.MasterPage = mp
980    shapes_core(doc, page)
981    save(doc, "draw-master.odg")
982    doc.storeToURL(uno.systemPathToFileUrl(os.path.join(out, "draw-master.otg")), (pv("FilterName", "draw8_template"),))
983    done(doc)
984
985
986ANCHORS = ("AS_CHARACTER", "AT_CHARACTER", "AT_PARAGRAPH", "AT_PAGE", "AT_FRAME")
987
988
989def anchor_enum(n):
990    return uno.Enum("com.sun.star.text.TextContentAnchorType", n)
991
992
993def writer_shapes():
994    doc = new("odt")
995    text = doc.Text
996    cur = text.createTextCursor()
997    text.insertString(cur, "Anchoring test paragraph with some text to anchor to.", False)
998    text.insertControlCharacter(cur, uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK"), False)
999    frame = doc.createInstance("com.sun.star.text.TextFrame")
1000    frame.Size = sz(8000, 4000)
1001    frame.Name = "Host"
1002    frame.AnchorType = anchor_enum("AT_PARAGRAPH")
1003    text.insertTextContent(cur, frame, False)
1004    frame.Text.insertString(frame.Text.createTextCursor(), "frame text", False)
1005    services = ("RectangleShape", "EllipseShape", "LineShape", "PolyPolygonShape", "ClosedBezierShape", "CustomShape", "TextShape")
1006    for sv in services:
1007        for a in ANCHORS:
1008            def one(sv=sv, a=a):
1009                if sv in ("LineShape", "PolyPolygonShape"):
1010                    sh = doc.createInstance("com.sun.star.drawing." + sv)
1011                    sh.PolyPolygon = (pts((0, 0), (1500, 800)) if sv == "LineShape" else pts((0, 0), (1500, 0), (800, 1000)),)
1012                elif sv == "ClosedBezierShape":
1013                    sh = doc.createInstance("com.sun.star.drawing.ClosedBezierShape")
1014                    coords = struct_("com.sun.star.drawing.PolyPolygonBezierCoords")
1015                    coords.Coordinates = (pts((0, 0), (1000, 0), (1000, 1000)),)
1016                    N = uno.Enum("com.sun.star.drawing.PolygonFlags", "NORMAL")
1017                    coords.Flags = ((N, N, N),)
1018                    sh.PolyPolygonBezier = coords
1019                else:
1020                    sh = doc.createInstance("com.sun.star.drawing." + sv)
1021                    sh.Size = sz(1500, 1000)
1022                    if sv == "CustomShape":
1023                        sh.CustomShapeGeometry = (pv("Type", "diamond"),)
1024                sh.AnchorType = anchor_enum(a)
1025                if a == "AT_PAGE":
1026                    sh.AnchorPageNo = 1
1027                cursor = text.createTextCursor()
1028                cursor.gotoStart(False)
1029                if a == "AT_FRAME":
1030                    sh.AnchorFrame = frame
1031                    frame.Text.insertTextContent(frame.Text.createTextCursor(), sh, False)
1032                else:
1033                    text.insertTextContent(cursor, sh, False)
1034                setp(sh, Title="t " + sv, Description="d " + a)
1035            step("%s-%s" % (sv, a), one)
1036    def group():
1037        for a in ANCHORS[:4]:
1038            g = doc.createInstance("com.sun.star.drawing.GroupShape")
1039            doc.DrawPage.add(g)
1040            for k in range(2):
1041                r = doc.createInstance("com.sun.star.drawing.RectangleShape")
1042                r.Size = sz(800, 800)
1043                r.Position = pt(k * 900, 0)
1044                g.add(r)
1045            g.AnchorType = anchor_enum(a)
1046            if a == "AT_PAGE":
1047                g.AnchorPageNo = 1
1048    step("groups", group)
1049
1050    def controls():
1051        form = doc.createInstance("com.sun.star.form.component.Form")
1052        doc.DrawPage.Forms.insertByIndex(0, form)
1053        for i, a in enumerate(ANCHORS):
1054            model = doc.createInstance("com.sun.star.form.component.CommandButton")
1055            model.Name = "btn%d" % i
1056            form.insertByIndex(i, model)
1057            cs = doc.createInstance("com.sun.star.drawing.ControlShape")
1058            cs.Size = sz(2000, 700)
1059            cs.Control = model
1060            cs.AnchorType = anchor_enum(a)
1061            if a == "AT_PAGE":
1062                cs.AnchorPageNo = 1
1063            if a == "AT_FRAME":
1064                cs.AnchorFrame = frame
1065            cursor = text.createTextCursor()
1066            cursor.gotoStart(False)
1067            if a == "AT_FRAME":
1068                frame.Text.insertTextContent(frame.Text.createTextCursor(), cs, False)
1069            else:
1070                text.insertTextContent(cursor, cs, False)
1071    step("controls", controls)
1072    save(doc, "anchors.odt")
1073    done(doc)
1074
1075
1076def writer_group_frame():
1077    doc = new("odt")
1078    text = doc.Text
1079    cur = text.createTextCursor()
1080    text.insertString(cur, "Group anchored to a frame.", False)
1081    frame = doc.createInstance("com.sun.star.text.TextFrame")
1082    frame.Size = sz(8000, 4000)
1083    frame.AnchorType = anchor_enum("AT_PARAGRAPH")
1084    text.insertTextContent(cur, frame, False)
1085    g = doc.createInstance("com.sun.star.drawing.GroupShape")
1086    r1 = doc.createInstance("com.sun.star.drawing.RectangleShape")
1087    r1.Size = sz(800, 800)
1088    g.AnchorType = anchor_enum("AT_FRAME")
1089    g.AnchorFrame = frame
1090    frame.Text.insertTextContent(frame.Text.createTextCursor(), g, False)
1091    g.add(r1)
1092    save(doc, "anchors-group-frame.odt")
1093    done(doc)
1094
1095
1096def writer_graphics():
1097    doc = new("odt")
1098    text = doc.Text
1099    cur = text.createTextCursor()
1100    text.insertString(cur, "Graphics with contours, image maps and chained frames.", False)
1101    text.insertControlCharacter(cur, uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK"), False)
1102    for i, (name, contour) in enumerate((("single", (pts((0, 0), (100, 0), (100, 100), (0, 100)),)),
1103                                         ("multi", (pts((0, 0), (50, 0), (50, 50)), pts((60, 60), (100, 60), (100, 100)))),
1104                                         ("none", None))):
1105        def one(i=i, name=name, contour=contour):
1106            g = doc.createInstance("com.sun.star.text.TextGraphicObject")
1107            g.GraphicURL = PNG_URL
1108            g.AnchorType = anchor_enum(("AS_CHARACTER", "AT_PARAGRAPH", "AT_PAGE")[i])
1109            g.Width = 3000
1110            g.Height = 2000
1111            g.Name = "grf-" + name
1112            if contour is not None:
1113                setp(g, ContourPolyPolygon=contour, SurroundContour=True, ContourOutside=bool(i % 2), IsPixelContour=bool(i // 1 % 2))
1114            setp(g, GraphicFilter="png", Title="graphic " + name, Description="described", GraphicRotation=900 * i)
1115            cursor = text.createTextCursor()
1116            cursor.gotoEnd(False)
1117            text.insertTextContent(cursor, g, False)
1118        step("grf-" + name, one)
1119    def chain():
1120        f1 = doc.createInstance("com.sun.star.text.TextFrame")
1121        f2 = doc.createInstance("com.sun.star.text.TextFrame")
1122        for f, nm in ((f1, "Chain1"), (f2, "Chain2")):
1123            f.Name = nm
1124            f.Size = sz(4000, 1500)
1125            f.AnchorType = anchor_enum("AT_PARAGRAPH")
1126            cursor = text.createTextCursor()
1127            cursor.gotoEnd(False)
1128            text.insertTextContent(cursor, f, False)
1129        f1.ChainNextName = "Chain2"
1130        f1.Text.insertString(f1.Text.createTextCursor(), "first frame of a chain " * 20, False)
1131        f3 = doc.createInstance("com.sun.star.text.TextFrame")
1132        f3.Name = "MinMax"
1133        f3.Width = 3000
1134        setp(f3, WidthType=uno.getConstantByName("com.sun.star.text.SizeType.MIN"), HeightType=uno.getConstantByName("com.sun.star.text.SizeType.MIN"),
1135             Height=800)
1136        f3.AnchorType = anchor_enum("AT_CHARACTER")
1137        cursor = text.createTextCursor()
1138        cursor.gotoEnd(False)
1139        text.insertTextContent(cursor, f3, False)
1140    step("chain", chain)
1141    save(doc, "graphics.odt")
1142    done(doc)
1143
1144
1145def calc_shapes():
1146    doc = new("ods")
1147    sh = doc.Sheets.getByIndex(0)
1148    dp = sh.DrawPage
1149    for r in range(6):
1150        sh.getCellByPosition(0, r).String = "row %d" % r
1151    kinds = ("RectangleShape", "EllipseShape", "LineShape", "PolyPolygonShape", "PolyLineShape", "ClosedBezierShape", "CustomShape",
1152             "GraphicObjectShape", "TextShape", "ConnectorShape", "CaptionShape", "MeasureShape", "PageShape")
1153    for i, sv in enumerate(kinds):
1154        def one(i=i, sv=sv):
1155            o = doc.createInstance("com.sun.star.drawing." + sv)
1156            dp.add(o)
1157            if sv in ("LineShape", "PolyPolygonShape", "PolyLineShape"):
1158                o.PolyPolygon = (pts((0, 0), (1500, 0), (800, 1000)),)
1159            elif sv == "ClosedBezierShape":
1160                coords = struct_("com.sun.star.drawing.PolyPolygonBezierCoords")
1161                coords.Coordinates = (pts((0, 0), (1000, 0), (1000, 1000)),)
1162                N = uno.Enum("com.sun.star.drawing.PolygonFlags", "NORMAL")
1163                coords.Flags = ((N, N, N),)
1164                o.PolyPolygonBezier = coords
1165            else:
1166                o.Size = sz(2500, 1500)
1167            if sv == "CustomShape":
1168                o.CustomShapeGeometry = (pv("Type", "diamond"),)
1169            if sv == "GraphicObjectShape":
1170                o.GraphicURL = PNG_URL
1171            if sv not in ("LineShape", "PolyPolygonShape", "PolyLineShape", "ClosedBezierShape", "ConnectorShape", "MeasureShape"):
1172                o.Position = pt(500 + (i % 4) * 4000, 500 + (i // 4) * 3000)
1173            elif sv == "ConnectorShape":
1174                o.StartPosition = pt(500, 12000)
1175                o.EndPosition = pt(5000, 14000)
1176            elif sv == "MeasureShape":
1177                o.StartPosition = pt(6000, 12000)
1178                o.EndPosition = pt(10000, 12000)
1179            if i % 2 == 0:
1180                setp(o, Anchor=sh.getCellByPosition(1 + i % 3, i))
1181            if i % 3 == 1:
1182                setp(o, LayerName="background")
1183            setp(o, Title="calc " + sv, Description="d")
1184        step("calc-" + sv, one)
1185    g = doc.createInstance("com.sun.star.drawing.GroupShape")
1186    dp.add(g)
1187    for k in range(2):
1188        r = doc.createInstance("com.sun.star.drawing.RectangleShape")
1189        r.Size = sz(800, 800)
1190        r.Position = pt(k * 900 + 15000, 1000)
1191        g.add(r)
1192    sh2 = doc.Sheets.getByIndex(0)
1193    save(doc, "shapes.ods")
1194    done(doc)
1195
1196
1197def annotations_docs():
1198    for kind in ("odp", "odg"):
1199        doc = new(kind)
1200        for n in range(2):
1201            page = doc.DrawPages.getByIndex(0) if n == 0 else add_page(doc)
1202            mk(doc, page, "RectangleShape", text="annotated %d" % n)
1203            for k in range(2):
1204                ann = page.createAndInsertAnnotation()
1205                KEEP.append(ann)
1206                ann.Author = "Tester %d" % k
1207                ann.Position = struct_("com.sun.star.geometry.RealPoint2D", 3.0 + k, 4.0)
1208                ann.Size = struct_("com.sun.star.geometry.RealSize2D", 5.0, 6.0)
1209                ann.TextRange.String = "annotation %d.%d" % (n, k)
1210                ann.DateTime = struct_("com.sun.star.util.DateTime", 0, 12, 30, 10, 5, 6, 2025)
1211        save(doc, "annotations.%s" % kind)
1212        done(doc)
1213
1214
1215def fills():
1216    doc = new("odg")
1217    page = doc.DrawPages.getByIndex(0)
1218    GS = lambda n: uno.Enum("com.sun.star.awt.GradientStyle", n)
1219    for i, gs in enumerate(("LINEAR", "AXIAL", "RADIAL", "ELLIPTICAL", "SQUARE", "RECT")):
1220        g = struct_("com.sun.star.awt.Gradient", GS(gs), 0xFF0000, 0x0000FF, 450 * i, 10 * i, 40 + i, 60 - i, 100, 80, 5)
1221        t = struct_("com.sun.star.awt.Gradient", GS(gs), 0x202020, 0xE0E0E0, 300 * i, 5 * i, 50, 50, 100, 100, 0)
1222        mk(doc, page, "RectangleShape", x=500 + i * 3000, y=500, w=2800, h=2000, FillStyle=uno.Enum("com.sun.star.drawing.FillStyle", "GRADIENT"),
1223           FillGradient=g)
1224        tt = doc.createInstance("com.sun.star.drawing.TransparencyGradientTable")
1225        tt.insertByName("trans-" + gs, t)
1226        mk(doc, page, "RectangleShape", x=500 + i * 3000, y=3000, w=2800, h=2000, FillStyle=uno.Enum("com.sun.star.drawing.FillStyle", "SOLID"),
1227           FillColor=0x3366CC, FillTransparenceGradientName="trans-" + gs)
1228    for i, hs in enumerate(("SINGLE", "DOUBLE", "TRIPLE")):
1229        h = struct_("com.sun.star.drawing.Hatch", uno.Enum("com.sun.star.drawing.HatchStyle", hs), 0x00AA00, 120 + 20 * i, 450 * i)
1230        mk(doc, page, "RectangleShape", x=500 + i * 3000, y=5500, w=2800, h=2000, FillStyle=uno.Enum("com.sun.star.drawing.FillStyle", "HATCH"),
1231           FillHatch=h, FillBackground=bool(i % 2), FillColor=0xFFEECC)
1232    for i, ds in enumerate(("RECT", "ROUND", "RECTRELATIVE", "ROUNDRELATIVE")):
1233        d = struct_("com.sun.star.drawing.LineDash", uno.Enum("com.sun.star.drawing.DashStyle", ds), 2, 100, 3, 300, 150)
1234        mk(doc, page, "LineShape" if False else "RectangleShape", x=500 + i * 3000, y=8000, w=2800, h=1500,
1235           LineStyle=uno.Enum("com.sun.star.drawing.LineStyle", "DASH"), LineDash=d, LineWidth=100)
1236    save(doc, "fills.odg")
1237    done(doc)
1238
1239
1240top("pres-settings", presentation_settings)
1241top("header-footer", header_footer)
1242top("layouts", layouts)
1243top("transitions", transitions)
1244top("animations", animations)
1245top("actions", actions)
1246top("draw-links", draw_links)
1247top("image-maps", image_maps)
1248top("shapes", shapes_docs)
1249top("layers", layers_doc)
1250top("special", special_docs)
1251top("annotations", annotations_docs)
1252top("fills", fills)
1253top("masters-notes", masters_and_notes)
1254top("writer-shapes", writer_shapes)
1255top("writer-group-frame", writer_group_frame)
1256top("writer-graphics", writer_graphics)
1257top("calc-shapes", calc_shapes)
1258top("custom-presets", custom_shapes)
1259top("custom-full", custom_full)
1260top("fontwork", fontwork)
1261top("3d-odg", threed, "odg")
1262top("3d-odp", threed, "odp")
1263
1264print("crashed steps (retried):", crashes)
1265print("failed:", failed)
1266sys.stdout.flush()
1267os._exit(1 if failed else 0)
1268