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.

Reply via email to