2 -- return a table of names associated with decorator symbols
3 function decorator_names(model)
4 local sheets = model.doc:sheets()
5 local symbols = sheets:allNames("symbol")
7 for _, name in pairs(symbols) do
8 if name:find("deco/") == 1 then
15 -- Changes the transformation matrix of a path object to the identitiy
16 -- matrix without canging the appearance of the object (i.e., the
17 -- transformation matrix is applied to the objects shape)
18 function cleanup_matrix(path_obj)
19 local matrix = path_obj:matrix()
20 local matrix_func = function (point) return matrix end
21 local shape = path_obj:shape()
22 transform_shape(shape, matrix_func)
23 path_obj:setShape(shape)
24 path_obj:setMatrix(ipe.Matrix())
27 -- Transform a shape by transforming every point using a matrix
28 -- returend by matrix_func. The function matrix_func should take a
29 -- point and return a matrix (that is then used to transform this
32 -- Arcs are also transformed.
33 function transform_shape(shape, matrix_func)
34 for _,path in pairs(shape) do
35 for _,subpath in ipairs(path) do
36 -- apply to every point
37 for i,point in ipairs(subpath) do
38 subpath[i] = matrix_func(point) * point
42 if (subpath["type"] == "arc") then
43 local arc = subpath["arc"]
44 local center = arc:matrix():translation()
45 subpath["arc"] = matrix_func(center) * arc
51 -- Resizes a given shape such that the same transformation also
52 -- transforms bbox_source to bbox_target. The transformation is done
53 -- by translating points of the shape such that all points in the same
54 -- quadrant with respect to center are translated in the same way.
56 -- Clearly, the center has to lie inside bbox_source.
57 function resize_shape(shape, center, bbox_source, bbox_target)
58 -- assert that the center lies inside bbox_source
59 assert(bbox_source:left() < center.x)
60 assert(center.x < bbox_source:left() + bbox_source:width())
61 assert(bbox_source:bottom() < center.y)
62 assert(center.y < bbox_source:bottom() + bbox_source:height())
64 -- translation of points to the left/right/top/bottom of the center
65 local dx_left = bbox_target:left() - bbox_source:left()
66 local dy_bottom = bbox_target:bottom() - bbox_source:bottom()
67 local dx_right = bbox_target:width() - bbox_source:width()
68 local dy_top = bbox_target:height() - bbox_source:height()
71 local matrix_func = function (point)
74 if (center.x < point.x) then dx = dx + dx_right end
75 if (center.y < point.y) then dy = dy + dy_top end
76 return ipe.Translation(dx, dy)
78 transform_shape(shape, matrix_func)
81 function bbox(obj, page)
82 local objno = #page + 1
83 page:insert(objno, obj, nil, page:layers()[1])
84 local bbox = page:bbox(objno)
89 function report_problem(model, text)
90 ipeui.messageBox(mainWindow(model), "warning", text, nil, nil)
93 function run_fancy_decorator (model)
94 local p = model:page()
95 local prim = p:primarySelection()
97 report_problem(model, "You must select somethings.")
100 local bbox_target = p:bbox(prim)
102 local deco_obj_group = ask_for_decorator(model)
103 if (not deco_obj_group) then return end
104 if (deco_obj_group:type() ~= "group") then
105 report_problem(model, "The decoration must be a group.")
109 local objects = deco_obj_group:elements()
110 local last_obj = table.remove(objects, #objects)
111 local bbox_source = bbox(last_obj, p)
112 local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
113 bbox_source:bottom() + 0.5 * bbox_source:height())
115 if (#objects == 0) then
116 report_problem(model, "The decoration must be a group of at least two elements.")
119 for i,deco_obj in ipairs(objects) do
120 if (deco_obj:type() ~= "path") then
121 report_problem(model, "Each decoration object needs to be a path.")
125 cleanup_matrix(deco_obj)
126 local deco_shape = deco_obj:shape()
128 resize_shape(deco_shape, center, bbox_source, bbox_target)
130 deco_obj:setShape(deco_shape)
133 local group = ipe.Group(objects)
135 model:creation("fancy decoration created", group)
138 -- Asks the user for a decorator and returns the chosen decorator
140 function ask_for_decorator(model)
141 local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
142 local decorators = decorator_names(model)
143 dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
144 dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
145 dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
146 local r = dialog:execute()
147 if not r then return end
148 local deco_name = decorators[dialog:get("deco")]
149 local symbol = model.doc:sheets():find("symbol", deco_name)
150 return symbol:clone()
153 function mainWindow(model)
154 if model.ui.win == nil then
157 return model.ui:win()
163 { label = "Fancy decorator", run=run_fancy_decorator},