]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
fe4da9b701476a3d02712d535ef524b2cb18e09e
[lyx.git] / src / insets / insetgraphics.C
1 /* This file is part of
2  * ====================================================== 
3  * 
4  *           LyX, The Document Processor
5  *       
6  *           Copyright 1995-2002 the LyX Team.
7  *           
8  * \author Baruch Even
9  * \author Herbert Voss <voss@lyx.org>
10  * ====================================================== */
11
12 /*
13 Known BUGS:
14     
15     * If the image is from the clipart, and the document is moved to another
16        directory, the user is screwed. Need a way to handle it.
17        This amounts to a problem of when to use relative or absolute file paths
18        We should probably use what the user asks to use... but when he chooses
19        by the file dialog we normally get an absolute path and this may not be 
20        what the user meant.
21        [Note that browseRelFile in helper_funcs.* provides a file name
22         which is relative if it is at reference path (here puffer path)
23         level or below, and an absolute path if the file name is not a
24         `natural' relative file name. In any case,
25             MakeAbsPath(filename, buf->filePath())
26         is guaranteed to provide the correct absolute path. This is what is
27         done know for include insets. Feel free to ask me -- JMarc
28         14/01/2002]
29         
30         * If we are trying to create a file in a read-only directory and there
31                 are graphics that need converting, the converting will fail because
32                 it is done in-place, into the same directory as the original image.
33                 This needs to be fixed in the src/converter.C file
34                 [ This is presumed to be fixed, needs testing.]
35
36         * We do not dither or resize the image in a WYSIWYM way, we load it at
37                 its original size and color, resizing is done in the final output,
38                 but not in the LyX window.
39
40 TODO Before initial production release:
41     * Replace insetfig everywhere
42         * Search for comments of the form
43             // INSET_GRAPHICS: remove this when InsetFig is thrown.
44           And act upon them. Make sure not to remove InsetFig code for the 
45                   1.2.0 release, only afterwards, after deployment shows InsetGraphics
46                   to be ok.
47         * What advanced features the users want to do?
48             Implement them in a non latex dependent way, but a logical way.
49             LyX should translate it to latex or any other fitting format.
50     * Add a way to roll the image file into the file format.
51     * When loading, if the image is not found in the expected place, try
52        to find it in the clipart, or in the same directory with the image.
53     * Keep a tab on the image file, if it changes, update the lyx view.
54         * The image choosing dialog could show thumbnails of the image formats
55           it knows of, thus selection based on the image instead of based on
56           filename.
57         * Add support for the 'picins' package.
58         * Add support for the 'picinpar' package.
59         * Improve support for 'subfigure' - Allow to set the various options
60                 that are possible.
61  */
62
63 /* NOTES:
64  * Fileformat:
65  * Current version is 1 (inset file format version), when changing it
66  * it should be changed in the Write() function when writing in one place
67  * and when reading one should change the version check and the error message.
68  * The filename is kept in  the lyx file in a relative way, so as to allow
69  * moving the document file and its images with no problem.
70  * 
71  *
72  * Conversions:
73  *   Postscript output means EPS figures.
74  *
75  *   PDF output is best done with PDF figures if it's a direct conversion
76  *   or PNG figures otherwise.
77  *      Image format
78  *      from        to
79  *      EPS         epstopdf
80  *      PS          ps2pdf
81  *      JPG/PNG     direct
82  *      PDF         direct
83  *      others      PNG
84  */
85
86 #include <config.h> 
87
88 #ifdef __GNUG__
89 #pragma implementation
90 #endif 
91
92 #include "insets/insetgraphics.h"
93 #include "insets/insetgraphicsParams.h"
94
95 #include "graphics/GraphicsCache.h"
96 #include "graphics/GraphicsImage.h"
97
98 #include "LyXView.h"
99 #include "buffer.h"
100 #include "BufferView.h"
101 #include "converter.h"
102 #include "Painter.h"
103 #include "lyxrc.h"
104 #include "font.h"    // For the lyxfont class.
105 #include "debug.h"
106 #include "gettext.h"
107 #include "LaTeXFeatures.h"
108
109 #include "frontends/Dialogs.h"
110 #include "frontends/Alert.h"
111 #include "frontends/controllers/helper_funcs.h" // getVectorFromString
112
113 #include "support/LAssert.h"
114 #include "support/filetools.h"
115 #include "support/lyxalgo.h" // lyx::count
116
117 #include <algorithm> // For the std::max
118
119 extern string system_tempdir;
120
121 using std::ostream;
122 using std::endl;
123
124 ///////////////////////////////////////////////////////////////////////////
125 int const VersionNumber = 1;
126 ///////////////////////////////////////////////////////////////////////////
127
128 namespace {
129
130 // This function is a utility function
131 // ... that should be with ChangeExtension ...
132 inline
133 string const RemoveExtension(string const & filename)
134 {
135         return ChangeExtension(filename, string());
136 }
137  
138 } // namespace anon
139
140
141 namespace {
142
143 string const unique_id()
144 {
145         static unsigned int seed = 1000;
146
147         ostringstream ost;
148         ost << "graph" << ++seed;
149
150         // Needed if we use lyxstring.
151         return ost.str().c_str();
152 }
153
154 } // namespace anon
155
156
157 InsetGraphics::InsetGraphics()
158         : graphic_label(unique_id()),
159           cached_status_(grfx::ErrorUnknown), cache_filled_(false)
160           
161 {}
162
163
164 InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
165         : Inset(ig, same_id),
166           SigC::Object(),
167           graphic_label(unique_id()),
168           cached_status_(grfx::ErrorUnknown), cache_filled_(false)
169 {
170         setParams(ig.params());
171         if (same_id)
172                 id_ = ig.id_;
173 }
174
175
176 InsetGraphics::~InsetGraphics()
177 {
178         cached_image_.reset(0);
179         grfx::GCache & gc = grfx::GCache::get();
180         gc.remove(*this);
181
182         // Emits the hide signal to the dialog connected (if any)
183         hideDialog();
184 }
185
186
187 string const InsetGraphics::statusMessage() const
188 {
189         string msg;
190
191         switch (cached_status_) {
192         case grfx::WaitingToLoad:
193                 msg = _("Waiting for draw request to start loading...");
194                 break;
195         case grfx::Loading:
196                 msg = _("Loading...");
197                 break;
198         case grfx::Converting:
199                 msg = _("Converting to loadable format...");
200                 break;
201         case grfx::ScalingEtc:
202                 msg = _("Loaded. Scaling etc...");
203                 break;
204         case grfx::ErrorNoFile:
205                 msg = _("No file found!");
206                 break;
207         case grfx::ErrorLoading:
208                 msg = _("Error loading file into memory");
209                 break;
210         case grfx::ErrorConverting:
211                 msg = _("Error converting to loadable format");
212                 break;
213         case grfx::ErrorScalingEtc:
214                 msg = _("Error scaling etc");
215                 break;
216         case grfx::ErrorUnknown:
217                 msg = _("No image associated with this inset is in the cache!");
218                 break;
219         case grfx::Loaded:
220                 msg = _("Loaded but not displaying");
221                 break;
222         }
223
224         return msg;
225 }
226
227
228 void InsetGraphics::setCache() const
229 {
230         if (cache_filled_)
231                 return;
232
233         grfx::GCache & gc = grfx::GCache::get();
234         cached_status_ = gc.status(*this);
235         cached_image_  = gc.image(*this);
236 }
237
238
239 bool InsetGraphics::drawImage() const
240 {
241         setCache();
242         Pixmap const pixmap =
243                 (cached_status_ == grfx::Loaded && cached_image_.get() != 0) ?
244                 cached_image_->getPixmap() : 0;
245
246         return pixmap != 0;
247 }
248
249         
250 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
251 {
252         if (drawImage())
253                 return cached_image_->getHeight();
254         else
255                 return 50;
256 }
257
258
259 int InsetGraphics::descent(BufferView *, LyXFont const &) const
260 {
261         return 0;
262 }
263
264
265 int InsetGraphics::width(BufferView *, LyXFont const & font) const
266 {
267         if (drawImage())
268                 return cached_image_->getWidth();
269         else {
270                 int font_width = 0;
271
272                 LyXFont msgFont(font);
273                 msgFont.setFamily(LyXFont::SANS_FAMILY);
274
275                 string const justname = OnlyFilename (params().filename);
276                 if (!justname.empty()) {
277                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
278                         font_width = lyxfont::width(justname, msgFont);
279                 }
280
281                 string const msg = statusMessage();
282                 if (!msg.empty()) {
283                         msgFont.setSize(LyXFont::SIZE_TINY);
284                         int const msg_width = lyxfont::width(msg, msgFont);
285                         font_width = std::max(font_width, msg_width);
286                 }
287                 
288                 return std::max(50, font_width + 15);
289         }
290 }
291
292
293 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
294                          int baseline, float & x, bool) const
295 {
296         int ldescent = descent(bv, font);
297         int lascent  = ascent(bv, font);
298         int lwidth   = width(bv, font);
299
300         // Make sure now that x is updated upon exit from this routine
301         int old_x = int(x);
302         x += lwidth;
303
304         // Initiate the loading of the graphics file
305         if (cached_status_ == grfx::WaitingToLoad) {
306                 grfx::GCache & gc = grfx::GCache::get();
307                 gc.startLoading(*this);
308         }
309
310         // This will draw the graphics. If the graphics has not been loaded yet,
311         // we draw just a rectangle.
312         Painter & paint = bv->painter();
313
314         if (drawImage()) {
315
316                 paint.image(old_x + 2, baseline - lascent,
317                             lwidth - 4, lascent + ldescent,
318                             *cached_image_.get());
319
320         } else {        
321
322                 paint.rectangle(old_x + 2, baseline - lascent,
323                                 lwidth - 4,
324                                 lascent + ldescent);
325
326                 // Print the file name.
327                 LyXFont msgFont(font);
328                 msgFont.setFamily(LyXFont::SANS_FAMILY);
329                 string const justname = OnlyFilename (params().filename);
330                 if (!justname.empty()) {
331                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
332                         paint.text(old_x + 8, 
333                                    baseline - lyxfont::maxAscent(msgFont) - 4,
334                                    justname, msgFont);
335                 }
336
337                 // Print the message.
338                 string const msg = statusMessage();
339                 if (!msg.empty()) {
340                         msgFont.setSize(LyXFont::SIZE_TINY);
341                         paint.text(old_x + 8, baseline - 4, msg, msgFont);
342                 }
343         }
344
345         // Reset the cache, ready for the next draw request
346         cached_status_ = grfx::ErrorUnknown;
347         cached_image_.reset(0);
348         cache_filled_ = false;
349 }
350
351
352 // Update the inset after parameters changed (read from file or changed in
353 // dialog. The grfx::GCache makes the decisions about whether or not to draw
354 // (interogates lyxrc, ascertains whether file exists etc)
355 void InsetGraphics::updateInset() const
356 {
357         grfx::GCache & gc = grfx::GCache::get();
358         gc.update(*this);
359 }
360
361
362 void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
363 {
364         bv->owner()->getDialogs()->showGraphics(this);
365 }
366
367
368 void InsetGraphics::edit(BufferView * bv, bool)
369 {
370         edit(bv, 0, 0, 0);
371 }
372
373
374 Inset::EDITABLE InsetGraphics::editable() const
375 {
376         return IS_EDITABLE;
377 }
378
379
380 void InsetGraphics::write(Buffer const * buf, ostream & os) const
381 {
382         os << "Graphics FormatVersion " << VersionNumber << '\n';
383         params().Write(buf, os);
384 }
385
386
387 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
388 {
389         string const token = lex.getString();
390
391         if (token == "Graphics")
392                 readInsetGraphics(buf, lex);
393         else if (token == "Figure") // Compatibility reading of FigInset figures.
394                 readFigInset(buf, lex);
395         else
396                 lyxerr[Debug::GRAPHICS] << "Not a Graphics or Figure inset!\n";
397
398         updateInset();
399 }
400
401 void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
402 {
403         bool finished = false;
404
405         while (lex.isOK() && !finished) {
406                 lex.next();
407
408                 string const token = lex.getString();
409                 lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\'' 
410                                     << std::endl;
411
412                 if (token.empty()) {
413                         continue;
414                 } else if (token == "\\end_inset") {
415                         finished = true;
416                 } else if (token == "FormatVersion") {
417                         lex.next();
418                         int version = lex.getInteger();
419                         if (version > VersionNumber)
420                                 lyxerr
421                                 << "This document was created with a newer Graphics widget"
422                                 ", You should use a newer version of LyX to read this"
423                                 " file."
424                                 << std::endl;
425                         // TODO: Possibly open up a dialog?
426                 }
427                 else {
428                         if (! params_.Read(buf, lex, token))
429                                 lyxerr << "Unknown token, " << token << ", skipping." 
430                                         << std::endl;
431                 }
432         }
433 }
434
435 // FormatVersion < 1.0  (LyX < 1.2)
436 void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
437 {
438         std::vector<string> const oldUnits =
439                 getVectorFromString("pt,cm,in,p%,c%");
440         bool finished = false;
441         // set the display default      
442         if (lyxrc.display_graphics == "mono") 
443             params_.display = InsetGraphicsParams::MONOCHROME;
444         else if (lyxrc.display_graphics == "gray") 
445             params_.display = InsetGraphicsParams::GRAYSCALE;
446         else if (lyxrc.display_graphics == "color") 
447             params_.display = InsetGraphicsParams::COLOR;
448         else
449             params_.display = InsetGraphicsParams::NONE;
450         while (lex.isOK() && !finished) {
451                 lex.next();
452
453                 string const token = lex.getString();
454                 lyxerr[Debug::GRAPHICS] << "Token: " << token << endl;
455                 
456                 if (token.empty())
457                         continue;
458                 else if (token == "\\end_inset") {
459                         finished = true;
460                 } else if (token == "file") {
461                         if (lex.next()) {
462                                 string const name = lex.getString();
463                                 string const path = buf->filePath();
464                                 params_.filename = MakeAbsPath(name, path);
465                         }
466                 } else if (token == "extra") {
467                         if (lex.next());
468                         // kept for backwards compability. Delete in 0.13.x
469                 } else if (token == "subcaption") {
470                         if (lex.eatLine())
471                                 params_.subcaptionText = lex.getString();
472                         params_.subcaption = true;
473                 } else if (token == "label") {
474                         if (lex.next());
475                         // kept for backwards compability. Delete in 0.13.x
476                 } else if (token == "angle") {
477                         if (lex.next())
478                                 params_.rotate = true;
479                                 params_.rotateAngle = lex.getFloat();
480                 } else if (token == "size") {
481                         if (lex.next())
482                                 params_.lyxwidth = LyXLength(lex.getString()+"pt");
483                         if (lex.next())
484                                 params_.lyxheight = LyXLength(lex.getString()+"pt");
485                 } else if (token == "flags") {
486                         if (lex.next())
487                                 switch (lex.getInteger()) {
488                                 case 1: params_.display = InsetGraphicsParams::MONOCHROME; 
489                                     break;
490                                 case 2: params_.display = InsetGraphicsParams::GRAYSCALE; 
491                                     break;
492                                 case 3: params_.display = InsetGraphicsParams::COLOR; 
493                                     break;
494                                 }
495                 } else if (token == "subfigure") {
496                         params_.subcaption = true;
497                 } else if (token == "width") {
498                     if (lex.next()) {
499                         int i = lex.getInteger();
500                         if (lex.next()) {
501                             if (i == 5) {
502                                 params_.scale = lex.getInteger();
503                                 params_.size_type = InsetGraphicsParams::SCALE;
504                             } else {
505                                 params_.width = LyXLength(lex.getString()+oldUnits[i]);
506                                 params_.size_type = InsetGraphicsParams::WH;
507                             }
508                         }
509                     }
510                 } else if (token == "height") {
511                     if (lex.next()) {
512                         int i = lex.getInteger();
513                         if (lex.next()) {
514                             params_.height = LyXLength(lex.getString()+oldUnits[i]);
515                             params_.size_type = InsetGraphicsParams::WH;
516                         }
517                     }
518                 }
519         }
520 }
521
522 string const InsetGraphics::createLatexOptions() const
523 {
524         // Calculate the options part of the command, we must do it to a string
525         // stream since we might have a trailing comma that we would like to remove
526         // before writing it to the output stream.
527         ostringstream options;
528         if (!params().bb.empty())
529             options << "  bb=" << strip(params().bb) << ",\n";
530         if (params().draft)
531             options << "  draft,\n";
532         if (params().clip)
533             options << "  clip,\n";
534         if (params().size_type == InsetGraphicsParams::WH) {
535             if (!params().width.zero())
536                 options << "  width=" << params().width.asLatexString() << ",\n";
537             if (!params().height.zero())
538                 options << "  height=" << params().height.asLatexString() << ",\n";
539         } else if (params().size_type == InsetGraphicsParams::SCALE) {
540             if (params().scale > 0)
541                 options << "  scale=" << double(params().scale)/100.0 << ",\n";
542         }
543         if (params().keepAspectRatio)
544             options << "  keepaspectratio,\n";
545         // Make sure it's not very close to zero, a float can be effectively
546         // zero but not exactly zero.
547         if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) {
548             options << "  angle=" << params().rotateAngle << ",\n";
549             if (!params().rotateOrigin.empty()) {
550                 options << "  origin=" << params().rotateOrigin[0];
551                 if (contains(params().rotateOrigin,"Top"))
552                     options << 't';
553                 else if (contains(params().rotateOrigin,"Bottom"))
554                     options << 'b';
555                 else if (contains(params().rotateOrigin,"Baseline"))
556                     options << 'B';
557                 options << ",\n";
558             }
559         }
560         if (!params().special.empty())
561             options << params().special << ",\n";
562         string opts = options.str().c_str();
563         return opts.substr(0,opts.size()-2);    // delete last ",\n"
564 }
565
566 namespace {
567 string findTargetFormat(string const & suffix)
568 {
569         // lyxrc.pdf_mode means:
570         // Are we creating a PDF or a PS file?
571         // (Should actually mean, are we using latex or pdflatex).      
572         lyxerr[Debug::GRAPHICS] << "decideOutput: lyxrc.pdf_mode = "
573                             << lyxrc.pdf_mode << std::endl;
574         if (lyxrc.pdf_mode) {
575                 if (contains(suffix,"ps") || suffix == "pdf")
576                         return "pdf";
577                 else if (suffix == "jpg")
578                         return suffix;
579                 else
580                         return "png";
581         }
582         // If it's postscript, we always do eps.
583         lyxerr[Debug::GRAPHICS] << "decideOutput: we have PostScript mode\n";
584         if (suffix != "ps")
585             return "eps";
586         else
587             return "ps";
588 }
589
590 } // Anon. namespace
591
592
593 string const InsetGraphics::prepareFile(Buffer const *buf) const
594 {
595         // do_convert = Do we need to convert the file?
596         // nice = Do we create a nice version?
597         //        This is used when exporting the latex file only.
598         // if (!do_convert)
599         //   return original filename
600         // if (!nice)
601         //   convert_place = temp directory
602         //   return new filename in temp directory
603         // else
604         //   convert_place = original file directory
605         //   return original filename without the extension
606         //
607         // if it's a zipped one, than let LaTeX do the rest!!!
608         string filename_  = params().filename;
609         bool const zipped = zippedFile(filename_);
610
611         if ((zipped && params().noUnzip) || buf->niceFile) {
612                 lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex" 
613                                     << filename_ << endl;
614                 return filename_;
615         }
616
617         if (zipped)
618                 filename_ = unzipFile(filename_);
619
620         string const from = getExtFromContents(filename_);
621         string const to   = findTargetFormat(from);
622
623         if (from == to) {
624                 // No conversion needed!
625                 return filename_;
626         }
627
628         string const temp = AddName(buf->tmppath, filename_);
629         string const outfile_base = RemoveExtension(temp);
630
631         lyxerr[Debug::GRAPHICS] << "tempname = " << temp << "\n";
632         lyxerr[Debug::GRAPHICS] << "buf::tmppath = " << buf->tmppath << "\n";
633         lyxerr[Debug::GRAPHICS] << "filename_ = " << filename_ << "\n";
634         lyxerr[Debug::GRAPHICS] << "outfile_base = " << outfile_base << endl;
635
636         converters.convert(buf, filename_, outfile_base, from, to);
637         return outfile_base;
638 }
639
640
641 int InsetGraphics::latex(Buffer const *buf, ostream & os,
642                          bool /*fragile*/, bool/*fs*/) const
643 {
644         // If there is no file specified, just output a message about it in
645         // the latex output.
646         if (params().filename.empty()) {
647                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
648                         << _("empty figure path") << "}\n";
649                 return 1; // One end of line marker added to the stream.
650         }
651         // These variables collect all the latex code that should be before and
652         // after the actual includegraphics command.
653         string before;
654         string after;
655         // Do we want subcaptions?
656         if (params().subcaption) {
657                 before += "\\subfigure[" + params().subcaptionText + "]{";
658                 after = '}';
659         }
660         // We never use the starred form, we use the "clip" option instead.
661         before += "\\includegraphics";
662         // Write the options if there are any.
663         string const opts = createLatexOptions();
664         if (!opts.empty()) {
665                 before += ("[%\n" + opts +']');
666         }
667         // Make the filename relative to the lyx file
668         // and remove the extension so the LaTeX will use whatever is
669         // appropriate (when there are several versions in different formats)
670         string const latex_str = before + '{' + prepareFile(buf) + '}' + after;
671         os << latex_str;
672
673         // Return how many newlines we issued.
674         int const newlines =
675                 int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
676                 
677         // lyxerr << "includegraphics: " << newlines << " lines of text"
678         //        << endl; 
679         return newlines;
680 }
681
682
683 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
684 {
685         // No graphics in ascii output. Possible to use gifscii to convert
686         // images to ascii approximation.
687         // 1. Convert file to ascii using gifscii
688         // 2. Read ascii output file and add it to the output stream.
689         // at least we send the filename
690         os << '<' << _("Graphicfile:") << params().filename << ">\n";
691         return 0;
692 }
693
694
695 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
696 {
697         // No graphics in LinuxDoc output. Should check how/what to add.
698         return 0;
699 }
700
701
702 // For explanation on inserting graphics into DocBook checkout:
703 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
704 // See also the docbook guide at http://www.docbook.org/
705 int InsetGraphics::docbook(Buffer const *, ostream & os) const
706 {
707         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
708         // need to switch to MediaObject. However, for now this is sufficient and 
709         // easier to use.
710         os << "<graphic fileref=\"&" << graphic_label << ";\">";
711         return 0;
712 }
713
714
715 void InsetGraphics::validate(LaTeXFeatures & features) const
716 {
717         // If we have no image, we should not require anything.
718         if (params().filename.empty())
719                 return ;
720
721         features.includeFile(graphic_label, RemoveExtension(params_.filename));
722
723         features.require("graphicx");
724
725         if (params().subcaption)
726                 features.require("subfigure");
727 }
728
729
730 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
731 {
732         // If nothing is changed, just return and say so.
733         if (params() == p && !p.filename.empty()) {
734                 return false;
735         }
736
737         // Copy the new parameters.
738         params_ = p;
739
740         // Update the inset with the new parameters.
741         updateInset();
742
743         // We have changed data, report it.
744         return true;
745 }
746
747
748 InsetGraphicsParams const & InsetGraphics::params() const
749 {
750         return params_;
751 }
752
753
754 Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
755 {
756         return new InsetGraphics(*this, same_id);
757 }