/src/openbabel/src/formats/svgformat.cpp
Line | Count | Source |
1 | | /* |
2 | | svgformat.cpp Format for rendering multiple molecules by SVG |
3 | | Copyright (C) 2009 by Chris Morley |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation version 2 of the License. |
8 | | |
9 | | This program is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | ***********************************************************************/ |
14 | | #include <openbabel/babelconfig.h> |
15 | | #include <openbabel/obmolecformat.h> |
16 | | #include <openbabel/mol.h> |
17 | | #include <openbabel/descriptor.h> |
18 | | #include <openbabel/op.h> |
19 | | #include <openbabel/text.h> |
20 | | #include <openbabel/depict/svgpainter.h> |
21 | | #include <openbabel/depict/depict.h> |
22 | | #include <openbabel/alias.h> |
23 | | #include <cstdlib> |
24 | | |
25 | | using namespace std; |
26 | | namespace OpenBabel |
27 | | { |
28 | | |
29 | | class SVGFormat : public OBFormat |
30 | | { |
31 | | public: |
32 | 6 | SVGFormat() : _ncols(0), _nrows(0), _nmax(0) |
33 | 6 | { |
34 | 6 | OBConversion::RegisterFormat("svg",this); |
35 | 6 | OBConversion::RegisterOptionParam("N", this, 1, OBConversion::OUTOPTIONS); |
36 | 6 | OBConversion::RegisterOptionParam("rows", this, 1, OBConversion::GENOPTIONS); |
37 | 6 | OBConversion::RegisterOptionParam("cols", this, 1, OBConversion::GENOPTIONS); |
38 | 6 | OBConversion::RegisterOptionParam("px", this, 1, OBConversion::GENOPTIONS); |
39 | 6 | } |
40 | | |
41 | 0 | virtual const char* NamespaceURI()const{return "http://www.w3.org/2000/svg";} |
42 | | const char* Description() override |
43 | 0 | { |
44 | 0 | return |
45 | 0 | "SVG 2D depiction\n" |
46 | 0 | "Scalable Vector Graphics 2D rendering of molecular structure.\n\n" |
47 | |
|
48 | 0 | "When called from commandline or GUI or otherwise via Convert(),\n" |
49 | 0 | "single molecules are displayed at a fixed scale, as in normal diagrams,\n" |
50 | 0 | "but multiple molecules are displayed in a table which expands to fill\n" |
51 | 0 | "the containing element, such as a browser window.\n" |
52 | 0 | "When WriteMolecule() is called directly, without going through\n" |
53 | 0 | "WriteChemObject, e.g. via OBConversion::Write(), a fixed size image by\n" |
54 | 0 | "default 200 x 200px containing a single molecule is written. The size\n" |
55 | 0 | "can be specified by the P output option.\n\n" |
56 | |
|
57 | 0 | "Multiple molecules are displayed in a grid of dimensions specified by\n" |
58 | 0 | "the ``-xr`` and ``-xc`` options (number of rows and columns respectively\n" |
59 | 0 | "and ``--rows``, ``--cols`` with obabel).\n" |
60 | 0 | "When displayed in most modern browsers, like Firefox, there is\n" |
61 | 0 | "javascript support for zooming (with the mouse wheel)\n" |
62 | 0 | "and panning (by dragging with the left mouse button).\n\n" |
63 | |
|
64 | 0 | "If both ``-xr`` and ``-xc`` are specified, they define the maximum number of\n" |
65 | 0 | "molecules that are displayed.\n" |
66 | 0 | "If only one of them is displayed, then the other is calculated so that\n" |
67 | 0 | "ALL the molecules are displayed.\n" |
68 | 0 | "If neither are specified, all the molecules are output in an\n" |
69 | 0 | "approximately square table.\n\n" |
70 | |
|
71 | 0 | "By default, 2D atom coordinates are generated (using gen2D) unless they\n" |
72 | 0 | "are already present. This can be slow with a large number of molecules.\n" |
73 | 0 | "(3D coordinates are ignored.) Include ``--gen2D`` explicitly if you wish\n" |
74 | 0 | "any existing 2D coordinates to be recalculated.\n\n" |
75 | |
|
76 | 0 | "Write Options e.g. -xu\n" |
77 | 0 | " u no element-specific atom coloring\n" |
78 | 0 | " Use this option to produce a black and white diagram\n" |
79 | 0 | " U do not use internally-specified color\n" |
80 | 0 | " e.g. atom color read from cml or generated by internal code\n" |
81 | 0 | " b <color> background color, default white\n" |
82 | 0 | " e.g ``-xb yellow`` or ``-xb #88ff00`` ``-xb none`` is transparent.\n" |
83 | 0 | " Just ``-xb`` is black with white bonds.\n" |
84 | 0 | " The atom symbol colors work with black and white backgrounds,\n" |
85 | 0 | " but may not with other colors.\n" |
86 | 0 | " B <color> bond color, default black\n" |
87 | 0 | " e.g ``-xB`` yellow or ``-xB #88ff00``\n" |
88 | 0 | " C do not draw terminal C (and attached H) explicitly\n" |
89 | 0 | " The default is to draw all hetero atoms and terminal C explicitly,\n" |
90 | 0 | " together with their attched hydrogens.\n" |
91 | 0 | " a draw all carbon atoms\n" |
92 | 0 | " So propane would display as H3C-CH2-CH3\n" |
93 | 0 | " d do not display molecule name\n" |
94 | 0 | " s use asymmetric double bonds\n" |
95 | 0 | " t use thicker lines\n" |
96 | 0 | " e embed molecule as CML\n" |
97 | 0 | " OpenBabel can read the resulting svg file as a cml file.\n" |
98 | 0 | " p# px Scale to bond length(single mol only)\n" |
99 | 0 | " P# px Single mol in defined size image\n" |
100 | 0 | " The General option --px # is an alternative to the above.\n" |
101 | 0 | " c# number of columns in table\n" |
102 | 0 | " cols# number of columns in table(not displayed in GUI)\n" |
103 | 0 | " r# number of rows in table\n" |
104 | 0 | " rows# number of rows in table(not displayed in GUI)\n" |
105 | 0 | " N# max number objects to be output\n" |
106 | 0 | " l draw grid lines\n" |
107 | 0 | " h <condition> highlight mol if condition is met\n" |
108 | 0 | " The condition can use descriptors and properties,\n" |
109 | 0 | " See documentation on ``--filter`` option for details.\n" |
110 | 0 | " To highlight in a particular color, follow the condition\n" |
111 | 0 | " by a color.\n" |
112 | 0 | " i add index to each atom\n" |
113 | 0 | " These indices are those in sd or mol files and correspond to the\n" |
114 | 0 | " order of atoms in a SMILES string.\n" |
115 | 0 | " j do not embed javascript\n" |
116 | 0 | " Javascript is not usually embedded if there is only one molecule,\n" |
117 | 0 | " but it is if the rows and columns have been specified as 1: ``-xr1 -xc1``\n" |
118 | 0 | " x omit XML declaration (not displayed in GUI)\n" |
119 | 0 | " Useful if the output is to be embedded in another xml file.\n" |
120 | 0 | " X All atoms are explicitly declared \n" |
121 | 0 | " Useful if we don't want any extra hydrogens drawn to fill the valence.\n" |
122 | 0 | " A display aliases, if present\n" |
123 | 0 | " This applies to structures which have an alternative, usually\n" |
124 | 0 | " shorter, representation already present. This might have been input\n" |
125 | 0 | " from an A or S superatom entry in an sd or mol file, or can be\n" |
126 | 0 | " generated using the --genalias option. For example::\n \n" |
127 | |
|
128 | 0 | " obabel -:\"c1cc(C=O)ccc1C(=O)O\" -O out.svg\n" |
129 | 0 | " --genalias -xA\n \n" |
130 | |
|
131 | 0 | " would add a aliases COOH and CHO to represent the carboxyl and\n" |
132 | 0 | " aldehyde groups and would display them as such in the svg diagram.\n" |
133 | 0 | " The aliases which are recognized are in data/superatom.txt, which\n" |
134 | 0 | " can be edited.\n" |
135 | 0 | " S Ball and stick depiction of molecules\n" |
136 | 0 | " Depicts the molecules as balls and sticks instead of the\n" |
137 | 0 | " normal line style.\n\n" |
138 | | |
139 | |
|
140 | 0 | "If the input molecule(s) contain explicit hydrogen, you could consider\n" |
141 | 0 | "improving the appearance of the diagram by adding an option ``-d`` to make\n" |
142 | 0 | "it implicit. Hydrogen on hetero atoms and on explicitly drawn C is\n" |
143 | 0 | "always shown.\n" |
144 | |
|
145 | 0 | "For example, if input.smi had 10 molecules::\n\n" |
146 | |
|
147 | 0 | " obabel input.smi -O out.svg -xb -xC -xe\n\n" |
148 | |
|
149 | 0 | "would produce a svg file with a black background, with no explicit\n" |
150 | 0 | "terminal carbon, and with an embedded cml representation of each\n" |
151 | 0 | "molecule. The structures would be in two rows of four and one row\n" |
152 | 0 | "of two.\n\n" |
153 | 0 | ; |
154 | 0 | } |
155 | | |
156 | | unsigned int Flags() override |
157 | 6 | { |
158 | 6 | return NOTREADABLE | ZEROATOMSOK | DEPICTION2D; |
159 | 6 | } |
160 | | |
161 | | bool WriteChemObject(OBConversion* pConv) override; |
162 | | bool WriteMolecule(OBBase* pOb, OBConversion* pConv) override; |
163 | | |
164 | | private: |
165 | | bool EmbedCML(OBMol* pmol, OBConversion* pConv, ostream* ofs); |
166 | | bool EmbedScript(ostream& ofs); |
167 | | bool WriteSVG(OBConversion* pConv, vector<OBBase*>& molecules); |
168 | | private: |
169 | | int _ncols, _nrows, _nmax; |
170 | | vector<OBBase*> _objects; |
171 | | OBText* _ptext; |
172 | | string::size_type _textpos; |
173 | | }; |
174 | | ///////////////////////////////////////////////////////////////// |
175 | | SVGFormat theSVGFormat; |
176 | | |
177 | | ///////////////////////////////////////////////////////////////// |
178 | | bool SVGFormat::WriteChemObject(OBConversion* pConv) |
179 | 0 | { |
180 | | //Molecules are stored here as pointers to OBBase objects, which are not deleted as usual. |
181 | | //When there are no more they are sent to WriteMolecule. |
182 | | //This allows their number to be determined whatever their source |
183 | | //(they may also have been filtered), so that the table can be properly dimensioned. |
184 | | |
185 | | //NOT CURRENTLY IMPLEMENTED |
186 | | //If the first object is OBText, the part of it before each insertion point (if it exists) |
187 | | //is output before every molecule. This allows molecule structures to be displayed |
188 | | //in a template. The x option to omit the XML header is set |
189 | |
|
190 | 0 | OBBase* pOb = pConv->GetChemObject(); |
191 | |
|
192 | 0 | if(pConv->GetOutputIndex()<=1) |
193 | 0 | { |
194 | 0 | _objects.clear(); |
195 | 0 | _nmax=0; |
196 | |
|
197 | 0 | pConv->AddOption("svgbswritechemobject"); // to show WriteMolecule that this function has been called |
198 | 0 | const char* pc = pConv->IsOption("c"); |
199 | | //alternative for obabel because -xc cannot take a parameter, because some other format uses it |
200 | | //similarly for -xr -xp |
201 | 0 | if(!pc) |
202 | 0 | pc = pConv->IsOption("cols", OBConversion::GENOPTIONS); |
203 | 0 | const char* pr = pConv->IsOption("r"); |
204 | 0 | if(!pr) |
205 | 0 | pr = pConv->IsOption("rows", OBConversion::GENOPTIONS); |
206 | 0 | if(pr) |
207 | 0 | _nrows = atoi(pr); |
208 | 0 | if(pc) |
209 | 0 | _ncols = atoi(pc); |
210 | 0 | if(pr && pc) // both specified: fixes maximum number objects to be output |
211 | 0 | _nmax = _nrows * _ncols; |
212 | | |
213 | | //explicit max number of objects |
214 | 0 | const char* pmax =pConv->IsOption("N"); |
215 | 0 | if(pmax) |
216 | 0 | _nmax = atoi(pmax); |
217 | 0 | } |
218 | |
|
219 | 0 | OBMoleculeFormat::DoOutputOptions(pOb, pConv); |
220 | | |
221 | | //save molecule |
222 | 0 | _objects.push_back(pOb); |
223 | |
|
224 | 0 | bool ret=true; |
225 | | //Finish if no more input or if the number of molecules has reached the allowed maximum(if specified) |
226 | 0 | bool nomore = _nmax && (_objects.size()==_nmax); |
227 | 0 | if((pConv->IsLast() || nomore)) |
228 | 0 | { |
229 | 0 | int nmols = _objects.size(); |
230 | | //Set table properties according to the options and the number of molecules to be output |
231 | 0 | if (!(nmols==0 || //ignore this block if there is no input or |
232 | 0 | (_nrows && _ncols) || //if the user has specified both rows and columns or |
233 | 0 | (!_nrows && !_ncols && nmols==1)))//if neither is specified and there is one output molecule |
234 | 0 | { |
235 | 0 | if(!_nrows && !_ncols ) //neither specified |
236 | 0 | { |
237 | | //assign cols/rows in square |
238 | 0 | _ncols = (int)ceil(sqrt(((double)nmols))); |
239 | 0 | } |
240 | |
|
241 | 0 | if(_nrows) |
242 | 0 | _ncols = (nmols-1) / _nrows + 1; //rounds up |
243 | 0 | else if(_ncols) |
244 | 0 | _nrows = (nmols-1) / _ncols + 1; |
245 | 0 | } |
246 | | |
247 | | //output all collected molecules |
248 | 0 | unsigned int n=0; |
249 | |
|
250 | 0 | ret = WriteSVG(pConv, _objects); |
251 | | |
252 | | //delete all the molecules |
253 | 0 | vector<OBBase*>::iterator iter; |
254 | 0 | for(iter=_objects.begin();iter!=_objects.end(); ++iter) |
255 | 0 | delete *iter; |
256 | 0 | delete _ptext;//delete text, NULL or not |
257 | |
|
258 | 0 | _objects.clear(); |
259 | 0 | _ptext = nullptr; |
260 | 0 | _nmax = _ncols = _nrows = 0; |
261 | 0 | } |
262 | 0 | return ret && !nomore; |
263 | 0 | } |
264 | | //////////////////////////////////////////////////////////////// |
265 | | bool SVGFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv) |
266 | 0 | { |
267 | 0 | OBMol* pmol = dynamic_cast<OBMol*>(pOb); |
268 | 0 | if(!pmol) |
269 | 0 | return false; |
270 | 0 | _objects.clear(); |
271 | 0 | _nmax =_nrows = _ncols = 1; |
272 | 0 | _objects.push_back(pOb); |
273 | 0 | bool ret = WriteSVG(pConv,_objects); |
274 | 0 | _objects.clear(); |
275 | 0 | return true; |
276 | 0 | } |
277 | | |
278 | | bool SVGFormat::WriteSVG(OBConversion* pConv, vector<OBBase*>& molecules) |
279 | 0 | { |
280 | |
|
281 | 0 | bool ret=true; |
282 | | |
283 | | //Check for option for single mol in fixed size image |
284 | 0 | const char* fixedpx = pConv->IsOption("P"); |
285 | 0 | if(!fixedpx) |
286 | 0 | fixedpx= pConv->IsOption("px", OBConversion::GENOPTIONS); |
287 | | //If WriteMolecule called directly, e.g. from OBConversion::Write() |
288 | | //the default mode is a fixed image size of 200px square |
289 | 0 | if(!fixedpx && molecules.size()==1) |
290 | 0 | fixedpx = "200"; |
291 | 0 | if(fixedpx) |
292 | 0 | { |
293 | 0 | _nmax = _nrows = _ncols = 1; |
294 | 0 | pConv->AddOption("j"); |
295 | 0 | } |
296 | |
|
297 | 0 | ostream &ofs = *pConv->GetOutStream(); |
298 | |
|
299 | 0 | bool hasTable = (_nrows>1) || (_ncols>1); |
300 | |
|
301 | 0 | bool transparent=false; |
302 | 0 | string background, bondcolor; |
303 | 0 | const char* bg = pConv->IsOption("b"); |
304 | 0 | background = bg ? "black" : "white"; |
305 | 0 | bondcolor = bg ? "white" : "black"; |
306 | 0 | if(bg && (!strcmp(bg, "none") || bg[0]=='0')) |
307 | 0 | { |
308 | 0 | transparent = true; |
309 | 0 | bondcolor = "gray"; |
310 | 0 | } |
311 | 0 | const char* bcol = pConv->IsOption("B"); |
312 | 0 | if(bcol && *bcol) |
313 | 0 | bondcolor = bcol; |
314 | 0 | if(bg && *bg) |
315 | 0 | background = bg; |
316 | |
|
317 | 0 | if(!pConv->IsOption("x")) |
318 | 0 | ofs << "<?xml version=\"1.0\"?>\n"; |
319 | |
|
320 | 0 | ofs << "<svg version=\"1.1\" id=\"topsvg\"\n" |
321 | 0 | "xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" |
322 | 0 | "xmlns:cml=\"http://www.xml-cml.org/schema\" "; |
323 | 0 | double vbwidth=100, vbheight=100; |
324 | 0 | if (_nrows>_ncols) |
325 | 0 | vbwidth = (100*_ncols)/_nrows; |
326 | 0 | else if(_ncols>_nrows) |
327 | 0 | vbheight = (100*_nrows)/_ncols; |
328 | |
|
329 | 0 | if(fixedpx)//fixed size image |
330 | 0 | ofs << "x=\"0\" y=\"0\" width=\"" << fixedpx << "px\" height=\"" << fixedpx <<"px\" "; |
331 | 0 | else |
332 | 0 | ofs << "x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" "; |
333 | |
|
334 | 0 | ofs << "viewBox=\"0 0 " << vbwidth << ' ' << vbheight << "\">\n"; |
335 | |
|
336 | 0 | if (hasTable) |
337 | 0 | ofs << "<title>Multiple Molecules - Open Babel Depiction</title>\n"; |
338 | 0 | else if(molecules.size() == 1) |
339 | 0 | ofs << "<title>" << molecules[0]->GetTitle() << " - Open Babel Depiction</title>\n"; |
340 | | |
341 | | // Draw the background unless transparent |
342 | 0 | if(!transparent) |
343 | 0 | ofs << "<rect x=\"0\" y=\"0\" width=\"" << vbwidth << "\" height=\"" << vbheight |
344 | 0 | << "\" fill=\"" << background << "\"/>\n"; |
345 | |
|
346 | 0 | unsigned opts = 0; |
347 | 0 | if(pConv->IsOption("u")) |
348 | 0 | opts |= OBDepict::bwAtoms; |
349 | 0 | if(!pConv->IsOption("U")) |
350 | 0 | opts |= OBDepict::internalColor; |
351 | 0 | if(!pConv->IsOption("C")) |
352 | 0 | opts |= OBDepict::drawTermC;// on by default |
353 | 0 | if(pConv->IsOption("a")) |
354 | 0 | opts |= OBDepict::drawAllC; |
355 | 0 | if(pConv->IsOption("W")) |
356 | 0 | opts |= OBDepict::noWedgeHashGen; |
357 | 0 | if(pConv->IsOption("s")) |
358 | 0 | opts |= OBDepict::asymmetricDoubleBond; |
359 | 0 | if(pConv->IsOption("X")) |
360 | 0 | opts |= OBDepict::allExplicit; |
361 | |
|
362 | 0 | bool balldepict = false; |
363 | 0 | if(pConv->IsOption("S")) { |
364 | 0 | balldepict = true; |
365 | 0 | } |
366 | |
|
367 | 0 | double factor = 1.0; |
368 | 0 | int nc = _ncols ? _ncols : 1; |
369 | 0 | int nr = (_nrows ? _nrows : 1); |
370 | 0 | double cellsize = 100. / std::max(nc, nr); |
371 | |
|
372 | 0 | stringstream molfs; |
373 | 0 | std::set<ColorGradient> gradients; |
374 | |
|
375 | 0 | OBOp* pOp = OBOp::FindType("gen2D"); |
376 | 0 | if(!balldepict && !pOp) |
377 | 0 | { |
378 | 0 | obErrorLog.ThrowError("SVGFormat", "gen2D not found", obError, onceOnly); |
379 | 0 | return false; |
380 | 0 | } |
381 | | |
382 | 0 | vector<OBBase*>::iterator iter; |
383 | 0 | int indx = 0; |
384 | 0 | for(iter=_objects.begin(); ret && iter!=_objects.end(); ++iter,++indx) |
385 | 0 | { |
386 | 0 | OBMol* pmol = dynamic_cast<OBMol*>(*iter); |
387 | |
|
388 | 0 | if (!pmol) |
389 | 0 | continue; |
390 | | //*** Coordinate generation *** |
391 | | //Generate coordinates only if no existing 2D coordinates and we're not doing ball-and-stick style |
392 | 0 | if( (pConv->IsOption("y") || !pmol->Has2D(true)) && (!pConv->IsOption("n") && !balldepict)) |
393 | 0 | { |
394 | 0 | if(!pOp->Do(pmol)) |
395 | 0 | { |
396 | 0 | obErrorLog.ThrowError("SVGFormat", string(pmol->GetTitle()) + "- Coordinate generation unsuccessful", obError); |
397 | 0 | return false; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | if(!pmol->Has2D() && pmol->NumAtoms()>1)//allows 3D coordinates (if passed by -xn above) |
401 | 0 | { |
402 | 0 | string mes("Molecule "); |
403 | 0 | mes += pmol->GetTitle(); |
404 | 0 | mes += " needs 2D coordinates to display in SVGformat"; |
405 | 0 | obErrorLog.ThrowError("SVGFormat", mes, obError); |
406 | 0 | return false; |
407 | 0 | } |
408 | 0 | double innerX = 0.0; |
409 | 0 | double innerY = 0.0; |
410 | 0 | if(hasTable) |
411 | 0 | { |
412 | | //*** Parameter for inner svg *** |
413 | 0 | innerX = (indx % nc) * cellsize; |
414 | 0 | innerY = (indx / nc) * cellsize; |
415 | | |
416 | | // Change the background in this cell if the condition in the first |
417 | | // parameter of the -xh option is met. Use a default color if |
418 | | // the highlight color is not specified in the second parameter. |
419 | 0 | const char* htxt = pConv->IsOption("h"); |
420 | 0 | if(htxt) |
421 | 0 | { |
422 | 0 | vector<string> vec; |
423 | 0 | tokenize(vec, htxt); |
424 | 0 | string highlight(vec.size()>1 ? vec[1] : "#f4f0ff"); |
425 | 0 | std::istringstream conditionText(vec[0]); |
426 | 0 | if(OBDescriptor::FilterCompare(*iter, conditionText, false)) |
427 | | //Still in outer <svg>, unfortunately |
428 | 0 | molfs << "<rect x=\"" << innerX << "\" y=\"" << innerY |
429 | 0 | << "\" width=\"" << cellsize << "\" height=\"" << cellsize |
430 | 0 | << "\" fill=\"" << highlight << "\"/>\n"; |
431 | 0 | } |
432 | 0 | } |
433 | |
|
434 | 0 | SVGPainter painter(molfs, &gradients, true, cellsize, cellsize); |
435 | 0 | OBDepict depictor(&painter, balldepict); |
436 | |
|
437 | 0 | depictor.SetOption(opts); |
438 | |
|
439 | 0 | if(pConv->IsOption("A")) |
440 | 0 | { |
441 | 0 | AliasData::RevertToAliasForm(*pmol); |
442 | 0 | depictor.SetAliasMode(); |
443 | 0 | } |
444 | 0 | painter.SetFontFamily("sans-serif"); |
445 | 0 | painter.SetPenColor(OBColor(bondcolor)); |
446 | 0 | depictor.SetBondColor(bondcolor); |
447 | 0 | if(pConv->IsOption("t")) |
448 | 0 | painter.SetPenWidth(4); |
449 | 0 | else |
450 | 0 | painter.SetPenWidth(2); |
451 | |
|
452 | 0 | molfs << "<g transform=\"translate(" << innerX << "," << innerY << ")\">\n"; |
453 | |
|
454 | 0 | ret = depictor.DrawMolecule(pmol); |
455 | | |
456 | | |
457 | | //Draw atom indices if requested |
458 | 0 | if(pConv->IsOption("i")) |
459 | 0 | depictor.AddAtomLabels(OBDepict::AtomIndex); |
460 | |
|
461 | 0 | painter.EndCanvas(); |
462 | | |
463 | | |
464 | | //Embed CML of molecule if requested |
465 | 0 | if(pConv->IsOption("e")) |
466 | 0 | EmbedCML(pmol, pConv, &molfs); |
467 | |
|
468 | 0 | molfs <<"</g>\n"; |
469 | | |
470 | | //*** Write molecule name *** |
471 | 0 | if(!pConv->IsOption("d")) { |
472 | 0 | if(hasTable) { |
473 | 0 | molfs << "<text text-anchor=\"middle\" font-size=\"" << 0.06*cellsize << "\"" |
474 | 0 | << " fill =\"" << bondcolor << "\" font-family=\"sans-serif\"\n" |
475 | 0 | << "x=\"" << innerX + cellsize * 0.5 << "\" y=\"" << innerY + cellsize - 2.0/nr << "\" >" |
476 | 0 | << pmol->GetTitle() << "</text>\n"; |
477 | 0 | } else { |
478 | 0 | molfs << "<text font-size=\"" << 18 * factor << "\"" |
479 | 0 | << " fill =\"" << bondcolor << "\" font-family=\"sans-serif\"\n" |
480 | 0 | << "x=\"" << 10 * factor << "\" y=\"" << 20 * factor << "\" >" |
481 | 0 | << pmol->GetTitle() << "</text>\n"; |
482 | 0 | } |
483 | 0 | } |
484 | 0 | } |
485 | | |
486 | | // finally write svg defs |
487 | 0 | SVGPainter painter(ofs, &gradients, true, cellsize,cellsize); |
488 | 0 | painter.WriteDefs(); |
489 | | |
490 | | // and stream back all molecule data |
491 | 0 | ofs << molfs.str(); |
492 | | |
493 | | //Draw grid lines |
494 | 0 | if(hasTable && pConv->IsOption("l")) |
495 | 0 | { |
496 | 0 | for(int i=1; i<_nrows; ++i) |
497 | 0 | ofs << " <line stroke=\"gray\" stroke-width=\"0.1\" x1=\"0\" x2=\"100\"" |
498 | 0 | << " y1=\"" << i*cellsize << "\" y2=\"" << i*cellsize << "\"/>\n"; |
499 | 0 | for(int i=1; i<_ncols; ++i) |
500 | 0 | ofs << " <line stroke=\"gray\" stroke-width=\"0.1\" y1=\"0\" y2=\"100\"" |
501 | 0 | << " x1=\"" << i*cellsize << "\" x2=\"" << i*cellsize << "\"/>\n"; |
502 | 0 | } |
503 | | |
504 | | //Insert javascript for zooming and panning |
505 | 0 | if(!pConv->IsOption("j")) |
506 | 0 | EmbedScript(ofs); |
507 | |
|
508 | 0 | ofs << "</svg>\n"; |
509 | 0 | return ret; |
510 | 0 | } |
511 | | |
512 | | ///////////////////////////////////////////////////////////// |
513 | | //returns true if the file "svgformat.script" was inserted into the output |
514 | | bool SVGFormat::EmbedScript(ostream& ofs) |
515 | 0 | { |
516 | 0 | ifstream ifs; |
517 | 0 | if(!ifs || OpenDatafile(ifs, "svgformat.script").empty()) |
518 | 0 | return false; |
519 | 0 | ofs << ifs.rdbuf(); //copy whole file |
520 | 0 | return true; |
521 | 0 | } |
522 | | |
523 | | /////////////////////////////////////////////////////////////////////////////////// |
524 | | bool SVGFormat::EmbedCML(OBMol* pmol, OBConversion* pConv, ostream* ofs) |
525 | 0 | { |
526 | 0 | OBConversion CMLConv(*pConv); |
527 | 0 | if(!CMLConv.SetOutFormat("cml")) |
528 | 0 | { |
529 | 0 | obErrorLog.ThrowError(__FUNCTION__, "CML format was not found\n",obError); |
530 | 0 | return false; |
531 | 0 | } |
532 | 0 | CMLConv.AddOption("MolsNotStandalone",OBConversion::OUTOPTIONS); |
533 | 0 | CMLConv.AddOption("N",OBConversion::OUTOPTIONS,"cml"); |
534 | 0 | CMLConv.AddOption("p",OBConversion::OUTOPTIONS); //include properties |
535 | | // CMLConv.AddOption("x",OBConversion::OUTOPTIONS); |
536 | 0 | return CMLConv.Write(pmol, ofs); |
537 | 0 | } |
538 | | |
539 | | /* |
540 | | The script below was originally (and still could be) in data/svgformat.script, |
541 | | the whole of which is embedded into the output. |
542 | | It works adequately in modern versions of Firefox, Chrome, Opera and |
543 | | Internet Explorer 9, to zoom with the mouse wheel and pan by dragging. |
544 | | |
545 | | <script type="text/ecmascript"> |
546 | | <![CDATA[ |
547 | | var svgEl = document.getElementById("topsvg"); |
548 | | svgEl.addEventListener('DOMMouseScroll', wheel, false); |
549 | | svgEl.addEventListener('mousewheel', wheel, false); |
550 | | var startx=0; |
551 | | var starty=0; |
552 | | function wheel(evt){ |
553 | | evt = evt ? evt : window.event; |
554 | | var normal = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; |
555 | | var vb = new Array(4); |
556 | | var vbtext = svgEl.getAttributeNS(null,"viewBox"); |
557 | | vb = vbtext.split(" "); |
558 | | var zoom = (normal<0)? 1.41 : 0.71; |
559 | | //var dwidth = parseFloat(Math.max(vb[2],vb[3])) * (1-zoom); |
560 | | vb[0] = parseFloat(vb[0]) + parseFloat(vb[2])*(1-zoom) * evt.clientX/innerWidth; |
561 | | vb[1] = parseFloat(vb[1]) + parseFloat(vb[3])*(1-zoom) * evt.clientY/innerHeight; |
562 | | vb[2] = parseFloat(vb[2]) * zoom; |
563 | | vb[3] = parseFloat(vb[3]) * zoom; |
564 | | svgEl.setAttributeNS(null, "viewBox", vb.join(" ")); |
565 | | if (evt.preventDefault) |
566 | | evt.preventDefault(); // Don't scroll the page when zooming |
567 | | } |
568 | | svgEl.onmousedown = function(evt) { |
569 | | startx = evt.clientX; |
570 | | starty = evt.clientY; |
571 | | } |
572 | | svgEl.onmousemove=function(evt) { |
573 | | if(startx!=0 && starty!=0 |
574 | | && ((evt.clientX - startx)*(evt.clientX - startx)+(evt.clientY - starty)*(evt.clientY - starty)>100)) |
575 | | { |
576 | | var vbtext = svgEl.getAttributeNS(null,"viewBox"); |
577 | | vb = vbtext.split(" "); |
578 | | var maxwh = Math.max(parseFloat(vb[2]),parseFloat(vb[3])); |
579 | | vb[0] = parseFloat(vb[0]) - (evt.clientX - startx)*maxwh/innerWidth; |
580 | | vb[1] = parseFloat(vb[1]) - (evt.clientY - starty)*maxwh/innerHeight; |
581 | | svgEl.setAttributeNS(null, "viewBox", vb.join(" ")); |
582 | | startx = evt.clientX; |
583 | | starty = evt.clientY; |
584 | | } |
585 | | } |
586 | | svgEl.onmouseup=function() { |
587 | | startx=0; |
588 | | starty=0; |
589 | | } |
590 | | svgEl.ondblclick=function() { |
591 | | location.reload(); |
592 | | } |
593 | | ]]> |
594 | | </script> |
595 | | |
596 | | Alternatively, svgformat.script could contain: |
597 | | |
598 | | <script type="text/ecmascript" xlink:href="morescript.js" /> |
599 | | |
600 | | with the real script in morescript.js. |
601 | | */ |
602 | | |
603 | | }//namespace |