]> git.lyx.org Git - lyx.git/blob - src/insets/ExternalTransforms.cpp
Change cursor shape for clickable math insets
[lyx.git] / src / insets / ExternalTransforms.cpp
1 /**
2  * \file ExternalTransforms.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "ExternalTransforms.h"
14
15 #include "support/convert.h"
16 #include "support/debug.h"
17 #include "support/lstrings.h"
18 #include "support/lyxlib.h" // float_equal
19 #include "support/Translator.h"
20
21 #include <cmath> // abs
22 #include <regex>
23 #include <sstream>
24
25 using namespace std;
26 using namespace lyx::support;
27
28 namespace lyx {
29 namespace external {
30
31
32 string const ExtraData::get(string const & id) const
33 {
34         map<string, string>::const_iterator it = data_.find(id);
35         return it == data_.end() ? string() : it->second;
36 }
37
38
39 void ExtraData::set(string const & id, string const & data)
40 {
41         data_[id] = data;
42 }
43
44
45 bool ResizeData::no_resize() const
46 {
47         return !usingScale() && width.zero() && height.zero();
48 }
49
50
51 bool ResizeData::usingScale() const
52 {
53         return (!scale.empty() && !float_equal(convert<double>(scale), 0.0, 0.05));
54 }
55
56
57 bool RotationData::no_rotation() const
58 {
59         return (angle.empty() || abs(convert<double>(angle)) < 0.1);
60 }
61
62
63 string const RotationData::adjAngle() const
64 {
65         // Ensure that angle lies in the range -360 < angle < 360
66         double rotAngle = convert<double>(angle);
67         if (abs(rotAngle) > 360.0) {
68                 rotAngle -= 360.0 * floor(rotAngle / 360.0);
69                 return convert<string>(rotAngle);
70         }
71         return angle;
72 }
73
74
75 namespace {
76
77 typedef Translator<RotationData::OriginType, string> OriginTranslator;
78 OriginTranslator const & originTranslator();
79
80 } // namespace
81
82
83 void RotationData::origin(string const & o)
84 {
85         origin_ = originTranslator().find(o);
86 }
87
88
89 string const RotationData::originString() const
90 {
91         return originTranslator().find(origin_);
92 }
93
94
95 string const ResizeLatexCommand::front_impl() const
96 {
97         if (data.no_resize())
98                 return string();
99
100         ostringstream os;
101         if (data.usingScale()) {
102                 double const scl = convert<double>(data.scale) / 100.0;
103                 os << "\\scalebox{" << scl << "}[" << scl << "]{";
104         } else {
105                 string width  = "!";
106                 string height = "!";
107                 if (data.keepAspectRatio) {
108                         if (data.width.inPixels(10) > data.height.inPixels(10))
109                                 width = data.width.asLatexString();
110                         else
111                                 height = data.height.asLatexString();
112                 } else {
113                         if (!data.width.zero())
114                                 width = data.width.asLatexString();
115                         if (!data.height.zero())
116                                 height = data.height.asLatexString();
117                 }
118
119                 os << "\\resizebox{"
120                    << width << "}{"
121                    << height << "}{";
122         }
123         return os.str();
124 }
125
126
127 string const ResizeLatexCommand::back_impl() const
128 {
129         if (data.no_resize())
130                 return string();
131
132         return "}";
133 }
134
135
136 namespace {
137
138 ostream & operator<<(ostream & os, RotationData::OriginType type)
139 {
140         switch (type) {
141         case RotationData::DEFAULT:
142         case RotationData::CENTER:
143                 break;
144         case RotationData::TOPLEFT:
145         case RotationData::TOPCENTER:
146         case RotationData::TOPRIGHT:
147                 os << 't';
148                 break;
149         case RotationData::BOTTOMLEFT:
150         case RotationData::BOTTOMCENTER:
151         case RotationData::BOTTOMRIGHT:
152                 os << 'b';
153                 break;
154         case RotationData::BASELINELEFT:
155         case RotationData::BASELINECENTER:
156         case RotationData::BASELINERIGHT:
157                 os << 'B';
158                 break;
159         }
160
161         switch (type) {
162         case RotationData::DEFAULT:
163                 break;
164         case RotationData::TOPLEFT:
165         case RotationData::BOTTOMLEFT:
166         case RotationData::BASELINELEFT:
167                 os << 'l';
168                 break;
169         case RotationData::CENTER:
170         case RotationData::TOPCENTER:
171         case RotationData::BOTTOMCENTER:
172         case RotationData::BASELINECENTER:
173                 os << 'c';
174                 break;
175         case RotationData::TOPRIGHT:
176         case RotationData::BOTTOMRIGHT:
177         case RotationData::BASELINERIGHT:
178                 os << 'r';
179                 break;
180         }
181
182         return os;
183 }
184
185 } // namespace
186
187
188 string const RotationLatexCommand::front_impl() const
189 {
190         if (data.no_rotation())
191                 return string();
192
193         ostringstream os;
194         os << "\\rotatebox";
195
196         if (data.origin() != RotationData::DEFAULT)
197                 os << "[origin=" << data.origin() << ']';
198
199         os << '{' << data.angle << "}{";
200         return os.str();
201 }
202
203
204 string const RotationLatexCommand::back_impl() const
205 {
206         if (data.no_rotation())
207                 return string();
208
209         return "}";
210 }
211
212
213 string const  ClipLatexOption::option_impl() const
214 {
215         if (!data.clip || data.bbox.empty())
216                 return string();
217
218         ostringstream os;
219         if (!data.bbox.empty())
220                 os << "bb=" << data.bbox.xl.asLatexString() << ' '
221                    << data.bbox.yb.asLatexString() << ' '
222                    << data.bbox.xr.asLatexString() << ' '
223                    << data.bbox.yt.asLatexString() << ',';
224         if (data.clip)
225                 os << "clip,";
226         return os.str();
227 }
228
229
230 string const ResizeLatexOption::option_impl() const
231 {
232         if (data.no_resize())
233                 return string();
234
235         ostringstream os;
236         if (data.usingScale()) {
237                 double const scl = convert<double>(data.scale);
238                 if (!float_equal(scl, 100.0, 0.05))
239                         os << "scale=" << scl / 100.0 << ',';
240                 return os.str();
241         }
242
243         if (!data.width.zero())
244                 os << "width=" << data.width.asLatexString() << ',';
245         if (!data.height.zero())
246                 os << "height=" << data.height.asLatexString() << ',';
247         if (data.keepAspectRatio)
248                 os << "keepaspectratio,";
249
250         return os.str();
251 }
252
253
254 string const RotationLatexOption ::option_impl() const
255 {
256         if (data.no_rotation())
257                 return string();
258
259         ostringstream os;
260         os << "angle=" << data.angle << ',';
261
262         if (data.origin() != RotationData::DEFAULT)
263                 os << "origin=" << data.origin() << ',';
264
265         return os.str();
266 }
267
268
269 string const sanitizeDocBookOption(string const & input)
270 {
271         return input;
272 }
273
274
275 string const sanitizeLatexOption(string const & input)
276 {
277         string::const_iterator begin = input.begin();
278         string::const_iterator end   = input.end();
279         string::const_iterator it = begin;
280
281         // Strip any leading commas
282         // "[,,,,foo..." -> "foo..." ("foo..." may be empty)
283         string output;
284         smatch what;
285         static regex const front("^( *\\[,*)(.*)$");
286
287         if (!regex_match(it, end, what, front)) {
288                 lyxerr << "Unable to sanitize LaTeX \"Option\": "
289                        << input << '\n';
290                 return string();
291         }
292         it =  what[1].second;
293
294         // Replace any consecutive commas with a single one
295         // "foo,,,,bar" -> "foo,bar"
296         // with iterator now pointing to 'b'
297         static regex const commas("([^,]*)(,,*)(.*)$");
298         for (; it != end;) {
299                 if (!regex_match(it, end, what, commas)) {
300                         output += string(it, end);
301                         break;
302                 }
303                 output += what.str(1) + ",";
304                 it = what[3].first;
305         }
306
307         // Strip any trailing commas
308         // "...foo,,,]" -> "...foo" ("...foo,,," may be empty)
309         static regex const back("^(.*[^,])?,*\\] *$");
310         if (!regex_match(output, what, back)) {
311                 lyxerr << "Unable to sanitize LaTeX \"Option\": "
312                        << output << '\n';
313                 return string();
314         }
315         output = what.str(1);
316
317         // Remove any surrounding whitespace
318         output = trim(output);
319
320         // If the thing is empty, leave it so, else wrap it in square brackets.
321         return output.empty() ? output : "[" + output + "]";
322 }
323
324
325 namespace {
326
327 template <typename Factory, typename Data, typename Transformer>
328 void extractIt(any const & any_factory,
329                Data const & data, Transformer & transformer)
330 {
331         if (any_factory.type() != typeid(Factory))
332                 return;
333
334         Factory factory = any_cast<Factory>(any_factory);
335         if (factory)
336                 transformer = factory(data);
337 }
338
339 } // namespace
340
341
342 TransformCommand::ptr_type
343 TransformStore::getCommandTransformer(RotationData const & data) const
344 {
345         TransformCommand::ptr_type ptr;
346         if (id == Rotate)
347                 extractIt<RotationCommandFactory>(any_factory, data, ptr);
348         return ptr;
349 }
350
351
352 TransformCommand::ptr_type
353 TransformStore::getCommandTransformer(ResizeData const & data) const
354 {
355         TransformCommand::ptr_type ptr;
356         if (id == Resize)
357                 extractIt<ResizeCommandFactory>(any_factory, data, ptr);
358         return ptr;
359 }
360
361
362 TransformOption::ptr_type
363 TransformStore::getOptionTransformer(RotationData const & data) const
364 {
365         TransformOption::ptr_type ptr;
366         if (id == Rotate)
367                 extractIt<RotationOptionFactory>(any_factory, data, ptr);
368         return ptr;
369 }
370
371
372 TransformOption::ptr_type
373 TransformStore::getOptionTransformer(ResizeData const & data) const
374 {
375         TransformOption::ptr_type ptr;
376         if (id == Resize)
377                 extractIt<ResizeOptionFactory>(any_factory, data, ptr);
378         return ptr;
379 }
380
381
382 TransformOption::ptr_type
383 TransformStore::getOptionTransformer(ClipData const & data) const
384 {
385         TransformOption::ptr_type ptr;
386         if (id == Clip)
387                 extractIt<ClipOptionFactory>(any_factory, data, ptr);
388         return ptr;
389 }
390
391
392
393 TransformOption::ptr_type
394 TransformStore::getOptionTransformer(string const & data) const
395 {
396         TransformOption::ptr_type ptr;
397         if (id == Extra)
398                 extractIt<ExtraOptionFactory>(any_factory, data, ptr);
399         return ptr;
400 }
401
402
403 namespace {
404
405 OriginTranslator const initOriginTranslator()
406 {
407         OriginTranslator translator(RotationData::DEFAULT, "default");
408         translator.addPair(RotationData::TOPLEFT,        "topleft");
409         translator.addPair(RotationData::BOTTOMLEFT,     "bottomleft");
410         translator.addPair(RotationData::BASELINELEFT,   "baselineleft");
411         translator.addPair(RotationData::CENTER,         "center");
412         translator.addPair(RotationData::TOPCENTER,      "topcenter");
413         translator.addPair(RotationData::BOTTOMCENTER,   "bottomcenter");
414         translator.addPair(RotationData::BASELINECENTER, "baselinecenter");
415         translator.addPair(RotationData::TOPRIGHT,       "topright");
416         translator.addPair(RotationData::BOTTOMRIGHT,    "bottomright");
417         translator.addPair(RotationData::BASELINERIGHT,  "baselineright");
418         return translator;
419 }
420
421
422 OriginTranslator const & originTranslator()
423 {
424         static OriginTranslator const translator = initOriginTranslator();
425         return translator;
426 }
427
428 } // namespace
429
430 } // namespace external
431 } // namespace lyx