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()
96 local bbox_target = p:bbox(prim)
98 local deco_obj_group = ask_for_decorator(model)
99 if (deco_obj_group:type() ~= "group") then
100 report_problem(model, "The decoration must be a group.")
104 local objects = deco_obj_group:elements()
105 local last_obj = table.remove(objects, #objects)
106 local bbox_source = bbox(last_obj, p)
107 local center = ipe.Vector(bbox_source:left() + 0.5 * bbox_source:width(),
108 bbox_source:bottom() + 0.5 * bbox_source:height())
110 for i,deco_obj in ipairs(objects) do
111 if (deco_obj:type() ~= "path") then
112 report_problem(model, "Each decoration object needs to be a path.")
116 cleanup_matrix(deco_obj)
117 local deco_shape = deco_obj:shape()
119 resize_shape(deco_shape, center, bbox_source, bbox_target)
121 deco_obj:setShape(deco_shape)
124 local group = ipe.Group(objects)
126 model:creation("fancy decoration created", group)
129 -- Asks the user for a decorator and returns the chosen decorator
131 function ask_for_decorator(model)
132 local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
133 local decorators = decorator_names(model)
134 dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
135 dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
136 dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
137 local r = dialog:execute()
138 if not r then return end
139 local deco_name = decorators[dialog:get("deco")]
140 local symbol = model.doc:sheets():find("symbol", deco_name)
141 return symbol:clone()
144 -- Decorate something given by its bounding box with a given deco
145 -- object, which needs to be a path.
146 function decorate(model, bbox, deco)
147 if (deco:type() ~= "path") then
148 report_problem(model, "The decoration needs to be a path.")
152 local shape = deco:shape()
153 local m = deco:matrix()
154 for _,path in pairs(shape) do
155 for _,subpath in ipairs(path) do
157 for i,point in ipairs(subpath) do
158 subpath[i] = translation(bbox, m*point) * m*point
161 -- for acs, the center must be translated separately
162 if (subpath["type"] == "arc") then
163 local arc = subpath["arc"]
164 local arc_pos = arc:matrix():translation()
165 subpath["arc"] = translation(bbox, m*arc_pos) * m * arc
171 deco:setMatrix(ipe.Matrix())
172 model:creation("create", deco)
175 -- The translation matrix that should be applied to a given point when
176 -- doing the decoration.
177 function translation(bbox, point)
180 if (point.x > 0) then
181 dx = dx + bbox:width()
183 if (point.y > 0) then
184 dy = dy + bbox:height()
186 dx = dx + bbox:left()
187 dy = dy + bbox:bottom()
188 return ipe.Translation(dx, dy)
191 function mainWindow(model)
192 if model.ui.win == nil then
195 return model.ui:win()
199 function run_decorator(model)
200 -- get bbox of primary selection
201 local p = model:page()
202 local prim = p:primarySelection()
204 model.ui:explain("An object must be selected.")
207 local bbox = p:bbox(prim)
209 -- create decorator object
210 local dialog = ipeui.Dialog(mainWindow(model), "Select a decorator.")
211 local decorators = decorator_names(model)
212 dialog:add("deco", "combo", decorators, 1, 1, 1, 2)
213 dialog:add("ok", "button", { label="&Ok", action="accept" }, 2, 2)
214 dialog:add("cancel", "button", { label="&Cancel", action="reject" }, 2, 1)
215 local r = dialog:execute()
216 if not r then return end
217 local deco_name = decorators[dialog:get("deco")]
218 local symbol = model.doc:sheets():find("symbol", deco_name)
219 local deco = symbol:clone()
221 -- run the decoration
222 decorate(model, bbox, deco)
227 { label = "Decorate", run=run_decorator},
228 { label = "Fancy decorator", run=run_fancy_decorator},