Asger Kunuk Alstrup wrote: > From what I see, you have managed to keep the changes to the external > definition file pretty simple. I'd like to read your documentation, > but I do not have LyX working. Can you export to ASCII and send it > privately?
Attached. The document describes the ideas behind the language and the rationale of the separation of Data from Transform. It does not (yet) describe how it all glues together. Instead I attach a small sample code illustrating how I think it should be done. Angus
#include <boost/any.hpp> #include <boost/function.hpp> #include <iostream> #include <memory> #include <sstream> #include <string> #include <vector> using std::string; // ExternalTransforms.h struct RotationData { RotationData(int a = 0) : angle(a) {} int angle; }; struct ResizeData { ResizeData(double w = 0) : width(w) {} double width; }; struct TransformCommand { typedef std::auto_ptr<TransformCommand const> ptr_type; virtual ~TransformCommand(){} virtual string const front() const = 0; virtual string const back() const = 0; }; struct RotateLatexCommand : public TransformCommand { static ptr_type factory(RotationData const & data); private: RotateLatexCommand(RotationData const & data_) : data(data_) {} virtual string const front() const; virtual string const back() const { return "}"; } RotationData data; }; struct ResizeLatexCommand : public TransformCommand { static ptr_type factory(ResizeData const & data); private: ResizeLatexCommand(ResizeData const & data_) : data(data_) {} virtual string const front() const; virtual string const back() const { return "}"; } ResizeData data; }; struct TransformStore { TransformStore() {} /** Stores \c factory and a reminder of what \c data this \c factory * operates on. */ template <typename Data, typename Factory> TransformStore(Data const & data, Factory const & factory) : any_factory(boost::any(factory)), any_data(boost::any(data)) {} /** Extracts the transformer from the stored factory * iff the types are those of the stored data. */ template<typename Factory, typename Data, typename Transformer> void getTransformer(Data const & data, Transformer & transformer) const { if (any_factory.type() != typeid(Factory) || any_data.type() != typeid(Data)) return; Factory factory = boost::any_cast<Factory>(any_factory); if (!factory.empty()) transformer = factory(data); } private: boost::any any_factory; boost::any any_data; }; typedef boost::function1<TransformCommand::ptr_type, RotationData> RotationCommandFactory; typedef boost::function1<TransformCommand::ptr_type, ResizeData> ResizeCommandFactory; // Hidden away inside ExternalTemplates.C void build_command_transformers(std::vector<TransformStore> & store) { RotationCommandFactory rotation_factory = RotateLatexCommand::factory; ResizeCommandFactory resize_factory = ResizeLatexCommand::factory; store.push_back(TransformStore(RotationData(), rotation_factory)); store.push_back(TransformStore(ResizeData(), resize_factory)); } TransformCommand::ptr_type getCommandTransformer(TransformStore const & store, RotationData const & rotationdata, ResizeData const & resizedata) { TransformCommand::ptr_type ptr; store.getTransformer<RotationCommandFactory>(rotationdata, ptr); if (!ptr.get()) store.getTransformer<ResizeCommandFactory>(resizedata, ptr); return ptr; } // This loop is all that needs go in insetexternal.C int main() { RotationData rotationdata(30); ResizeData resizedata(2); // Transform the data using command-style transformers. string command = "\\input{foo}"; std::vector<TransformStore> command_transformers; build_command_transformers(command_transformers); typedef std::vector<TransformStore> Transformers; Transformers::const_iterator cit = command_transformers.begin(); Transformers::const_iterator cend = command_transformers.end(); for (; cit != cend; ++cit) { TransformCommand::ptr_type ptr = getCommandTransformer(*cit, rotationdata, resizedata); if (!ptr.get()) continue; std::ostringstream os; os << ptr->front() << command << ptr->back(); command = os.str(); } std::cout << command << std::endl; } TransformCommand::ptr_type RotateLatexCommand::factory(RotationData const & data) { return ptr_type(new RotateLatexCommand(data)); } TransformCommand::ptr_type ResizeLatexCommand::factory(ResizeData const & data) { return ptr_type(new ResizeLatexCommand(data)); } string const RotateLatexCommand::front() const { std::ostringstream os; os << "\\rotatebox{" << data.angle << "}{"; return os.str(); } string const ResizeLatexCommand::front() const { std::ostringstream os; os << "\\resizebox{" << data.width << "cm}{!}{"; return os.str(); }
Transforming InsetExternal If we are to merge InsetGraphics into InsetExternal, some way must be found to handle the complex transformations that InsetGraphics supports. Moreover, it would be nice to give the user the ability to manipulate an XFig InsetExternal in the same sort of way. InsetGraphics uses the the graphicx LaTeX package, passing transformation data to the \insetgraphics command as options. Here are the supported options (obviously, not all of them are output at the same time): [draft, bb=23 45 321 345,clip, scale=0.5,width=2cm,height=5cm,keepaspectratio, angle=45,origin=B, ...] where ... are any "special" options not supported directly by the GUI. Transformation of an XFig InsetExternal would use the LaTeX commands \rotatebox and \resizebox, something like: \rotatebox{45}{\resizebox{2cm}{3cm}{\include{my_figure.pstex_t}}} Generally speaking, the LyX way is to use insets. Indeed, it is clear that InsetRotatebox and InsetResizebox would be entirely conventional insets, the data they wrap knowing nothing about their presence. Unfortunately, the optional arguments that are passed to \insetgraphics do not fit this model well. I suspect that it would be possible to make these hypothetical transformation insets "intelligent", but instead I have chosen to use a different approach in which InsetExternal is fully aware of the transformations. 1 Specifying the external template language The various possibilities are too complex to be handled entirely by the external template language, not least because some mechanism must be found to adjust the parameters to these commands and options. Given that some support is needed within LyX itself, what modification is needed to the external template language? It is probably easiest to describe with the aid of a couple of examples. Here is the proposed definition of the XFig external template suitably edited to show the changes, highlighted in red. Template XFig GuiName "XFig: $$Basename" InputFormat fig AutomaticProduction true Transform Rotate Transform Resize Format LaTeX TransformCommand Rotate RotationLatexCommand TransformCommand Resize ResizeLatexCommand Product "$$RotateFront$$ResizeFront \\input{$$Basename.pstex_t}$$ResizeBack$$RotateBack" UpdateFormat pstex UpdateResult "$$Basename.pstex_t" Requirement "graphicx" FormatEnd The lines "Transform Rotate" and "Transform Resize" specify that the Xfig template can handle two transforms named Rotate and Resize. In general, these transforms will require LyX to store data (for example the angle of rotation) and to provide the user with some means of modifying this data. However, the output of the data is dependent upon the output format (LaTeX, LinuxDoc, DocBook etc.) and so the specification of how the data is output belongs in the appropriate output Format specification. The line "TransformCommand Rotate RotationLatexCommand" specifies that the transform named Rotate will generate LaTeX output by invoking class RotationLatexCommand. It is important to note that the name Rotate matches that of one of the declared Transforms, above. In this particular case, we wish to output a LaTeX command, defined for these purposes as having a front block and a back block. The beginning block of the Rotate command is named implicitly as $$RotateFront. The ending block is also named implicitly as $$RotateEnd. The details of the contents of these variables are not presented to the external template language. Instead, they are hard-coded within LyX which is told by the language that it must create a variable of type RotationLatexCommand in order to generate the necessary output. What this class contains is detailed fully below. Finally, the line "Product "$$RotateFront$$ResizeFront \\input{$$Basename.pstex_t}$$ResizeBack$$RotateBack"" specifies how all this fits together to produce the final product of a snippet of LaTeX code. Note that LyX will replace these placeholders $$RotateFront and $$RotateBack with appropriate output from class RotationLatexCommand. The second example illustrates how the language would specify transforms as options to the basic output command. Here is the proposed definition of the RasterImage external template that may one day replace InsetGraphics. Again, the interesting parts are highlighted in red. Template RasterImage GuiName "Image: $$FName" InputFormat "*" AutomaticProduction true Transform Rotate Transform Resize Transform Clip Transform Extra Format LaTeX TransformOption Rotate RotationLatexOption TransformOption Resize ResizeLatexOption TransformOption Clip ClipLatexOption TransformOption Extra ExtraLatexOption Option Arg "[$$Extra,$$Rotate,$$Resize,$$Clip]" Product "\\includegraphics$$Arg{$$Basename}" UpdateFormat eps UpdateResult "$$Basename.eps" Requirement "graphicx" FormatEnd TemplateEnd This definition specifies that the template can handle four different transforms of which Rotate, Resize and Clip will presumably have explicit support from the GUI, making it easy for the user to modify their content. Conversely, Extra is expected to contain any options for which direct support is not provided. Nonethless, by enabling the user to input a string of data, the full power of the \includegraphics command will be available to him. The line "TransformOption Rotate RotationLatexOption" specifies that the transform named Rotate will generate LaTeX output by invoking class RotationLatexOption. This class will be used to replace the implictly-named placeholder $$Rotate as appropriate. It is detailed fully below. The various individual options are concatenated to produce an optional argument to the LaTeX \includegraphics command. The line "Option Arg "[$$Extra,$$Rotate,$$Resize,$$Clip]"" indicates how this will be done, creating a variable named Arg. If the user decides not to rotate, resize or otherwise transform the data, LyX has been made intelligent enough to remove the extra commas after the placeholders have been substituted. For example, sanitizeLatexOption("[,,width=2cm,]") will generate "[width=2cm]", whilst sanitizeLatexOption("[,,,]") would become an empty string. Giving the Option command a name allows the user to pass multiple optional arguments to the LaTeX command. Thus we might have something like: Option Arg1 "[foo,bar,$$Rotate]" Option Arg2 "[gee,whizz]" Product "\somecommand$$Arg1$$Arg2{$$Basename}" This example also illustrates how string literals could be passed to the LaTeX command, although in this particular case Arg2 could also be hard-coded into the Product. 2 An example transformation: implementing rotation Let us consider rotation in greater detail. In general, the transformation will require that the same basic data be output differently for LaTeX, LinuxDoc and DocBook. All that InsetExternal does to implement this is replace the placeholders such as $$Rotate with the output from a RotateXYZCommand or a RotationXYZOption variable, where XYZ is Latex, Linuxdoc, etc. To this end, define a RotationData class simply to hold the data. class RotationData { public: enum OriginType { DEFAULT, TOP, BOTTOM, BASELINE }; RotationData() : angle_(0), origin_(DEFAULT) {} void angle(double a) { angle_ = a; } double angle() const { return angle_; } void origin(OriginType o) { origin_ = o; } OriginType origin() const { return origin_; } bool no_rotation() const { return std::abs(angle()) < 0.1; } private: double angle_; OriginType origin_; }; InsetExternal has a RotationData member variable which can be modified by the user. In order to generate Latex code to rotate the image from the RasterImage template it uses class RotateLatexOption, which may be written in stand-alone fashion as class RotateLatexOption { RotationData data_; public: RotateLatexOption(RotationData const & data) : data_(data) {} /// The string from the External Template that we /// seek to replace. string const placeholder() const { return "$$Rotate"; } /// The appropriate replacement for the placeholder string. string const option() const { if (data_.no_rotation()) return string(); std::ostringstream ss; ss << "angle=" << data_.angle(); if (data_.origin() == DEFAULT) return ss.str(); ss << ",origin=" << data_.option(); return ss.str(); } }; The details of operator<<(std::ostream &, RotationData::OriginType) are unimportant here. Similarly, the XFig template uses class RotateLatexCommand which may also be written in stand-alone fashion as class RotateLatexCommand { RotationData data_; public: RotateLatexCommand(RotationData const & data) : data_(data) {} /// The strings from the External Template that we /// seek to replace. string const before_placeholder() const { return "$$RotateBegin"; } string const after_placeholder() const { return "$$RotateEnd"; } /// The appropriate replacements for the /// placeholder strings. string const before() const { if (data_.no_rotation()) return string(); std::ostringstream os; os << "\\rotatebox"; if (data_.origin() != RotationData::DEFAULT) os << "[origin=" << data_.origin() << ']'; os << '{' << data_.angle() << "}{"; return os.str(); } string const after() const { if (data_.no_rotation()) return string(); return "}"; } }; These two classes illustrate all the important points needed to generate output for the transformation data. The strategy is as simple as "replace a placeholder string with the transformation instructions". These instructions can take the form as an option to the primary command, in which case the transformation class must implement placeholder and option member functions. Alternatively, the instructions can be in the form of another command, wrapping the primary one. In this case we must implement functions defining the front and back, both of the placeholder and of its replacements. The actual implementation uses a heirarchy of derived classes. Again, this is unimportant here.