xref: /trunk/test/aoo-test/src/aootest_worker/ops/generate.py (revision 85d6e4e58e50c1ed01601e2737a66d9715456246)
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"""Builders for documents the tests start from: each creates one or more documents through the API and stores them. Ported from the
21ODF harness's fixtures/*/make.py; the expected values read back from them are in data/checks/."""
22import os
23import re
24import zipfile
25
26import uno
27from com.sun.star.awt import Gradient, Point, Size
28from com.sun.star.awt.GradientStyle import LINEAR
29from com.sun.star.drawing import Hatch
30from com.sun.star.drawing.BitmapMode import NO_REPEAT, REPEAT, STRETCH
31from com.sun.star.drawing.FillStyle import BITMAP, GRADIENT, HATCH, SOLID
32from com.sun.star.drawing.HatchStyle import SINGLE
33
34from aootest_worker.ops import core, scripts
35from aootest_worker.ops.core import pv
36from aootest_worker.ops.encrypted import _png
37
38
39def _grad():
40    g = Gradient()
41    g.Style = LINEAR
42    g.StartColor, g.EndColor = 0xFF0000, 0x0000FF
43    g.Angle = g.Border = 0
44    g.XOffset = g.YOffset = 50
45    g.StartIntensity = g.EndIntensity = 100
46    g.StepCount = 0
47    return g
48
49
50def _new(kind):
51    doc = core._desktop().loadComponentFromURL("private:factory/" + kind, "_blank", 0, (pv("Hidden", True),))
52    if doc is None:
53        raise core.Fail("uno", "could not create a new %s document" % kind)
54    return doc
55
56
57def _save(doc, outdir, name, flt):
58    try:
59        doc.storeToURL(uno.systemPathToFileUrl(os.path.join(outdir, name)), (pv("FilterName", flt),))
60    finally:
61        core._close(doc)
62    return name
63
64
65def page_fill(outdir):
66    """Writer page styles whose page, header and footer carry each kind of fill (fixtures/page-fill/make.py)."""
67    png = os.path.join(outdir, "tile.png")
68    _png(png)
69    png_url = uno.systemPathToFileUrl(png)
70    doc = _new("swriter")
71
72    def hatch():
73        h = Hatch()
74        h.Style, h.Color, h.Distance, h.Angle = SINGLE, 0x00AA00, 200, 450
75        return h
76
77    def fill(o, pre, kind, color=0xFFCC00):
78        if kind == "solid":
79            setattr(o, pre + "FillStyle", SOLID)
80            setattr(o, pre + "FillColor", color)
81        elif kind == "solid_transp":
82            setattr(o, pre + "FillStyle", SOLID)
83            setattr(o, pre + "FillColor", color)
84            setattr(o, pre + "FillTransparence", 40)
85        elif kind == "gradient":
86            setattr(o, pre + "FillStyle", GRADIENT)
87            setattr(o, pre + "FillGradient", _grad())
88        elif kind == "hatch":
89            setattr(o, pre + "FillStyle", HATCH)
90            setattr(o, pre + "FillHatch", hatch())
91            setattr(o, pre + "FillColor", 0xFFFFCC)
92            setattr(o, pre + "FillBackground", True)
93        elif kind == "bitmap":
94            setattr(o, pre + "FillStyle", BITMAP)
95            setattr(o, pre + "FillBitmapURL", png_url)
96
97    ps = doc.StyleFamilies.getByName("PageStyles")
98    specs = [("P_solid", "solid", "solid", "solid"), ("P_transp", "solid_transp", "solid_transp", "solid_transp"),
99             ("P_grad", "gradient", "gradient", "hatch"), ("P_bitmap", "bitmap", "bitmap", "bitmap")]
100    for name, pg, hd, ft in specs:
101        s = doc.createInstance("com.sun.star.style.PageStyle")
102        ps.insertByName(name, s)
103        s.HeaderIsOn = True
104        s.FooterIsOn = True
105        fill(s, "", pg, 0x66CCFF)
106        fill(s, "Header", hd, 0xCCFF66)
107        fill(s, "Footer", ft, 0xFF66CC)
108    text = doc.Text
109    cur = text.createTextCursor()
110    pb = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
111    for name, *_ in specs:
112        text.insertString(cur, "page " + name, False)
113        text.insertControlCharacter(cur, pb, False)
114    en = text.createEnumeration()
115    for name, *_ in specs:
116        en.nextElement().PageDescName = name
117    return [_save(doc, outdir, "page-fill.odt", "writer8")]
118
119
120def fill_others(outdir):
121    """Other consumers of fills: Writer frame/table/section, Calc cell/page/shape, Draw shape/page, Impress shape/slide."""
122    def shape(doc, page, x, y, kind, text, fillkind):
123        s = doc.createInstance(kind)
124        page.add(s)
125        s.Position = Point(x, y)
126        s.Size = Size(4000, 2000)
127        s.String = text
128        if fillkind == "solid":
129            s.FillStyle = SOLID
130            s.FillColor = 0x33AA55
131        else:
132            s.FillStyle = GRADIENT
133            s.FillGradient = _grad()
134        return s
135
136    done = []
137    doc = _new("swriter")
138    text = doc.Text
139    cur = text.createTextCursor()
140    for fk in ("solid", "gradient"):
141        fr = doc.createInstance("com.sun.star.text.TextFrame")
142        fr.Width, fr.Height = 4000, 1500
143        text.insertTextContent(cur, fr, False)
144        fr.Text.String = "frame " + fk
145        if fk == "solid":
146            fr.FillStyle = SOLID
147            fr.FillColor = 0x33AA55
148        else:
149            fr.FillStyle = GRADIENT
150            fr.FillGradient = _grad()
151    tbl = doc.createInstance("com.sun.star.text.TextTable")
152    tbl.initialize(2, 2)
153    text.insertTextContent(text.End, tbl, False)
154    tbl.getCellByName("A1").BackColor = 0xFFCC00
155    tbl.getCellByName("B1").BackColor = 0xFFAACC
156    tbl.BackColor = 0xCCFFCC
157    sec = doc.createInstance("com.sun.star.text.TextSection")
158    text.insertTextContent(text.End, sec, False)
159    sec.BackColor = 0xCCCCFF
160    done.append(_save(doc, outdir, "others-writer.odt", "writer8"))
161
162    doc = _new("scalc")
163    sh = doc.Sheets.getByIndex(0)
164    sh.getCellByPosition(0, 0).String = "cell"
165    sh.getCellByPosition(0, 0).CellBackColor = 0xFFCC00
166    sh.getCellByPosition(1, 0).String = "grad"
167    doc.StyleFamilies.getByName("PageStyles").getByName("Default").BackColor = 0xDDEEFF
168    shape(doc, sh.DrawPage, 1000, 3000, "com.sun.star.drawing.RectangleShape", "calc shape", "gradient")
169    done.append(_save(doc, outdir, "others-calc.ods", "calc8"))
170
171    doc = _new("sdraw")
172    page = doc.DrawPages.getByIndex(0)
173    shape(doc, page, 1000, 1000, "com.sun.star.drawing.RectangleShape", "draw solid", "solid")
174    shape(doc, page, 6000, 1000, "com.sun.star.drawing.EllipseShape", "draw grad", "gradient")
175    try:
176        page.Background = None
177        bg = doc.createInstance("com.sun.star.drawing.Background")
178        bg.FillStyle = GRADIENT
179        bg.FillGradient = _grad()
180        page.Background = bg
181    except Exception:
182        pass
183    done.append(_save(doc, outdir, "others-draw.odg", "draw8"))
184
185    doc = _new("simpress")
186    page = doc.DrawPages.getByIndex(0)
187    shape(doc, page, 1000, 1000, "com.sun.star.drawing.RectangleShape", "impress solid", "solid")
188    shape(doc, page, 6000, 1000, "com.sun.star.drawing.RectangleShape", "impress grad", "gradient")
189    try:
190        bg = doc.createInstance("com.sun.star.drawing.Background")
191        bg.FillStyle = SOLID
192        bg.FillColor = 0xCCEEFF
193        page.Background = bg
194    except Exception:
195        pass
196    done.append(_save(doc, outdir, "others-impress.odp", "impress8"))
197    return done
198
199
200def t13(outdir):
201    """The features ODF 1.3 changes: drop-down fields, annotation initials, Calc tab colour, Calc page scale-to, chart inner
202    plot position and legend size (fixtures/t13/make.py). Everything is fixed: no dates from the clock, nothing random."""
203    def dt(y, mo, d, h, mi):
204        return uno.createUnoStruct("com.sun.star.util.DateTime", 0, 0, mi, h, d, mo, y)
205
206    def ensure_sheets(doc, n):
207        sheets = doc.Sheets
208        while sheets.Count > n:
209            sheets.removeByName(sheets.getByIndex(sheets.Count - 1).Name)
210        while sheets.Count < n:
211            sheets.insertNewByName("Sheet%d" % (sheets.Count + 1), sheets.Count)
212        return sheets
213
214    def put_field(doc, cur, kind, **props):
215        f = doc.createInstance("com.sun.star.text.TextField." + kind)
216        for k, v in props.items():
217            setattr(f, k, v)
218        doc.Text.insertTextContent(cur, f, False)
219
220    done = []
221    doc = _new("swriter")
222    cur = doc.Text.createTextCursor()
223    doc.Text.insertString(cur, "First: ", False)
224    put_field(doc, cur, "DropDown", Name="dd1", Items=("one", "two", "three"), SelectedItem="two", Help="help one", Tooltip="tip one")
225    doc.Text.insertControlCharacter(cur, 0, False)
226    doc.Text.insertString(cur, "Second: ", False)
227    # SwDropDownField accepts only a listed SelectedItem: "zzz" reads back as "" (nothing selected, the field shows "a")
228    put_field(doc, cur, "DropDown", Name="dd2", Items=("a", "b"), SelectedItem="zzz", Help="help two", Tooltip="tip two")
229    done.append(_save(doc, outdir, "dropdown.odt", "writer8"))
230
231    doc = _new("swriter")
232    cur = doc.Text.createTextCursor()
233    doc.Text.insertString(cur, "with initials ", False)
234    put_field(doc, cur, "Annotation", Author="Ann Author", Initials="AB", Content="first note", DateTimeValue=dt(2024, 7, 8, 9, 10))
235    doc.Text.insertControlCharacter(cur, 0, False)
236    doc.Text.insertString(cur, "without initials ", False)
237    put_field(doc, cur, "Annotation", Author="Bob Author", Content="second note", DateTimeValue=dt(2024, 7, 9, 9, 10))
238    done.append(_save(doc, outdir, "annot.odt", "writer8"))
239
240    doc = _new("scalc")
241    sheets = ensure_sheets(doc, 3)
242    for i, color in ((0, 0xFF0000), (2, 0x3366CC)):
243        sheets.getByIndex(i).TabColor = color
244    for i in range(3):
245        sheets.getByIndex(i).getCellByPosition(0, 0).setString("sheet %d" % i)
246    done.append(_save(doc, outdir, "tabcolor.ods", "calc8"))
247
248    doc = _new("scalc")
249    ps = doc.StyleFamilies.getByName("PageStyles")
250    sheets = ensure_sheets(doc, 4)
251    # setting ScaleToPagesX/Y clears ScaleToPages and PageScale in the style (sc/source/ui/unoobj/styleuno.cxx)
252    for name, props in (("PS_xy", {"ScaleToPagesX": 2, "ScaleToPagesY": 3}), ("PS_x", {"ScaleToPagesX": 2}),
253                        ("PS_pages", {"ScaleToPages": 4}), ("PS_scale", {"PageScale": 80})):
254        ps.insertByName(name, doc.createInstance("com.sun.star.style.PageStyle"))
255        st = ps.getByName(name)
256        for k, v in props.items():
257            setattr(st, k, v)
258    for i, name in enumerate(("PS_xy", "PS_x", "PS_pages", "PS_scale")):
259        sh = sheets.getByIndex(i)
260        sh.PageStyle = name
261        sh.getCellByPosition(0, 0).setString(name)
262    done.append(_save(doc, outdir, "scaleto.ods", "calc8"))
263
264    doc = _new("scalc")
265    sh = doc.Sheets.getByIndex(0)
266    for r in range(3):
267        sh.getCellByPosition(0, r).setString("c%d" % r)
268        sh.getCellByPosition(1, r).setValue(r + 1)
269    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle", 1000, 1000, 12000, 9000)
270    sh.Charts.addNewByName("C1", rect, (sh.getCellRangeByName("A1:B3").RangeAddress,), True, True)
271    ch = sh.Charts.getByName("C1").EmbeddedObject
272    ch.HasLegend = True
273    dg = ch.Diagram
274    # inner plot position in 1/100 mm; the legend is positioned and given a custom size
275    dg.setDiagramPositionExcludingAxes(uno.createUnoStruct("com.sun.star.awt.Rectangle", 1500, 1200, 6000, 4500))
276    lg = ch.Legend
277    lg.Expansion = uno.Enum("com.sun.star.chart.ChartLegendExpansion", "CUSTOM")
278    lg.setSize(uno.createUnoStruct("com.sun.star.awt.Size", 2500, 1800))
279    lg.setPosition(uno.createUnoStruct("com.sun.star.awt.Point", 8800, 1500))
280    done.append(_save(doc, outdir, "chart.ods", "calc8"))
281    return done
282
283
284def page_fill_13(outdir):
285    """Page styles with each kind of fill, built in Writer (aoo-src.odt), and a Calc page style with a background image and
286    colour (calc-src.ods) with a copy whose master page names a usable drawing page style (fixtures/page-fill-1_3)."""
287    png = os.path.join(outdir, "tile.png")
288    _png(png)
289    png_url = uno.systemPathToFileUrl(png)
290    kinds = "solid,solid_transp,gradient,gradient_transp,gradient_tgrad,hatch,hatch_nobg,bitmap_tile,bitmap_stretch,bitmap_once,none,shared".split(",")
291    doc = _new("swriter")
292
293    def gr(c1, c2, b):
294        g = Gradient()
295        g.Style, g.StartColor, g.EndColor = LINEAR, c1, c2
296        g.Angle, g.Border, g.XOffset, g.YOffset = 0, b, 50, 50
297        g.StartIntensity = g.EndIntensity = 100
298        g.StepCount = 0
299        return g
300
301    def fill(o, pre, kind):
302        def sp(n, v):
303            o.setPropertyValue(pre + n, v)
304        if kind == "solid":
305            sp("FillStyle", SOLID)
306            sp("FillColor", 0x33AA55)
307        elif kind == "solid_transp":
308            sp("FillStyle", SOLID)
309            sp("FillColor", 0x3355AA)
310            sp("FillTransparence", 40)
311        elif kind in ("gradient", "shared"):
312            sp("FillStyle", GRADIENT)
313            sp("FillGradient", gr(0xFF0000, 0x0000FF, 10))
314        elif kind == "gradient_transp":
315            sp("FillStyle", GRADIENT)
316            sp("FillGradient", gr(0xFF0000, 0x0000FF, 10))
317            sp("FillTransparence", 30)
318        elif kind == "gradient_tgrad":
319            sp("FillStyle", GRADIENT)
320            sp("FillGradient", gr(0x00FF00, 0xFF00FF, 0))
321            tg = doc.createInstance("com.sun.star.drawing.TransparencyGradientTable")
322            if not tg.hasByName("TG1"):
323                tg.insertByName("TG1", gr(0x000000, 0xFFFFFF, 0))
324            sp("FillTransparenceGradientName", "TG1")
325        elif kind in ("hatch", "hatch_nobg"):
326            h = Hatch()
327            h.Style, h.Color, h.Distance, h.Angle = SINGLE, 0x00AA00, 200, 450
328            sp("FillStyle", HATCH)
329            sp("FillHatch", h)
330            if kind == "hatch":
331                sp("FillColor", 0xFFFFCC)
332                sp("FillBackground", True)
333            else:
334                sp("FillBackground", False)
335        elif kind.startswith("bitmap"):
336            sp("FillStyle", BITMAP)
337            sp("FillBitmapURL", png_url)
338            sp("FillBitmapMode", {"bitmap_tile": REPEAT, "bitmap_stretch": STRETCH, "bitmap_once": NO_REPEAT}[kind])
339
340    ps = doc.StyleFamilies.getByName("PageStyles")
341    for k in kinds:
342        ps.insertByName("P_" + k, doc.createInstance("com.sun.star.style.PageStyle"))
343        if k != "none":
344            fill(ps.getByName("P_" + k), "", k)
345    ps.getByName("P_shared").FollowStyle = "P_shared"
346    ps.insertByName("P_shared2", doc.createInstance("com.sun.star.style.PageStyle"))
347    fill(ps.getByName("P_shared2"), "", "shared")
348    s = ps.getByName("P_gradient")
349    s.HeaderIsOn = True
350    fill(s, "Header", "hatch")
351    fill(ps.getByName("Standard"), "", "solid")
352    fill(ps.getByName("First Page"), "", "hatch_nobg")
353    text = doc.Text
354    cur = text.createTextCursor()
355    pb = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
356    names = ["First Page"] + ["P_" + k for k in kinds] + ["P_shared2"]
357    for i, n in enumerate(names):
358        text.insertString(cur, "page " + n, False)
359        if i < len(names) - 1:
360            text.insertControlCharacter(cur, pb, False)
361    en = text.createEnumeration()
362    for n in names:
363        en.nextElement().PageDescName = n
364    done = [_save(doc, outdir, "aoo-src.odt", "writer8")]
365
366    doc = _new("scalc")
367    ps = doc.StyleFamilies.getByName("PageStyles")
368    ps.insertByName("P_image", doc.createInstance("com.sun.star.style.PageStyle"))
369    s = ps.getByName("P_image")
370    s.BackColor = 0x66CCFF
371    s.BackGraphicURL = png_url
372    s.BackGraphicLocation = uno.Enum("com.sun.star.style.GraphicLocation", "TILED")
373    sheet = doc.Sheets.getByIndex(0)
374    sheet.PageStyle = "P_image"
375    sheet.getCellByPosition(0, 0).setString("page P_image")
376    done.append(_save(doc, outdir, "calc-src.ods", "calc8"))
377
378    src = os.path.join(outdir, "calc-src.ods")
379    with zipfile.ZipFile(src) as zi, zipfile.ZipFile(os.path.join(outdir, "calc-drawing-page.ods"), "w") as zo:
380        for it in zi.infolist():
381            data = zi.read(it.filename)
382            if it.filename == "styles.xml":
383                t = data.decode("utf-8")
384                m = re.search(r'<style:master-page style:name="P_5f_image"[^>]*?(/?)>', t)
385                if not (m and t.count("</office:automatic-styles>") == 1):
386                    raise core.Fail("uno", "calc-src.ods has an unexpected styles.xml: no master page P_5f_image")
387                t = t.replace(m.group(0), m.group(0)[:-len(m.group(1)) - 1] + ' draw:style-name="Mdp9"' + m.group(1) + ">", 1)
388                t = t.replace("</office:automatic-styles>", '<style:style style:name="Mdp9" style:family="drawing-page"><style:drawing-page-properties '
389                              'draw:fill="solid" draw:fill-color="#ff0000"/></style:style></office:automatic-styles>', 1)
390                data = t.encode("utf-8")
391            zo.writestr(it, data, compress_type=zipfile.ZIP_STORED if it.filename == "mimetype" else zipfile.ZIP_DEFLATED)
392    done.append("calc-drawing-page.ods")
393    return done
394
395
396def row_split(outdir):
397    doc = _new("swriter")
398    tbl = doc.createInstance("com.sun.star.text.TextTable")
399    tbl.initialize(3, 2)
400    doc.Text.insertTextContent(doc.Text.End, tbl, False)
401    rows = tbl.Rows
402    rows.getByIndex(0).IsSplitAllowed = True
403    rows.getByIndex(1).IsSplitAllowed = False
404    rows.getByIndex(2).IsSplitAllowed = True
405    rows.getByIndex(2).Height = 1000
406    return [_save(doc, outdir, "row-split.odt", "writer8")]
407
408
409def grid_snap(outdir):
410    doc = _new("swriter")
411    ps = doc.StyleFamilies.getByName("PageStyles")
412    specs = [("G_snap_on", True, 2), ("G_snap_off", False, 2), ("G_lines_off", False, 1)]
413    for name, snap, mode in specs:
414        s = doc.createInstance("com.sun.star.style.PageStyle")
415        ps.insertByName(name, s)
416        s.GridMode = mode
417        s.StandardPageMode = True
418        s.GridSnapToChars = snap
419    text = doc.Text
420    cur = text.createTextCursor()
421    pb = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
422    for name, *_ in specs:
423        text.insertString(cur, "grid " + name, False)
424        text.insertControlCharacter(cur, pb, False)
425    en = text.createEnumeration()
426    for name, *_ in specs:
427        en.nextElement().PageDescName = name
428    out = os.path.join(outdir, "grid-snap.odt")
429    _save(doc, outdir, "grid-snap.odt", "writer8")
430    # older builds did not write standard-mode next to base-width and the importer does not infer it, so add it where it is missing
431    tmp = out + ".tmp"
432    tag = re.compile(rb"<[^<>]*style:layout-grid-base-width=[^<>]*>")
433    with zipfile.ZipFile(out) as zi, zipfile.ZipFile(tmp, "w") as zo:
434        for it in zi.infolist():
435            data = zi.read(it.filename)
436            if it.filename == "styles.xml":
437                data = tag.sub(lambda m: m.group(0) if b"style:layout-grid-standard-mode=" in m.group(0)
438                               else m.group(0).replace(b"style:layout-grid-base-width=", b'style:layout-grid-standard-mode="true" style:layout-grid-base-width=', 1), data)
439            zo.writestr(it, data, compress_type=it.compress_type)
440    os.replace(tmp, out)
441    return ["grid-snap.odt"]
442
443
444def ole_text(outdir):
445    """A Draw page with an OLE object without text and one with text."""
446    doc = _new("sdraw")
447    page = doc.DrawPages.getByIndex(0)
448    for i, text in enumerate(("", "OLE label")):
449        s = doc.createInstance("com.sun.star.drawing.OLE2Shape")
450        s.CLSID = "078B7ABA-54FC-457F-8551-6147e776a997"
451        page.add(s)
452        s.Position = Point(2000 + i * 8000, 2000)
453        s.Size = Size(5000, 3000)
454        if text:
455            s.String = text
456    return [_save(doc, outdir, "ole-text.odg", "draw8")]
457
458
459def ole_text_edge(outdir):
460    """OLE shapes whose text is: none, a label, one space, a line break only, a date field only."""
461    doc = _new("sdraw")
462    page = doc.DrawPages.getByIndex(0)
463    for i, text in enumerate(("", "OLE label", " ", "\n", None)):
464        s = doc.createInstance("com.sun.star.drawing.OLE2Shape")
465        s.CLSID = "078B7ABA-54FC-457F-8551-6147e776a997"
466        page.add(s)
467        s.Position = Point(1000 + i * 5000, 2000)
468        s.Size = Size(4000, 3000)
469        if text is None:
470            f = doc.createInstance("com.sun.star.text.TextField.DateTime")
471            f.IsFixed = True
472            f.IsDate = True
473            s.insertTextContent(s.createTextCursor(), f, False)
474        elif text:
475            s.String = text
476    return [_save(doc, outdir, "ole-text-edge.odg", "draw8")]
477
478
479def chart_shapes(outdir):
480    """A Calc chart with additional shapes (polyline, rectangle, text) in the chart draw page."""
481    from com.sun.star.awt import Rectangle
482    from com.sun.star.table import CellRangeAddress
483    doc = _new("scalc")
484    sh = doc.Sheets.getByIndex(0)
485    for r, (a, b) in enumerate(((1, 2), (2, 4), (3, 3))):
486        sh.getCellByPosition(0, r).Value = a
487        sh.getCellByPosition(1, r).Value = b
488    rng = CellRangeAddress()
489    rng.Sheet, rng.StartColumn, rng.StartRow, rng.EndColumn, rng.EndRow = 0, 0, 0, 1, 2
490    r = Rectangle()
491    r.X, r.Y, r.Width, r.Height = 1000, 4000, 10000, 7000
492    sh.Charts.addNewByName("c1", r, (rng,), False, False)
493    ch = sh.Charts.getByName("c1").EmbeddedObject
494    page = ch.DrawPage
495
496    def mk(kind, x, y, w, h):
497        s = ch.createInstance(kind)
498        page.add(s)
499        s.Position = Point(x, y)
500        s.Size = Size(w, h)
501        return s
502    pl = mk("com.sun.star.drawing.PolyLineShape", 500, 500, 3000, 1500)
503    pl.PolyPolygon = ((Point(0, 1500), Point(1000, 500), Point(3000, 0)),)
504    mk("com.sun.star.drawing.RectangleShape", 4000, 500, 2000, 1000).String = "rect"
505    mk("com.sun.star.drawing.TextShape", 6500, 500, 2000, 800).String = "note"
506    return [_save(doc, outdir, "chart-shapes.ods", "calc8")]
507
508
509def normal_values_5b(outdir):
510    """Two charts, one with a graphic as the series symbol, and a hidden column."""
511    from com.sun.star.awt import Rectangle
512    png = os.path.join(outdir, "symbol.png")
513    _png(png)
514    doc = _new("scalc")
515    sh = doc.Sheets.getByIndex(0)
516    for r, row in enumerate([["", "a", "b"], ["x", 1, 2], ["y", 3, 5], ["z", 4, 8]]):
517        for c, v in enumerate(row):
518            cell = sh.getCellByPosition(c, r)
519            if isinstance(v, str):
520                cell.String = v
521            else:
522                cell.Value = v
523    rng = sh.getCellRangeByName("A1:C4").getRangeAddress()
524    r = Rectangle()
525    r.X, r.Y, r.Width, r.Height = 8000, 1000, 9000, 7000
526    sh.Charts.addNewByName("c1", r, (rng,), True, True)
527    m = sh.Charts.getByName("c1").EmbeddedObject
528    gp = core._create("com.sun.star.graphic.GraphicProvider")
529    g = gp.queryGraphic((pv("URL", uno.systemPathToFileUrl(png)),))
530    s = m.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0].getDataSeries()[0]
531    sym = s.Symbol
532    sym.Style = uno.Enum("com.sun.star.chart2.SymbolStyle", "GRAPHIC")
533    sym.Graphic = g
534    s.Symbol = sym
535    sh.getColumns().getByIndex(2).IsVisible = False
536    r2 = Rectangle()
537    r2.X, r2.Y, r2.Width, r2.Height = 8000, 9000, 9000, 7000
538    sh.Charts.addNewByName("c2", r2, (sh.getCellRangeByName("A1:C4").getRangeAddress(),), True, True)
539    return [_save(doc, outdir, "normal5b.ods", "calc8")]
540
541
542def connector_viewbox(outdir):
543    """Connectors of every kind, connected and free, axis-aligned and diagonal, in Draw and Impress."""
544    from com.sun.star.drawing.ConnectorType import CURVE, LINE, LINES, STANDARD
545
546    def build(doc, page):
547        def rect(x, y, w, h):
548            s = doc.createInstance("com.sun.star.drawing.RectangleShape")
549            page.add(s)
550            s.Position = Point(x, y)
551            s.Size = Size(w, h)
552            return s
553
554        def conn(kind, a, b, name):
555            c = doc.createInstance("com.sun.star.drawing.ConnectorShape")
556            page.add(c)
557            c.EdgeKind = kind
558            c.StartPosition = Point(*a)
559            c.EndPosition = Point(*b)
560            c.Name = name
561            return c
562        r1, r2 = rect(1000, 12000, 3000, 2000), rect(12000, 16000, 3000, 2000)
563        r3, r4 = rect(1000, 20000, 3000, 2000), rect(9000, 24000, 3000, 2000)
564        conn(STANDARD, (2000, 2000), (8000, 6000), "std_free")
565        conn(CURVE, (10000, 2000), (16000, 6000), "curve_free")
566        conn(LINE, (2000, 8000), (8000, 8000), "line_h")
567        conn(LINE, (10000, 8000), (10000, 11000), "line_v")
568        conn(LINE, (18000, 9000), (14000, 5000), "line_rev")
569        conn(STANDARD, (2000, 28000), (8000, 28000), "std_h")
570        conn(STANDARD, (16000, 28000), (16000, 32000), "std_v")
571        c = conn(STANDARD, (0, 0), (0, 0), "std_conn")
572        c.StartShape, c.EndShape = r1, r2
573        c = conn(CURVE, (0, 0), (0, 0), "curve_conn")
574        c.StartShape, c.EndShape = r3, r4
575        conn(LINES, (2000, 34000), (9000, 38000), "lines_free")
576
577    done = []
578    for kind, name, flt in (("sdraw", "conn.odg", "draw8"), ("simpress", "conn.odp", "impress8")):
579        doc = _new(kind)
580        build(doc, doc.DrawPages.getByIndex(0))
581        done.append(_save(doc, outdir, name, flt))
582    return done
583
584
585def para_fill(outdir):
586    """Paragraphs and paragraph styles with each kind of fill, direct and through a style (fixtures/para-fill/make.py)."""
587    png = os.path.join(outdir, "tile.png")
588    _png(png)
589    png_url = uno.systemPathToFileUrl(png)
590    doc = _new("swriter")
591    text = doc.Text
592
593    def hatch():
594        h = Hatch()
595        h.Style, h.Color, h.Distance, h.Angle = SINGLE, 0x00AA00, 200, 450
596        return h
597
598    def fill(o, kind):
599        if kind == "solid":
600            o.ParaBackColor = 0xFFCC00
601            o.ParaBackTransparent = False
602        elif kind == "solid_fill":
603            o.FillStyle = SOLID
604            o.FillColor = 0x33AA55
605        elif kind == "solid_transp":
606            o.FillStyle = SOLID
607            o.FillColor = 0x3355AA
608            o.FillTransparence = 40
609        elif kind == "gradient":
610            o.FillStyle = GRADIENT
611            o.FillGradient = _grad()
612        elif kind == "hatch":
613            o.FillStyle = HATCH
614            o.FillHatch = hatch()
615            o.FillColor = 0xFFFFCC
616            o.FillBackground = True
617        elif kind == "bitmap":
618            o.FillStyle = BITMAP
619            o.FillBitmapURL = png_url
620
621    kinds = ["solid", "solid_fill", "solid_transp", "gradient", "hatch", "bitmap"]
622    styles = doc.StyleFamilies.getByName("ParagraphStyles")
623    for k in kinds:
624        s = doc.createInstance("com.sun.star.style.ParagraphStyle")
625        s.ParentStyle = "Standard"
626        styles.insertByName("S_" + k, s)
627        fill(s, k)
628    pb = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
629    cur = text.createTextCursor()
630    labels = []
631    for k in kinds:
632        for kind in ("direct", "styled"):
633            text.insertString(cur, kind + " " + k, False)
634            text.insertControlCharacter(cur, pb, False)
635            labels.append((kind, k))
636    paras = doc.Text.createEnumeration()
637    for kind, k in labels:
638        p = paras.nextElement()
639        if kind == "direct":
640            fill(p, k)
641        else:
642            p.ParaStyleName = "S_" + k
643    return [_save(doc, outdir, "para-fill.odt", "writer8")]
644
645
646def alien_attrs(outdir):
647    """Preserved alien attributes in ODF namespaces and in a foreign namespace on the carriers that keep them: Writer paragraph,
648    character, section, table and table cell, a Draw shape, a Calc cell."""
649    ns = {"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0", "style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
650          "draw": "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "table": "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
651          "office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0", "svg": "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
652          "fo": "http://www.w3.org/1999/XSL/Format", "ex": "urn:example:foreign"}
653    attrs = (("text", "enable-numbering"), ("style", "alien-style"), ("draw", "alien-draw"), ("table", "alien-table"),
654             ("office", "alien-office"), ("svg", "alien-svg"), ("fo", "alien-fo"), ("ex", "alien-foreign"))
655
656    def put(container):
657        for p, n in attrs:
658            a = uno.createUnoStruct("com.sun.star.xml.AttributeData")
659            a.Namespace, a.Type, a.Value = ns[p], "CDATA", "v-" + p
660            container.insertByName(p + ":" + n, a)
661
662    done = []
663    doc = _new("swriter")
664    text = doc.Text
665    cur = text.createTextCursor()
666    text.insertString(cur, "para text", False)
667    text.insertControlCharacter(cur, 0, False)
668    cur.gotoStart(False)
669    cur.goRight(4, True)
670    cont = cur.ParaUserDefinedAttributes
671    put(cont)
672    cur.ParaUserDefinedAttributes = cont
673    cont = cur.TextUserDefinedAttributes
674    put(cont)
675    cur.TextUserDefinedAttributes = cont
676    cur.gotoEnd(False)
677    tbl = doc.createInstance("com.sun.star.text.TextTable")
678    tbl.initialize(2, 2)
679    text.insertTextContent(cur, tbl, False)
680    for tgt in (tbl, tbl.getCellByName("A1")):
681        cont = tgt.UserDefinedAttributes
682        put(cont)
683        tgt.UserDefinedAttributes = cont
684    sec = doc.createInstance("com.sun.star.text.TextSection")
685    text.insertTextContent(text.End, sec, False)
686    cont = sec.UserDefinedAttributes
687    put(cont)
688    sec.UserDefinedAttributes = cont
689    done.append(_save(doc, outdir, "alien-writer.odt", "writer8"))
690
691    doc = _new("sdraw")
692    page = doc.DrawPages.getByIndex(0)
693    s = doc.createInstance("com.sun.star.drawing.RectangleShape")
694    page.add(s)
695    s.Position = Point(2000, 2000)
696    s.Size = Size(4000, 2000)
697    s.String = "alien"
698    cont = s.UserDefinedAttributes
699    put(cont)
700    s.UserDefinedAttributes = cont
701    done.append(_save(doc, outdir, "alien-draw.odg", "draw8"))
702
703    doc = _new("scalc")
704    c = doc.Sheets.getByIndex(0).getCellByPosition(0, 0)
705    c.Value = 1
706    cont = c.UserDefinedAttributes
707    put(cont)
708    c.UserDefinedAttributes = cont
709    done.append(_save(doc, outdir, "alien-calc.ods", "calc8"))
710    return done
711
712
713def para_fill_foreign(outdir):
714    """The paragraphs and paragraph styles another producer writes with fill properties, built with AOO's own (aoo-form.odt)."""
715    png = os.path.join(outdir, "tile.png")
716    _png(png)
717    png_url = uno.systemPathToFileUrl(png)
718    doc = _new("swriter")
719
720    def grad(c1, c2):
721        g = Gradient()
722        g.Style, g.StartColor, g.EndColor = LINEAR, c1, c2
723        g.Angle, g.Border, g.XOffset, g.YOffset = 300, 10, 50, 50
724        g.StartIntensity = g.EndIntensity = 100
725        g.StepCount = 0
726        return g
727
728    def hatch():
729        h = Hatch()
730        h.Style, h.Color, h.Distance, h.Angle = SINGLE, 0xFFAA00, 200, 450
731        return h
732
733    def fill(o, kind):
734        if kind == "solid":
735            o.FillStyle = SOLID
736            o.FillColor = 0x33AA55
737        elif kind == "solid_transp":
738            o.FillStyle = SOLID
739            o.FillColor = 0x3355AA
740            o.FillTransparence = 40
741        elif kind == "gradient":
742            o.FillStyle = GRADIENT
743            o.FillGradient = grad(0xFF0000, 0x0000FF)
744        elif kind == "gradient_transp":
745            o.FillStyle = GRADIENT
746            o.FillGradient = grad(0xFF0000, 0x0000FF)
747            o.FillTransparence = 30
748        elif kind == "hatch":
749            o.FillStyle = HATCH
750            o.FillHatch = hatch()
751            o.FillColor = 0xFFFFCC
752            o.FillBackground = True
753        elif kind == "hatch_nobg":
754            o.FillStyle = HATCH
755            o.FillHatch = hatch()
756            o.FillBackground = False
757        elif kind.startswith("bitmap"):
758            o.FillStyle = BITMAP
759            o.FillBitmapURL = png_url
760            o.FillBitmapMode = {"bitmap_stretch": STRETCH, "bitmap_once": NO_REPEAT}.get(kind, REPEAT)
761            if kind == "bitmap_transp":
762                o.FillTransparence = 50
763
764    kinds = ["solid", "solid_transp", "gradient", "gradient_transp", "hatch", "hatch_nobg", "bitmap_tile", "bitmap_stretch", "bitmap_once",
765             "bitmap_transp"]
766    styles = doc.StyleFamilies.getByName("ParagraphStyles")
767    for k in kinds:
768        s = doc.createInstance("com.sun.star.style.ParagraphStyle")
769        s.ParentStyle = "Standard"
770        styles.insertByName("S_" + k, s)
771        fill(s, k)
772    pb = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
773    cur = doc.Text.createTextCursor()
774    for k in kinds:
775        for kind in ("direct", "styled"):
776            doc.Text.insertString(cur, kind + " " + k, False)
777            doc.Text.insertControlCharacter(cur, pb, False)
778    paras = doc.Text.createEnumeration()
779    for k in kinds:
780        fill(paras.nextElement(), k)
781        paras.nextElement().ParaStyleName = "S_" + k
782    return [_save(doc, outdir, "aoo-form.odt", "writer8")]
783
784
785def boolean_writer(outdir):
786    """Writer table cells and set-expression fields with a boolean number format, holding 12345, 1 and 0."""
787    from com.sun.star.lang import Locale
788    from com.sun.star.util.NumberFormat import LOGICAL
789    doc = _new("swriter")
790    key = doc.NumberFormats.getStandardFormat(LOGICAL, Locale("en", "US", ""))
791    tbl = doc.createInstance("com.sun.star.text.TextTable")
792    tbl.initialize(1, 3)
793    doc.Text.insertTextContent(doc.Text.End, tbl, False)
794    for i, v in enumerate((12345, 1, 0)):
795        c = tbl.getCellByPosition(i, 0)
796        c.NumberFormat = key
797        c.setValue(v)
798    cur = doc.Text.createTextCursor()
799    cur.gotoEnd(False)
800    for i, v in enumerate((12345.0, 1.0, 0.0)):
801        f = doc.createInstance("com.sun.star.text.TextField.SetExpression")
802        mst = doc.createInstance("com.sun.star.text.FieldMaster.SetExpression")
803        mst.Name = "bv%d" % i
804        mst.SubType = 0
805        f.Content = ""
806        f.NumberFormat = key
807        f.SubType = 0
808        f.IsShowFormula = False
809        f.attachTextFieldMaster(mst)
810        doc.Text.insertTextContent(cur, f, False)
811        f.Value = v
812        doc.Text.insertString(cur, " ", False)
813    return [_save(doc, outdir, "boolean-writer.odt", "writer8")]
814
815
816def normal_values(outdir):
817    """A Writer table with a merged cell, a page break, a footnote with references to it, and two frames anchored to a page."""
818    from com.sun.star.style.BreakType import PAGE_BEFORE
819    from com.sun.star.text.TextContentAnchorType import AT_PAGE
820    doc = _new("swriter")
821    text = doc.Text
822    tbl = doc.createInstance("com.sun.star.text.TextTable")
823    tbl.initialize(2, 3)
824    text.insertTextContent(text.End, tbl, False)
825    c = tbl.createCursorByCellName("A1")
826    c.goRight(1, True)
827    c.mergeRange()
828    cur = text.createTextCursor()
829    cur.gotoEnd(False)
830    text.insertString(cur, "\nbody\n", False)
831    cur.gotoEnd(False)
832    cur.BreakType = PAGE_BEFORE
833    text.insertString(cur, "page two\n", False)
834    fn = doc.createInstance("com.sun.star.text.Footnote")
835    fn.Label = ""
836    cur.gotoEnd(False)
837    text.insertTextContent(cur, fn, False)
838    fn.String = "note text"
839    text.insertString(cur, " ", False)
840    for part in (0, 3, 2):
841        f = doc.createInstance("com.sun.star.text.TextField.GetReference")
842        f.ReferenceFieldSource = 3
843        f.SourceName = ""
844        f.SequenceNumber = fn.ReferenceId
845        f.ReferenceFieldPart = part
846        text.insertTextContent(cur, f, False)
847        text.insertString(cur, " ", False)
848    for name, page in (("frame_p2", 2), ("frame_nopage", 0)):
849        fr = doc.createInstance("com.sun.star.text.TextFrame")
850        fr.AnchorType = AT_PAGE
851        if page:
852            fr.AnchorPageNo = page
853        fr.Width = 3000
854        fr.Height = 1000
855        text.insertTextContent(cur, fr, False)
856        fr.setName(name)
857        fr.Text.String = name
858    return [_save(doc, outdir, "normal-values.odt", "writer8")]
859
860
861def note_ref(outdir):
862    """A footnote and an endnote, each with reference fields for every part (page, direction, text, the three sequence-only ones)."""
863    doc = _new("swriter")
864    text = doc.Text
865    cur = text.createTextCursor()
866    text.insertString(cur, "body", False)
867    fn = doc.createInstance("com.sun.star.text.Footnote")
868    text.insertTextContent(cur, fn, False)
869    fn.String = "note text"
870    en = doc.createInstance("com.sun.star.text.Endnote")
871    text.insertTextContent(cur, en, False)
872    en.String = "end text"
873    text.insertString(cur, " ", False)
874    for src, note in ((3, fn), (4, en)):
875        for part in (0, 3, 2, 5, 6, 7):
876            f = doc.createInstance("com.sun.star.text.TextField.GetReference")
877            f.ReferenceFieldSource = src
878            f.SourceName = ""
879            f.SequenceNumber = note.ReferenceId
880            f.ReferenceFieldPart = part
881            text.insertTextContent(cur, f, False)
882            text.insertString(cur, " ", False)
883    return [_save(doc, outdir, "note-ref.odt", "writer8")]
884
885
886BUILDERS = {"page-fill": page_fill, "fill-others": fill_others, "t13": t13, "page-fill-1_3": page_fill_13, "row-split": row_split,
887            "grid-snap": grid_snap, "ole-text": ole_text, "ole-text-edge": ole_text_edge, "chart-shapes": chart_shapes,
888            "normal-values5b": normal_values_5b, "connector-viewbox": connector_viewbox, "para-fill": para_fill,
889            "alien-attrs": alien_attrs, "para-fill-foreign": para_fill_foreign, "boolean-writer": boolean_writer,
890            "normal-values": normal_values, "note-ref": note_ref}
891
892# The large builders are the old harness's scripts, run as they are (see ops/scripts.py). In a fresh office the draw script stalls at
893# its `annotations` step (twice in a row); that step alone and the script without it both finish, so they are two builders.
894_DRAW_STEPS = ("pres-settings header-footer layouts transitions animations actions draw-links image-maps shapes layers special fills "
895               "masters-notes writer-shapes writer-group-frame writer-graphics calc-shapes custom-presets custom-full fontwork 3d-odg "
896               "3d-odp").split()
897SCRIPTS = {"writer": ("writer.py", ()), "draw": ("draw.py", _DRAW_STEPS), "draw-annotations": ("draw.py", ("annotations",)),
898           "calc": ("calc.py", ()), "styles-gfx": ("styles_gfx.py", ()), "chart": ("chart.py", ()), "styles-text": ("styles_text.py", ()),
899           "leak": ("leak.py", ()), "base": ("base.py", ()), "p8-drawings": ("make_drawings.py", ()), "doctypes": ("doctypes.py", ())}
900BUILDERS.update({name: (lambda outdir, script=script, args=args: scripts.run_script(script, outdir, args))
901                 for name, (script, args) in SCRIPTS.items()})
902
903
904def generate(builder, outdir):
905    """Runs the named builder; the documents it makes are stored in `outdir`. Returns {"files": [names]}."""
906    if builder not in BUILDERS:
907        raise core.Fail("bad_args", "unknown builder %r" % builder)
908    os.makedirs(outdir, exist_ok=True)
909    return {"files": BUILDERS[builder](os.path.abspath(outdir))}
910
911
912OPS = {"generate": core.guarded(generate)}
913