1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include <QDomElement>
8 #include <QFile>
9 #include <QFileInfo>
10 #include <QtAlgorithms>
11 #include <QCursor>
12 #include <QRegExp>
13 #include <QDir>
14 #include <QTextCodec>
15 #include <QCheckBox>
16 #include <QMessageBox>
17 #include <QProgressBar>
18
19 #include <QList>
20 #include <cstdlib>
21 #include <cmath>
22 #include <QTextStream>
23
24 #include "scconfig.h"
25
26 #include "commonstrings.h"
27 #include "fileloader.h"
28 #include "hyphenator.h"
29 #include "loadsaveplugin.h"
30 #include "pagestructs.h"
31 #include "pluginmanager.h"
32 #include "prefsmanager.h"
33 #include "resourcecollection.h"
34 #include "scclocale.h"
35 #include "scpage.h"
36 #include "scribuscore.h"
37 #include "scribusXml.h"
38 #include "units.h"
39 #include "util.h"
40
41 #include "plugins/formatidlist.h"
42 #include "ui/guidemanager.h"
43 #include "ui/fontreplacedialog.h"
44 #include "ui/missing.h"
45
46 // We need to include the headers for the plugins we support until we start
47 // using LoadSavePlugin to pick them for us. We only use these headers to
48 // get the format IDs, NOTHING ELSE.
49 // #include "plugins/svgimplugin/svgplugin.h"
50 // #include "plugins/psimport/importpsplugin.h"
51 // #include "plugins/fileloader/oodraw/oodrawimp.h"
52 #include <zlib.h>
53
54 /*!
55 \author Franz Schmid
56 \date
57 \brief Constructor, sets the variable "FileName" to the input parameter fileName
58 \param fileName filename to load
59 \retval None
60 */
FileLoader(const QString & fileName)61 FileLoader::FileLoader(const QString & fileName) :
62 QObject(nullptr),
63 formatSLA12x(LoadSavePlugin::getFormatById(FORMATID_SLA12XIMPORT)),
64 formatSLA13x(LoadSavePlugin::getFormatById(FORMATID_SLA13XIMPORT)),
65 formatSLA134(LoadSavePlugin::getFormatById(FORMATID_SLA134IMPORT)),
66 formatSLA150(LoadSavePlugin::getFormatById(FORMATID_SLA150IMPORT)),
67 m_prefsManager(PrefsManager::instance()),
68 m_fileName(fileName),
69 m_fileType(-1)
70 {
71 QString realPath(QFileInfo(fileName).canonicalFilePath());
72 if (!realPath.isEmpty())
73 m_fileName = realPath;
74 m_newReplacement = false;
75 }
76
77 // FIXME: This static method is here as a temporary transitional
78 // measure during the process of converting to file loader plugins.
getLoadFilterString()79 const QString FileLoader::getLoadFilterString()
80 {
81 // return LoadSavePlugin::fileDialogLoadFilter().join(";;");
82 QStringList fmts = LoadSavePlugin::fileDialogLoadFilter();
83 QString fmtString = QObject::tr("All Supported Formats")+" (";
84 int ind = -1;
85 for (int i = 0; i < fmts.count() - 1; ++i)
86 {
87 QString fmt(fmts[i]);
88 int s = fmt.indexOf("(");
89 int e = fmt.lastIndexOf(")");
90 QString f(fmt.mid(s + 1, e - s - 1));
91 #ifndef HAVE_POPPLER
92 if (f.contains("pdf")) // for removing PDF from the list
93 {
94 ind = i;
95 continue;
96 }
97 #endif
98 fmtString += f + " ";
99 }
100 fmtString += ");;";
101 if (ind != -1)
102 fmts.removeAt(ind);
103 fmtString += fmts.join(";;");
104 return fmtString.simplified();
105 }
106
107 /*!
108 \fn int FileLoader::TestFile()
109 \author Franz Schmid
110 \date
111 \brief Tests if the file "FileName" exists and determines the type of the file.
112 \retval int -1 if the file doesn't exist or any other error has occurred, 0 for the old Format, 1 for the new Format, 2 for EPS and PS files, 3 for SVG files and 4 for PDF files
113 */
testFile()114 int FileLoader::testFile()
115 {
116 QFileInfo fi = QFileInfo(m_fileName);
117 int ret = -1;
118 if (!fi.exists())
119 ret = -1;
120 QString lwrFileName(m_fileName.toLower());
121
122 bool found = false;
123 QList<FileFormat> fileFormats(LoadSavePlugin::supportedFormats());
124 QList<FileFormat>::const_iterator it(fileFormats.constBegin());
125 QList<FileFormat>::const_iterator itEnd(fileFormats.constEnd());
126 for ( ; (it != itEnd) && (!found); ++it)
127 {
128 if (!it->plug)
129 continue;
130 for (int a = 0; a < it->fileExtensions.count(); a++)
131 {
132 QString ext = it->fileExtensions[a].toLower();
133 if (lwrFileName.endsWith("." + ext)) // Beware of file names containing multiple points
134 {
135 if (it->plug->fileSupported(nullptr, m_fileName))
136 {
137 ret = it->formatId;
138 found = true;
139 break;
140 }
141 }
142 }
143 }
144 if (!found)
145 {
146 // now try for the last suffix
147 QString ext(fi.suffix().toLower());
148 it = fileFormats.constBegin();
149 itEnd = fileFormats.constEnd();
150 for ( ; (it != itEnd) && (!found); ++it)
151 {
152 if (!it->plug)
153 continue;
154 bool found = false;
155 for (int i = 0; i < it->fileExtensions.count(); i++)
156 {
157 QString exts(it->fileExtensions[i].toLower());
158 if (ext == exts)
159 {
160 if (it->plug->fileSupported(nullptr, m_fileName))
161 {
162 ret = it->formatId;
163 found = true;
164 break;
165 }
166 }
167 }
168 if (found)
169 break;
170 }
171 }
172 m_fileType = ret;
173 return ret;
174 }
175
loadPage(ScribusDoc * currDoc,int PageToLoad,bool Mpage,const QString & renamedPageName)176 bool FileLoader::loadPage(ScribusDoc* currDoc, int PageToLoad, bool Mpage, const QString& renamedPageName)
177 {
178 bool ret = false;
179 // newReplacement = false;
180 m_ReplacedFonts = currDoc->AllFonts->getSubstitutions();
181 // dummyScFaces.clear();
182 QList<FileFormat>::const_iterator it;
183 if (findFormat(m_fileType, it))
184 {
185 if (m_fileType == FORMATID_SLA12XIMPORT)
186 {
187 it->plug->setupTargets(currDoc, currDoc->view(), currDoc->scMW(), currDoc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
188 ret = it->plug->loadPage(m_fileName, PageToLoad, Mpage, renamedPageName);
189 // if (ret)
190 // it->plug->getReplacedFontData(newReplacement, ReplacedFonts, dummyScFaces);
191 }
192 if (m_fileType == FORMATID_SLA13XIMPORT || m_fileType == FORMATID_SLA134IMPORT || m_fileType == FORMATID_SLA150IMPORT)
193 {
194 it->plug->setupTargets(currDoc, nullptr, currDoc->scMW(), currDoc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
195 ret = it->plug->loadPage(m_fileName, PageToLoad, Mpage, renamedPageName);
196 // if (ret)
197 // it->plug->getReplacedFontData(newReplacement, ReplacedFonts, dummyScFaces);
198 }
199 }
200 if (ret)
201 ret = postLoad(currDoc); // FIXME: return false if user doesn't want to replace fonts??
202
203 return ret;
204 }
205
206 /*!
207 \fn bool FileLoader::LoadFile(ScribusDoc* currDoc)
208 \author Franz Schmid
209 \date
210 \brief Loads the file "FileName" as a Scribus document
211 \param currDoc the current document
212 \retval bool true when loading is succsessful, false otherwise
213 */
loadFile(ScribusDoc * currDoc)214 bool FileLoader::loadFile(ScribusDoc* currDoc)
215 {
216 m_newReplacement = false;
217 currDoc->guidesPrefs().marginsShown = m_prefsManager.appPrefs.guidesPrefs.marginsShown;
218 currDoc->guidesPrefs().framesShown = m_prefsManager.appPrefs.guidesPrefs.framesShown;
219 currDoc->guidesPrefs().layerMarkersShown = m_prefsManager.appPrefs.guidesPrefs.layerMarkersShown;
220 currDoc->guidesPrefs().gridShown = m_prefsManager.appPrefs.guidesPrefs.gridShown;
221 currDoc->guidesPrefs().guidesShown = m_prefsManager.appPrefs.guidesPrefs.guidesShown;
222 currDoc->guidesPrefs().colBordersShown = m_prefsManager.appPrefs.guidesPrefs.colBordersShown;
223 currDoc->guidesPrefs().baselineGridShown = m_prefsManager.appPrefs.guidesPrefs.baselineGridShown;
224 currDoc->guidesPrefs().linkShown = m_prefsManager.appPrefs.guidesPrefs.linkShown;
225 currDoc->itemToolPrefs().polyCorners = m_prefsManager.appPrefs.itemToolPrefs.polyCorners;
226 currDoc->itemToolPrefs().polyFactor = m_prefsManager.appPrefs.itemToolPrefs.polyFactor;
227 currDoc->itemToolPrefs().polyRotation = m_prefsManager.appPrefs.itemToolPrefs.polyRotation;
228 currDoc->itemToolPrefs().polyCurvature = m_prefsManager.appPrefs.itemToolPrefs.polyCurvature;
229 currDoc->itemToolPrefs().polyOuterCurvature = m_prefsManager.appPrefs.itemToolPrefs.polyOuterCurvature;
230 currDoc->itemToolPrefs().polyInnerRot = m_prefsManager.appPrefs.itemToolPrefs.polyInnerRot;
231 currDoc->itemToolPrefs().polyUseFactor = m_prefsManager.appPrefs.itemToolPrefs.polyUseFactor;
232 currDoc->setAutoSave(m_prefsManager.appPrefs.docSetupPrefs.AutoSave);
233 currDoc->setAutoSaveTime(m_prefsManager.appPrefs.docSetupPrefs.AutoSaveTime);
234 currDoc->setAutoSaveCount(m_prefsManager.appPrefs.docSetupPrefs.AutoSaveCount);
235 currDoc->setAutoSaveKeep(m_prefsManager.appPrefs.docSetupPrefs.AutoSaveKeep);
236 currDoc->setAutoSaveInDocDir(m_prefsManager.appPrefs.docSetupPrefs.AutoSaveLocation);
237 currDoc->setAutoSaveDir(m_prefsManager.appPrefs.docSetupPrefs.AutoSaveDir);
238 m_ReplacedFonts = currDoc->AllFonts->getSubstitutions();
239 //dummyScFaces.clear();
240 bool ret = false;
241 QList<FileFormat>::const_iterator it;
242 if (findFormat(m_fileType, it))
243 {
244 // qDebug("fileloader: type %d plugin %s"),m_fileType,it->trName);
245 switch (m_fileType)
246 {
247 case FORMATID_SLA12XIMPORT:
248 {
249 it->setupTargets(currDoc, currDoc->view(), currDoc->scMW(), currDoc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
250 ret = it->loadFile(m_fileName, LoadSavePlugin::lfCreateDoc);
251 // if (ret)
252 // it->getReplacedFontData(newReplacement, ReplacedFonts, dummyScFaces);
253 }
254 break;
255 case FORMATID_SLA13XIMPORT:
256 case FORMATID_SLA134IMPORT:
257 case FORMATID_SLA150IMPORT:
258 {
259 it->setupTargets(currDoc, nullptr, currDoc->scMW(), currDoc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
260 ret = it->loadFile(m_fileName, LoadSavePlugin::lfCreateDoc);
261 // if (ret)
262 // it->getReplacedFontData(newReplacement, ReplacedFonts, dummyScFaces);
263 }
264 break;
265 default:
266 it->setupTargets(currDoc, currDoc->view(), currDoc->scMW(), currDoc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
267 ret = it->loadFile(m_fileName, LoadSavePlugin::lfCreateDoc);
268 break;
269 }
270 }
271 return ret;
272 }
273
saveFile(const QString & fileName,ScribusDoc * doc,QString * savedFile)274 bool FileLoader::saveFile(const QString& fileName, ScribusDoc *doc, QString *savedFile)
275 {
276 bool ret = false;
277 QList<FileFormat>::const_iterator it;
278 if (findFormat(FORMATID_SLA150EXPORT, it))
279 {
280 it->setupTargets(doc, doc->view(), doc->scMW(), doc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
281 ret = it->saveFile(fileName);
282 if (savedFile)
283 *savedFile = it->lastSavedFile();
284 }
285 return ret;
286 }
287
readStyles(ScribusDoc * doc,StyleSet<ParagraphStyle> & docParagraphStyles)288 bool FileLoader::readStyles(ScribusDoc* doc, StyleSet<ParagraphStyle> &docParagraphStyles)
289 {
290 QList<FileFormat>::const_iterator it;
291 if (findFormat(m_fileType, it))
292 {
293 it->plug->setupTargets(doc, nullptr, doc->scMW(), doc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
294 return it->readStyles(m_fileName, doc, docParagraphStyles);
295 }
296 return false;
297 }
298
readCharStyles(ScribusDoc * doc,StyleSet<CharStyle> & docCharStyles)299 bool FileLoader::readCharStyles(ScribusDoc* doc, StyleSet<CharStyle> &docCharStyles)
300 {
301 QList<FileFormat>::const_iterator it;
302 if (findFormat(m_fileType, it))
303 {
304 it->plug->setupTargets(doc, nullptr, doc->scMW(), doc->scMW()->mainWindowProgressBar, &(m_prefsManager.appPrefs.fontPrefs.AvailFonts));
305 return it->readCharStyles(m_fileName, doc, docCharStyles);
306 }
307 return false;
308 }
309
readColors(ColorList & colors)310 bool FileLoader::readColors(ColorList & colors)
311 {
312 QList<FileFormat>::const_iterator it;
313 if (findFormat(m_fileType, it))
314 return it->readColors(m_fileName, colors);
315 return false;
316 }
317
readPageCount(int * num1,int * num2,QStringList & masterPageNames)318 bool FileLoader::readPageCount(int *num1, int *num2, QStringList & masterPageNames)
319 {
320 QList<FileFormat>::const_iterator it;
321 if (findFormat(m_fileType, it))
322 return it->readPageCount(m_fileName, num1, num2, masterPageNames);
323 return false;
324 }
325
readLineStyles(QHash<QString,multiLine> * Sty)326 bool FileLoader::readLineStyles(QHash<QString,multiLine> *Sty)
327 {
328 QList<FileFormat>::const_iterator it;
329 if (findFormat(m_fileType, it))
330 return it->readLineStyles(m_fileName, Sty);
331 return false;
332 }
333
readParagraphStyle(ParagraphStyle & vg,const QDomElement & pg,SCFonts & avail,ScribusDoc * currDoc)334 void FileLoader::readParagraphStyle(ParagraphStyle& vg, const QDomElement& pg, SCFonts &avail, ScribusDoc *currDoc)
335 {
336 vg.setName(pg.attribute("NAME"));
337 vg.setLineSpacingMode(static_cast<ParagraphStyle::LineSpacingMode>(pg.attribute("LINESPMode", "0").toInt()));
338 vg.setLineSpacing(ScCLocale::toDoubleC(pg.attribute("LINESP")));
339 vg.setLeftMargin(ScCLocale::toDoubleC(pg.attribute("INDENT"), 0.0));
340 if (pg.hasAttribute("RMARGIN"))
341 vg.setRightMargin(ScCLocale::toDoubleC(pg.attribute("RMARGIN"), 0.0));
342 else
343 vg.setRightMargin(0);
344 vg.setFirstIndent(ScCLocale::toDoubleC(pg.attribute("FIRST"), 0.0));
345 vg.setAlignment(static_cast<ParagraphStyle::AlignmentType>(pg.attribute("ALIGN").toInt()));
346 vg.setGapBefore(ScCLocale::toDoubleC(pg.attribute("VOR"), 0.0));
347 vg.setGapAfter(ScCLocale::toDoubleC(pg.attribute("NACH"), 0.0));
348 QString tmpf(pg.attribute("FONT", currDoc->itemToolPrefs().textFont));
349 currDoc->AllFonts->findFont(tmpf, currDoc);
350 vg.charStyle().setFont((*currDoc->AllFonts)[tmpf]);
351 vg.charStyle().setFontSize(qRound(ScCLocale::toDoubleC(pg.attribute("FONTSIZE"), 12.0) * 10.0));
352 vg.setHasDropCap(static_cast<bool>(pg.attribute("DROP", "0").toInt()));
353 vg.setPeCharStyleName(pg.attribute("DROPCHSTYLE", QString()));
354 vg.setPeCharStyleName(pg.attribute("PECHSTYLE", QString()));
355 vg.setDropCapLines(pg.attribute("DROPLIN", "2").toInt());
356 vg.setParEffectOffset(ScCLocale::toDoubleC(pg.attribute("DROPDIST"), 0.0));
357 vg.setParEffectOffset(ScCLocale::toDoubleC(pg.attribute("PEDIST"), 0.0));
358 vg.charStyle().setFeatures(static_cast<StyleFlag>(pg.attribute("EFFECT", "0").toInt()).featureList());
359 vg.charStyle().setFillColor(pg.attribute("FCOLOR", currDoc->itemToolPrefs().shapeFillColor));
360 vg.charStyle().setFillShade(pg.attribute("FSHADE", "100").toInt());
361 vg.charStyle().setStrokeColor(pg.attribute("SCOLOR", currDoc->itemToolPrefs().shapeLineColor));
362 vg.charStyle().setStrokeShade(pg.attribute("SSHADE", "100").toInt());
363 if (static_cast<bool>(pg.attribute("BASE", "0").toInt()))
364 vg.setLineSpacingMode(ParagraphStyle::BaselineGridLineSpacing);
365 vg.charStyle().setShadowXOffset(qRound(ScCLocale::toDoubleC(pg.attribute("TXTSHX"), 5.0)) * 10);
366 vg.charStyle().setShadowYOffset(qRound(ScCLocale::toDoubleC(pg.attribute("TXTSHY"), -5.0)) * 10);
367 vg.charStyle().setOutlineWidth(qRound(ScCLocale::toDoubleC(pg.attribute("TXTOUT"), 1.0)) * 10);
368 vg.charStyle().setUnderlineOffset(qRound(ScCLocale::toDoubleC(pg.attribute("TXTULP"), -0.1)) * 10);
369 vg.charStyle().setUnderlineWidth(qRound(ScCLocale::toDoubleC(pg.attribute("TXTULW"), -0.1)) * 10);
370 vg.charStyle().setStrikethruOffset(qRound(ScCLocale::toDoubleC(pg.attribute("TXTSTP"), -0.1)) * 10);
371 vg.charStyle().setStrikethruWidth(qRound(ScCLocale::toDoubleC(pg.attribute("TXTSTW"), -0.1)) * 10);
372 vg.charStyle().setScaleH(qRound(ScCLocale::toDoubleC(pg.attribute("SCALEH"), 100.0)) * 10);
373 vg.charStyle().setScaleV(qRound(ScCLocale::toDoubleC(pg.attribute("SCALEV"), 100.0)) * 10);
374 vg.charStyle().setBaselineOffset(qRound(ScCLocale::toDoubleC(pg.attribute("BASEO"), 0.0)) * 10);
375 vg.charStyle().setTracking(qRound(ScCLocale::toDoubleC(pg.attribute("KERN"), 0.0)) * 10);
376 if ((pg.hasAttribute("NUMTAB")) && (pg.attribute("NUMTAB", "0").toInt() != 0))
377 {
378 QList<ParagraphStyle::TabRecord> tbs;
379 ParagraphStyle::TabRecord tb;
380 QString tmp = pg.attribute("TABS");
381 QTextStream tgv(&tmp, QIODevice::ReadOnly);
382 QString xf, xf2;
383 for (int cxv = 0; cxv < pg.attribute("NUMTAB", "0").toInt(); cxv += 2)
384 {
385 tgv >> xf;
386 tgv >> xf2;
387 tb.tabPosition = ScCLocale::toDoubleC(xf2);
388 tb.tabType = static_cast<int>(ScCLocale::toDoubleC(xf));
389 tb.tabFillChar = QChar();
390 tbs.append(tb);
391 }
392 vg.setTabValues(tbs);
393 tmp.clear();
394 }
395 else
396 {
397 QList<ParagraphStyle::TabRecord> tbs;
398 QDomNode domNode = pg.firstChild();
399 while (!domNode.isNull())
400 {
401 QDomElement it = domNode.toElement();
402 if (it.tagName() == "Tabs")
403 {
404 ParagraphStyle::TabRecord tb;
405 tb.tabPosition = ScCLocale::toDoubleC(it.attribute("Pos"));
406 tb.tabType = it.attribute("Type").toInt();
407 QString tbCh = it.attribute("Fill", QString());
408 if (tbCh.isEmpty())
409 tb.tabFillChar = QChar();
410 else
411 tb.tabFillChar = tbCh[0];
412 tbs.append(tb);
413 }
414 domNode = domNode.nextSibling();
415 }
416 vg.setTabValues(tbs);
417 }
418 }
419
readThumbnail()420 QImage FileLoader::readThumbnail()
421 {
422 QList<FileFormat>::const_iterator it;
423 if (findFormat(m_fileType, it))
424 return it->readThumbnail(m_fileName);
425 return QImage();
426 }
427
postLoad(ScribusDoc * currDoc)428 bool FileLoader::postLoad(ScribusDoc* currDoc)
429 {
430 m_ReplacedFonts = currDoc->AllFonts->getSubstitutions(m_ReplacedFonts.keys());
431 if (m_ReplacedFonts.isEmpty())
432 return true;
433
434 if (!(m_prefsManager.appPrefs.fontPrefs.askBeforeSubstitute))
435 return true;
436
437 qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
438 FontReplaceDialog dia(nullptr, &m_ReplacedFonts);
439 if (!dia.exec())
440 return false;
441
442 QMap<QString, QString>::Iterator itfsu;
443 for (itfsu = m_ReplacedFonts.begin(); itfsu != m_ReplacedFonts.end(); ++itfsu)
444 {
445 if (dia.stickyReplacements->isChecked())
446 m_prefsManager.appPrefs.fontPrefs.GFontSub[itfsu.key()] = itfsu.value();
447 }
448 currDoc->AllFonts->setSubstitutions(m_ReplacedFonts, currDoc);
449 ResourceCollection repl;
450 repl.availableFonts = currDoc->AllFonts;
451 repl.mapFonts(m_ReplacedFonts);
452 currDoc->replaceNamedResources(repl);
453 return true;
454 }
455
informReplacementFonts()456 void FileLoader::informReplacementFonts()
457 {
458 if (m_ReplacedFonts.count() == 0)
459 return;
460
461 qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
462 QString mess = tr("Some fonts used by this document have been substituted:") + "\n\n";
463 for (auto it = m_ReplacedFonts.begin(); it != m_ReplacedFonts.end(); ++it)
464 {
465 mess += it.key() + tr(" was replaced by: ") + it.value() +"\n";
466 }
467 ScMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning, mess);
468 }
469
findFormat(uint formatId,QList<FileFormat>::const_iterator & it)470 bool FileLoader::findFormat(uint formatId, QList<FileFormat>::const_iterator &it)
471 {
472 QList<FileFormat> fileFormats(LoadSavePlugin::supportedFormats());
473 it = fileFormats.constBegin();
474 QList<FileFormat>::const_iterator itEnd(fileFormats.constEnd());
475 for ( ; it != itEnd ; ++it)
476 {
477 if (formatId == it->formatId)
478 return true;
479 }
480 return false;
481 }
482